一张图片就是一个二维信号,它包含了不一样频率的成分。以下图所示,亮度变化小的区域是低频成分,它描述大范围的信息。而亮度变化剧烈的区域(好比物体的边缘)就是高频的成分,它描述具体的细节。或者说高频能够提供图片详细的信息,而低频能够提供一个框架。 ios
而一张大的,详细的图片有很高的频率,而小图片缺少图像细节,因此都是低频的。因此咱们平时的下采样,也就是缩小图片的过程,其实是损失高频信息的过程。 算法
均值哈希算法主要是利用图片的低频信息,其工做过程以下:框架
8x8
的尺寸,总共64
个像素。不要保持纵横比,只需将其变成8*8
的正方形。这样就能够比较任意大小的图片,摒弃不一样尺寸、比例带来的图片差别。8*8
的小图片转换成灰度图像。计算一个图片的hash指纹的过程就是这么简单。刚开始的时候以为这样就损失了图片的不少信息了,竟然还能有效。简单的算法也许存在另外一种美。若是图片放大或缩小,或改变纵横比,结果值也不会改变。增长或减小亮度或对比度,或改变颜色,对hash值都不会太大的影响。最大的优势:计算速度快!ui
这时候,比较两个图片的类似性,就是先计算这两张图片的hash指纹,也就是64位0或1值,而后计算不一样位的个数(汉明距离)。若是这个值为0,则表示这两张图片很是类似,若是汉明距离小于5,则表示有些不一样,但比较相近,若是汉明距离大于10则代表彻底不一样的图片。spa
均值哈希虽然简单,但受均值的影响很是大。例如对图像进行伽马校订或直方图均衡就会影响均值,从而影响最终的hash值。存在一个更健壮的算法叫pHash。它将均值的方法发挥到极致。使用离散余弦变换(DCT)来获取图片的低频成分。code
离散余弦变换(DCT)是种图像压缩算法,它将图像从像素域变换到频率域。而后通常图像都存在不少冗余和相关性的,因此转换到频率域以后,只有不多的一部分频率份量的系数才不为0,大部分系数都为0(或者说接近于0)。下图的右图是对lena图进行离散余弦变换(DCT)获得的系数矩阵图。从左上角依次到右下角,频率愈来愈高,由图能够看到,左上角的值比较大,到右下角的值就很小很小了。换句话说,图像的能量几乎都集中在左上角这个地方的低频系数上面了。 blog
** pHash的工做过程以下:**图片
缩小尺寸:pHash以小图片开始,但图片大于8*8
,32*32
是最好的。这样作的目的是简化了DCT的计算,而不是减少频率。string
简化色彩:将图片转化成灰度图像,进一步简化计算量。hash
计算DCT:计算图片的DCT变换,获得32*32
的DCT系数矩阵。
缩小DCT:虽然DCT的结果是32*32
大小的矩阵,但咱们只要保留左上角的8*8的矩阵,这部分呈现了图片中的最低频率。
计算平均值:如同均值哈希同样,计算DCT的均值。
计算hash值:这是最主要的一步,根据8*8
的DCT矩阵,设置0或1的64位的hash值,大于等于DCT均值的设为”1
”,小于DCT均值的设为“0
”。组合在一块儿,就构成了一个64位的整数,这就是这张图片的指纹。
结果并不能告诉咱们真实性的低频率,只能粗略地告诉咱们相对于平均值频率的相对比例。只要图片的总体结构保持不变,hash结果值就不变。可以避免伽马校订或颜色直方图被调整带来的影响。
与均值哈希同样,pHash一样能够用汉明距离来进行比较。(只须要比较每一位对应的位置并算计不一样的位的个数)
#include<iostream> #include "highgui/highgui.hpp" #include "opencv2/nonfree/nonfree.hpp" #include "opencv2/legacy/legacy.hpp" using namespace cv; using namespace std; //pHash算法 string pHashValue(Mat &src) { Mat img ,dst; string rst(64,'\0'); double dIdex[64]; double mean = 0.0; int k = 0; if(src.channels()==3) { cvtColor(src,src,CV_BGR2GRAY); img = Mat_<double>(src); } else { img = Mat_<double>(src); } /* 第一步,缩放尺寸*/ resize(img, img, Size(8,8)); /* 第二步,离散余弦变换,DCT系数求取*/ dct(img, dst); /* 第三步,求取DCT系数均值(左上角8*8区块的DCT系数)*/ for (int i = 0; i < 8; ++i) { for (int j = 0; j < 8; ++j) { dIdex[k] = dst.at<double>(i, j); mean += dst.at<double>(i, j)/64; ++k; } } /* 第四步,计算哈希值。*/ for (int i =0;i<64;++i) { if (dIdex[i]>=mean) { rst[i]='1'; } else { rst[i]='0'; } } return rst; } //汉明距离计算 int HanmingDistance(string &str1,string &str2) { if((str1.size()!=64)||(str2.size()!=64)) return -1; int difference = 0; for(int i=0;i<64;i++) { if(str1[i]!=str2[i]) difference++; } return difference; } int main() { Mat src1 = imread("F:\\My_Test\\2018-06-19\\test\\1_CaptureStudio20180619091831_0_1.jpg"); Mat src2 = imread("F:\\My_Test\\2018-06-19\\test\\1_CaptureStudio20180619091831_1_2.jpg"); string sHash1 = pHashValue(src1); string sHash2 = pHashValue(src2); int nRet = HanmingDistance(sHash1,sHash2); printf("definition: %f\n\n",nRet); cvWaitKey(0); return 0; }