OpenCV 学习笔记(4) Counting the pixels with Histograms

In this chapter, we will cover:

  • Computing the image histogram(计算图像的直方图)
  • Applying look-up tables to modify image appearance(使用查找表修改图像外观)
  • Equalizing the image histogram(直方图均衡化)
  • Backprojecting a histogram to detect specific image content(反向投影直方图去检测特定的图像内容)
  • Using the mean shift algorithm to find an object(使用均值漂移算法去寻找物体)
  • Retrieving similar images using histogram comparison(比较直方图来检索相似图像)

Introduction

  像素值在图像中的分布情况是一幅图像的重要特征,直方图可以描述图像内容,检测图像中特定的对象或纹理。这里分享一个知乎上关于google图片搜索原理的问题,其中有个答案就说了,计算图片的直方图,看直方图的相似情况。

二.Computing the image histogram

Definition

  A Histogram is a simple table that gives the number of pixels that have a given value in an image(or sometime a set of images).The histogram of a gray-level image will therefore have 256 entries (or bins).Bin 0 gives the number of pixels having value 0, bin 1 the number of pixels having value 1, and so on.

计算Histogram

  Computing a histogram with OpenCV can be easily done by using the cv::calcHist function. This is a general function which can compute the histogram of multiple channel images of any pixel value type. See more in cv::calcHist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//单通道图像的处理
#include "stdafx.h"

#include <opencv2/opencv.hpp>

class Histogram1D
{
public:
Histogram1D(){
nimages = 1;
channels[0] = 0;
dims = 1;
histSize[0] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] = hranges;
}
cv::MatND GetHistogram(const cv::Mat &image);
cv::Mat GetHistogramImage(cv::Mat &image);
private:
int nimages;
int channels[1];
int dims;
int histSize[1];
const float *ranges[1]; //必须是const float**,不能是float**
float hranges[2];

};

cv::MatND Histogram1D::GetHistogram(const cv::Mat &image)
{
cv::MatND hist;
cv::calcHist(&image, nimages, channels, cv::Mat(), hist, dims, histSize, ranges);
return hist;
}

cv::Mat Histogram1D::GetHistogramImage(cv::Mat &image)
{
cv::MatND hist = GetHistogram(image);

double minVal = 0.0;
double maxVal = 0.0;
cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);

//! constucts 2D matrix and fills it with the specified value _s(cv::Scalar).
cv::Mat showImg(histSize[0], histSize[0], CV_8U, cv::Scalar(255));

int hpt = static_cast<int>(histSize[0] * 0.9);
for (int i = 0; i < histSize[0]; ++i)
{
int intensity = static_cast<int>(hist.at<float>(i) * hpt / maxVal);
cv::line(showImg,
cv::Point(i, histSize[0]), cv::Point(i, histSize[0] - intensity), //之所以 histSize[0] - intensity,是为了符合人的观看习惯
cv::Scalar::all(0)); //这里为何加all?不加效果也是一样的
}
return showImg;
}

int main()
{

cv::Mat image = cv::imread("../../resource/test1.jpg");
Histogram1D h;
cv::Mat showImg = h.GetHistogramImage(image);

cv::imshow("ds", showImg);
cv::waitKey();

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//三通道
#include <opencv2/opencv.hpp>

class ColorHistogram
{
public:
ColorHistogram();
cv::SparseMat getHistogram(const cv::Mat &image);
void getHistogramImage(const cv::Mat &image);
private:
int nimages;
int channels[3];
int dims;
int histSize[3];
const float *ranges[3]; //必须是const float**,不能是float**
float hranges[2];
};

ColorHistogram::ColorHistogram()
{
nimages = 1;
channels[0] = 0;
channels[1] = 1;
channels[2] = 2;
dims = 3;
histSize[0] = histSize[1] = histSize[2] = 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] = hranges;
ranges[1] = hranges;
ranges[2] = hranges;
}

