Histogram matching and LUT

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.

Histogram matching and LUT

Output of the histMatching example

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset