我们知道,直方图可以在一定程度上反应图像的一些统计信息。所以,可以考虑用直方图对比的方法,进行基于内容的图像检索。
通常我们搜索图片,都是根据图片的标签搜索的。基于内容的搜索,就是假设我们不知道标签,而是直接输入一幅图像,然后从得出一些跟这幅图像的直方图比较相似的图像。
那么我们不禁要问,如何度量两幅直方图的相似程度呢?
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; }
正如前面所说的,如果自己跟自己比较,结果就是整幅图像像素数。
最后,做一点小小的说明,这里演示的直方图比较法其实不是特别靠谱。举一个简单的例子,对于一幅图像,如果把它进行翻转,或者分割以后在重新随机组合起来(尽管结果可能没有什么意义),尽管图像发生了显著地变化,但是他的直方图是不会变的。所以又有人想出了对图像分块,然后再做直方图之类的方法。我也不是这方面的专家,如果大家真的想弄一个靠谱的做法,最好是查查论文,看看牛人们都是怎么搞的。