我们知道,直方图可以在一定程度上反应图像的一些统计信息。所以,可以考虑用直方图对比的方法,进行基于内容的图像检索。
通常我们搜索图片,都是根据图片的标签搜索的。基于内容的搜索,就是假设我们不知道标签,而是直接输入一幅图像,然后从得出一些跟这幅图像的直方图比较相似的图像。
那么我们不禁要问,如何度量两幅直方图的相似程度呢?
OpenCV的compareHist函数提供了一个参数供你选择。最简单的就是CV_COMP_INTERSECT。这个方法原理其实很简单,就是对于两幅的同一个bin,选择他们的最小值,然后把所有的bin都加起来。举个例子,如果两幅图像没有任何相同的颜色,那么这个比较的计算结果就为0;如果是两幅完全相同的图像,那么计算结果就应该是整幅图像的像素数。其他的度量方法可以参考OpenCV的帮助手册,这里就不一一细说了。
下面看看代码吧。因为需要计算彩色图像的直方图,所以先建立一个计算彩色图像直方图的类:
#if!defined COLORHISTOGRAM
#define COLORHISTOGRAM
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
class ColorHistogram
{
private:
int histSize[3];
float hranges[2];
const float* ranges[3];
int channels[3];
public:
//构造函数
ColorHistogram()
{
histSize[0]= histSize[1]= histSize[2]= 256;
hranges[0] = 0.0;
hranges[1] = 255.0;
ranges[0] = hranges;
ranges[1] = hranges;
ranges[2] = hranges;
channels[0] = 0;
channels[1] = 1;
channels[2] = 2;
}
//计算彩色图像直方图
Mat getHistogram(const Mat& image)
{
Mat hist;
//BGR直方图
hranges[0]= 0.0;
hranges[1]= 255.0;
channels[0]= 0;
channels[1]= 1;
channels[2]= 2;
//计算
calcHist(&image,1,channels,Mat(),hist,3,histSize,ranges);
return hist;
}
//计算颜色的直方图
Mat getHueHistogram(const Mat &image)
{
Mat hist;
Mat hue;
//转换到HSV空间
cvtColor(image,hue,CV_BGR2HSV);
//设置1维直方图使用的参数
hranges[0] = 0.0;
hranges[1] = 180.0;
channels[0] = 0;
//计算直方图
calcHist(&hue,1,channels,Mat(),hist,1,histSize,ranges);
return hist;
}
//减少颜色
Mat colorReduce(const Mat &image,int div = 64)
{
int n = static_cast<int>(log(static_cast<double>(div))/log(2.0));
uchar mask = 0xFF<<n;
Mat_<Vec3b>::const_iterator it = image.begin<Vec3b>();
Mat_<Vec3b>::const_iterator itend = image.end<Vec3b>();
//设置输出图像
Mat result(image.rows,image.cols,image.type());
Mat_<Vec3b>::iterator itr = result.begin<Vec3b>();
for(;it != itend;++it,++itr)
{
(*itr)[0] = ((*it)[0]&mask) + div/2;
(*itr)[1] = ((*it)[1]&mask) + div/2;
(*itr)[2] = ((*it)[2]&mask) + div/2;
}
return result;
}
};
#endif
然后再建立一个比较直方图的类:
#if!defined IMAGECOMPARATOR
#define IMAGECOMPARATOR
#include "colorhistogram.h"
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
class ImageComparator
{
private:
Mat reference;
Mat input;
Mat refH;
Mat inputH;
ColorHistogram hist;
int div;
public:
ImageComparator():div(32){}
void setColorReducation(int factor)
{
div = factor;
}
int getColorReduction()
{
return div;
}
void setRefrenceImage(const Mat &image)
{
reference = hist.colorReduce(image,div);
refH = hist.getHistogram(reference);
}
double compare(const Mat &image)
{
input = hist.colorReduce(image,div);
inputH = hist.getHistogram(input);
return compareHist(refH,inputH,CV_COMP_INTERSECT);
}
};
#endif
注意到在计算直方图前,我对图像的颜色进行了减少,这样做的主要目的是为了减少运算量。
有了这两个类,我们的主程序就变得简单多了:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "imageComparator.h"
#include <iostream>
using namespace std;
int main()
{
Mat image = imread("D:/picture/images/waves.jpg");
if(!image.data)
return -1;
imshow("待检测图像",image);
cout<<"图像像素数为:"<<image.cols*image.rows<<endl;
ImageComparator c;
c.setRefrenceImage(image);
//跟自己比
Mat input = imread("D:/picture/images/waves.jpg");
imshow("自己",input);
cout<<"waves VS waves:"<<c.compare(input)<<endl;
input = imread("D:/picture/images/dog.jpg");
imshow("dog",input);
cout<<"waves VS dog:"<<c.compare(input)<<endl;
input = imread("D:/picture/images/marais.jpg");
imshow("沼泽",input);
cout<<"waves VS marsh:"<<c.compare(input)<<endl;
input= cv::imread("D:/picture/images/bear.jpg");
imshow("熊",input);
cout<<"waves VS bear:"<<c.compare(input)<<endl;
waitKey(0);
return 0;
}
正如前面所说的,如果自己跟自己比较,结果就是整幅图像像素数。
最后,做一点小小的说明,这里演示的直方图比较法其实不是特别靠谱。举一个简单的例子,对于一幅图像,如果把它进行翻转,或者分割以后在重新随机组合起来(尽管结果可能没有什么意义),尽管图像发生了显著地变化,但是他的直方图是不会变的。所以又有人想出了对图像分块,然后再做直方图之类的方法。我也不是这方面的专家,如果大家真的想弄一个靠谱的做法,最好是查查论文,看看牛人们都是怎么搞的。