cv::SparseMat ColorHistogram::getHistogram(const cv::Mat &image) {
cv::SparseMat hist(3, histSize, CV_32F); //histSize数组必须有三个元素才行,对应维度3
// Compute histogram
cv::calcHist(&image,
1, // histogram of 1 image only
channels, // the channel used
cv::Mat(), // no mask is used
hist, // the resulting histogram
3, // it is a 3D histogram,通道数是3
histSize, // number of bins
ranges // pixel value range
);
return hist;
}
void ColorHistogram::getHistogramImage(const cv::Mat &image)
{
cv::SparseMat hist = getHistogram(image);

double minValue = 0.0;
double maxValue = 255.0;
minMaxLoc(hist, &minValue, &maxValue, 0, 0);

cv::SparseMatConstIterator_<float>
it = hist.begin<float>(),
itend = hist.end<float>();

double s = 0.0;
int dims = hist.dims();
for (; it != itend; ++it)
{
const cv::SparseMat::Node *n = it.node();
printf("(");
for (int i = 0; i < dims; ++i)
{
printf("%d%s", n->idx[i], i < dims - 1 ? "," : ")");
}
printf(": %g\n", it.value<float>());
s += *it;
}
printf("Element sum is %g\n", s);

return showImg;
}

int main()
{

cv::Mat image = cv::imread("../../resource/test1.jpg");

ColorHistogram h;
h.getHistogramImage(image);

system("pause");
return 0;
}

  解释一下这里为何没有画出直方图,因为这是三通道的,本例是在RGB颜色空间下,那意味着有256^3种可能性,而且这是三维,那么要想表示出这个直方图,你得在四维空间下才能表示出来,所以这根本就画不出来,对于一个单通道的,也就256种可能性,只有一维,在二维空间下就能表示,所以还是可以画完的,有兴趣可以将三通道图分离出三个三通道,然后分别绘制出各个单通道的直方图。可以看看这个例子
  
  把刚才的话说的在形象一点,在灰度图中,只有一个强度值,那么这是一维图像(不知道有没有这概念,打比方),那我统计每个强度值的像素有多少个,是不是还需要一个维度来表示,映射到二维坐标系中,好比x轴是强度值,y轴是像素个数。如果是提取(Hue-Saturation)的直方图,Hue和Saturation 各占一维,我们还需要一维来表示在Hue和Saturation确定的情况下这样的像素有多少个,所以需要三维空间来表示。比如:x表示Hue,y表示Saturation,z表示像素个数。opencv中的画法我没理解错的话,采用的是二维图像来表示,就是一张图像,x,y对应我刚才说的,用图像各个位置的强度值来表示像素个数,比如强度值255代表多少,200代表多少,这样还是可以看出来的。OpenCV里有这样的demo,这里有图这也是为啥如果画三通道的直方图画不出来的原因,没有多余的维度来表示像素个数了,但是这仅仅限制其不能画出来,我们还是可以用数学来描述的。
  
  对比单通道代码,可以看出在cv::calcHistchannels,histSize,ranges跟以前的区别就是元素个数变成了3。
  
  这里我写的时候,遇到一个bug,这个bug的知识范畴属于C++,就是cv::calcHist函数中ranges的类型是const float **,如果你在私有变量中声明的ranges成员的类型为float **(在本例中是float *ranges[3]),那么就会报错,无法转换。这里有解释.不要以为float*可以转换成const float*就以为这个也可以,下午搞了半天才知道这里有问题,C++基础不牢啊。


Applying look-up tables to modify image appearance

Definition

  A look-up table is a simple one-to-one (or many-to-one) function that defines how pixel values are transformed into new values.

1
  映射函数:newIntensity= lookup[oldIntensity];

How to do it

