blImageSerialization — Simple functions to serialize/unserialize an IplImage using opencv

Introduction

Whenever working with any type of data, especially images or videos, we usually run into the need of having to save the data to then load it back up for later use.

The images can either be saved in binary format or text format.  The process of saving/loading data is called serialization/deserialization and it could be considered a fairly complex subject.

The blImageAPI makes it very easy to work images, so naturally, in this article I introduce a few simple functions that will allows us to save/load images using only a couple of lines of code.

What problems can I solve with these functions?

These functions will allow you to code your way around the following questions with only a couple of lines of code:

  1. How can I save an IplImage to an XML or YAML file stream?
  2. How can I save a blImage to an XML or YAML file stream?
  3. How can I load an IplImage from an XML or YAML file stream?
  4. How can I load a blImage from an XML or YAML file stream?

A little background

OpenCV provides a few low-level functions to deal with data persistence.  But the basic idea is that when writing to a file stream, data is written sequentially, but when reading from a file stream, the data is loaded into a hash table for easy access.  It uses the following two structures:

  1. CvFileStorage
    1. The structure used to open/create a data file.  They can be opened to write or read.
  2. CvFileNode
    1. The structure used to hold the data once its loaded.  Each file node has a unique name identifier and thus can be searched by name or a pointer to their name.  Check this Opencv manual for further study.

Once the data is loaded into file nodes, the file nodes can be searched and accessed by their name.  The class presented here wraps the CvFileStorage structure, and provides a few functions to allows us to save/load images using one line of code.

Note:  Since the file nodes can be accessed by their name, when writing/saving data to a file storage, especially if we save multiple images to one file storage, we have to make sure that each image or set of data has a unique name.  OpenCV will yell at you if you don’t abide by that, telling you that you have duplicate keys.

The list of functions so far

So far, the following functions have been defined:

  1. DetermineTypeOfImage
    1. It takes an IplImage and builds a string that represents its type (depth and number of channels)
  2. DetermineDepthAndNumOfChannels
    1. Determines what the depth and number of channels are from a string.  (The opposite of the above function)
  3. WriteImageToFileStorage
    1. Writes an IplImage to a file storage using a specified image tag (remember all tags in a file storage have to be unique, or OpenCV won’t be happy when loading the file).
    2. This function actually takes the IplImage, and writes it to an XML or YAML file storage the same way that the function “cvSave” does it.
  4. ReadImageFromFileStorage
    1. Reads/Loads an image from a file storage looking for a specified image tag.  There are two overloaded forms of this function:
      1. The first one reads an image into an IplImage, where the final IplImage depth and number of channels will be dictated by the original image that was saved.
      2. The second one reads an image into a blImage, where the final depth and number of channels is statically determined by the type of the blImage.
        1. For example blImage<unsigned char> will always load the image as having depth IPL_DEPTH_8U and having one channel.

The code

I have saved all the functions in a file called blImageSerialization.hpp which follows:

#ifndef BL_IMAGESERIALIZATION_HPP
#define BL_IMAGESERIALIZATION_HPP

//-------------------------------------------------------------------
// FILE:            blImageSerialization.hpp
// CLASS:           None
// BASE CLASS:      None
//
// PURPOSE:         A collection of simple functions used to
//                  serialize/unserialize images to/from a
//                  file storage
//
// AUTHOR:          Vincenzo Barbato
//                  http://www.barbatolabs.com
//                  navyenzo@gmail.com
//
// LISENSE:         MIT-LICENCE
//                  http://www.opensource.org/licenses/mit-license.php
//
// DEPENDENCIES:    - Opencv cxcore library
//
// NOTES:
//
// DATE CREATED:    Jun/05/2011
// DATE UPDATED:
//-------------------------------------------------------------------

//-------------------------------------------------------------------
// Includes and libs needed for this file
//-------------------------------------------------------------------
//-------------------------------------------------------------------

//-------------------------------------------------------------------
// Enums needed for this file
//-------------------------------------------------------------------
//-------------------------------------------------------------------

