New normalization in histogram comparison tutorial to use KV divergence

The Kullback-Leibler divergence works with histogram that have integral = 1,
otherwise it can return negative values. The normalization of the histograms
have been changed accordingly, and all the six comparison methods have been
used in the histogram comparison tutorial.
This commit is contained in:
ClaudioMartino 2025-07-05 11:15:35 +02:00
parent c48dad1d9d
commit 6a5884c04f
4 changed files with 37 additions and 29 deletions

View File

@ -25,7 +25,7 @@ Theory
- To compare two histograms ( \f$H_{1}\f$ and \f$H_{2}\f$ ), first we have to choose a *metric* - To compare two histograms ( \f$H_{1}\f$ and \f$H_{2}\f$ ), first we have to choose a *metric*
(\f$d(H_{1}, H_{2})\f$) to express how well both histograms match. (\f$d(H_{1}, H_{2})\f$) to express how well both histograms match.
- OpenCV implements the function @ref cv::compareHist to perform a comparison. It also offers 4 - OpenCV implements the function @ref cv::compareHist to perform a comparison. It also offers 6
different metrics to compute the matching: different metrics to compute the matching:
-# **Correlation ( cv::HISTCMP_CORREL )** -# **Correlation ( cv::HISTCMP_CORREL )**
\f[d(H_1,H_2) = \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}}\f] \f[d(H_1,H_2) = \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}}\f]
@ -36,12 +36,18 @@ Theory
-# **Chi-Square ( cv::HISTCMP_CHISQR )** -# **Chi-Square ( cv::HISTCMP_CHISQR )**
\f[d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)}\f] \f[d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)}\f]
-# **Intersection ( method=cv::HISTCMP_INTERSECT )** -# **Intersection ( cv::HISTCMP_INTERSECT )**
\f[d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I))\f] \f[d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I))\f]
-# **Bhattacharyya distance ( cv::HISTCMP_BHATTACHARYYA )** -# **Bhattacharyya distance ( cv::HISTCMP_BHATTACHARYYA )**
\f[d(H_1,H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}}\f] \f[d(H_1,H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}}\f]
-# **Alternative Chi-Square ( cv::HISTCMP_CHISQR_ALT )**
\f[d(H_1,H_2) = 2 * \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)+H_2(I)}\f]
-# **Kullback-Leibler divergence ( cv::HISTCMP_KL_DIV )**
\f[d(H_1,H_2) = \sum _I H_1(I) \log \left(\frac{H_1(I)}{H_2(I)}\right)\f] */
Code Code
---- ----
@ -123,7 +129,7 @@ Explanation
@snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Convert to HSV half @snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Convert to HSV half
@end_toggle @end_toggle
- Initialize the arguments to calculate the histograms (bins, ranges and channels H and S ). - Initialize the arguments to calculate the histograms (bins, ranges and channels H and S).
@add_toggle_cpp @add_toggle_cpp
@snippet samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp Using 50 bins for hue and 60 for saturation @snippet samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp Using 50 bins for hue and 60 for saturation
@ -151,7 +157,7 @@ Explanation
@snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Calculate the histograms for the HSV images @snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Calculate the histograms for the HSV images
@end_toggle @end_toggle
- Apply sequentially the 4 comparison methods between the histogram of the base image (hist_base) - Apply sequentially the 6 comparison methods between the histogram of the base image (hist_base)
and the other histograms: and the other histograms:
@add_toggle_cpp @add_toggle_cpp
@ -182,15 +188,17 @@ Results
are from the same source. For the other two test images, we can observe that they have very 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: different lighting conditions, so the matching should not be very good:
-# Here the numeric results we got with OpenCV 3.4.1: -# Here the numeric results we got with OpenCV 4.12.0:
*Method* | Base - Base | Base - Half | Base - Test 1 | Base - Test 2 *Method* | Base - Base | Base - Half | Base - Test 1 | Base - Test 2
----------------- | ------------ | ------------ | -------------- | --------------- ------------------- | ------------ | ------------ | -------------- | ---------------
*Correlation* | 1.000000 | 0.880438 | 0.20457 | 0.0664547 *Correlation* | 1.000000 | 0.880438 | 0.20457 | 0.065752
*Chi-square* | 0.000000 | 4.6834 | 2697.98 | 4763.8 *Chi-square* | 0.000000 | 0.328307 | 181.674 | 80.1494
*Intersection* | 18.8947 | 13.022 | 5.44085 | 2.58173 *Intersection* | 1.000000 | 0.75005 | 0.315061 | 0.0908022
*Bhattacharyya* | 0.000000 | 0.237887 | 0.679826 | 0.874173 *Bhattacharyya* | 0.000000 | 0.237866 | 0.679825 | 0.873709
*Chi-Square alt.* | 0.000000 | 0.395046 | 2.31572 | 3.41024
*KL divergence* | 0.000000 | 0.321064 | 2.6616 | 9.55412
For the *Correlation* and *Intersection* methods, the higher the metric, the more accurate the 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 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, that the match *base-half* is the second best match (as we predicted). For the other four metrics,
the less the result, the better the match. We can observe that the matches between the test 1 and the less the result, the better the match.
test 2 with respect to the base are worse, which again, was expected.

