Save Image from Serial L2 Camera

I am trying to capture and save to SD card an image from the L2 Serial Camera.

I can capture the image and save on the SD without problems, but when I try to open the file on my PC, Windows says the file type is not recognized.

Below is my test program. The image appears in the upper corner of my LCD and the file is on the SD card. It is 76,800 bytes so there is something there. I am using a Raptor, NETMF 4.3 and Visual Studio 2012.

I have looked on the forums and code share.

I found [quote]Serial Camera L2 Module take single image and save to microSD[/quote] from which I got the basic code.

I also found [quote]Webserver displaying picture captured when button is pressed[/quote]. I could not get it to work (black image). Also did not understand why it used a DrawImage method instead of just calling GetImage, after apparently getting and ignoring the bitmap.

using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.IO;
using System.IO;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;

namespace Camera_Test
{
    public partial class Program
    {
        private GT.Timer timer;

        void ProgramStarted()
        {
            serialCameraL2.ImageResolution = SerialCameraL2.Resolution.QQVGA;
            this.serialCameraL2.StartStreaming();

            this.timer = new GT.Timer(100);
            this.timer.Tick += this.timer_Tick;
            this.timer.Start();
        }

        private void timer_Tick(GT.Timer timer)
        {
            if (this.serialCameraL2.NewImageReady)
            {
                Bitmap image = this.serialCameraL2.GetImage();
                this.displayTE35.SimpleGraphics.DisplayImage(image, 0, 0);

                while (!sdCard.IsCardInserted) Thread.Sleep(1000);
                if (!sdCard.IsCardMounted) sdCard.Mount();
                while (!sdCard.IsCardMounted) System.Threading.Thread.Sleep(50);

                byte[] data = image.GetBitmap();

                VolumeInfo[] vi2 = VolumeInfo.GetVolumes();
                if (vi2[0].IsFormatted)
                {
                    try
                    {

                        string rootDirectory = VolumeInfo.GetVolumes()[0].RootDirectory;
                        FileStream FileHandle = new FileStream(rootDirectory + @ "\picture.bmp", FileMode.Create);
                        FileHandle.Write(data, 0, data.Length);
                        FileHandle.Close();
                        Debug.Print("Picture stored on sd");
                        Debug.Print("Finalize volumes");
                        VolumeInfo[] vi = VolumeInfo.GetVolumes();
                        for (int i = 0; i < vi.Length; i++)
                            vi[i].FlushAll();

                    }
                    catch (Exception exp)
                    {
                        Debug.Print(exp.Message.ToString());
                    }
                }
                else
                {
                    Debug.Print("Storage is not formatted. " + "Format on PC with FAT32/FAT16 first!");
                }

                serialCameraL2.StopStreaming();
            }
        }
    }
}

@ Duncan - The GetBitmap call does not return a fully formatted bitmap file, it is just the raw pixel data. Our libraries provide a method to do this for you, but it is broken in the current release. See https://www.ghielectronics.com/community/forum/topic?id=17180&page=1#msg171087

@ John - I had read that article but did not understand that it applied to my case.

To do what I need with the temporary fix you supply do I need to do it all or can I just use the ConvertToFile on the bitmap that is returned?

Thanks

Duncan

@ Duncan - You should be able to call the ConvertToFile function in the post I linked with the result of GetBitmap.

@ John:

Thanks for the help. Making good progress here now. I have a program now that can capture images and save them to disk and load the image on the PC for QQVGA and QVGA. When I try VGA I get an exception in the commented line of ConvertToFile.

Below is the code and exception:

using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.IO;
using System.IO;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;
using GHI.Utilities;
using Microsoft.SPOT.Hardware;

namespace Camera_Test
{
    public partial class Program
    {
        private GT.Timer timer;

        void ProgramStarted()
        {
            serialCameraL2.ImageResolution = SerialCameraL2.Resolution.VGA;
            this.serialCameraL2.StartStreaming();

            this.timer = new GT.Timer(100);
            this.timer.Tick += this.timer_Tick;
            this.timer.Start();
        }