1
2
3
4
5
6
cv::Mat applyLookUp(const cv::Mat& image, const cv::Mat& lookup) // lookup is 1*256 uchar matrix
{

cv::Mat result;
cv::LUT(image,lookup,result); // apply lookup table
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{

cv::Mat image = cv::imread("../resource/test1.jpg");
// Create an image inversion table
int dim(256);
cv::Mat lookup(1, // 1 dimension
&dim, // 256 entries
CV_8U)
; // uchar

for (int i=0; i<256; i++)
{
lookup.at<uchar>(i)= 255-i;
}

cv::Mat result = applyLookUp(image, lookup);
}

  上面已经说了lookup的映射关系(映射函数newIntensity= lookup[oldIntensity];),所以对应到这个例子的意思就是newIntensity[i]= lookup[image[i]];,如果image某点像素值为255,那么及你经过lookup表映射后,新的图像result对应该位置像素值就是255-i.因为这里写的规则是255-i = lookup[i].但是多数情况下,图像在视觉上的缺陷并非源于使用过窄的强度范围,而是因为某些颜色值出现的频率过高。我们可以认为一幅高质量图像应该平均使用所有的像素强度。
  前面说过,lookup不仅是一对一,也可以是多对一,也就是说你输入一个三通道图像进来,他会把你各个通道分别按照这个规则进行映射,得到新的三通道图像。补充一下,opencv官网上的说明

1
2
3
4
dst(I) <-- lut(stc(I) + d)
where
d = 0 if src has depth CV_8U
d = 128 if src has depth CV_8S

  但是之前写的Histogram1D类中,即使你输入的image是三通道的,他也不会报错,不过因为通道等参数只设定了一个,所以他只会这个彩色图像的第一个通道的直方图。(对于RGB颜色空间来说,取得第一个通道是B通道)
  在书上下面还写了一大段代码,相当于将原图像像素值范围从50~200映射到新图像0~255,拉伸了直方图,使用的规则如下:

1
255.0*(i-imin)/(imax-imin)+0.5);	//我觉得这里的0.5并没有什么作用,谁知道求告知


Equalizing the image histogram

  In the previous recipe, we showed how the contrast of an image(图像对比度) can be improved by stretching a histogram so that it occupies the full range of available intensity values.This is the idea behind the concept of histogram equalization(直方图均衡化).

How to do it

  opencv 提供 直方图均衡化函数: cv::equalizeHist. See more in cv::equalizeHist

1
2
3
4
5
6
cv::Mat equalize(const cv::Mat &image)
{

cv::Mat result;
cv::equalizeHist(image,result);
return result; //histogram equalization后的图像
}

  这里我参考计算机视觉–算法与应用这本书和这篇博客,用OpenCV实现了一下直方图均衡化,效果跟cv::equalizeHist几乎一样,但是速度肯定不如他,因为查看OpenCV源码的时候,他在实现的时候应该使用了并行技术,因为看他判断原图像个数,原图像个数超过640*480,就用了这个函数parallel_for_.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void Histogram1D::equalizeHist(cv::Mat src, cv::Mat &dst)
{
cv::MatND hist = GetHistogram(src);

float sum = 0.0;
for (int i = 0; i < 256; ++i)
{
sum += hist.at<float>(i);
}

cv::Mat C(1, 256, CV_32F); //cumulative distribution function using Mat c to storage it
float *cData = C.ptr<float>(0);

*cData++ = hist.at<float>(0) / sum; //compute cumulative distribution function
for (int i = 1; i < 256; ++i)
{
*cData++ = hist.at<float>(i) / sum + *(cData - 1);
}

cv::Mat lut(1, 256, CV_8U); //create lookup table
cData = C.ptr<float>(0);
uchar *lutData = lut.data;
for (int i = 0; i < 256; ++i) //map
{
*lutData++ = static_cast<int>(255.0 * (*cData++));
}

cv::LUT(src, lut, dst);
}

  思路是这样的:先计算直方图,根据直方图计算累计分布函数,这样就得到了像素的强度值(intensity)与累计分布函数的对应关系,比如intensity为128对应的就是intensity小于128的所有像素值占像素个数总数的比值,我将这个关系存储在矩阵C中。
  接下来就是建立lookup table了,映射后函数值为255乘以累计分布函数,比如以intensity为128来举例,记intensity小于128的像素个数所占比例为c,那像素intensity为128映射后其intensity就变成255*c。
  在计算机视觉算法与应用一书3.1.4节图3.7可以看出来累计分布函数相对直方图更加平坦。还有一种局部自适应直方图均衡化在这书上也有介绍。
  这里说个bug,不知道算不算bug,反正我实际测试的时候有问题,cv::Mat::total函数可以返回图像的像素个数,OpenCV在实现的时候就是调用这个函数来获取像素个数总数,但是我在调用的时候发现我累计的sumcv::Mat::total函数得到的值不一样,后者得到的值大一点,这就导致我刚开始得到的直方图相对于OpenCV整体缩小了一个比例,直观一点就是变矮了,后来用hist一个个累计出来得到的sum没有这问题,计算出来的结果基本和OpenCV一致。不知道为啥?有人知道请告诉我

  其实上面的思想跟书上提到的差不多,