//-------------------------------------------------------------------
// FUNCTION:            DetermineTypeOfImage
//
// ARGUMENTS:           SrcImage
//
// TEMPLATE ARGUMENTS:  None
//
// PURPOSE:             Build a string used to represent
//                      the type of image when saving
//                      the image to disk
//
// DEPENDENCIES:        IplImage
//-------------------------------------------------------------------
inline string DetermineTypeOfImage(const IplImage* SrcImage)
{
    string TypeOfImage = &amp;quot;&amp;quot;;

    // Check to see if the image is NULL
    if(SrcImage == NULL)
    {
        // Error -- Tried to determine
        //          the type of a NULL
        //          image
        return TypeOfImage;
    }

    // Check the number of channels
    switch(SrcImage-&amp;gt;nChannels)
    {
    case 2:
        TypeOfImage.append(&amp;quot;2&amp;quot;);
        break;
    case 3:
        TypeOfImage.append(&amp;quot;3&amp;quot;);
        break;
    case 4:
        TypeOfImage.append(&amp;quot;4&amp;quot;);
        break;
    default:
        // In this case we
        // have one channel so
        // we do not have to write it
        break;
    }

    // Check the image depth
    switch(SrcImage-&amp;gt;depth)
    {
    case IPL_DEPTH_1U:
        TypeOfImage.append(&amp;quot;u&amp;quot;);
        break;
    case IPL_DEPTH_8U:
        TypeOfImage.append(&amp;quot;u&amp;quot;);
        break;
    case IPL_DEPTH_8S:
        TypeOfImage.append(&amp;quot;c&amp;quot;);
        break;
    case IPL_DEPTH_16U:
        TypeOfImage.append(&amp;quot;w&amp;quot;);
        break;
    case IPL_DEPTH_16S:
        TypeOfImage.append(&amp;quot;s&amp;quot;);
        break;
    case IPL_DEPTH_32S:
        TypeOfImage.append(&amp;quot;i&amp;quot;);
        break;
    case IPL_DEPTH_32F:
        TypeOfImage.append(&amp;quot;f&amp;quot;);
        break;
    case IPL_DEPTH_64F:
        TypeOfImage.append(&amp;quot;d&amp;quot;);
        break;
    default:
        TypeOfImage.append(&amp;quot;u&amp;quot;);
        break;
    }

    return TypeOfImage;
}
//-------------------------------------------------------------------