        public void ConvertToFile(Bitmap bitmap, byte[] outputBuffer)
        {
            var inputBuffer = bitmap.GetBitmap();       //<<-- Exception Here
            var width = (uint)bitmap.Width;
            var height = (uint)bitmap.Height;

            if (outputBuffer.Length != width * height * 3 + 54) throw new System.ArgumentException("outputBuffer.Length must be width * height * 3 + 54.", "outputSize");

            System.Array.Clear(outputBuffer, 0, outputBuffer.Length);

            Utility.InsertValueIntoArray(outputBuffer, 0, 2, 19778);
            Utility.InsertValueIntoArray(outputBuffer, 2, 4, width * height * 3 + 54);
            Utility.InsertValueIntoArray(outputBuffer, 10, 4, 54);
            Utility.InsertValueIntoArray(outputBuffer, 14, 4, 40);
            Utility.InsertValueIntoArray(outputBuffer, 18, 4, width);
            Utility.InsertValueIntoArray(outputBuffer, 22, 4, (uint)(-height));
            Utility.InsertValueIntoArray(outputBuffer, 26, 2, 1);
            Utility.InsertValueIntoArray(outputBuffer, 28, 2, 24);

            for (int i = 0, j = 54; i < width * height * 4; i += 4, j += 3)
            {
                outputBuffer[j + 0] = inputBuffer[i + 2];
                outputBuffer[j + 1] = inputBuffer[i + 1];
                outputBuffer[j + 2] = inputBuffer[i + 0];
            }
        }

        private void timer_Tick(GT.Timer timer)
        {
            if (this.serialCameraL2.NewImageReady)
            {
                Bitmap image = this.serialCameraL2.GetImage();
                this.displayTE35.SimpleGraphics.DisplayImage(image, 0, 0);

                while (!sdCard.IsCardInserted) Thread.Sleep(1000);
                if (!sdCard.IsCardMounted) sdCard.Mount();
                while (!sdCard.IsCardMounted) System.Threading.Thread.Sleep(50);

                VolumeInfo[] vi2 = VolumeInfo.GetVolumes();
                if (vi2[0].IsFormatted)
                {
                    try
                    {
                        int fileSize = image.Height * image.Width * 3 + 54;
                        LargeBuffer data = new LargeBuffer(fileSize);
                        ConvertToFile(image, data.Bytes);
                        string rootDirectory = VolumeInfo.GetVolumes()[0].RootDirectory;
                        FileStream FileHandle = new FileStream(rootDirectory + @ "\picture.bmp", FileMode.Create);
                        FileHandle.Write(data.Bytes, 0, fileSize);
                        FileHandle.Close();
                        Debug.Print("Picture stored on sd");
                        Debug.Print("Finalize volumes");
                        VolumeInfo[] vi = VolumeInfo.GetVolumes();
                        for (int i = 0; i < vi.Length; i++)
                            vi[i].FlushAll();

                    }
                    catch (Exception exp)
                    {
                        Debug.Print(exp.Message.ToString());
                    }
                }
                else
                {
                    Debug.Print("Storage is not formatted. " + "Format on PC with FAT32/FAT16 first!");
                }

                serialCameraL2.StopStreaming();
            }
        }
    }
}

Using mainboard GHI Electronics FEZ Raptor version 1.0
GC: performing heap compaction…
#### Exception System.OutOfMemoryException - CLR_E_OUT_OF_MEMORY (1) ####
#### Message:
#### Microsoft.SPOT.Bitmap::GetBitmap [IP: 0000] ####
#### Camera_Test.Program::ConvertToFile [IP: 0005] ####
#### Camera_Test.Program::timer_Tick [IP: 00a4] ####
#### Gadgeteer.Timer::dt_Tick [IP: 0018] ####
#### Microsoft.SPOT.DispatcherTimer::FireTick [IP: 0010] ####
#### Microsoft.SPOT.Dispatcher::PushFrameImpl [IP: 0054] ####
#### Microsoft.SPOT.Dispatcher::PushFrame [IP: 001a] ####
#### Microsoft.SPOT.Dispatcher::Run [IP: 0006] ####
#### Gadgeteer.Program::Run [IP: 001d] ####
A first chance exception of type ‘System.OutOfMemoryException’ occurred in Microsoft.SPOT.Graphics.dll
Exception was thrown: System.OutOfMemoryException

@ Duncan - you should never sleep in an event handler. You a thread with sleep instead of a timer event handler. I think you might be filling memory with queued timer events.

@ Mike:

Thanks for that heads up. I’ll move it when I get to the real code.

However in this case I don’t think that is it. When I do the test the card is inserted and mounted. I did this since sometimes I forget to take it out of my PC.

Also it works fine with the smaller images sizes.

DUncan

[quote]Using mainboard GHI Electronics FEZ Raptor version 1.0
GC: performing heap compaction…