1
lookup.at<uchar>(i)= static_cast<uchar>(255.0*p[i]);

  但是书上说:where p[i] is the number of pixels having an intensity lower than or equal to i。我把p[i]理解成所占比例,不是number,而是percentage。当然书上也说了是累计直方图。有兴趣可以看看这个例子


Backprojecting a histogram to detect specific image content

  如果一幅图像的区域显示的是一种独特的纹理,那么这个区域的直方图可以看成一个概率函数,他给出的是某个像素属于该纹理或物体的概率。假设直方图最大高度为1,那假如有256个bin,每一个bin value值可能大小不一,那既然在0~1范围内,我们就可以把它看作一个概率,这样我们就知道了哪一个bin的概率最大,哪一个最小,这样映射函数就可以用255*bin value来表示,我们就可以简单的认为,越亮的就是我们越与我们设置的ROI相似(很可能就是ROI一样的区域),越暗的就是我们不感兴趣的,因为他的概率小导致他映射出来的像素值低,所以暗。

1
2
3
4
5
6
7
8
9
10
11
void Histogram1D::calcBackProject(cv::Mat src, cv::Mat &dst)
{
cv::Mat ROI = src(cv::Rect(110, 150, 30, 15));
// imshow("ROI", ROI);

cv::MatND hist = GetHistogram(ROI); //计算直方图

cv::normalize(hist, hist, 1.0); //归一化

cv::calcBackProject(&src, 1, channels, hist, dst, ranges, 255.0); //反向投影
}

  注意我这样设计不好,应该将ROI设置的选择权给用户,而不是自己就在函数里写死了,我只是举例。

  那到底什么是反向投影?我觉得OpenCV官方这个tutorial比较不错,Here

What is Back Projection?

  • Back Projection is a way of recording how well the pixels of a given image fit the distribution of pixels in a histogram model.
  • To make it simpler: For Back Projection, you calculate the histogram model of a feature and then use it to find this feature in an image.
  • Application example: If you have a histogram of flesh color (say, a Hue-Saturation histogram ), then you can use it to find flesh color areas in an image

How does it work?

  这里上面的链接里面说的很清楚了:

  • (1)对于测试图像的每一个像素(即P(i,j)),收集数据看他在直方图(model histogram)哪个bin下面,即找到在直方图里的位置.
  • (2)找到在直方图啥位置了,那么这个位置的值(bin value)肯定能知道了。
  • (3)然后在新图像中存储对应像素位置(P(i,j))存储这个bin value,就是让这位置像素值为bin value,这里也说了如果你想看到这个反向投影后的新图像(对应到代码就是dst),那么我们需要在计算BackProjection前,将直方图(hist)先归一化。
  • (4)然后重复上述步骤,把测试图像中每一个像素都这样操作一遍(相当于前面讲的遍历像素)

  可能你会说原图像的作用是干嘛的?他的作用是提供直方图,也就是(1)里说的model histogram。
  在OpenCV官方提供的这个demo里,我们可以看到原图像的直方图比较纯,就是手占得比重比较大,所以直方图基本集中在手势那一块,但是test图像手占比重比较小,所以最亮的那一块内就不是手了。
  那BackProjection就是:给你一个原图像,让你找出在test图像中和原图像相似的一部分出来。这是我的理解,如有错误,欢迎指正 !
  那这样我之前的那段代码(Histogram1D::calcBackProject)也就不难理解了,先取ROI,看看图像中有多少和ROI相似的,给你提取出来,用的方法就是上面说的四个步骤。

mixChannels

  OpenCV的demo里用了这个函数,我觉得以后用着也挺方便的,所以在这里提一下。See more in cv::mixChannels顺手把示例拷贝过来看一下

