Back in the good old days of OpenCV 1.1 and earlier, the main image structure used throughout their algorithms was IplImage. Playing with IplImage requires skillful manipulation of data in memory, and thus makes it very difficult for beginners to play. Even experts run into the dreaded memory leaks caused by an image they forget to release somewhere in their code.
Then of course, you want to access image pixels, and that’s where it gets tough. OpenCV does provide get and set functions, but the performance hits start to accumulate when building real-time systems.
In this article, with the help of boost::shared_ptr, I design blImage, a simple, very fast, secure, yet scalable image structure, which I will use for all my work.
Here’s some sample code showing the potential of what will be possible with blImage:
int main(int argc, char *argv[])
{
// Create a blank 8-bit color image
blImage< blColor3<unsigned char> > MyImage;
// Load an image from file
MyImage.LoadImageFromFile("C:\\tux.png");
// Create a floating point grayscale
// version of our colored image
blImage<unsigned char> MyImageFloatGray = MyImage;
// Create two windows and show
// both images side by side
cvNamedWindow("ColorImage",CV_WINDOW_AUTOSIZE);
cvNamedWindow("GrayscaleImage",CV_WINDOW_AUTOSIZE);
cvShowImage("ColorImage",MyImage);
cvShowImage("GrayscaleImage",MyImageFloatGray);
// wait for a key
cvWaitKey(0);
return 0;
}
CvScalar from the OpenCV library (for loading images into blImage)
Design Philosophy
Accessibility is the mother of all catalysts when it comes to invention. The more people have access to more tools, the faster we move forward in a never ending journey of advancing technology. But, as the world would have it, easy access to technology can lead to a chaotic soup of unsustainable and unstable systems.
Thus, our image structure abides by the following principles:
Simplicity
Easy to read/understand code (a kid should be able to use it)
Follows a well documented layout
Scalability
Easy to derive from to add functionality
Could be used for small or large programs
Could be used as a generalized Matrix structure
Performance
Incur as little cost as possible compared to a raw IplImage* pointer
Robustness
Account for possible errors and provide feedback
Image Depth
Before we build our image structure, we have to account for the different depths that we could need. For example, we might be playing with an 8-bit image in one algorithm, and a 32-bit image in another algorithm, or be mixing and matching images of different depths.
In this design, we account for the following depths:
“bool” — (1-bit)
“unsigned char” and “char” — (8-bits unsigned and signed)
“unsigned short” and “short” — (16-bits unsigned and signed)
“int” — (32-bits signed, we Only account for the 32-bit signed)
“float” — (32-bits floating point)
“double” — (64-bits floating point)
Note that for each depth, we have a range for the possible pixel values:
1-bit — [0,1]
8-bit unsigned — [0,255]
8-bit signed — [-128,127]
16-bit unsigned — [0,65535]
16-bit signed — [-32768,32767]
32-bit signed — [-2147483648,2147483647]
32-bit float — [0,1]
64-bit double — [0,1]
Image Channels
Of course, we can’t have an image without having color. We represent color by introducing the idea of channels. OpenCV uses a BGR sequence as opposed to the more common RGB to store values. Also, since our image structure can be used as a generalized matrix, we need to account for several cases:
One Channel — One dimensional color
Grayscale images
Single-valued matrices (Linear Algebra)
Two Channels –Two dimensional color
For special type images
Complex valued matrices (Complex Algebra)
Three Channels — Three dimensional color
Colored images (BGR or HSV images)
Four dimensional color
Colored images with alpha transparency values (ex. BGRA)
Representation of Color
For One-Channel images we’re going to use c++ basic data types corresponding to the depth of the images.
For Two-Channel images we’re going to use std::complex<T> with T corresponding to the depth of the images.
For Three-Channel images, we’re going to build a basic three-color structure, remembering that IplImage uses a BGR sequence instead of the widespread RGB sequence. We’re calling this color structure blColor3, and saving it in a header file called blColor3.hpp
Since we’re going to use boost::shared_ptr to hold our images, we’re going to need a simple functor for shared_ptr to properly release our images after we’re done using them. We’re going to put the functor in a separate file called blCleanResources.hpp, which will serve as a depository for other clean up code.
#ifndef BL_CLEANRESOURCES_HPP
#define BL_CLEANRESOURCES_HPP
//-------------------------------------------------------------------
// FILE: blCleanResources.hpp
// CLASS: None
// BASE CLASS: None
// PURPOSE: Special functions needed to handle the release
// of various resources such as OpenCV's IplImage
// AUTHOR: Vincenzo Barbato
// http://www.barbatolabs.com
// navyenzo@gmail.com
// LISENSE: MIT-LICENCE
// http://www.opensource.org/licenses/mit-license.php
// DEPENDENCIES: IplImage -- Image structure from opencv
// cvReleaseImage -- To release the image
// CvCapture -- Capture device structure from opencv
// cvReleaseCapture -- To release the capture device
// CvMemStorage -- Memory storage structure from opencv
// cvReleaseMemStorage -- To realase the memory storage
// NOTES:
// DATE CREATED: May/30/2010
// DATE UPDATED:
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Includes and libs needed for this file and sub-files
//-------------------------------------------------------------------
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Enums used for this file and sub-files
//-------------------------------------------------------------------
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Functor used to delete a shared_ptr without
// destroying the objects it's pointing to
//-------------------------------------------------------------------
class null_deleter
{
public:
// Overloaded operator used to release an IplImage
void operator()(void const*)const
{
}
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Functor used to release an IplImage
//-------------------------------------------------------------------
class ReleaseImage
{
public:
// Overloaded operator used to release an IplImage
void operator()(IplImage* Image)
{
// Check if we have an image
if(Image == NULL)
return;
// Release the image
cvReleaseImage(&Image);
// Nullify the pointer
Image = NULL;
}
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Functor used to release an OpenCV Capture Device
//-------------------------------------------------------------------
class ReleaseCaptureDevice
{
public:
// Overloaded operator used to
// release a capture device
void operator()(CvCapture* CaptureDevice)
{
// Check if we have a capture device
if(CaptureDevice == NULL)
return;
// Release the image
cvReleaseCapture(&CaptureDevice);
// Nullify the pointer
CaptureDevice = NULL;
}
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Functor used to release an OpenCV memory storage
//-------------------------------------------------------------------
class ReleaseMemStorage
{
public:
// Overloaded operator used to
// release a memory storage
void operator()(CvMemStorage* MemoryStorage)
{
// Check if we have a capture device
if(MemoryStorage == NULL)
return;
// Release the image
cvReleaseMemStorage(&MemoryStorage);
// Nullify the pointer
MemoryStorage = NULL;
}
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Functor used to release an OpenCV file storage
//-------------------------------------------------------------------
class ReleaseFileStorage
{
public:
// Overloaded operator used to
// release a memory storage
void operator()(CvFileStorage* FileStorage)
{
// Check if we have a capture device
if(FileStorage == NULL)
return;
// Release the image
cvReleaseFileStorage(&FileStorage);
// Nullify the pointer
FileStorage = NULL;
}
};
//-------------------------------------------------------------------
#endif // BL_CLEANRESOURCES_HPP
Our Image Structure
Finally, we have come to our basic image structure. We’re going to separate the image structure into two classes:
blImg — (Base class used to wrap raw IplImage pointers)
Automatic garbage collection (with the use of boost::shared_ptr)
Generic image coding (through the use of template meta-programming)
Direct, efficient and easy access to pixels.
blImage — (Derived class used to add basic image functionality)
Provides constructors and functions to build, load and copy images from several sources, such as image files, images in memory, images from webcams, generalized matrix data and
The base blImg class is defined in a header file called blImg.hpp. This is a very long file that contains all the template specialization code to handle different image depths and number of channels
#ifndef BL_IMAGE_HPP
#define BL_IMAGE_HPP
//-------------------------------------------------------------------
// FILE: blImage.hpp
// CLASS: blImage
// BASE CLASS: blImg
//
// PURPOSE: Based on blImg, adds more functionality to the
// OpenCV IplImage wrap
//
// AUTHOR: Vincenzo Barbato
// http://www.barbatolabs.com
// navyenzo@gmail.com
//
// LISENSE: MIT-LICENCE
// http://www.opensource.org/licenses/mit-license.php
//
// DEPENDENCIES: blImg
// CvScalar -- To covert between different type images
//
// NOTES:
// DATE CREATED: Jun/03/2010
// DATE UPDATED:
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Includes and libs needed for this file
//-------------------------------------------------------------------
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Enums needed for this file
//-------------------------------------------------------------------
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
class blImage : public blImg<blType>
{
public: // Constructors and destructors
// Default constructor
blImage(const int& Rows = 1,const int& Cols = 1);
// Copy constructor
blImage(const blImage<blType>& Image);
// Copy constructor from a different type image
template<typename blType2>
blImage(const blImage<blType2>& Image,const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2 = 0);
// Constructor from a raw IplImage*
blImage(const IplImage* Img,const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2 = 0);
// Constructor from a raw CvMat*
blImage(const CvMat* Img,const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2 = 0);
// Construct image from raw 2D array
template<int NumOfRows,int NumOfCols>
blImage(const blType (&MatrixArray)[NumOfRows][NumOfCols]);
// Destructor
~blImage()
{
}
public: // Public functions
// Function used to wrap an existing
// IplImage with a blImage and NULLIFY
// the original IplImage pointer
void WrapIplImage(IplImage*& Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2 = 0);
// Function used to load an IplImage into this object
void LoadImage(const IplImage* Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2 = 0);
// Function used to load a CvMat into this object
void LoadMatrix(const CvMat* Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2 = 0);
// Function used to load an image from file
void LoadImageFromFile(const string& Filename,
const bool& ShouldImageBeFlipped = false,
const int& ImageFlipMode = 0,
const int& HowToReadImageColorAndDepth =
CV_LOAD_IMAGE_ANYDEPTH |
CV_LOAD_IMAGE_ANYCOLOR,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2 = 0);
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline blImage<blType>::blImage(const int& Rows,const int& Cols)
: blImg<blType>(Rows,Cols)
{
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline blImage<blType>::blImage(const blImage<blType>& Image) : blImg<blType>(Image)
{
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
template<typename blType2>
inline blImage<blType>::blImage(const blImage<blType2>& Image,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
: blImg<blType>()
{
// Since we have a different type image, we
// have to do some extra work, but we do want
// to copy image and not link
LoadImage(Image,InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2);
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline blImage<blType>::blImage(const IplImage* Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
: blImg<blType>()
{
LoadImage(Img,InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2);
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline blImage<blType>::blImage(const CvMat* Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
: blImg<blType>()
{
this->LoadMatrix(Img,InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2);
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
template<int NumOfRows,int NumOfCols>
inline blImage<blType>::blImage(const blType (&MatrixArray)[NumOfRows][NumOfCols])
: blImg<blType>(NumOfRows,NumOfCols)
{
// Fill the image values using the passed constant array
for(int i = 0; i < NumOfRows; ++i)
for(int j = 0; j < NumOfCols; ++j)
(*this)[i][j] = MatrixArray[i][j];
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline void blImage<blType>::WrapIplImage(IplImage*& Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
{
// First we have to check
// if the passed image pointer
// is NULL
if(Img == NULL)
{
// Error -- Tried to wrap an
// IplImage pointer
// that was NULL
return;
}
// Since blImage has a statically
// determined depth and number of
// channels, if we want to wrap
// an existing IplImage pointer, we
// have to check if both the depth
// and number of channels match this
// blImage
if(Img->depth == this->GetDepth() &&
Img->nChannels == this->GetNumOfChannels())
{
// In this case they match, therefore
// we can simply link to the existing
// image pointer and then NULLIFY it
this->m_Image = boost::shared_ptr<IplImage>(Img,ReleaseImage());
Img = NULL;
}
else
{
// In this case they don't match,
// therefore we first copy the
// existing image pointer to this
// blImage and then release the original
// image and NULLIFY the pointer
this->LoadImage(Img,InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2);
cvReleaseImage(&Img);
Img = NULL;
}
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline void blImage<blType>::LoadMatrix(const CvMat* Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
{
// First we need to check
// the validity of the
// CvMat* pointer
if(Img == NULL)
{
// Error -- Tried to load a
// CvMat* structure
// into this image
// using a NULL pointer.
// In this case we
// simply default
// this image to
// size 1x1 and return
this->CreateImage(1,1);
return;
}
// Now that we know we don't
// have a NULL pointer, we
// create an IplImage header
// from the CvMat image
IplImage MatToImg;
cvGetImage(Img,&MatToImg);
// Now we can finally load
// the matrix into this image
this->LoadImage(&MatToImg,InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2);
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline void blImage<blType>::LoadImage(const IplImage* Img,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
{
// Because the image has some default depth and number of channels,
// we have to check if it corresponds to this object's image depth and number
// of channels, otherwise we have to do some extra work
if(Img->depth == this->GetDepth() &&
Img->nChannels == this->GetNumOfChannels())
{
this->m_Image = boost::shared_ptr<IplImage>(cvCloneImage(Img),ReleaseImage());
}
else
{
// In this case, the depths or channels don't match, so we
// have to do some extra work. We have to create a new image
// with the correct depth and number of channels and then
// copy the passed image into this one
this->CreateImage(Img->height,Img->width);
// Check for zero size
if(Img->width == 0 || Img->height == 0)
return;
// If we created the image successfully then copy the images
if(this->m_Image.use_count() > 0)
{
// Check the number of channels and call the appropriate functions
if(this->GetNumOfChannels() == Img->nChannels)
{
// At this point we know that the number of channels match, but
// that the depths are different, so we use the cvConvertScale
// function to convert the image to the correct depth
this->ConvertScale(Img,(*this));
}
else if(this->GetNumOfChannels() == 1 && Img->nChannels == 3)
{
if(this->GetDepth() == Img->depth)
{
if(Img->depth == IPL_DEPTH_8U ||
Img->depth == IPL_DEPTH_16U ||
Img->depth == IPL_DEPTH_32F)
{
cvCvtColor(Img,(*this),CV_BGR2GRAY);
}
else
{
CvScalar Color;
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
Color = cvGet2D(Img,i,j);
(*this)[i][j] = 0.114*Color.val[0] + 0.587*Color.val[1] + 0.299*Color.val[2];
}
}
}
}
else
{
// The depths are different so we have to create an intermediary
// image to take care of the two conversions
// Create a new temporary image from the loaded one
IplImage* Img2 = cvCreateImage(cvSize(Img->width,Img->height),Img->depth,1);
// Convert the loaded image into this new image (to grayscale)
if(Img->depth == IPL_DEPTH_8U ||
Img->depth == IPL_DEPTH_16U ||
Img->depth == IPL_DEPTH_32F)
{
cvCvtColor(Img,Img2,CV_BGR2GRAY);
}
else
{
CvScalar Color;
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
Color = cvGet2D(Img,i,j);
cvSet2D(Img2,i,j,cvScalar(0.114*Color.val[0] + 0.587*Color.val[1] + 0.299*Color.val[2]));
}
}
}
// Convert the new grayscale image to this object's image
this->ConvertScale(Img2,(*this));
// Clean up the resources
if(Img2)
cvReleaseImage(&Img2);
}
}
else if((this->GetNumOfChannels() == 3 && Img->nChannels == 1) ||
(this->GetNumOfChannels() == 4 && Img->nChannels == 1))
{
if(this->GetDepth() == Img->depth)
{
// Depths are the same so we just use the convert color function
if(Img->depth == IPL_DEPTH_8U ||
Img->depth == IPL_DEPTH_16U ||
Img->depth == IPL_DEPTH_32F)
{
cvCvtColor(Img,(*this),CV_GRAY2RGB);
}
else
{
CvScalar Color;
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
Color = cvGet2D(Img,i,j);
cvSet2D((*this),i,j,cvScalar(Color.val[0],Color.val[0],Color.val[0]));
}
}
}
}
else
{
// The depths are different so we have to create an intermediary
// image to take care of the two conversions
// Create a new temporary image from the loaded one
IplImage* Img2 = cvCreateImage(cvSize(Img->width,Img->height),Img->depth,this->GetNumOfChannels());
// Convert the loaded image into this new image (to grayscale)
// Depths are the same so we just use the convert color function
if(Img->depth == IPL_DEPTH_8U ||
Img->depth == IPL_DEPTH_16U ||
Img->depth == IPL_DEPTH_32F)
{
cvCvtColor(Img,Img2,CV_GRAY2RGB);
}
else
{
CvScalar Color;
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
Color = cvGet2D(Img,i,j);
cvSet2D(Img2,i,j,cvScalar(Color.val[0],Color.val[0],Color.val[0]));
}
}
}
// Convert the new grayscale image to this object's image
this->ConvertScale(Img2,(*this));
// Clean up the resources
if(Img2)
cvReleaseImage(&Img2);
}
}
else if((this->GetNumOfChannels() == 1 && Img->nChannels == 2))
{
// At this point we know that we were passed a
// complex type image and have to save it to
// a single-channel image
// Color variable used to get the pixel color
CvScalar Color;
// Loop through the image pixel by pixel and convert
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
// Get the color
Color = cvGet2D(Img,i,j);
// Depending on the passed parameter assign the pixel's value
switch(InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
{
case 0:
// In this case we just copy the real value
cvSet2D((*this),i,j,cvScalar(Color.val[0]));
break;
case 1:
// In this case we just copy the imaginary value
cvSet2D((*this),i,j,cvScalar(Color.val[1]));
break;
case 2:
// In this case, we assign the absolute value to this image's pixel
cvSet2D((*this),i,j,cvScalar(std::sqrt(Color.val[0]*Color.val[0] + Color.val[1]*Color.val[1])));
break;
default:
// As a default we just copy the real value
cvSet2D((*this),i,j,cvScalar(Color.val[0]));
break;
}
}
}
}
else if((this->GetNumOfChannels() == 3 && Img->nChannels == 2) ||
(this->GetNumOfChannels() == 4 && Img->nChannels == 2))
{
// At this point we know that we were passed a
// complex type image and have to save it to
// a three-channel or four-channel image
// Color variable used to get the pixel color
CvScalar Color;
// Loop through the image pixel by pixel and convert
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
// Get the color
Color = cvGet2D(Img,i,j);
// Variable used to calculate the absolute value
double AbsValue;
// Depending on the passed parameter assign the pixel's value
switch(InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
{
case 0:
// In this case we just copy the real value
cvSet2D((*this),i,j,cvScalar(Color.val[0],Color.val[0],Color.val[0]));
break;
case 1:
// In this case we just copy the imaginary value
cvSet2D((*this),i,j,cvScalar(Color.val[1],Color.val[1],Color.val[1]));
break;
case 2:
// In this case, we assign the absolute value to this image's pixel
AbsValue = std::sqrt(Color.val[0]*Color.val[0] + Color.val[1]*Color.val[1]);
cvSet2D((*this),i,j,cvScalar(AbsValue,AbsValue,AbsValue));
break;
default:
// As a default we just copy the real value
cvSet2D((*this),i,j,cvScalar(Color.val[0],Color.val[0],Color.val[0]));
break;
}
}
}
}
else if(this->GetNumOfChannels() == 2 && Img->nChannels == 1)
{
// At this point we know that we were passed a
// single channel image but have to save it
// to a complex image
// Color variable used to get the pixel color
CvScalar Color;
// Loop through the image pixel by pixel and convert
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
// Get the color
Color = cvGet2D(Img,i,j);
// Assign the absolute value to this image's pixel
cvSet2D((*this),i,j,cvScalar(Color.val[0]));
}
}
}
else if((this->GetNumOfChannels() == 2 && Img->nChannels == 3) ||
(this->GetNumOfChannels() == 2 && Img->nChannels == 4))
{
// At this point we know that we were passed a
// three-channel image, so we just assign the
// average value as the real values of our complex image
// Color variable used to get the pixel color
CvScalar Color;
// Loop through the image pixel by pixel and convert
for(int i = 0; i < this->size1(); ++i)
{
for(int j = 0; j < this->size2(); ++j)
{
// Get the color
Color = cvGet2D(Img,i,j);
// Assign the average value to this image's pixel
cvSet2D((*this),i,j,cvScalar((Color.val[0] + Color.val[1] + Color.val[2])/3.0f));
}
}
}
}
else
{
// Error
}
}
return;
}
//-------------------------------------------------------------------
//-------------------------------------------------------------------
template<typename blType>
inline void blImage<blType>::LoadImageFromFile(const string& Filename,
const bool& ShouldImageBeFlipped,
const int& ImageFlipMode,
const int& HowToReadImageColorAndDepth,
const int& InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2)
{
// Load image from file
IplImage* img = NULL;
img = cvLoadImage(Filename.c_str(),HowToReadImageColorAndDepth);
// Check if the image successfully loaded
if(img == NULL)
{
// Error
return;
}
// Flip image if being asked to
if(ShouldImageBeFlipped)
cvFlip(img,img,ImageFlipMode);
// Load the file image into this one (we want
// to copy the image and not just link to it)
LoadImage(img,InCaseOfComplexToRealConversionDoYouWantReal_0_Imaginary_1_OrAbsoluteValue_2);
// Release the image that was loaded from file
cvReleaseImage(&img);
return;
}
//-------------------------------------------------------------------
#endif // BL_IMAGE_HPP
Using the code
Using our image structure could not be any easier. This design super-simplifies the use of IplImage* pointers. The following code shows some of the possible uses of blImage. Notice how easy it is to access pixel data without having to worry about types and depths. Pixel access is also very fast as it provides direct access to the pixel data.
int main(int argc, char *argv[])
{
// Create a blank 8-bit color image
blImage< blColor3<unsigned char> > MyImage;
// Load an image from file
MyImage.LoadImageFromFile("C:\\tux.png");
// Create a floating point grayscale
// version of our colored image
blImage<float> MyImage2 = MyImage;
// We can easily access pixel data very efficiently
for(int i = 0; i < MyImage2.size1(); ++i)
for(int j = 0; j < MyImage2.size2(); ++j)
MyImage2[i][j] += 100;
blImage<float> MyImage3 = MyImage2;
// Let's take the first derivative in both directions
cvSobel(MyImage2,MyImage2,1,1);
cvNamedWindow("tux",CV_WINDOW_AUTOSIZE);
cvShowImage("tux",MyImage3);
cvWaitKey(0);
return 0;
}
Running this little snippet I get the following image:
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 "blImageAPI/blImageAPI.hpp"
using namespace blImageAPI;
Note: Everything is declared in a namespace blImageAPI, and such you would use it as: blImageAPI::blImage
Good Luck and have fun. I will be releasing subsequent algorithms and data structures for image analysis, so be sure to check back in and get the updated code files.
I have changed the blImg.hpp file to clean out the template specialization code. Much more efficient and clean now. I also have overloaded operators to handle colors in a natural way.
I had used a virtual destructor for the blColor3 and blColor4 structures, but I had to change it to a normal destructor, as declaring it virtual added an extra 8 bytes of size to the structures.
Fixed errors caused by converting pictures. Turns out that cvCvtColor function only works with input images of type “unsigned char”, “unsigned short” or “float”, but fails for the rest.
Oct/28/2010 — Updated the blColor3 and blColor4 structures to include a default constructor that takes one value only. This makes them compatible with other types when writing generic image algorithms using the blImageAPI.
Nov/10/2010 — Added a simple “GetImageDataCast()” function to the blImg.hpp file to get a pointer to the raw image data, but cast to the type of the image.
This function allows us to get a casted pointer to the raw image data, for those of you that prefer using raw pointers.
GetImageData() — returns a const char* pointer
GetImageDataCast() — returns a const blType* pointer, where blType is the type of the image
Nov/11/2010 — Fixed a bug in the “LoadImageFromFile” function. Now it works perfectly
Nov/16/2010 — Changed the blColor4 constructor to have a default value for alpha. I had left the alpha value as a required value before.
Nov/30/2010 — Added two operators to blColor3 and blColor4 to multiply and divide two colors. Now, when two colors are multiplied or divided, the individual color components are multiplied or divided.
Dec/28/2010 — Added an assignment operator to the blImg class. Without it, when using the assignment operator, the copied image referred to the same IplImage, now the assignment operator clones the original image.
Jan/02/2011
Changed the destructors from virtual to normal
Fixed a bug in the LoadImage function when converting a single channel image to a color image when the single channel image was of type double.
May/25/2011
Added a null deleter functor to blCleanResources so that we can wrap a pointer with a shared_ptr without calling its destructor when it goes out of scope. For example we can wrap the “this” pointer with a shared_ptr doing the following:
// Let's say we're inside the blImage class, we
// can send a shared_ptr of the "this" pointer doing
// the following:
shared_ptr< blImage<unsigned char> > MyPointer = shared_ptr< blImage<unsigned char> >(this,null_deleter());
Added a constructor and a LoadMatrix function to the blImage class so that we can load CvMat* structures into a blImage structure. For example we can do this:
// Let's say we have a CvMat*
// structure from somewhere in
// our code like this
CvMat* MyMatrix;
// Then later in our code we want to
// create a blImage out of that matrix
// we can do the following
blImage<unsigned char> MyImage(MyMatrix);
// Or we could do
blImage<unsigned char> MyImage = MyMatrix;
// Or maybe this
blImage<unsigned char> MyImage;
MyImage.LoadMatrix(MyMatrix);
Added a function to blImg to get retrieve the size in byte of an image like the following:
// We want to get the size of a blImage
blImage<unsigned char> MyImage;
cout << "The size of the image = " << MyImage.GetSizeInBytes();
Jun/06/2011
Added a function to blImage called WrapIplImage — It wraps an IplImage with a blImage and NULLIFIES the IplImage pointer. In case the depth and number of channels don’t match, the IplImage is copied and the IplImage pointer is released and NULLIFIED.
Facebook comments:
blImage — An image data structure based on shared_ptr and IplImage*
No comments yet.