Threshold

Frequently we have done many layers of processing steps and want either to make a final decision about the pixels in an image or to categorically reject those pixels below or above some value while keeping the others. The OpenCV function cvThreshold() accomplishes these tasks (see survey [Sezgin04]). The basic idea is that an array is given, along with a threshold, and then something happens to every element of the array depending on whether it is below or above the threshold.

double cvThreshold(
   CvArr* src,
   CvArr* dst,
   double threshold,
   double max_value,
   int threshold_type
);

As shown in Table 5-5, each threshold type corresponds to a particular comparison operation between the ith source pixel (srci) and the threshold (denoted in the table by T). Depending on the relationship between the source pixel and the threshold, the destination pixel dsti may be set to 0, the srci, or the max_value (denoted in the table by M).

Table 5-5. cvThreshold() threshold_type options

Threshold type

Operation

CV_THRESH_BINARY

image with no caption

CV_THRESH_BINARY_INV

image with no caption

CV_THRESH_TRUNC

image with no caption

CV_THRESH_TOZERO_INV

image with no caption

CV_THRESH_TOZERO

image with no caption

Figure 5-23 should help to clarify the exact implications of each threshold type.

Results of varying the threshold type in cvThreshold(). The horizontal line through each chart represents a particular threshold level applied to the top chart and its effect for each of the five types of threshold operations below

Figure 5-23. Results of varying the threshold type in cvThreshold(). The horizontal line through each chart represents a particular threshold level applied to the top chart and its effect for each of the five types of threshold operations below

Let's look at a simple example. In Example 5-2 we sum all three channels of an image and then clip the result at 100.

Example 5-2. Example code making use of cvThreshold()

#include <stdio.h>
#include <cv.h>
#include <highgui.h>
void sum_rgb( IplImage* src, IplImage* dst ) {

  // Allocate individual image planes.
  IplImage* r = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1 );
  IplImage* g = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1 );
  IplImage* b = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1 );

  // Split image onto the color planes.
  cvSplit( src, r, g, b, NULL );

  // Temporary storage.
  IplImage* s = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1 );

  // Add equally weighted rgb values.
  cvAddWeighted( r, 1./3., g, 1./3., 0.0, s );
  cvAddWeighted( s, 2./3., b, 1./3., 0.0, s );

  // Truncate values above 100.
  cvThreshold( s, dst, 100, 100, CV_THRESH_TRUNC );

  cvReleaseImage( &r );
  cvReleaseImage( &g );
  cvReleaseImage( &b );
  cvReleaseImage( &s );
}

int main(int argc, char** argv)
{
  // Create a named window with the name of the file.
  cvNamedWindow( argv[1], 1 );

  // Load the image from the given file name.
  IplImage* src = cvLoadImage( argv[1] );
  IplImage* dst = cvCreateImage( cvGetSize(src), src->depth, 1);
  sum_rgb( src, dst);

  // Show the image in the named window
  cvShowImage( argv[1], dst );

  // Idle until the user hits the "Esc" key.
  while( 1 ) { if( (cvWaitKey( 10 )&0x7f) == 27 ) break; }

  // Clean up and don't be piggies
  cvDestroyWindow( argv[1] );
  cvReleaseImage( &src );
  cvReleaseImage( &dst );

}

Some important ideas are shown here. One thing is that we don't want to add into an 8-bit array because the higher bits will overflow. Instead, we use equally weighted addition of the three color channels (cvAddWeighted()); then the results are truncated to saturate at the value of 100 for the return. The cvThreshold() function handles only 8-bit or floating-point grayscale source images. The destination image must either match the source image or be an 8-bit image. In fact, cvThreshold() also allows the source and destination images to be the same image. Had we used a floating-point temporary image s in Example 5-2, we could have substituted the code shown in Example 5-3. Note that cvAcc() can accumulate 8-bit integer image types into a floating-point image; however, cdADD() cannot add integer bytes into floats.

Example 5-3. Alternative method to combine and threshold image planes

IplImage* s = cvCreateImage(cvGetSize(src), IPL_DEPTH_32F, 1);
cvZero(s);
cvAcc(b,s);
cvAcc(g,s);
cvAcc(r,s);
cvThreshold( s, s, 100, 100, CV_THRESH_TRUNC );
cvConvertScale( s, dst, 1, 0 );

There is a modified threshold technique in which the threshold level is itself variable. In OpenCV, this method is implemented in the cvAdaptiveThreshold() [Jain86] function:

void cvAdaptiveThreshold(
   CvArr*         src,
   CvArr*         dst,
   double         max_val,
   int            adaptive_method = CV_ADAPTIVE_THRESH_MEAN_C
   int            threshold_type  = CV_THRESH_BINARY,
   int            block_size      = 3,
   double         param1          = 5
);

cvAdaptiveThreshold() allows for two different adaptive threshold types depending on the settings of adaptive_method. In both cases the adaptive thresholdT(x, y) is set on a pixel-by-pixel basis by computing a weighted average of the b-by-b region around each pixel location minus a constant, where b is given by block_size and the constant is given by param1. If the method is set to CV_ADAPTIVE_THRESH_MEAN_C, then all pixels in the area are weighted equally. If it is set to CV_ADAPTIVE_THRESH_GAUSSIAN_C, then the pixels in the region around (x, y) are weighted according to a Gaussian function of their distance from that center point.

Finally, the parameter threshold_type is the same as for cvThreshold() shown in Table 5-5.

The adaptive threshold technique is useful when there are strong illumination or reflectance gradients that you need to threshold relative to the general intensity gradient. This function handles only single-channel 8-bit or floating-point images, and it requires that the source and destination images be distinct.

Source code for comparing cvAdaptiveThreshold() and cvThreshold() is shown in Example 5-4. Figure 5-24 displays the result of processing an image that has a strong lighting gradient across it. The lower-left portion of the figure shows the result of using a single global threshold as in cvThreshold(); the lower-right portion shows the result of adaptive local threshold using cvAdaptiveThreshold(). We get the whole checkerboard via adaptive threshold, a result that is impossible to achieve when using a single threshold. Note the calling-convention comments at the top of the code in Example 5-4; the parameters used for Figure 5-24 were:

./adaptThresh 15 1 1 71 15 ../Data/cal3-L.bmp

Example 5-4. Threshold versus adaptive threshold