![]() |
University of Murcia, Spain ![]() |
Image processingIn the QVision framework, images are represented as objects. These objects are derived from the templated class QVImage, which conveniently encapsulates several values needed to operate with the image, such as a pointer to the image data buffer, the dimensions of the image, the step, etc...The QVImage is a template class with two parameteres:
When creating an image, it's bit-depth is specified with the first template argument Type. Virtually, you can specify images with pixel types of any kind, but QVision has defined functionality to process pixels of the following types:
In practice, most of the functions will work with images of type uChar, sShort, and sFloat. The second parameter of the QVImage template class indicates the number of channels in which the image stores the data. For example, the following lines of code create images of 8 bit-depth, 16 bit-depth, and 32 bit-depth, of one and three channels respectively:
QVImage<uChar,1> image8uC1; QVImage<sShort,1> image16sC1; QVImage<sInt,1> image32sC1; QVImage<uChar,3> image8uC3; QVImage<sShort,3> image16sC3; QVImage<sInt,3> image32sC3; You can ommit the number of channels if you want to create a one channel image, because the default value for the channel number parameter is one channel. Thus, the first three lines of code of the previous example could be simplified this way:
QVImage<uChar> image8uC1; QVImage<sShort> image16sC1; QVImage<sInt> image32sC1; As in the case of the Type parameter, you can assign this parameter any arbitrary positive integer value, but generally the QVision includes functionality to work only with one, three and four channel number images. Conversion between image types.When using the constructor and the assign operator with a source image different in type or channel number to the created or destination image respectively, QVision automatically makes a conversion. This conversion will generally be performed with calls to the proper ippConvert function. A review of the Convert set of functions in the IPP wrapper functions function group is recommended for futher learning about image conversions.Pixel accessThere are three ways of accessing pixel values in an image (listed in increasing order of efficiency):
Using the operator ()It is the least efficient access method regarding execution time performance, but it is the easiest one to use, and also the safest. This operator is overloaded for the QVImage class to allow reading and writing pixel values using QPoints or simply pairs of (x,y) integers.It is slow because it performs some sanity checks, for example to ensure you don't read pixels outside the image area. An usage example follows:
QVImage<uChar> imageCharBis = imageChar; [...] // This line assigns the value 0 to pixel (10,10) in the image imageChar: imageCharBis(10,10) = 0; // And this line reads value of pixel (11,11) of the image imageChar // in the variable value: uChar value = imageCharBis(11,11); [...] // The following code shows how to read and write all the pixels // from QVImage's of size 100x100: QVImage<uChar> imageChar1(100,100), imageChar2(100,100); [...] for (uInt row = 0; row != imageChar1.getRows(); row++) for (uInt col = 0; col != imageChar1.getCols(); col++) imageChar2(col, row) = imageChar1(col, row); [...] Using the pixel access macrosThe second pixel access method is based on a set of macros provided by the QVision for image pixel accessing. These macros are:
The first two macros, QVIMAGE_INIT_READ and QVIMAGE_INIT_WRITE are used to initalize macro access for a given image. The following code illustrates their usage:
[...] QVImage<sFloat, 1> imageFloat(100,100); QVImage<uChar, 3> imageChar3(100,100); [...] QVIMAGE_INIT_READ(sFloat,imageFloat); QVIMAGE_INIT_WRITE(uChar,imageChar3); [...] To access a random pixel located in a previously initialized image, you should use the macro QVIMAGE_PIXEL like this:
[ ... follows from previous example code ...] for(uInt row = 0; row != imageFloat.getRows(); row++) for(uInt col = 0; col != imageFloat.getCols(); col++) QVIMAGE_PIXEL(imageChar3, col, row,1) = QVIMAGE_PIXEL(imageFloat, col, row,0); [...] Macro QVIMAGE_PIXEL takes always the following parameters: image name, row, column, and channel number, starting with number 0 for the first channel. In one channel images, only 0 value should be valid for this last parameter. Macros QVIMAGE_PTR_INIT_READ and QVIMAGE_PTR_INIT_WRITE work just like macros QVIMAGE_INIT_READ and QVIMAGE_INIT_WRITE, but in this case they are given a pointer to the image to be accessed with macro QVIMAGE_PIXEL, instead of the image itself. An example of usage of these macros follows:
QVImage<sFloat,1> * ptrImageFloat(100,100); QVImage<uChar,3> * ptrImageChar3(100,100); [...] QVIMAGE_PTR_INIT_READ(sFloat,ptrImageFloat); QVIMAGE_PTR_INIT_WRITE(uChar,ptrImageChar3); // The contents of 1-channel image pointed by ptrImageFloat will be // copied to the second channel of the 3-channel image pointed by ptrImageChar3. for(uInt row = 0; row != ptrImageFloat.getRows(); row++) for(uInt col = 0; col != ptrImageFloat.getCols(); col++) QVIMAGE_PIXEL(ptrImageChar3, col, row,1) = QVIMAGE_PIXEL(ptrImageFloat, col, row,0); [...] Observe that the use of macro QVIMAGE_PIXEL is the same for both pointer and non-pointer macro types. Moreover, if we are going to iterate through an image in a sequential way -say, by rows, or even by columns-, macros QVIMAGE_PIXEL_PTR, QVIMAGE_COL_INCREMENT_PTR, QVIMAGE_ROW_INCREMENT_PTR, QVIMAGE_NEXT_LINE_INCREMENT_PTR may be used to perform a still more efficient image access than the random access previously reviewed with macro QVIMAGE_PIXEL. Like the latter, macros QVIMAGE_INIT_READ or QVIMAGE_INIT_WRITE must be used to initialize the image. Later, a pointer to the starting pixel in the image is obtained with the macro QVIMAGE_PIXEL_PTR, and using the macros QVIMAGE_COL_INCREMENT_PTR and QVIMAGE_ROW_INCREMENT_PTR we can traverse the image from each pixel to another one in a 4-connected neighbourhood. Using the macro QVIMAGE_NEXT_LINE_INCREMENT_PTR we can obtain the distance from the last pixel of a line, to the location in memory of the first pixel of the following line for sequential accesses through the lines of an image. The following code ilustrates their usage:
QVImage<sFloat, 1> imageFloat(100,100); QVImage<uChar, 1> imageChar(100,100); [...] QVIMAGE_INIT_READ(sFloat,imageFloat); QVIMAGE_INIT_WRITE(uChar,imageChar); const sFloat *imagePtr = QVIMAGE_PIXEL_PTR(imageFloat, 0, 0, 0); uChar *destPtr = QVIMAGE_PIXEL_PTR(imageChar, 0, 0, 0); // The contents of imageFloat will be copied to image imageChar: for ( int i = 0; i != rows; ++i, imagePtr += QVIMAGE_NEXT_LINE_INCREMENT_PTR(imageFloat), destPtr += QVIMAGE_NEXT_LINE_INCREMENT_PTR(imageChar) ) for ( int j = 0; j != cols; ++j, imagePtr += QVIMAGE_COL_INCREMENT_PTR(imageFloat), destPtr += QVIMAGE_COL_INCREMENT_PTR(imageChar) ) *destPtr = *imagePtr; [...] This kind of access is very common, and using these macros to access the pixels of the image is more efficient than with the QVIMAGE_PIXEL macro. Accessing though the raw data buffer pointerThe third method for image pixel accessing is working directly with the raw data buffer. It is the fastest and the rawest of the three methods for image pixel access.You can obtain a pointer to the memory area containing the image data with the functions QVImage::getWriteData() and QVImage::getReadData(). The method QVImage::getStep() returns the row size of an image in memory, and the methods QVImage::getCols() and QVImage::getRows() can be used to obtain the real bounds of the memory area of the image (i.e., its true size, without padding). The following code illustrates the use of these functions for raw image pixel access method. It copies the content of image imageChar1 to a second image imageChar2:
QVImage<uChar> imageChar1(100,100), imageChar2(100, 100); [...] uInt step1 = imageChar1.getStep(), step2 = imageChar2.getStep(); uChar *imageCharData1 = imageChar1.getReadData(), const *imageCharData2 = imageChar2.getWriteData(); // The contents of imageChar1 will be copied to the image imageChar2. for (uInt row = 0; row != imageChar1.getRows(); row++) for (uInt col = 0; col != imageChar1.getCols(); col++) imageCharData2[row*step2 + col] = imageCharData1[row*step1 + col]; [...] Image processing functions in the QVisionThe group Image processing contains all the image processing functionality offered by the QVision. Most of it is built over the Intel Performance Primitives library. This group contains two subgroups: IPP wrapper functions and Image features.Intel Performance Primitives wrapper functionsThe IPP wrapper functions is a subgroup included in the Image processing group. It contains a set of wrapper functions for the Intel's Performance Primitives library, in order to increase its usability in the developing of new applications using the QVision.Each one of these functions corresponds to a function in the IPP library. Most of the functions from the IPP library perform computations on one or two input images, returning another image as a result. When directly using the IPP library, the developer must generally provide a couple of parameters for each input/output image to the corresponding IPP function: a pointer to the data buffer of the image and its step -number of bytes between consecutive rows of the image-. This can be considered a "C-like", low-level approach. Instead, the QVision framework, which is more object oriented, defines the class QVImage to create image objects which encapsulate all the information needed to work with an image: amongst them, of course, the needed pointer to the data buffer, and the step of the image. The IPP wrapper functions group contains wrapper functions which can receive QVImage objects instead of the pointer and the step of each image, simplifying the interface of the IPP function by avoiding the use of pointers and extra parameters, and freeing the developer fo working directly with error-prone low level pointers. Thus, this wrapper package is included to offer a clearer and object oriented alternative to direct usage of the IPP functions. Example of IPP wrapper functionTake for instance the functions used for adding images in the IPP library. There is a whole family of functions, denoted with the prefix ippiAdd_, followed by a subfix indicating some relevant information, like the storage type for the elements of the image, its channel number, and so on. An example is the function ippiAdd_8u_C1RSfs, used to add two 8-bit unsigned data type images composed by one single channel and store the result in a destination image of the same type. This function has the following header:
IppStatus ippiAdd_<mod>( const Ipp<datatype>* pSrc1, int src1Step, const Ipp<datatype>* pSrc2, int src2Step, Ipp<datatype>* pDst, int dstStep, IppiSize roiSize, int scaleFactor); The package IPP wrapper functions contains a corresponding wrapper function named Add, which uses the following header:
void Add( const QVImage<uChar, 1> & qvimage_pSrc1, const QVImage<uChar, 1> & qvimage_pSrc2, QVImage<uChar, 1> & qvimage_pDst, const int scaleFactor = 1, const QPoint &destROIOffset = QPoint(0,0)); The IPP wrapper functions package substitutes the input parameters pSrc1 and src1Step from the IPP function ippiAdd_8u_C1RSfs, which correspond to the first input image, by the input image object type parameter qvimage_pSrc1 in the Add function. The latter function will basically call the IPP function, mapping the pointer to the data buffer of the image qvimage_pSrc1 and its step value respectively to the input parameters pSrc1 and src1Step of the IPP function. There is a double advantage with this approach. Besides the clearer interface of the Add function, which allows an easier and more robust, object-oriented usage, the naming of the function simplifies by eliminating the function subfix. This can be done by overloading the Add function, to apply the proper version of the ippiAdd function of the IPP library depending on the number, type, and channel number of the input parameters provided by the developer using the function.
Image features groupThis group contains several medium-level image processing functions, which obtain some kind high-level data structures from the images. Image features are pieces of information which can be extracted from an image, and are relevant for solving a computational task, generally related to Computer Vision.By now, QVision includes several interesting image feature extraction algorithms: MSER, SIFT, borders, corners, etc... We keep constantly adding new methods in this group. You can check the documentation of the group Image features for a full description the implemented functions. Regions of Interest (ROI)Most of the IPP functions restrict their operations to a rectangular region of the image, which can be either the whole image, or some part of it. QVImage objects contain a property named ROI, which can be modified and read using several methods, like QVImage::getROI, QVImage::setROI, and so on. This property contains a rectangle inside the image, which will be used as the Region of Interest by the IPP functions which operate with it, in the IPP wrapper functions.Example ROI Usage:The following code illustrates the usage of the Copy function, which performs operations on the ROI of the input image:
#include <qvipp.h> // QVIPP functions are included in this header file. [...] QVImage<uChar> sourceImage = tajMahal; sourceimage.setROI(10,10,90,90); // This specifies the ROI of the input image. QVImage<uChar> destinationImage(200, 200); Copy(sourceimage, destinationImage); [...] Figure below shows the result of that code on an input image. The left image is the source image, containing the tajMahal image. The right image shows the result of the Copy operation. The black area can contain unspecified content, not necesarily black, if it was not previously initialized so.
![]() The ROI is commonly used to specify the portion of the image which contains valid information. Thus, after the copy operation of the previous example, the ROI of the destinationImage will be set to contain only the part of the image which really shows a part of the Taj Mahal -that is, that contains "valid" data-. Each image processing function should update the ROI of the output image or images, so that they contain the correct area computed by the operations, and which can be used in posterior processing. The following code is useful to illustrate this:
[...] QVImage<uChar> sourceImage = lena; sourceImage.setROI(100, 110, 90, 40); // This specifies the ROI of the input image. QVImage<uChar> temporaryImage(256, 256); FilterGauss(sourceImage, temporaryImage, 5); QVImage<uChar> destinationImage(256, 256); FilterGauss(temporaryImage, destinationImage, 5); [...] It applies two Gaussian filter over the lena image. The first call to FilterGauss will store in the ROI of the temporaryImage the area of the image which contains valid data from its operations, so the next call to FilterGauss will only work with that part of the image -the one that now is supposed to be valid-, saving an important amount of performance time.
![]() Of course, and if needed, the programmer can anyway set another ROI at any time by explicitly calling the method QVImage::setROI(). Destination ROIIn some cases the developer may want to indicate a location for the ROI in the destination image. The (optional) parameter destROIOffset can be used to indicate a point in the destination image where the IPP wrapper functions function must locate the top left corner of the ROI.This parameter is included in the header of several functions from the package IPP wrapper functions, the Copy function amongst them. The following code illustrates the usage of the destROIOffset parameter:
[...] QVImage<uChar> sourceImage = tajMahal; sourceimage.setROI(10,10,90,90); // This specifies the ROI of the input image. QVImage<uChar> destinationImage(200, 200); Copy(sourceimage, destinationImage, QPoint(60,30)); [...] The QPoint(60,30) parameter in the call to the Copy function indicates that it should copy the content of the ROI in the source image to the destination location, starting at the point (60,30):
![]()
[...] // lena is an image of size 256x256. tajMahal is an image of size 200x200. lena.setROI(45,65,155,85); // Set initial ROI for image sourceImage1. tajMahal.setROI(30,20,155,85); // Set initial ROI for image sourceImage2. // ROI's have a dimention of 155x85 pixels QVImage<uChar> destinationImage(230,230); // 'destinationImage' has size 230x230 Add(lena, tajMahal, destinationImage, QPoint(10,20)); [...] The following picture illustrates the result of that code:
![]() If the developer provides the Add function two images with an incorrect ROI size, a Q_ASSERT will be raised, and if the program executes in DEBUG mode it will stop, indicating the error. |