//-------------------------------------------------------------------
// FUNCTION:            DetermineDepthAndNumOfChannels
//
// ARGUMENTS:           TypeOfImage
//                      Depth
//                      NumOfChannels
//
// TEMPLATE ARGUMENTS:  None
//
// PURPOSE:             Determine the depth and number of
//                      channels of an image from a string
//                      when loading an image from file
//
// DEPENDENCIES:        IplImage
//-------------------------------------------------------------------
inline void DetermineDepthAndNumOfChannels(const string&amp;amp; TypeOfImage,
                                           int&amp;amp; Depth,
                                           int&amp;amp; NumOfChannels)
{
    // We simply check the type
    // of image and determine the
    // appropriate depth and number
    // of channels
    if(TypeOfImage == &amp;quot;u&amp;quot;)
    {
        Depth = IPL_DEPTH_8U;
        NumOfChannels = 1;
    }
    else if(TypeOfImage == &amp;quot;c&amp;quot;)
    {
        Depth = IPL_DEPTH_8S;
        NumOfChannels = 1;
    }
    else if(TypeOfImage == &amp;quot;w&amp;quot;)
    {
        Depth = IPL_DEPTH_16U;
        NumOfChannels = 1;
    }
    else if(TypeOfImage == &amp;quot;s&amp;quot;)
    {
        Depth = IPL_DEPTH_16S;
        NumOfChannels = 1;
    }
    else if(TypeOfImage == &amp;quot;i&amp;quot;)
    {
        Depth = IPL_DEPTH_32S;
        NumOfChannels = 1;
    }
    else if(TypeOfImage == &amp;quot;f&amp;quot;)
    {
        Depth = IPL_DEPTH_32F;
        NumOfChannels = 1;
    }
    else if(TypeOfImage == &amp;quot;d&amp;quot;)
    {
        Depth = IPL_DEPTH_64F;
        NumOfChannels = 1;
    }
    else if(TypeOfImage == &amp;quot;2u&amp;quot;)
    {
        Depth = IPL_DEPTH_8U;
        NumOfChannels = 2;
    }
    else if(TypeOfImage == &amp;quot;2c&amp;quot;)
    {
        Depth = IPL_DEPTH_8S;
        NumOfChannels = 2;
    }
    else if(TypeOfImage == &amp;quot;2w&amp;quot;)
    {
        Depth = IPL_DEPTH_16U;
        NumOfChannels = 2;
    }
    else if(TypeOfImage == &amp;quot;2s&amp;quot;)
    {
        Depth = IPL_DEPTH_16S;
        NumOfChannels = 2;
    }
    else if(TypeOfImage == &amp;quot;2i&amp;quot;)
    {
        Depth = IPL_DEPTH_32S;
        NumOfChannels = 2;
    }
    else if(TypeOfImage == &amp;quot;2f&amp;quot;)
    {
        Depth = IPL_DEPTH_32F;
        NumOfChannels = 2;
    }
    else if(TypeOfImage == &amp;quot;2d&amp;quot;)
    {
        Depth = IPL_DEPTH_64F;
        NumOfChannels = 2;
    }
    else if(TypeOfImage == &amp;quot;3u&amp;quot;)
    {
        Depth = IPL_DEPTH_8U;
        NumOfChannels = 3;
    }
    else if(TypeOfImage == &amp;quot;3c&amp;quot;)
    {
        Depth = IPL_DEPTH_8S;
        NumOfChannels = 3;
    }
    else if(TypeOfImage == &amp;quot;3w&amp;quot;)
    {
        Depth = IPL_DEPTH_16U;
        NumOfChannels = 3;
    }
    else if(TypeOfImage == &amp;quot;3s&amp;quot;)
    {
        Depth = IPL_DEPTH_16S;
        NumOfChannels = 3;
    }
    else if(TypeOfImage == &amp;quot;3i&amp;quot;)
    {
        Depth = IPL_DEPTH_32S;
        NumOfChannels = 3;
    }
    else if(TypeOfImage == &amp;quot;3f&amp;quot;)
    {
        Depth = IPL_DEPTH_32F;
        NumOfChannels = 3;
    }
    else if(TypeOfImage == &amp;quot;3d&amp;quot;)
    {
        Depth = IPL_DEPTH_64F;
        NumOfChannels = 3;
    }
    else if(TypeOfImage == &amp;quot;4u&amp;quot;)
    {
        Depth = IPL_DEPTH_8U;
        NumOfChannels = 4;
    }
    else if(TypeOfImage == &amp;quot;4c&amp;quot;)
    {
        Depth = IPL_DEPTH_8S;
        NumOfChannels = 4;
    }
    else if(TypeOfImage == &amp;quot;4w&amp;quot;)
    {
        Depth = IPL_DEPTH_16U;
        NumOfChannels = 4;
    }
    else if(TypeOfImage == &amp;quot;4s&amp;quot;)
    {
        Depth = IPL_DEPTH_16S;
        NumOfChannels = 4;
    }
    else if(TypeOfImage == &amp;quot;4i&amp;quot;)
    {
        Depth = IPL_DEPTH_32S;
        NumOfChannels = 4;
    }
    else if(TypeOfImage == &amp;quot;4f&amp;quot;)
    {
        Depth = IPL_DEPTH_32F;
        NumOfChannels = 4;
    }
    else if(TypeOfImage == &amp;quot;4d&amp;quot;)
    {
        Depth = IPL_DEPTH_64F;
        NumOfChannels = 4;
    }
    else
    {
        // Error -- We did not recognize the
        //          image type from the string
        //          so we simply assume a single
        //          channel unsigned char image
        Depth = IPL_DEPTH_8U;
        NumOfChannels = 1;
    }
}
//-------------------------------------------------------------------

