How it works...

The core process of this algorithm is easy to build. It is a simple scanning loop that goes over each pixel, comparing its color with the target color. Using what we learned in the Scanning an image with iterators recipe of Chapter 2, Manipulating the Pixels, this loop can be written as follows:

     // get the iterators 
     cv::Mat_<cv::Vec3b>::const_iterator it=  
                           image.begin<cv::Vec3b>(); 
     cv::Mat_<cv::Vec3b>::const_iterator itend=  
                           image.end<cv::Vec3b>(); 
     cv::Mat_<uchar>::iterator itout= result.begin<uchar>(); 
 
     // for each pixel 
     for ( ; it!= itend; ++it, ++itout) { 
 
            // compute distance from target color 
            if (getDistanceToTargetColor(*it)<=maxDist) { 
                  *itout= 255; 
            } else { 
                 *itout= 0; 
            } 
    } 

The cv::Mat variable's image refers to the input image, while result refers to the binary output image. Therefore, the first step consists of setting up the required iterators. The scanning loop then becomes easy to implement. The distance between the current pixel color and the target color is evaluated on each iteration, in order to check whether it is within the tolerance parameter defined by maxDist. If that is the case, the value 255 (white) is then assigned to the output image; if not, 0 (black) is assigned. To compute the distance to the target color, the getDistanceToTargetColor method is used. There are different ways to compute this distance. One could, for example, calculate the Euclidean distance between the three vectors that contain the RGB color values. To keep this computation simple, we simply sum the absolute differences of the RGB values (this is also known as the city-block distance) in our case. Note that in modern architecture, a floating-point Euclidean distance can be faster to compute than a simple city-block distance; this is also something to take into consideration in your design. Also, for more flexibility, we write the getDistanceToTargetColor method in terms of a getColorDistance method, as follows:

// Computes the distance from target color. 
int getDistanceToTargetColor(const cv::Vec3b& color) const { 
  return getColorDistance(color, target); 
} 
 
// Computes the city-block distance between two colors. 
   int getColorDistance(const cv::Vec3b& color1,  
                        const cv::Vec3b& color2) const { 
  return abs(color1[0]-color2[0])+ 
                abs(color1[1]-color2[1])+ 
                abs(color1[2]-color2[2]); 
}

Note how we used cv::Vec3d to hold the three unsigned characters that represent the RGB values of a color. The target variable obviously refers to the specified target color, and as you will see, it is defined as a class variable in the class algorithm that we will define. Now, let's complete the definition of the processing method. Users will provide an input image, and the result will be returned once the image scanning has completed:

cv::Mat ColorDetector::process(const cv::Mat &image) { 
 
     // re-allocate binary map if necessary 
     // same size as input image, but 1-channel 
     result.create(image.size(),CV_8U); 
     // processing loop above goes here 
      ... 
 
     return result; 
} 

Each time this method is called, it is important to check if the output image that contains the resulting binary map needs to be reallocated to fit the size of the input image. This is why we use the create method of cv::Mat. Remember that this method will only proceed to reallocation if the specified size and depth do not correspond to the current image structure.

Now that we have defined the core processing method, let's see what additional methods should be added in order to deploy this algorithm. We previously determined what input and output data our algorithm requires. Therefore, we will first define the class attributes that will hold this data:

class ColorDetector { 
 
  private: 
 
     // minimum acceptable distance 
     int maxDist;  
 
     // target color 
     cv::Vec3b target;  
 
     // image containing resulting binary map 
     cv::Mat result;

In order to create an instance of the class that encapsulates our algorithm (which we have named ColorDetector), we need to define a constructor. Remember that one of the objectives of the strategy design pattern is to make algorithm deployment as easy as possible. The simplest constructor that can be defined is an empty one. It will create an instance of the class algorithm in a valid state. We then want the constructor to initialize all the input parameters to their default values (or the values that are generally known to give a good result). In our case, we decided that a distance of 100 is generally an acceptable tolerance parameter. We also set the default target color. We chose black, for no particular reason. The idea is to make sure we always start with predictable and valid input values:

     // empty constructor 
     // default parameter initialization here 
     ColorDetector() : maxDist(100), target(0,0,0) {} 

At this point, a user who creates an instance of our class algorithm can immediately call the process method with a valid image and obtain a valid output. This is another objective of the strategy pattern, that is, to make sure that the algorithm always runs with valid parameters. Obviously, the users of this class will want to use their own settings. This is done by providing the user with the appropriate getters and setters. Let's start with the color tolerance parameter:

     // Sets the color distance threshold. 
     // Threshold must be positive,  
     // otherwise distance threshold is set to 0. 
     void setColorDistanceThreshold(int distance) { 
 
        if (distance<0) 
           distance=0; 
        maxDist= distance; 
     } 
 
     // Gets the color distance threshold 
     int getColorDistanceThreshold() const { 
 
        return maxDist; 
     } 

Note how we first check the validity of the input. Again, this is to make sure that our algorithm will never be run in an invalid state. The target color can be set in a similar manner, as follows:

     // Sets the color to be detected 
     void setTargetColor(uchar blue,  
                         uchar green,  
                         uchar red) { 
 
    // BGR order 
           target = cv::Vec3b(blue, green, red); 
     } 
 
     // Sets the color to be detected 
     void setTargetColor(cv::Vec3b color) { 
 
        target= color; 
     } 
 
     // Gets the color to be detected 
     cv::Vec3b getTargetColor() const { 
 
        return target; 
     } 

This time, it is interesting to note that we have provided the user with two definitions of the setTargetColor method. In the first version of the definition, the three color components are specified as three arguments, while in the second version, cv::Vec3b is used to hold the color values. Again, the objective is to facilitate the use of our class algorithm. The user can simply select the setter that best fits their needs.