Accessing pixel values
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.
Getting ready
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.
How to do it...
We create a function that receives an input image. This is the image that will be modified by our function. The second parameter is the number of pixels on which we want to overwrite white values:
void salt(cv::Mat image, int n) { int i,j; for (int k=0; k<n; k++) { // rand() is the random number generator i= std::rand()%image.cols; j= std::rand()%image.rows; if (image.type() == CV_8UC1) { // gray-level image image.at<uchar>(j,i)= 255; } else if (image.type() == CV_8UC3) { // color image image.at<cv::Vec3b>(j,i)[0]= 255; image.at<cv::Vec3b>(j,i)[1]= 255; image.at<cv::Vec3b>(j,i)[2]= 255; } } }
The preceding function is made of a single loop that assigns n
times the value 255
to randomly selected pixels. Here, the pixel column i
and row j
are selected using a random number generator. Note that using the type
method, we distinguish the two cases of gray-level and color images. In the case of a gray-level image, the number 255
is assigned to the single 8-bit value. For a color image, you need to assign 255
to the three primary color channels in order to obtain a white pixel.
You can call this function by passing it an image you have previously opened. Refer to the following code:
// open the image cv::Mat image= cv::imread("boldt.jpg"); // call function to add noise salt(image,3000); // display image cv::namedWindow("Image"); cv::imshow("Image",image);
The resulting image will look as follows:
How it works...
The cv::Mat
class includes several methods to access the different attributes of an image. The public member variables, cols
and rows
, give you the number of columns and rows in the image. For element access, cv::Mat
has the at
(int y
, int x
) method. However, the type returned by a method must be known at compile time, and since cv::Mat
can hold elements of any type, the programmer needs to specify the return type that is expected. This is why the at
method has been implemented as a template method. So, when you call it, you must specify the image element type as follows:
image.at<uchar>(j,i)= 255;
It is important to note that it is the programmer's responsibility to make sure that the type specified matches the type contained in the matrix. The at
method does not perform any type conversion.
In color images, each pixel is associated with three components: the red, green, and blue channels. Therefore, a cv::Mat
class that contains a color image will return a vector of three 8-bit values. OpenCV has defined a type for such short vectors, and it is called cv::Vec3b
. This is a vector of three unsigned characters. This explains why the element access to the pixels of a color pixel is written as follows:
image.at<cv::Vec3b>(j,i)[channel]= value;
The channel
index designates one of the three color channels. OpenCV stores the channel values in the order blue, green, and red (blue is, therefore, channel 0
).
Similar vector types also exist for 2-element and 4-element vectors (cv::Vec2b
and cv::Vec4b
) as well as for other element types. For example, for a 2-element float vector, the last letter of the type name would be replaced by an f, that is, cv::Vec2f
. In the case of a short integer, the last letter is replaced with s
, with i
for an integer, and with d
for a double precision floating point vector. All of these types are defined using the cv::Vec<T,N>
template class, where T
is the type and N
is the number of vector elements.
As a last note, you might have been surprised by the fact that our image-modifying function uses a pass-by-value image parameter. This works because when images are copied, they still share the same image data. So, you do not have to necessarily transmit images by references when you want to modify their content. Incidentally, pass-by-value parameters often make code optimization easier for the compiler.
There's more...
The cv::Mat
class has been made generic by defining it using C++ templates.
Using the at
method of the cv::Mat
class can sometimes be cumbersome because the returned type must be specified as a template argument in each call. In cases where the matrix type is known, it is possible to use the cv::Mat_
class, which is a template subclass of cv::Mat
. This class defines a few extra methods but no new data attributes so that pointers or references to one class can be directly converted to another class. Among the extra methods, there is operator()
, which allows direct access to matrix elements. Therefore, if image
is a cv::Mat
variable that corresponds to a uchar
matrix, then you can write the following code:
// use image with a Mat_ template cv::Mat_<uchar> im2(image); im2(50,100)= 0; // access to row 50 and column 100
Since the type of the cv::Mat_
elements is declared when the variable is created, the operator()
method knows at compile time which type is to be returned. Other than the fact that it is shorter to write, using the operator()
method provides exactly the same result as the at
method.
See also
- The There's more… section of the Scanning an image with pointers recipe explains how to create a function with input and output parameters
- The Writing efficient image-scanning loops recipe proposes a discussion on the efficiency of this method