//-------------------------------------------------------------------
// FUNCTION:            WriteImageToFileStorage
//
// ARGUMENTS:           FileStorage
//                      SrcImage
//                      ImageTag
//
// TEMPLATE ARGUMENTS:  None
//
// PURPOSE:             Serialize an image and write it to a
//                      file storage using a specified image
//                      tag
//
// DEPENDENCIES:        IplImage
//-------------------------------------------------------------------
inline void WriteImageToFileStorage(CvFileStorage* FileStorage,
                                    const IplImage* SrcImage,
                                    const string&amp;amp; ImageTag)
{
    // Here we check if the
    // file storage is NULL
    if(FileStorage == NULL)
    {
        // Error -- File storage used to
        //          store image into is
        //          not open
        return;
    }

    // Now we check if the image
    // we're supposed to save is
    // NULL
    if(SrcImage == NULL)
    {
        // Error -- Tried to save an image
        //          to a file storage but
        //          the image is NULL
        return;
    }

    // First:       Create the image node
    cvStartWriteStruct(FileStorage,ImageTag.c_str(),CV_NODE_MAP,&amp;quot;opencv-image&amp;quot;);

    // Second:      Store the width of the image
    cvWriteInt(FileStorage,&amp;quot;width&amp;quot;,SrcImage-&amp;gt;width);

    // Third:       Store the height of the image
    cvWriteInt(FileStorage,&amp;quot;height&amp;quot;,SrcImage-&amp;gt;height);

    // Fourth:      Store whether the image's
    //              origin is top-left or
    //              bottom-left
    if(SrcImage-&amp;gt;origin == 0)
        cvWriteString(FileStorage,&amp;quot;origin&amp;quot;,&amp;quot;top-left&amp;quot;);
    else
        cvWriteString(FileStorage,&amp;quot;origin&amp;quot;,&amp;quot;bottom-left&amp;quot;);

    // Fifth:       Store whether the image
    //              is interleaved or has
    //              separate color channels
    if(SrcImage-&amp;gt;dataOrder == 0)
        cvWriteString(FileStorage,&amp;quot;layout&amp;quot;,&amp;quot;interleaved&amp;quot;);
    else
        cvWriteString(FileStorage,&amp;quot;layout&amp;quot;,&amp;quot;separate&amp;quot;);

    // Sixth:       Now we have to store the
    //              type of image, meaning the
    //              number of channels and its
    //              depth
    string TypeOfImage = DetermineTypeOfImage(SrcImage);
    cvWriteString(FileStorage,&amp;quot;dt&amp;quot;,TypeOfImage.c_str());

    // Seventh:     Finally we get to the data
    //              here we write the data
    //              as a string
    cvStartWriteStruct(FileStorage,&amp;quot;data&amp;quot;,CV_NODE_SEQ,NULL);
    cvWriteRawData(FileStorage,SrcImage-&amp;gt;imageData,SrcImage-&amp;gt;height*SrcImage-&amp;gt;width,TypeOfImage.c_str());
    cvEndWriteStruct(FileStorage);

    // Eighth:      Here we simply close
    //              the image node we
    //              opened earlier
    cvEndWriteStruct(FileStorage);
}
//-------------------------------------------------------------------