View File

@ -66,20 +66,20 @@ int main( int argc, char** argv )
Mat hist_base, hist_half_down, hist_test1, hist_test2; Mat hist_base, hist_half_down, hist_test1, hist_test2;
calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false ); calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false );
normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() ); normalize( hist_base, hist_base, 1, 0, NORM_L1, -1, Mat() );
calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false ); 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() ); normalize( hist_half_down, hist_half_down, 1, 0, NORM_L1, -1, Mat() );
calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false ); calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false );
normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() ); normalize( hist_test1, hist_test1, 1, 0, NORM_L1, -1, Mat() );
calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false ); calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false );
normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() ); normalize( hist_test2, hist_test2, 1, 0, NORM_L1, -1, Mat() );
//! [Calculate the histograms for the HSV images] //! [Calculate the histograms for the HSV images]
//! [Apply the histogram comparison methods] //! [Apply the histogram comparison methods]
for( int compare_method = 0; compare_method < 4; compare_method++ ) for( int compare_method = 0; compare_method < 6; compare_method++ )
{ {
double base_base = compareHist( hist_base, hist_base, 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_half = compareHist( hist_base, hist_half_down, compare_method );

View File

@ -52,23 +52,23 @@ class CompareHist {
List<Mat> hsvBaseList = Arrays.asList(hsvBase); List<Mat> hsvBaseList = Arrays.asList(hsvBase);
Imgproc.calcHist(hsvBaseList, new MatOfInt(channels), new Mat(), histBase, new MatOfInt(histSize), new MatOfFloat(ranges), false); Imgproc.calcHist(hsvBaseList, new MatOfInt(channels), new Mat(), histBase, new MatOfInt(histSize), new MatOfFloat(ranges), false);
Core.normalize(histBase, histBase, 0, 1, Core.NORM_MINMAX); Core.normalize(histBase, histBase, 1, 0, Core.NORM_L1);
List<Mat> hsvHalfDownList = Arrays.asList(hsvHalfDown); List<Mat> hsvHalfDownList = Arrays.asList(hsvHalfDown);
Imgproc.calcHist(hsvHalfDownList, new MatOfInt(channels), new Mat(), histHalfDown, new MatOfInt(histSize), new MatOfFloat(ranges), false); Imgproc.calcHist(hsvHalfDownList, new MatOfInt(channels), new Mat(), histHalfDown, new MatOfInt(histSize), new MatOfFloat(ranges), false);
Core.normalize(histHalfDown, histHalfDown, 0, 1, Core.NORM_MINMAX); Core.normalize(histHalfDown, histHalfDown, 1, 0, Core.NORM_L1);
List<Mat> hsvTest1List = Arrays.asList(hsvTest1); List<Mat> hsvTest1List = Arrays.asList(hsvTest1);
Imgproc.calcHist(hsvTest1List, new MatOfInt(channels), new Mat(), histTest1, new MatOfInt(histSize), new MatOfFloat(ranges), false); Imgproc.calcHist(hsvTest1List, new MatOfInt(channels), new Mat(), histTest1, new MatOfInt(histSize), new MatOfFloat(ranges), false);
Core.normalize(histTest1, histTest1, 0, 1, Core.NORM_MINMAX); Core.normalize(histTest1, histTest1, 1, 0, Core.NORM_L1);
List<Mat> hsvTest2List = Arrays.asList(hsvTest2); List<Mat> hsvTest2List = Arrays.asList(hsvTest2);
Imgproc.calcHist(hsvTest2List, new MatOfInt(channels), new Mat(), histTest2, new MatOfInt(histSize), new MatOfFloat(ranges), false); Imgproc.calcHist(hsvTest2List, new MatOfInt(channels), new Mat(), histTest2, new MatOfInt(histSize), new MatOfFloat(ranges), false);
Core.normalize(histTest2, histTest2, 0, 1, Core.NORM_MINMAX); Core.normalize(histTest2, histTest2, 1, 0, Core.NORM_L1);
//! [Calculate the histograms for the HSV images] //! [Calculate the histograms for the HSV images]
//! [Apply the histogram comparison methods] //! [Apply the histogram comparison methods]
for( int compareMethod = 0; compareMethod < 4; compareMethod++ ) { for( int compareMethod = 0; compareMethod < 6; compareMethod++ ) {
double baseBase = Imgproc.compareHist( histBase, histBase, compareMethod ); double baseBase = Imgproc.compareHist( histBase, histBase, compareMethod );
double baseHalf = Imgproc.compareHist( histBase, histHalfDown, compareMethod ); double baseHalf = Imgproc.compareHist( histBase, histHalfDown, compareMethod );
double baseTest1 = Imgproc.compareHist( histBase, histTest1, compareMethod ); double baseTest1 = Imgproc.compareHist( histBase, histTest1, compareMethod );

View File

@ -45,20 +45,20 @@ channels = [0, 1]
## [Calculate the histograms for the HSV images] ## [Calculate the histograms for the HSV images]
hist_base = cv.calcHist([hsv_base], channels, None, histSize, ranges, accumulate=False) hist_base = cv.calcHist([hsv_base], channels, None, histSize, ranges, accumulate=False)
cv.normalize(hist_base, hist_base, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) cv.normalize(hist_base, hist_base, alpha=1, beta=0, norm_type=cv.NORM_L1)
hist_half_down = cv.calcHist([hsv_half_down], channels, None, histSize, ranges, accumulate=False) hist_half_down = cv.calcHist([hsv_half_down], channels, None, histSize, ranges, accumulate=False)
cv.normalize(hist_half_down, hist_half_down, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) cv.normalize(hist_half_down, hist_half_down, alpha=1, beta=0, norm_type=cv.NORM_L1)
hist_test1 = cv.calcHist([hsv_test1], channels, None, histSize, ranges, accumulate=False) hist_test1 = cv.calcHist([hsv_test1], channels, None, histSize, ranges, accumulate=False)
cv.normalize(hist_test1, hist_test1, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) cv.normalize(hist_test1, hist_test1, alpha=1, beta=0, norm_type=cv.NORM_L1)
hist_test2 = cv.calcHist([hsv_test2], channels, None, histSize, ranges, accumulate=False) hist_test2 = cv.calcHist([hsv_test2], channels, None, histSize, ranges, accumulate=False)
cv.normalize(hist_test2, hist_test2, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) cv.normalize(hist_test2, hist_test2, alpha=1, beta=0, norm_type=cv.NORM_L1)
## [Calculate the histograms for the HSV images] ## [Calculate the histograms for the HSV images]
## [Apply the histogram comparison methods] ## [Apply the histogram comparison methods]
for compare_method in range(4): for compare_method in range(6):
base_base = cv.compareHist(hist_base, hist_base, compare_method) base_base = cv.compareHist(hist_base, hist_base, compare_method)
base_half = cv.compareHist(hist_base, hist_half_down, compare_method) base_half = cv.compareHist(hist_base, hist_half_down, compare_method)
base_test1 = cv.compareHist(hist_base, hist_test1, compare_method) base_test1 = cv.compareHist(hist_base, hist_test1, compare_method)