The histogram may also be used to modify the color of an image. Histogram matching is a method of color adjustment between two color images. Given a reference image and a target image, the result (destination image) will be equal to the target image except that its (three) histograms will look like those of the reference image. This effect is known as color mapping or color transfer.
The histogram matching algorithm is run over each of the three color histograms independently. For each channel, the cumulative distribution function (cdf) has to be calculated. For a given channel, let Fr
be the cdf of the reference image and Ft
be the cdf of the target image. Then, for each pixel v
in the reference image, we find the gray level w
, for which Fr(v)=Ft(w)
. The pixel with value v
is thus changed to w
.
Next, we provide another example of histograms in which we use a technique called histogram matching. The example also uses
look-up tables (LUT). A look-up table transformation assigns a new pixel value to each pixel in the input image (there is a good explanation and example of an LUT at http://docs.opencv.org/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html). The new values are given by a table. Thus, the first entry in this table gives the new value for pixel value 0, the second the new value for pixel value 1, and so on. Assuming we use a source and destination image, the transform is then given by Dst(x,y)=LUT(Src(x,y))
.
The OpenCV function for performing a look-up table transformation is LUT(InputArray src, InputArray lut, OutputArray dst, int interpolation=0)
. The parameter src
is an 8-bit image. The table is given in the parameter lut
, which has 256 elements. The table has either one channel or the same number of channels as the source image.
The following is the
histMatching
example:
#include "opencv2/opencv.hpp" #include <iostream> using namespace std; using namespace cv; void histMatch(const Mat &reference, const Mat &target, Mat &result){ float const HISTMATCH = 0.000001; double min, max; vector<Mat> ref_channels; split(reference, ref_channels); vector<Mat> tgt_channels; split(target, tgt_channels); int histSize = 256; float range[] = {0, 256}; const float* histRange = { range }; bool uniform = true; //For every channel (B, G, R) for ( int i=0 ; i<3 ; i++ ) { Mat ref_hist, tgt_hist; Mat ref_hist_accum, tgt_hist_accum; //Calculate histograms calcHist(&ref_channels[i], 1, 0, Mat(), ref_hist, 1, &histSize, &histRange, uniform, false); calcHist(&tgt_channels[i], 1, 0, Mat(), tgt_hist, 1, &histSize, &histRange, uniform, false); //Normalize histograms minMaxLoc(ref_hist, &min, &max); if (max==0) continue; ref_hist = ref_hist / max; minMaxLoc(tgt_hist, &min, &max); if (max==0) continue; tgt_hist = tgt_hist / max; //Calculate accumulated histograms ref_hist.copyTo(ref_hist_accum); tgt_hist.copyTo(tgt_hist_accum); float * src_cdf_data = ref_hist_accum.ptr<float>(); float * dst_cdf_data = tgt_hist_accum.ptr<float>(); for ( int j=1 ; j < 256 ; j++ ) { src_cdf_data[j] = src_cdf_data[j] + src_cdf_data[j-1]; dst_cdf_data[j] = dst_cdf_data[j] + dst_cdf_data[j-1]; } //Normalize accumulated minMaxLoc(ref_hist_accum, &min, &max); ref_hist_accum = ref_hist_accum / max; minMaxLoc(tgt_hist_accum, &min, &max); tgt_hist_accum = tgt_hist_accum / max; //Result max Mat Mv(1, 256, CV_8UC1); uchar * M = Mv.ptr<uchar>(); uchar last = 0; for ( int j=0 ; j < tgt_hist_accum.rows ; j++ ) { float F1 = dst_cdf_data[j]; for ( uchar k=last ; k < ref_hist_accum.rows ; k++ ) { float F2 = src_cdf_data[k]; if ( std::abs(F2 - F1) < HISTMATCH || F2 > F1 ) { M[j] = k; last = k; break; } } } Mat lut(1, 256, CV_8UC1, M); LUT(tgt_channels[i], lut, tgt_channels[i]); } //Merge the three channels into the result image merge(tgt_channels, result); } int main(int argc, char *argv[]) { //Read original image and clone it to contain results Mat ref = imread("baboon.jpg", CV_LOAD_IMAGE_COLOR ); Mat tgt = imread("lena.jpg", CV_LOAD_IMAGE_COLOR ); Mat dst = tgt.clone(); //Create three windows namedWindow("Reference", WINDOW_AUTOSIZE); namedWindow("Target", WINDOW_AUTOSIZE); namedWindow("Result", WINDOW_AUTOSIZE); imshow("Reference", ref); imshow("Target", tgt); histMatch(ref, tgt, dst); imshow("Result", dst); // Position windows on screen moveWindow("Reference", 0,0); moveWindow("Target", ref.cols,0); moveWindow("Result", ref.cols+tgt.cols,0); waitKey(); // Wait for key press return 0; }
The code explanation is given here: the example first reads the reference and target images. The output image is also allocated. The main function is histMatch
. In it, the reference and target images are first split into the three color channels. Then, for every channel, we obtain the normalized histograms of reference and target images, followed by the respective cdfs. Next, the histogram matching transformation is performed.
Finally, we apply the new pixel values using the look-up table. Note that the transformation could also be applied by iterating over every pixel in the result image. The look-up table option is, however, much faster. The following screenshot shows the output of the sample. The color palette of the reference image (the baboon.jpg
image) is transferred to the target image.