Exception System.OutOfMemoryException - CLR_E_OUT_OF_MEMORY (1) ####[/quote]

You have something that is holding memory and you’re running out of memory.

You need to debug more what is going on. Your timer is being called every 100ms, and you are blocking several seconds. You should perhaps disable the timer within your IF block so you don’t get that banking up. You may get value in adding debug.print when you don’t have an image ready so you can at least get an idea how many loops thru you’re hitting before you get into the long process of writing out the image, but fundamentally I would have suggested extending the timer ticks as a wise move.

Hi Brett,

I don’t think that is it. In any case, to be sure I have removed all the SD card handling and sleeps since the issue right now is not writing to it.

I had a version earlier that started a thread and used a while(not ready) sleep construct. For some reason I got nothing from the camera. The camera seems dependent on the timing. I tried a few different duration for the timer and other than the number of tick messages, it makes no difference.

I shut everything down as soon as I can so the timer and the image streaming should be stopped.

So here is the new further simplified code (with old code commented out). If I set the format to QQVGA or QVGA it works fine. Image does display on the screen

If I set it to VGA the image displays on the screen, but the code crashes when I try and get the bitmap, as shown below the code. I do a garbage collection and output the free memory. I have heaps of memory (pun intended).

Something is going off the rails in the GetBitmap for the larger image.

using System;
using System.Collections;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Controls;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Presentation.Shapes;
using Microsoft.SPOT.Touch;
using Microsoft.SPOT.IO;
using System.IO;

using Gadgeteer.Networking;
using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;
using GHI.Utilities;
using Microsoft.SPOT.Hardware;

namespace Camera_Test
{
    public partial class Program
    {
        private GT.Timer timer;

        void ProgramStarted()
        {
            serialCameraL2.ImageResolution = SerialCameraL2.Resolution.VGA;
            this.serialCameraL2.StartStreaming();

            //Debug.Print("Checking SD ...");

            //while (!sdCard.IsCardInserted) Thread.Sleep(1000);
            //if (!sdCard.IsCardMounted) sdCard.Mount();
            //while (!sdCard.IsCardMounted) System.Threading.Thread.Sleep(50);

            //Debug.Print("SD Inserted and Mounted and ready to go ...");

            this.timer = new GT.Timer(100);
            this.timer.Tick += this.timer_Tick;
            this.timer.Start();
        }

        public void ConvertToFile(Bitmap bitmap, byte[] outputBuffer)
        {
            Debug.Print("About to die!!!");
            Debug.Print(Debug.GC(true).ToString());
            var inputBuffer = bitmap.GetBitmap();       //<<-- Exception Here
            Debug.Print("Dodged that bullet!!");
            var width = (uint)bitmap.Width;
            var height = (uint)bitmap.Height;

            if (outputBuffer.Length != width * height * 3 + 54) throw new System.ArgumentException("outputBuffer.Length must be width * height * 3 + 54.", "outputSize");

            System.Array.Clear(outputBuffer, 0, outputBuffer.Length);

            Utility.InsertValueIntoArray(outputBuffer, 0, 2, 19778);
            Utility.InsertValueIntoArray(outputBuffer, 2, 4, width * height * 3 + 54);
            Utility.InsertValueIntoArray(outputBuffer, 10, 4, 54);
            Utility.InsertValueIntoArray(outputBuffer, 14, 4, 40);
            Utility.InsertValueIntoArray(outputBuffer, 18, 4, width);
            Utility.InsertValueIntoArray(outputBuffer, 22, 4, (uint)(-height));
            Utility.InsertValueIntoArray(outputBuffer, 26, 2, 1);
            Utility.InsertValueIntoArray(outputBuffer, 28, 2, 24);

            for (int i = 0, j = 54; i < width * height * 4; i += 4, j += 3)
            {
                outputBuffer[j + 0] = inputBuffer[i + 2];
                outputBuffer[j + 1] = inputBuffer[i + 1];
                outputBuffer[j + 2] = inputBuffer[i + 0];
            }
        }

