OpenCV
Open Source Computer Vision
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
Histogram Comparison

Table of Contents

Prev Tutorial: Histogram Calculation
Next Tutorial: Back Projection

Original author Ana Huamán
Compatibility OpenCV >= 3.0

Goal

In this tutorial you will learn how to:

  • Use the function cv::compareHist to get a numerical parameter that express how well two histograms match with each other.
  • Use different metrics to compare histograms

Theory

  • To compare two histograms ( H1 and H2 ), first we have to choose a metric ( d(H1,H2)) to express how well both histograms match.
  • OpenCV implements the function cv::compareHist to perform a comparison. It also offers 4 different metrics to compute the matching:
    1. Correlation ( cv::HISTCMP_CORREL )

      d(H1,H2)=I(H1(I)H1¯)(H2(I)H2¯)I(H1(I)H1¯)2I(H2(I)H2¯)2

      where

      Hk¯=1NJHk(J)

      and N is the total number of histogram bins.
    2. Chi-Square ( cv::HISTCMP_CHISQR )

      d(H1,H2)=I(H1(I)H2(I))2H1(I)

    3. Intersection ( method=cv::HISTCMP_INTERSECT )

      d(H1,H2)=Imin(H1(I),H2(I))

    4. Bhattacharyya distance ( cv::HISTCMP_BHATTACHARYYA )

      d(H1,H2)=11H1¯H2¯N2IH1(I)H2(I)

Code

  • What does this program do?
    • Loads a base image and 2 test images to be compared with it.
    • Generate 1 image that is the lower half of the base image
    • Convert the images to HSV format
    • Calculate the H-S histogram for all the images and normalize them in order to compare them.
    • Compare the histogram of the base image with respect to the 2 test histograms, the histogram of the lower half base image and with the same base image histogram.
    • Display the numerical matching parameters obtained.
  • Downloadable code: Click here
  • Code at glance:
    #include <iostream>
    using namespace std;
    using namespace cv;
    const char* keys =
    "{ help h| | Print help message. }"
    "{ @input1 |Histogram_Comparison_Source_0.jpg | Path to input image 1. }"
    "{ @input2 |Histogram_Comparison_Source_1.jpg | Path to input image 2. }"
    "{ @input3 |Histogram_Comparison_Source_2.jpg | Path to input image 3. }";
    int main( int argc, char** argv )
    {
    CommandLineParser parser( argc, argv, keys );
    samples::addSamplesDataSearchSubDirectory( "doc/tutorials/imgproc/histograms/histogram_comparison/images" );
    Mat src_base = imread(samples::findFile( parser.get<String>( "@input1" ) ) );
    Mat src_test1 = imread(samples::findFile( parser.get<String>( "@input2" ) ) );
    Mat src_test2 = imread(samples::findFile( parser.get<String>( "@input3" ) ) );
    if( src_base.empty() || src_test1.empty() || src_test2.empty() )
    {
    cout << "Could not open or find the images!\n" << endl;
    parser.printMessage();
    return -1;
    }
    Mat hsv_base, hsv_test1, hsv_test2;
    cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
    cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
    cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );
    Mat hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows ), Range( 0, hsv_base.cols ) );
    int h_bins = 50, s_bins = 60;
    int histSize[] = { h_bins, s_bins };
    // hue varies from 0 to 179, saturation from 0 to 255
    float h_ranges[] = { 0, 180 };
    float s_ranges[] = { 0, 256 };
    const float* ranges[] = { h_ranges, s_ranges };
    // Use the 0-th and 1-st channels
    int channels[] = { 0, 1 };
    Mat hist_base, hist_half_down, hist_test1, hist_test2;
    calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
    normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
    calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
    normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );
    calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
    normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
    calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
    normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );
    for( int compare_method = 0; compare_method < 4; compare_method++ )
    {
    double base_base = compareHist( hist_base, hist_base, compare_method );
    double base_half = compareHist( hist_base, hist_half_down, compare_method );
    double base_test1 = compareHist( hist_base, hist_test1, compare_method );
    double base_test2 = compareHist( hist_base, hist_test2, compare_method );
    cout << "Method " << compare_method << " Perfect, Base-Half, Base-Test(1), Base-Test(2) : "
    << base_base << " / " << base_half << " / " << base_test1 << " / " << base_test2 << endl;
    }
    cout << "Done \n";
    return 0;
    }
    Designed for command line parsing.
    Definition utility.hpp:890
    n-dimensional dense array class
    Definition mat.hpp:829
    int cols
    Definition mat.hpp:2155
    bool empty() const
    Returns true if the array has no elements.
    int rows
    the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
    Definition mat.hpp:2155
    Template class specifying a continuous subsequence (slice) of a sequence.
    Definition types.hpp:630
    void normalize(InputArray src, InputOutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray())
    Normalizes the norm or value range of an array.
    std::string String
    Definition cvstd.hpp:151
    void calcHist(const Mat *images, int nimages, const int *channels, InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform=true, bool accumulate=false)
    Calculates a histogram of a set of arrays.
    double compareHist(InputArray H1, InputArray H2, int method)
    Compares two histograms.
    int main(int argc, char *argv[])
    Definition highgui_qt.cpp:3
    Definition core.hpp:107
    STL namespace.