1
2
3
4
5
6
7
8
9
10
11
Mat rgba( 100, 100, CV_8UC4, Scalar(1,2,3,4) );
Mat bgr( rgba.rows, rgba.cols, CV_8UC3 );
Mat alpha( rgba.rows, rgba.cols, CV_8UC1 );

// forming an array of matrices is a quite efficient operation,
// because the matrix data is not copied, only the headers
Mat out[] = { bgr, alpha };
// rgba[0] -> bgr[2], rgba[1] -> bgr[1],
// rgba[2] -> bgr[0], rgba[3] -> alpha[0]
int from_to[] = { 0,2, 1,1, 2,0, 3,3 };
mixChannels( &rgba, 1, out, 2, from_to, 4 );

  In the example, the code splits a 4-channel RGBA image into a 3-channel BGR (with R and B channels swapped) and a separate alpha-channel image.


Using the mean shift algorithm to find an object

  反向投影直方图的结果是一个概率映射(可以看成越亮,概率越大,越暗,概率越小),体现了已知的图像内容出现在图像中特定位置的概率。假设我们现在知道物体的近似位置,概率映射可用于找到对象的确切位置(这里我的理解是进行与操作,与之后还在的就是确切位置了)。最有可能的位置是在已知窗口区域中得到最大概率的位置。因此,如果我们从最初的位置开始,并且迭代移动,便可找出精确位置。这便是均值漂移(Mean Shift)算法所要完成的任务。先直接放代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
class MeanShift
{
public:
MeanShift(int minSaturation, cv::Rect rectangle) : minSat(minSaturation), rect(rectangle){
hranges[0] = 0.0;
hranges[1] = 180.0;
ranges[0] = hranges;
channels[0] = 0;
histSize[0] = 181;
}
cv::MatND getHist(cv::Mat src);
cv::Mat meanShift(cv::MatND hist, cv::Mat src2);
private:
float hranges[2];
const float *ranges[1];
int channels[1];
cv::MatND hist;
int histSize[1];
int minSat;
cv::Rect rect;
};

cv::MatND MeanShift::getHist(cv::Mat src)
{
cv::Mat hsv;
cv::cvtColor(src, hsv, CV_BGR2HSV);

cv::Mat mask; //设置掩模,至于这里为啥设置掩模,我也不确定,总之目的是为了视提取出来的直方图更能表现出ROI
if (minSat > 0)
{
std::vector<cv::Mat> v;
cv::split(hsv, v);
cv::threshold(v[1], mask, minSat, 255, CV_THRESH_BINARY);
}

cv::calcHist(&hsv, 1, channels, mask, hist, 1, histSize, ranges);
cv::normalize(hist, hist, 1.0); //归一化
return hist;
}

cv::Mat MeanShift::meanShift(cv::MatND hist, cv::Mat src2)
{
cv::Mat hsv;
cv::Mat src2copy = src2.clone();
cv::cvtColor(src2copy, hsv, CV_BGR2HSV);

cv::Mat mask;
if (minSat > 0)
{
std::vector<cv::Mat> v;
cv::split(src2copy, v);
cv::threshold(v[1], mask, minSat, 255, CV_THRESH_BINARY);
}

cv::Mat backpoj;
cv::calcBackProject(&hsv, 1, channels, hist, backpoj, ranges, 255); //反向投影
// cv::threshold(backpoj, backpoj, 255 * 0.5, 255, CV_THRESH_BINARY);

cv::bitwise_and(backpoj, mask, backpoj); //
imshow("backpoj", backpoj);
cv::rectangle(src2copy, rect, cv::Scalar(0, 0, 255));

cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER, 10, 0.01);
int countNum = cv::meanShift(backpoj, rect, criteria);
printf("%d", countNum);

cv::rectangle(src2copy, rect, cv::Scalar(0, 255, 0));