//-------------------------------------------------------------------
// FUNCTION:            ReadImageFromFileStorage
//
// ARGUMENTS:           FileStorage
//                      SrcImage
//                      ImageTag
//
// TEMPLATE ARGUMENTS:  None
//
// PURPOSE:             Read an image from a file storage using
//                      a specified image tag, and unserialize
//                      it and store it into an IplImage
//
// DEPENDENCIES:        IplImage
//-------------------------------------------------------------------
inline void ReadImageFromFileStorage(CvFileStorage* FileStorage,
                                     CvFileNode* Map,
                                     IplImage*&amp;amp; DstImage,
                                     const string&amp;amp; ImageTag)
{
    // Here we check if the
    // file storage is opened
    if(FileStorage == NULL)
    {
        // Error -- File storage used to
        //          read image from is
        //          not open
        return;
    }

    CvFileNode* ImageNode = NULL;

    // We check if we're inside the
    // blImage node that contains
    // our image
    string ImageNodeName = &amp;quot;&amp;quot;;
    if(Map != NULL)
        ImageNodeName = cvGetFileNodeName(Map);

    // If we're not in the image
    // node, we search for one
    if(ImageNodeName != ImageTag)
    {
        ImageNode = cvGetFileNodeByName(FileStorage,Map,ImageTag.c_str());

        // If we failed to find a node, then
        // we simply quit since the file has
        // to be the wrong format
        if(ImageNode == NULL)
        {
            // Error -- We failed to
            //          find a node that
            //          contains an image,
            //          the file format is
            //          wrong
            return;
        }
    }
    else
        ImageNode = Map;

    CvFileNode* CurrentFileNode = NULL;

    // Find the width node
    CurrentFileNode = cvGetFileNodeByName(FileStorage,ImageNode,&amp;quot;width&amp;quot;);
    if(CurrentFileNode == NULL)
    {
        // Error -- We failed to find
        //          the width node
        return;
    }

    // Read the width of the image
    int Cols = cvReadInt(CurrentFileNode);

    // Find the height node
    CurrentFileNode = cvGetFileNodeByName(FileStorage,ImageNode,&amp;quot;height&amp;quot;);
    if(CurrentFileNode == NULL)
    {
        // Error -- We failed to find
        //          the height node
        return;
    }

    // Read the height of the image
    int Rows = cvReadInt(CurrentFileNode);

    // Find the type of image node
    CurrentFileNode = cvGetFileNodeByName(FileStorage,ImageNode,&amp;quot;dt&amp;quot;);
    if(CurrentFileNode == NULL)
    {
        // Error -- We failed to find
        //          the type of image node
        return;
    }

    // Read the type of data
    string TypeOfImage = cvReadString(CurrentFileNode,&amp;quot;u&amp;quot;);

    // From the TypeOfImage string, we
    // determine the depth and number
    // of channels of the image
    int Depth,NumOfChannels;
    DetermineDepthAndNumOfChannels(TypeOfImage,Depth,NumOfChannels);

    // Find the data node
    CurrentFileNode = cvGetFileNodeByName(FileStorage,ImageNode,&amp;quot;data&amp;quot;);
    if(CurrentFileNode == NULL)
    {
        // Error -- We failed to find
        //          the data node
        return;
    }

    // Before we read the data, we check
    // if the image already exists, and if
    // it does, we release it before creating
    // the new one
    if(DstImage != NULL)
    {
        cvReleaseImage(&amp;amp;DstImage);
        DstImage = NULL;
    }

    // Now we create the destination image
    DstImage = cvCreateImage(cvSize(Cols,Rows),Depth,NumOfChannels);

    // Check if the destination image
    // was successfully created
    if(DstImage == NULL)
    {
        // Error -- Failed to make a temporary
        //          image and thus we quite
        return;
    }

    // Here we let opencv
    // load the date into it
    cvReadRawData(FileStorage,CurrentFileNode,DstImage-&amp;gt;imageData,TypeOfImage.c_str());

    // We're done
    return;
}
//-------------------------------------------------------------------

//-------------------------------------------------------------------
// FUNCTION:            ReadImageFromFileStorage
//
// ARGUMENTS:           FileStorage
//                      SrcImage
//                      ImageTag
//
// TEMPLATE ARGUMENTS:  blType
//
// PURPOSE:             This function, just like the one above,
//                      reads an image from a file storage using
//                      a specified image tag, but unserializes it
//                      and stores it inside a blImage of a specified
//                      type, where the type is assumed by the
//                      type of the blImage
//
// DEPENDENCIES:        blImage
//-------------------------------------------------------------------
template
inline void ReadImageFromFileStorage(CvFileStorage* FileStorage,
                                     CvFileNode* Map,
                                     blImage&amp;amp; DstImage,
                                     const string&amp;amp; ImageTag)
{
    // First we load the image into
    // a generic IplImage pointer
    IplImage* Img = NULL;
    ReadImageFromFileStorage(FileStorage,Map,Img,ImageTag);

    // We then simply wrap the
    // loaded image by the
    // DstImage blImage passed to
    // this function
    DstImage.WrapIplImage(Img);
}
//-------------------------------------------------------------------

#endif // BL_IMAGESERIALIZATION_HPP

Usage

The whole point of these functions is to make it super easy to save/load images or to write images to xml/yaml to send them over the network (I’m currently working on the network functionality).

The following snippet loads an image from disk into memory.  It then encodes it while in memory, and then saves the encoded image to an xml file.  The xml file is then loaded, the encoded image is read from the file storage and then decoded and shown in an opencv window.

#include "blImageAPI/blImageAPI.hpp"
using namespace blImageAPI;

