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.