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).
Figure 5-23 should help to clarify the exact implications of each threshold type.
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
Figure 5-24. Binary threshold versus adaptive binary threshold: the input image (top) was turned into a Boolean image using a global threshold (lower left ) and an adaptive threshold (lower right); raw image courtesy of Kurt Konolidge
Example 5-4. Threshold versus adaptive threshold
// Compare thresholding with adaptive thresholding // CALL: // ./adaptThreshold Threshold 1binary 1adaptivemean \ // blocksize offset filename #include "cv.h" #include "highgui.h" #include "math.h" IplImage *Igray=0, *It = 0, *Iat; int main( int argc, char** argv ) { if(argc != 7){return -1; } //Command line double threshold = (double)atof(argv[1]); int threshold_type = atoi(argv[2]) ? CV_THRESH_BINARY : CV_THRESH_BINARY_INV; int adaptive_method = atoi(argv[3]) ? CV_ADAPTIVE_THRESH_MEAN_C : CV_ADAPTIVE_THRESH_GAUSSIAN_C; int block_size = atoi(argv[4]); double offset = (double)atof(argv[5]); //Read in gray image if((Igray = cvLoadImage( argv[6], CV_LOAD_IMAGE_GRAYSCALE)) == 0){ return -1;} // Create the grayscale output images It = cvCreateImage(cvSize(Igray->width,Igray->height), IPL_DEPTH_8U, 1); Iat = cvCreateImage(cvSize(Igray->width,Igray->height), IPL_DEPTH_8U, 1); //Threshold cvThreshold(Igray,It,threshold,255,threshold_type); cvAdaptiveThreshold(Igray, Iat, 255, adaptive_method, threshold_type, block_size, offset); //PUT UP 2 WINDOWS cvNamedWindow("Raw",1); cvNamedWindow("Threshold",1); cvNamedWindow("Adaptive Threshold",1); //Show the results cvShowImage("Raw",Igray); cvShowImage("Threshold",It); cvShowImage("Adaptive Threshold",Iat); cvWaitKey(0); //Clean up cvReleaseImage(&Igray); cvReleaseImage(&It); cvReleaseImage(&Iat); cvDestroyWindow("Raw"); cvDestroyWindow("Threshold"); cvDestroyWindow("Adaptive Threshold"); return(0); }