In order to build computer vision applications, you need to be able to access the image content and eventually modify or create images. This chapter will teach you how to manipulate the picture elements (also known as pixels). You will learn how to scan an image and process each of its pixels. You will also learn how to do this efficiently, since even images of modest dimensions can contain hundreds of thousands of pixels.
Fundamentally, an image is a matrix of numerical values. This is why, as we learned in Chapter 1, Playing with Images, OpenCV 2 manipulates them using the cv::Mat
data structure. Each element of the matrix represents one pixel. For a gray-level image (a black-and-white image), pixels are unsigned 8-bit values where 0
corresponds to black and 255
corresponds to white. In the case of color images, three primary color values are required in order to reproduce the different visible colors. This is a consequence of the fact that our human visual system is
trichromatic...
In order to access each individual element of a matrix, you just need to specify its row and column numbers. The corresponding element, which can be a single numerical value or a vector of values in the case of a multi-channel image, will be returned.
To illustrate the direct access to pixel values, we will create a simple function that adds salt-and-pepper noise to an image. As the name suggests, salt-and-pepper noise is a particular type of noise in which some randomly selected pixels are replaced by a white or a black pixel. This type of noise can occur in faulty communications when the value of some pixels is lost during the transmission. In our case, we will simply randomly select a few pixels and assign them a white color.
In most image-processing tasks, you need to scan all pixels of the image in order to perform a computation. Considering the large number of pixels that will need to be visited, it is essential that you perform this task in an efficient way. This recipe, and the next one, will show you different ways of implementing efficient scanning loops. This recipe uses the pointer arithmetic.
We will illustrate the image-scanning process by accomplishing a simple task: reducing the number of colors in an image.
Color images are composed of 3-channel pixels. Each of these channels corresponds to the intensity value of one of the three primary colors, red, green, and blue. Since each of these values is an 8-bit unsigned character, the total number of colors is 256x256x256
, which is more than 16 million colors. Consequently, to reduce the complexity of an analysis, it is sometimes useful to reduce the number of colors in an image. One way to achieve this goal...
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.
In this recipe, we again use the color reduction example described in the previous recipe.
In the previous recipes of this chapter, we presented different ways of scanning an image in order to process its pixels. In this recipe, we will compare the efficiency of these different approaches.
When you write an image-processing function, efficiency is often a concern. When you design your function, you will frequently need to check the computational efficiency of your code in order to detect any bottleneck in your processing that might slow down your program.
However, it is important to note that unless necessary, optimization should not be done at the price of reducing the program clarity. Simple code is indeed always easier to debug and maintain. Only code portions that are critical to a program's efficiency should be heavily optimized.
In image processing, it is common to have a processing function that computes a value at each pixel location based on the value of the neighboring pixels. When this neighborhood includes pixels of the previous and next lines, you then need to simultaneously scan several lines of the image. This recipe shows you how to do it.
To illustrate this recipe, we will apply a processing function that sharpens an image. It is based on the Laplacian operator (which will be discussed in Chapter 6, Filtering the Images). It is indeed a well-known result in image processing that if you subtract the Laplacian from an image, the image edges are amplified, thereby giving a sharper image.
This sharpened value is computed as follows:
sharpened_pixel= 5*current-left-right-up-down;
Here, left
is the pixel that is immediately on the left-hand side of the current one, up
is the corresponding one on the previous line, and so on.
Images can be combined in different ways. Since they are regular matrices, they can be added, subtracted, multiplied, or divided. OpenCV offers various image arithmetic operators, and their use is discussed in this recipe.
Let's work with a second image that we will combine to our input image using an arithmetic operator. The following represents this second image:
Here, we add two images. This is useful when we want to create some special effects or to overlay information over an image. We do this by calling the cv::add
function, or more precisely here, the cv::addWeighted
function, since we want a weighted sum as follows:
cv::addWeighted(image1,0.7,image2,0.9,0.,result);
The operation results in a new image, as seen in the following screenshot:
In the recipes of this chapter, you learned how to read and modify the pixel values of an image. The last recipe will teach you how to modify the appearance of an image by moving its pixels. The pixel values are not changed by this process; it is rather the position of each pixel that is remapped to a new location. This is useful in order to create special effects on an image or to correct image distortions caused, for example, by a lens.
In order to use the OpenCV remap
function, you simply have to first define the map to be used in the remapping process. Second, you have to apply this map on an input image. Obviously, it is the way you define your map that will determine the effect that will be produced. In our example, we define a transformation function that will create a wavy effect on the image:
// remapping an image by creating wave effects void wave(const cv::Mat &image, cv::Mat &result) { // the map functions cv::Mat srcX(image.rows,image...