一張圖片就是一個二維信號,它包含了不一樣頻率的成分。以下圖所示,亮度變化小的區域是低頻成分,它描述大範圍的信息。而亮度變化劇烈的區域(好比物體的邊緣)就是高頻的成分,它描述具體的細節。或者說高頻能夠提供圖片詳細的信息,而低頻能夠提供一個框架。 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; }