Explanation

  • Load the base image (src_base) and the other two test images:

    CommandLineParser parser( argc, argv, keys );
    samples::addSamplesDataSearchSubDirectory( "doc/tutorials/imgproc/histograms/histogram_comparison/images" );
    Mat src_base = imread(samples::findFile( parser.get<String>( "@input1" ) ) );
    Mat src_test1 = imread(samples::findFile( parser.get<String>( "@input2" ) ) );
    Mat src_test2 = imread(samples::findFile( parser.get<String>( "@input3" ) ) );
    if( src_base.empty() || src_test1.empty() || src_test2.empty() )
    {
    cout << "Could not open or find the images!\n" << endl;
    parser.printMessage();
    return -1;
    }
  • Convert them to HSV format:

    Mat hsv_base, hsv_test1, hsv_test2;
    cvtColor( src_base, hsv_base, COLOR_BGR2HSV );
    cvtColor( src_test1, hsv_test1, COLOR_BGR2HSV );
    cvtColor( src_test2, hsv_test2, COLOR_BGR2HSV );
  • Also, create an image of half the base image (in HSV format):

    Mat hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows ), Range( 0, hsv_base.cols ) );
  • Initialize the arguments to calculate the histograms (bins, ranges and channels H and S ).

    int h_bins = 50, s_bins = 60;
    int histSize[] = { h_bins, s_bins };
    // hue varies from 0 to 179, saturation from 0 to 255
    float h_ranges[] = { 0, 180 };
    float s_ranges[] = { 0, 256 };
    const float* ranges[] = { h_ranges, s_ranges };
    // Use the 0-th and 1-st channels
    int channels[] = { 0, 1 };
  • Calculate the Histograms for the base image, the 2 test images and the half-down base image:

    Mat hist_base, hist_half_down, hist_test1, hist_test2;
    calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
    normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() );
    calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false );
    normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() );
    calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
    normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() );
    calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
    normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() );
  • Apply sequentially the 4 comparison methods between the histogram of the base image (hist_base) and the other histograms:

    for( int compare_method = 0; compare_method < 4; compare_method++ )
    {
    double base_base = compareHist( hist_base, hist_base, compare_method );
    double base_half = compareHist( hist_base, hist_half_down, compare_method );
    double base_test1 = compareHist( hist_base, hist_test1, compare_method );
    double base_test2 = compareHist( hist_base, hist_test2, compare_method );
    cout << "Method " << compare_method << " Perfect, Base-Half, Base-Test(1), Base-Test(2) : "
    << base_base << " / " << base_half << " / " << base_test1 << " / " << base_test2 << endl;
    }

Results

  1. We use as input the following images:

where the first one is the base (to be compared to the others), the other 2 are the test images. We will also compare the first image with respect to itself and with respect of half the base image.

  1. We should expect a perfect match when we compare the base image histogram with itself. Also, compared with the histogram of half the base image, it should present a high match since both are from the same source. For the other two test images, we can observe that they have very different lighting conditions, so the matching should not be very good:
  2. Here the numeric results we got with OpenCV 3.4.1:
    Method Base - Base Base - Half Base - Test 1 Base - Test 2
    Correlation 1.000000 0.880438 0.20457 0.0664547
    Chi-square 0.000000 4.6834 2697.98 4763.8
    Intersection 18.8947 13.022 5.44085 2.58173
    Bhattacharyya 0.000000 0.237887 0.679826 0.874173
    For the Correlation and Intersection methods, the higher the metric, the more accurate the match. As we can see, the match base-base is the highest of all as expected. Also we can observe that the match base-half is the second best match (as we predicted). For the other two metrics, the less the result, the better the match. We can observe that the matches between the test 1 and test 2 with respect to the base are worse, which again, was expected.