        private void timer_Tick(GT.Timer timer)
        {
            Debug.Print("Tick ...");
            if (this.serialCameraL2.NewImageReady)
            {
                Debug.Print("Image Ready ...");
                timer.Stop();

                Bitmap image = this.serialCameraL2.GetImage();

                serialCameraL2.StopStreaming();


                this.displayTE35.SimpleGraphics.DisplayImage(image, 0, 0);

                //VolumeInfo[] vi2 = VolumeInfo.GetVolumes();
                //if (vi2[0].IsFormatted)
                //{
                //    try
                //    {
                        int fileSize = image.Height * image.Width * 3 + 54;
                        LargeBuffer data = new LargeBuffer(fileSize);
                        ConvertToFile(image, data.Bytes);
                //        string rootDirectory = VolumeInfo.GetVolumes()[0].RootDirectory;
                //        FileStream FileHandle = new FileStream(rootDirectory + @ "\picture.bmp", FileMode.Create);
                //        FileHandle.Write(data.Bytes, 0, fileSize);
                //        FileHandle.Close();
                //        Debug.Print("Picture stored on sd");
                //        Debug.Print("Finalize volumes");
                //        VolumeInfo[] vi = VolumeInfo.GetVolumes();
                //        for (int i = 0; i < vi.Length; i++)
                //            vi[i].FlushAll();

                //    }
                //    catch (Exception exp)
                //    {
                //        Debug.Print(exp.Message.ToString());
                //    }
                //}
                //else
                //{
                //    Debug.Print("Storage is not formatted. " + "Format on PC with FAT32/FAT16 first!");
                //}
            }
        }
    }
}

Using mainboard GHI Electronics FEZ Raptor version 1.0
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Image Ready …
The thread ‘’ (0x3) has exited with code 0 (0x0).
The thread ‘’ (0x6) has exited with code 0 (0x0).
About to die!!!
GC: 1msec 563004 bytes used, 66542760 bytes available
Type 0F (STRING ): 852 bytes
Type 11 (CLASS ): 14844 bytes
Type 12 (VALUETYPE ): 1248 bytes
Type 13 (SZARRAY ): 53424 bytes
Type 03 (U1 ): 48624 bytes
Type 04 (CHAR ): 1032 bytes
Type 07 (I4 ): 1332 bytes
Type 11 (CLASS ): 2436 bytes
Type 15 (FREEBLOCK ): 66542760 bytes
Type 16 (CACHEDBLOCK ): 648 bytes
Type 17 (ASSEMBLY ): 35040 bytes
Type 18 (WEAKCLASS ): 96 bytes
Type 19 (REFLECTION ): 168 bytes
Type 1B (DELEGATE_HEAD ): 1080 bytes
Type 1D (OBJECT_TO_EVENT ): 264 bytes
Type 1E (BINARY_BLOB_HEAD ): 447132 bytes
Type 1F (THREAD ): 1536 bytes
Type 20 (SUBTHREAD ): 144 bytes
Type 21 (STACK_FRAME ): 1908 bytes
Type 22 (TIMER_HEAD ): 72 bytes
Type 27 (FINALIZER_HEAD ): 264 bytes
Type 31 (IO_PORT ): 216 bytes
Type 34 (APPDOMAIN_HEAD ): 72 bytes
Type 36 (APPDOMAIN_ASSEMBLY ): 3996 bytes
GC: performing heap compaction…
66542760
GC: performing heap compaction…
#### Exception System.OutOfMemoryException - CLR_E_OUT_OF_MEMORY (1) ####
#### Message:
#### Microsoft.SPOT.Bitmap::GetBitmap [IP: 0000] ####
#### Camera_Test.Program::ConvertToFile [IP: 001b] ####
#### Camera_Test.Program::timer_Tick [IP: 005e] ####
#### Gadgeteer.Timer::dt_Tick [IP: 0018] ####
#### Microsoft.SPOT.DispatcherTimer::FireTick [IP: 0010] ####
#### Microsoft.SPOT.Dispatcher::PushFrameImpl [IP: 0054] ####
#### Microsoft.SPOT.Dispatcher::PushFrame [IP: 001a] ####
#### Microsoft.SPOT.Dispatcher::Run [IP: 0006] ####
#### Gadgeteer.Program::Run [IP: 001d] ####
A first chance exception of type ‘System.OutOfMemoryException’ occurred in Microsoft.SPOT.Graphics.dll
Exception performing Timer operation

Can you report free memory (no GC) as you enter the convert function, force GC, and see if that cleans anything up before the exception hits.

But you ARE talking about VGA size too, that’s not a small amount of memory to have used (plus the incoming bitmap). You may have hit heap size limit ? (someone step in here and use the right terms :slight_smile: @ Taylorza, looking for you ! :slight_smile: )

