OpenCV Computer Vision Application Programming Cookbook Second Edition
上QQ阅读APP看书,第一时间看更新

Scanning an image with iterators

In object-oriented programming, looping over a data collection is usually done using iterators. Iterators are specialized classes that are built to go over each element of a collection, hiding how the iteration over each element is specifically done for a given collection. This application of the information-hiding principle makes scanning a collection easier and safer. In addition, it makes it similar in form no matter what type of collection is used. The Standard Template Library (STL) has an iterator class associated with each of its collection classes. OpenCV then offers a cv::Mat iterator class that is compatible with the standard iterators found in the C++ STL.

Getting ready

In this recipe, we again use the color reduction example described in the previous recipe.

How to do it...

An iterator object for a cv::Mat instance can be obtained by first creating a cv::MatIterator_ object. As is the case with cv::Mat_, the underscore indicates that this is a template subclass. Indeed, since image iterators are used to access the image elements, the return type must be known at the time of compilation. The iterator is then declared as follows:

     cv::MatIterator_<cv::Vec3b> it;

Alternatively, you can also use the iterator type defined inside the Mat_ template class as follows:

     cv::Mat_<cv::Vec3b>::iterator it;

You then loop over the pixels using the usual begin and end iterator methods, except that these ones are, again, template methods. Consequently, our color reduction function is now written as follows:

void colorReduce(cv::Mat &image, int div=64) {

     // obtain iterator at initial position
     cv::Mat_<cv::Vec3b>::iterator it= 
               image.begin<cv::Vec3b>();
     // obtain end position
     cv::Mat_<cv::Vec3b>::iterator itend= 
               image.end<cv::Vec3b>();

     // loop over all pixels
     for ( ; it!= itend; ++it) {
        // process each pixel ---------------------

       (*it)[0]= (*it)[0]/div*div + div/2;
       (*it)[1]= (*it)[1]/div*div + div/2;
       (*it)[2]= (*it)[2]/div*div + div/2;

        // end of pixel processing ----------------
     }
}

Remember that the iterator here returns a cv::Vec3b instance because we are processing a color image. Each color channel element is accessed using the dereferencing operator [].

How it works...

Working with iterators always follows the same pattern no matter what kind of collection is scanned.

First, you create your iterator object using the appropriate specialized class, which in our example is cv::Mat_<cv::Vec3b>::iterator (or cv::MatIterator_<cv::Vec3b>).

You then obtain an iterator initialized at the starting position (in our example, the upper-left corner of the image). This is done using a begin method. With a cv::Mat instance, you obtain it as image.begin<cv::Vec3b>(). You can also use arithmetic on the iterator. For example, if you wish to start at the second row of an image, you can initialize your cv::Mat iterator at image.begin<cv::Vec3b>()+image.cols. The end position of your collection is obtained similarly but using the end method. However, the iterator thus obtained is just outside your collection. This is why your iterative process must stop when it reaches the end position. You can also use arithmetic on this iterator; for example, if you wish to stop before the last row, your final iteration would stop when the iterator reaches image.end<cv::Vec3b>()-image.cols.

Once your iterator is initialized, you create a loop that goes over all elements until the end is reached. A typical while loop will look like the following code:

     while (it!= itend) { 

        // process each pixel ---------------------
         

        // end of pixel processing ----------------

        ++it;
     }

The ++ operator is the one that is to be used to move to the next element. You can also specify the larger step size. For example, it+=10 would process the image every 10 pixels.

Finally, inside the processing loop, you use the dereferencing operator * in order to access the current element, using which, you can read (for example, element= *it;) or write (for example, *it= element;). Note that it is also possible to create constant iterators that you use if you receive a reference to const cv::Mat or if you wish to signify that the current loop does not modify the cv::Mat instance. These are declared as follows:

     cv::MatConstIterator_<cv::Vec3b> it;

Or, they are declared as follows:

     cv::Mat_<cv::Vec3b>::const_iterator it;

There's more...

In this recipe, the start and end positions of the iterator were obtained using the begin and end template methods. As we did in the first recipe of this chapter, we could have also obtained them using a reference to a cv::Mat_ instance. This would avoid the need to specify the iterator type in the begin and end methods since this one is specified when the cv::Mat_ reference is created.

     cv::Mat_<cv::Vec3b> cimage(image);
     cv::Mat_<cv::Vec3b>::iterator it= cimage.begin();
     cv::Mat_<cv::Vec3b>::iterator itend= cimage.end();

See also

  • The Writing efficient image-scanning loops recipe proposes a discussion on the efficiency of iterators when scanning an image.
  • Also, if you are not familiar with the concept of iterators in object-oriented programming and how they are implemented in ANSI C++, you should read a tutorial on STL iterators. Simply search the Web with the keywords "STL Iterator" and you will find numerous references on the subject.