For a small project, I need to compare one image with another - to determine if the images are approximately the same or not. The images are smallish, varying from 25 to 100px across. The images are meant to be of the same picture data but are sublty different, so a simple pixel equality check won't work. Consider these two possible scenarios: Show
I've decided to represent each image using histograms, using three 1D histograms: one for each RGB channel - it's safe for me to just use colour and to ignore texture and edge histograms (An alternative approach uses a single 3D histogram for each image, but I'm avoiding that as it adds extra complexity). Therefore I will need to compare the histograms to see how similar they are, and if the similarity measure passes some threshold value then I can say with confidence the respective images are visually the same - I would be comparing each image's corresponding channel histograms (e.g. image 1's red histogram with image 2's red histogram, then image 1's blue histogram with image 2's blue histogram, then the green histograms - so I'm not comparing image 1's red histogram with image 2's blue histogram, that would just be silly). Let's say I have these three histograms, which represent a summary of the red RGB channel for three images (using 5 bins for 7-pixel images for simplicity): H1 H2 H3 X X X X X X X X X X X X X X X X X X X X X 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 H1 = [ 1, 3, 0, 2, 1 ] H2 = [ 3, 1, 0, 1, 2 ] H3 = [ 1, 1, 1, 1, 3 ]Image 1 (H1) is my reference image, and I want to see if Image 2 (H2) and/or Image 3 (H3) is similar to Image 1. Note that in this example, Image 2 is similar to Image 1, but Image 3 is not. When I did a cursory search for "histogram difference" algorithms (at least those I could understand) I found a popular approach was to just sum the differences between each bin, however this approach often fails because it weighs all bin differences the same. To demonstrate the problem with this approach, in C# code, like this: Int32[] image1RedHistogram = new Int32[] { 1, 3, 0, 2, 1 }; Int32[] image2RedHistogram = new Int32[] { 3, 2, 0, 1, 2 }; Int32[] image3RedHistogram = new Int32[] { 1, 1, 1, 1, 3 }; Int32 GetDifference(Int32[] x, Int32[] y) { Int32 sumOfDifference = 0; for( int i = 0; i < x.Length; i++ ) { sumOfDifference += Math.Abs( x[i] - y[i] ); } return sumOfDifferences; }The output of which is: GetDifference( image1RedHistogram, image2RedHistogram ) == 6 GetDifference( image1RedHistogram, image3RedHistogram ) == 6This is incorrect. Is there a way to determine the difference between two histograms that takes into account the shape of the distribution? Prev Tutorial: Histogram Calculation Next Tutorial: Back Projection GoalIn this tutorial you will learn how to:
Theory
Code
C++
Java
Python
C++
CommandLineParser parser( argc, argv, keys ); Mat src_base = imread( parser.get<String>("input1") ); Mat src_test1 = imread( parser.get<String>("input2") ); Mat src_test2 = imread( 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; } Java
if (args.length != 3) { System.err.println("You must supply 3 arguments that correspond to the paths to 3 images."); System.exit(0); } Mat srcBase = Imgcodecs.imread(args[0]); Mat srcTest1 = Imgcodecs.imread(args[1]); Mat srcTest2 = Imgcodecs.imread(args[2]); if (srcBase.empty() || srcTest1.empty() || srcTest2.empty()) { System.err.println("Cannot read the images"); System.exit(0); } Python
parser = argparse.ArgumentParser(description='Code for Histogram Comparison tutorial.') parser.add_argument('--input1', help='Path to input image 1.') parser.add_argument('--input2', help='Path to input image 2.') parser.add_argument('--input3', help='Path to input image 3.') args = parser.parse_args() src_base = cv.imread(args.input1) src_test1 = cv.imread(args.input2) src_test2 = cv.imread(args.input3) if src_base is None or src_test1 is None or src_test2 is None: print('Could not open or find the images!') exit(0)
C++ Java
Mat hsvBase = new Mat(), hsvTest1 = new Mat(), hsvTest2 = new Mat(); Imgproc.cvtColor( srcBase, hsvBase, Imgproc.COLOR_BGR2HSV ); Imgproc.cvtColor( srcTest1, hsvTest1, Imgproc.COLOR_BGR2HSV ); Imgproc.cvtColor( srcTest2, hsvTest2, Imgproc.COLOR_BGR2HSV ); Python
C++
Mat hsv_half_down = hsv_base( Range( hsv_base.rows/2, hsv_base.rows ), Range( 0, hsv_base.cols ) ); Java
Mat hsvHalfDown = hsvBase.submat( new Range( hsvBase.rows()/2, hsvBase.rows() - 1 ), new Range( 0, hsvBase.cols() - 1 ) ); Python
hsv_half_down = hsv_base[hsv_base.shape[0]//2:,:]
C++
int h_bins = 50, s_bins = 60; int histSize[] = { h_bins, s_bins }; float h_ranges[] = { 0, 180 }; float s_ranges[] = { 0, 256 }; const float* ranges[] = { h_ranges, s_ranges }; int channels[] = { 0, 1 }; Java
int hBins = 50, sBins = 60; int[] histSize = { hBins, sBins }; float[] ranges = { 0, 180, 0, 256 }; int[] channels = { 0, 1 }; Python
h_bins = 50 s_bins = 60 histSize = [h_bins, s_bins] h_ranges = [0, 180] s_ranges = [0, 256] ranges = h_ranges + s_ranges channels = [0, 1]
C++
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() ); Java
Mat histBase = new Mat(), histHalfDown = new Mat(), histTest1 = new Mat(), histTest2 = new Mat(); List<Mat> hsvBaseList = Arrays.asList(hsvBase); 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); List<Mat> hsvHalfDownList = Arrays.asList(hsvHalfDown); 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); List<Mat> hsvTest1List = Arrays.asList(hsvTest1); 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); List<Mat> hsvTest2List = Arrays.asList(hsvTest2); 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); Python
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) 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) 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) 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)
C++
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; } Java
for( int compareMethod = 0; compareMethod < 4; compareMethod++ ) { double baseBase = Imgproc.compareHist( histBase, histBase, compareMethod ); double baseHalf = Imgproc.compareHist( histBase, histHalfDown, compareMethod ); double baseTest1 = Imgproc.compareHist( histBase, histTest1, compareMethod ); double baseTest2 = Imgproc.compareHist( histBase, histTest2, compareMethod ); System.out.println("Method " + compareMethod + " Perfect, Base-Half, Base-Test(1), Base-Test(2) : " + baseBase + " / " + baseHalf + " / " + baseTest1 + " / " + baseTest2); } Python
for compare_method in range(4): base_base = cv.compareHist(hist_base, hist_base, compare_method) base_half = cv.compareHist(hist_base, hist_half_down, compare_method) base_test1 = cv.compareHist(hist_base, hist_test1, compare_method) base_test2 = cv.compareHist(hist_base, hist_test2, compare_method) print('Method:', compare_method, 'Perfect, Base-Half, Base-Test(1), Base-Test(2) :',\ base_base, '/', base_half, '/', base_test1, '/', base_test2)
|