return src2copy;
}
int main()
{

cv::Mat img1 = cv::imread("../../resource/baboon1.jpg");
cv::Mat img2 = cv::imread("../../resource/baboon3.jpg");

cv::Rect rect(110, 260, 35, 40); //定义初始位置
cv::Mat img1ROI = img1(rect); //设置ROI

int minSat = 65;
MeanShift ms(minSat, rect);

cv::MatND hist = ms.getHist(img1ROI); //提取ROI的直方图

cv::Mat result = ms.meanShift(hist, img2); //运用meanshift算法

cv::rectangle(img1, rect, cv::Scalar(0, 0, 255)); //画出第一张图像ROI的位置

cv::namedWindow("img1");
cv::namedWindow("img2");
cv::imshow("img1", img1);
cv::imshow("img2", img2);
cv::namedWindow("result");
cv::imshow("result", result);

cv::waitKey();

system("pause");
return 0;

}

  代码写的比较烂,按照书上说的思路写的,得到的结果就如书上所示,这里就不贴图了。先计算直方图,根据直方图在计算在当前图像上的反向投影,根据方向投影的结果利用meanShift算法一步步去迭代找到局部最优。

  引用书上的话:均值漂移算法以迭代的方式锁定概率函数的局部最大值。它的原理是寻找预定义窗口中数据点的重心,或者说是加权平均值。该算法将窗口中心移动到数据点的重心处,并重复这个过程直到窗口重心收敛到一个稳定点。OpenCV文档定义了两种终止条件:迭代的最大次数以及窗口中心的位移值(低于该值即认为算法以及收敛)。这两个条件保存在cv::TermCriteria实例中。cv::meanShift函数返回本次调用中迭代的次数。显而易见,结果的质量取决于输入的概率图及初始位置。

  此外,更多meanshift算法理解可以参考文章,讲的还算比较浅显易懂。


Retrieving similar images using histogram comparison

Histogram Comparison,参考这个链接写的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class RetrieveImage
{
public:
RetrieveImage(cv::Mat image) : src(image)
{
src_hist = getHist(src);
}
cv::MatND getHist(const cv::Mat &image);
double compare(const cv::Mat &image, int compareMethod);
private:
cv::Mat src;
cv::MatND src_hist;
};

cv::MatND RetrieveImage::getHist(const cv::Mat &image)
{
cv::Mat hsv;
cv::Mat imageCopy = image.clone();
cv::cvtColor(imageCopy, hsv, CV_BGR2HSV);

float h_ranges[2] = { 0.0, 180.0 };
float s_ranges[2] = { 0.0, 200.0 };
int channels[2] = { 0, 1 };
int h_bins = 30, s_bins = 50;
int histSize[2] = { h_bins, s_bins};
const float *ranges[2] = { h_ranges, s_ranges };

cv::MatND hist;
cv::calcHist(&hsv, 1, channels, cv::Mat(), hist, 2, histSize, ranges);
normalize(hist, hist, 0, 1, cv::NORM_MINMAX);
return hist;
}

double RetrieveImage::compare(const cv::Mat &image, int compareMethod)
{
cv::MatND hist = getHist(image);
return cv::compareHist(src_hist, hist, compareMethod);
}
int main()
{

cv::Mat img1 = cv::imread("../../resource/church01.jpg");
cv::Mat img2 = cv::imread("../../resource/church02.jpg");
cv::Mat img3 = cv::imread("../../resource/church03.jpg");
cv::Mat img4 = cv::imread("../../resource/building.jpg");

RetrieveImage ri(img1);
for (int i = 0; i < 4; ++i)
{
double img1_img1 = ri.compare(img1, i);
double img1_img2 = ri.compare(img2, i);
double img1_img3 = ri.compare(img3, i);
double img1_img4 = ri.compare(img4, i);

printf("Method [%d] compare result: %f, %f, %f, %f \n", i, img1_img1, img1_img2, img1_img3, img1_img4);
}
system("pause");
return 0;
}
#endif

  上面的图片是从本书的网站上下载的图片,前三张图片比较相似,最后一张图片是非常不相似,所以放结果如下:

Method img1_img1 img1_img2 img1_img3 img1_img4
Correlation 1 0.867352 0.857962 0.0196779
Chi-square 0 3.06412 11.6898 1287.79
Intersection 13.9362 10.585 10.2582 3.03085
Bhattacharyya 0 0.22271 0.242008 0.759152

For the Correlation and Intersection methods, the higher the metric, the more accurate the match. As we can see, the match img1_img1 is the highest of all as expected.For the other two metrics(Chi-square and Bhattacharyya), the less the result, the better the match.

see more in cv::compareHist


END