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

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 to do it...

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.

The cv::Mat_ template class

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