Drawing a histogram
A histogram is a statistical graphic representation of variable distribution that allows us to understand the density estimation and probability distribution of data. A histogram is created by dividing the entire range of variable values into a small range of values, and then counting how many values fall into each interval.
If we apply this histogram concept to an image, it seems to be difficult to understand but, in fact, it is very simple. In a gray image, our variable values' ranges are each possible gray value (from 0 to 255), and the density is the number of pixels of the image that have this value. This means that we have to count the number of pixels of the image that have a value of 0, the number of pixels with a value of 1, and so on.
The callback function that shows the histogram of the input image is showHistoCallback ; this function calculates the histogram of each channel image and shows the result of each histogram channel in a new image.
Now, check the following code:
void showHistoCallback(int state, void* userData) { // Separate image in BRG vector<Mat> bgr; split(img, bgr); // Create the histogram for 256 bins // The number of possibles values [0..255] int numbins= 256; /// Set the ranges for B,G,R last is not included float range[] = { 0, 256 } ; const float* histRange = { range }; Mat b_hist, g_hist, r_hist; calcHist(&bgr[0], 1, 0, Mat(), b_hist, 1, &numbins, &histRange); calcHist(&bgr[1], 1, 0, Mat(), g_hist, 1, &numbins, &histRange); calcHist(&bgr[2], 1, 0, Mat(), r_hist, 1, &numbins, &histRange); // Draw the histogram // We go to draw lines for each channel int width= 512; int height= 300; // Create image with gray base Mat histImage(height, width, CV_8UC3, Scalar(20,20,20)); // Normalize the histograms to height of image normalize(b_hist, b_hist, 0, height, NORM_MINMAX); normalize(g_hist, g_hist, 0, height, NORM_MINMAX); normalize(r_hist, r_hist, 0, height, NORM_MINMAX); int binStep= cvRound((float)width/(float)numbins); for(int i=1; i< numbins; i++) { line(histImage, Point( binStep*(i-1), height-cvRound(b_hist.at<float>(i-1) )), Point( binStep*(i), height-cvRound(b_hist.at<float>(i) )), Scalar(255,0,0) ); line(histImage, Point(binStep*(i-1), height-cvRound(g_hist.at<float>(i-1))), Point(binStep*(i), height-cvRound(g_hist.at<float>(i))), Scalar(0,255,0) ); line(histImage, Point(binStep*(i-1), height-cvRound(r_hist.at<float>(i-1))), Point(binStep*(i), height-cvRound(r_hist.at<float>(i))), Scalar(0,0,255) ); } imshow("Histogram", histImage); }
Let's understand how to extract each channel histogram and how to draw it. First, we need to create three matrices to process each input image channel. We use a vector-type variable to store each one and use the split OpenCV function to divide the input image among these three channels:
// Separate image in BRG vector<Mat> bgr; split(img, bgr);
Now, we are going to define the number of bins of our histogram, in our case, one per possible pixel value:
int numbins= 256;
Let's define our range of variables and create three matrices to store each histogram:
/// Set the ranges for B,G,R float range[] = {0, 256} ; const float* histRange = {range}; Mat b_hist, g_hist, r_hist;
We can calculate the histograms using the calcHist OpenCV function. This function has several parameters with this order:
- The input image: In our case, we use one image channel stored in the bgr vector
- The number of images in the input to calculate the histogram: In our case, we only use 1 image
- The number channel dimensions used to compute the histogram: We use 0 in our case
- The optional mask matrix.
- The variable to store the calculated histogram.
- Histogram dimensionality: This is the dimension of the space where the image (here, a gray plane) is taking its values, in our case 1
- Number of bins to calculate: In our case 256 bins, one per pixel value
- Range of input variables: In our case, from 0 to 255 possible pixels values
Our calcHist function for each channel looks as follows:
calcHist(&bgr[0], 1, 0, Mat(), b_hist, 1, &numbins, &histRange ); calcHist(&bgr[1], 1, 0, Mat(), g_hist, 1, &numbins, &histRange ); calcHist(&bgr[2], 1, 0, Mat(), r_hist, 1, &numbins, &histRange );
Now that we have calculated each channel histogram, we have to draw each one and show it to the user. To do this, we are going to create a color image that is 512 by 300 pixels in size:
// Draw the histogram // We go to draw lines for each channel int width= 512; int height= 300; // Create image with gray base Mat histImage(height, width, CV_8UC3, Scalar(20,20,20));
Before we draw the histogram values into our image, we are going to normalize the histogram matrices between the minimum value, 0, and a maximum value; the maximum value is the same as the height of our output histogram image:
// Normalize the histograms to height of image normalize(b_hist, b_hist, 0, height, NORM_MINMAX); normalize(g_hist, g_hist, 0, height, NORM_MINMAX); normalize(r_hist, r_hist, 0, height, NORM_MINMAX);
Now we have to draw a line from bin 0 to bin 1, and so on. Between each bin, we have to calculate how many pixels there are; then, a binStep variable is calculated by dividing the width by the number of bins. Each small line is drawn from horizontal position i-1 to i; the vertical position is the histogram value in the corresponding i, and it is drawn with the color channel representation:
int binStep= cvRound((float)width/(float)numbins); for(int i=1; i< numbins; i++) { line(histImage, Point(binStep*(i-1), height-cvRound(b_hist.at<float>(i-1))), Point(binStep*(i), height-cvRound(b_hist.at<float>(i))), Scalar(255,0,0) ); line(histImage, Point(binStep*(i-1), height-cvRound(g_hist.at<float>(i-1))), Point( binStep*(i), height-cvRound(g_hist.at<float>(i))), Scalar(0,255,0) ); line(histImage, Point(binStep*(i-1), height-cvRound(r_hist.at<float>(i-1))), Point( binStep*(i), height-cvRound(r_hist.at<float>(i))), Scalar(0,0,255) ); }
Finally, we show the histogram image with the imshow function:
imshow("Histogram", histImage);
This is the result for the lena.png image: