That was pretty good, and we are learning to do more interesting things. In Example 2-4 we chose to allocate a new IplImage
structure, and into this new structure we wrote the
output of a single transformation. As mentioned, we could have applied the transformation in
such a way that the output overwrites the original, but this is not always a good idea. In
particular, some operators do not produce images with the same size, depth, and number of
channels as the input image. Typically, we want to perform a sequence
of operations on some initial image and so produce a chain of transformed images.
In such cases, it is often useful to introduce simple wrapper functions that both allocate the output image and perform the
transformation we are interested in. Consider, for example, the reduction of an image by a
factor of 2 [Rosenfeld80]. In OpenCV this is accomplished by the function cvPyrDown()
, which performs a Gaussian smooth and then removes every other line from an image. This is useful
in a wide variety of important vision algorithms. We can implement the simple function
described in Example 2-5.
Example 2-5. Using cvPyrDown() to create a new image that is half the width and height of the input image
IplImage* doPyrDown( IplImage* in, int filter = IPL_GAUSSIAN_5x5 ) { // Best to make sure input image is divisible by two. // assert( in->width%2 == 0 && in->height%2 == 0 ); IplImage* out = cvCreateImage( cvSize( in->width/2, in->height/2 ), in->depth, in->nChannels ); cvPyrDown( in, out ); return( out ); };
Notice that we allocate the new image by reading the needed parameters from the old image. In OpenCV, all of the important data types are implemented as structures and passed around as structure pointers. There is no such thing as private data in OpenCV! Let's now look at a similar but slightly more involved example involving the Canny edge detector [Canny86] (see Example 2-6). In this case, the edge detector generates an image that is the full size of the input image but needs only a single channel image to write to.
Example 2-6. The Canny edge detector writes its output to a single channel (grayscale) image
IplImage* doCanny( IplImage* in, double lowThresh, double highThresh, double aperture ) { If(in->nChannels != 1) return(0); //Canny only handles gray scale images IplImage* out = cvCreateImage( cvGetSize( in ), in->depth, //IPL_DEPTH_8U, 1); cvCanny( in, out, lowThresh, highThresh, aperture ); return( out ); };
This allows us to string together various operators quite easily. For example, if we wanted to shrink the image twice and then look for lines that were present in the twice-reduced image, we could proceed as in Example 2-7.
Example 2-7. Combining the pyramid down operator (twice) and the Canny subroutine in a simple image pipeline
IplImage* img1 = doPyrDown( in, IPL_GAUSSIAN_5x5 ); IplImage* img2 = doPyrDown( img1, IPL_GAUSSIAN_5x5 ); IplImage* img3 = doCanny( img2, 10, 100, 3 ); // do whatever with 'img3' // ... cvReleaseImage( &img1 ); cvReleaseImage( &img2 ); cvReleaseImage( &img3 );
It is important to observe that nesting the calls to various stages of our filtering pipeline is not a good idea, because then we would have no way to free the images that we are allocating along the way. If we are too lazy to do this cleanup, we could opt to include the following line in each of the wrappers:
cvReleaseImage( &in );
This "self-cleaning" mechanism would be very tidy, but it would have the following disadvantage: if we actually did want to do something with one of the intermediate images, we would have no access to it. In order to solve that problem, the preceding code could be simplified as described in Example 2-8.
Example 2-8. Simplifying the image pipeline of Example 2-7 by making the individual stages release their intermediate memory allocations
IplImage* out; out = doPyrDown( in, IPL_GAUSSIAN_5x5 ); out = doPyrDown( out, IPL_GAUSSIAN_5x5 ); out = doCanny( out, 10, 100, 3 ); // do whatever with 'out' // ... cvReleaseImage ( &out );
One final word of warning on the self-cleaning filter pipeline: in OpenCV we must always
be certain that an image (or other structure) being de-allocated is one that was, in fact,
explicitly allocated previously. Consider the case of the IplImage*
pointer returned by cvCreateFileCapture()
. Here the pointer points to a structure allocated as part
of the CvCapture
structure, and the target structure is
allocated only once when the CvCapture
is initialized and
an AVI is loaded. De-allocating this structure with a call to cvReleaseImage()
would result in some nasty surprises. The moral of this story
is that, although it's important to take care of garbage collection in OpenCV, we should
only clean up the garbage that we have created.