int main(int argc, char *argv[])
{
    /*// Create the app
    blMyApp MyApp;

    // Setup the app
    MyApp.SetupApp();

    // Run the app
    MyApp.MainLoop();*/

    // Load an image from disk
    blImage&amp;lt; blColor3  &amp;gt; MyImage;
    MyImage.LoadImageFromFile(&amp;quot;C:/Documents and Settings/VBarbato/Desktop/Ball.png&amp;quot;);

    // Show the original image
    cvShowImage(&amp;quot;Original Image&amp;quot;,MyImage);

    // Now we encode the image
    blImage MyEncodedImage;
    EncodeImage(MyImage,MyEncodedImage);

    // Now create a file storage
    CvFileStorage* MyFileStorage = NULL;
    MyFileStorage = cvOpenFileStorage(&amp;quot;C:/Documents and Settings/VBarbato/Desktop/MyImages.xml&amp;quot;,NULL,CV_STORAGE_WRITE);

    // Write/Save the encoded image to the file storage
    // Note how it's only one line of code
    WriteImageToFileStorage(MyFileStorage,MyEncodedImage,&amp;quot;Ball&amp;quot;);

    // Close the file storage
    cvReleaseFileStorage(&amp;amp;MyFileStorage);

    // Now open the file storage again,
    // but this time to read the image we
    // saved above
    CvFileStorage* MyFileStorage2 = NULL;
    MyFileStorage2 = cvOpenFileStorage(&amp;quot;C:/Documents and Settings/VBarbato/Desktop/MyImages.xml&amp;quot;,NULL,CV_STORAGE_READ);

    // Let's read the image into
    // a destination image (remember the
    // image was encoded before saving it)
    blImage&amp;lt; unsigned char &amp;gt; DstEncodedImage;
    ReadImageFromFileStorage(MyFileStorage2,NULL,DstEncodedImage,&amp;quot;Ball&amp;quot;);

    // Finally we clode the file storage
    cvReleaseFileStorage(&amp;amp;MyFileStorage2);

    // Now we can decode the image so
    // that we can display it
    blImage&amp;lt; blColor3 &amp;gt; DstImage;
    DecodeImage(DstEncodedImage,DstImage);

    // Here we show the read image
    cvShowImage(&amp;quot;Read Image&amp;quot;,DstImage);

    // Wait for a key input to quit the program
    cvWaitKey(0);

    return 0;
}

The output of the program looks like the following:

Note how the original image and the encoded, then saved, then read then decoded image look the same, all with a couple of lines of code

Downloads

I have put all the files into a zip file which can be downloaded here.  All you have to do is extract it somewhere, let’s say in a directory called blImageAPI, and then include the blImageAPI.hpp file as follows:


#include &amp;quot;blImageAPI/blImageAPI.hpp&amp;quot;

using namespace blImageAPI;

Note: Everything is declared in a namespace blImageAPI, and such you would use it as: blImageAPI::blImage

[download#1]

Updates

Updates regarding these functions will be listed here

About Vincenzo Barbato

Known to his friends as Enzo, he's an outside-the-box engineer/researcher whose interests and expertise span many fields, including controls systems, multi-physics simulations, mechatronics, oil technologies, data analysis and machine vision just to name a few.

Refusing to grow up, he's on a continuous journey to develop simple and creative solutions that have the power of disrupting industries by optimizing systems and processes.

Married to a beautiful wife, with two beautiful daughters and two identical twin boys, his home is a never ending chaotic fountain of inspiration.

His outlook on life:

"Never blindly accept what you're told, listen, but then question, with curiosity, creativity and collaboration we can change the world"

About Enzo

Known to his friends as Enzo, he's an outside-the-box engineer/researcher whose interests and expertise span many fields, including controls systems, multi-physics simulations, mechatronics, oil technologies, data analysis and machine vision just to name a few. Refusing to grow up, he's on a continuous journey to develop simple and creative solutions that have the power of disrupting industries by optimizing systems and processes. Married to a beautiful wife, with two beautiful daughters and two identical twin boys, his home is a never ending chaotic fountain of inspiration. His outlook on life: "Never blindly accept what you're told, listen, but then question, with curiosity, creativity and collaboration we can change the world"

Leave a Reply

Your email address will not be published. Required fields are marked *