I put the debug.gc(false) just before the call to ConvertToFIle. Also printed out the size of the buffer I have allocated for the file.

Using mainboard GHI Electronics FEZ Raptor version 1.0
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
The thread ‘’ (0x3) has exited with code 0 (0x0).
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Tick …
Image Ready …
The thread ‘’ (0x6) has exited with code 0 (0x0).
fileSize=921654
GC: 1msec 561984 bytes used, 66543780 bytes available
Type 0F (STRING ): 852 bytes
Type 11 (CLASS ): 14844 bytes
Type 12 (VALUETYPE ): 1248 bytes
Type 13 (SZARRAY ): 52884 bytes
Type 03 (U1 ): 48084 bytes
Type 04 (CHAR ): 1032 bytes
Type 07 (I4 ): 1332 bytes
Type 11 (CLASS ): 2436 bytes
Type 15 (FREEBLOCK ): 66543780 bytes
Type 16 (CACHEDBLOCK ): 648 bytes
Type 17 (ASSEMBLY ): 35052 bytes
Type 18 (WEAKCLASS ): 96 bytes
Type 19 (REFLECTION ): 168 bytes
Type 1B (DELEGATE_HEAD ): 1080 bytes
Type 1D (OBJECT_TO_EVENT ): 264 bytes
Type 1E (BINARY_BLOB_HEAD ): 446892 bytes
Type 1F (THREAD ): 1536 bytes
Type 20 (SUBTHREAD ): 144 bytes
Type 21 (STACK_FRAME ): 1656 bytes
Type 22 (TIMER_HEAD ): 72 bytes
Type 27 (FINALIZER_HEAD ): 264 bytes
Type 31 (IO_PORT ): 216 bytes
Type 34 (APPDOMAIN_HEAD ): 72 bytes
Type 36 (APPDOMAIN_ASSEMBLY ): 3996 bytes
66543780
About to die!!!
GC: 1msec 563664 bytes used, 66542100 bytes available
Type 0F (STRING ): 852 bytes
Type 11 (CLASS ): 14820 bytes
Type 12 (VALUETYPE ): 1224 bytes
Type 13 (SZARRAY ): 52884 bytes
Type 03 (U1 ): 48084 bytes
Type 04 (CHAR ): 1032 bytes
Type 07 (I4 ): 1332 bytes
Type 11 (CLASS ): 2436 bytes
Type 15 (FREEBLOCK ): 66542100 bytes
Type 16 (CACHEDBLOCK ): 48 bytes
Type 17 (ASSEMBLY ): 35052 bytes
Type 18 (WEAKCLASS ): 96 bytes
Type 19 (REFLECTION ): 168 bytes
Type 1B (DELEGATE_HEAD ): 1080 bytes
Type 1D (OBJECT_TO_EVENT ): 264 bytes
Type 1E (BINARY_BLOB_HEAD ): 448548 bytes
Type 1F (THREAD ): 1920 bytes
Type 20 (SUBTHREAD ): 192 bytes
Type 21 (STACK_FRAME ): 1920 bytes
Type 22 (TIMER_HEAD ): 72 bytes
Type 27 (FINALIZER_HEAD ): 240 bytes
Type 31 (IO_PORT ): 216 bytes
Type 34 (APPDOMAIN_HEAD ): 72 bytes
Type 36 (APPDOMAIN_ASSEMBLY ): 3996 bytes
GC: performing heap compaction…
66542100
GC: performing heap compaction…
#### Exception System.OutOfMemoryException - CLR_E_OUT_OF_MEMORY (1) ####
#### Message:
#### Microsoft.SPOT.Bitmap::GetBitmap [IP: 0000] ####
#### Camera_Test.Program::ConvertToFile [IP: 001b] ####
#### Camera_Test.Program::timer_Tick [IP: 007d] ####
#### Gadgeteer.Timer::dt_Tick [IP: 0018] ####
#### Microsoft.SPOT.DispatcherTimer::FireTick [IP: 0010] ####
#### Microsoft.SPOT.Dispatcher::PushFrameImpl [IP: 0054] ####
#### Microsoft.SPOT.Dispatcher::PushFrame [IP: 001a] ####
#### Microsoft.SPOT.Dispatcher::Run [IP: 0006] ####
#### Gadgeteer.Program::Run [IP: 001d] ####
A first chance exception of type ‘System.OutOfMemoryException’ occurred in Microsoft.SPOT.Graphics.dll
Exception performing Timer operation