平均哈希算法
實現步驟
- 縮小尺寸:將圖像縮小到8*8的尺寸,總共64個像素。這一步的作用是去除圖像的細節,只保留結構/明暗等基本信息,摒棄不同尺寸/比例帶來的圖像差異;
- 簡化色彩:將縮小后的圖像,轉為64級灰度,即所有像素點總共只有64種顏色;
- 計算平均值:計算所有64個像素的灰度平均值;
- 比較像素的灰度:將每個像素的灰度,與平均值進行比較,大於或等於平均值記為1,小於平均值記為0;
- 計算哈希值:將上一步的比較結果,組合在一起,就構成了一個64位的整數,這就是這張圖像的指紋。組合的次序並不重要,只要保證所有圖像都采用同樣次序就行了;
- 得到指紋以后,就可以對比不同的圖像,看看64位中有多少位是不一樣的。在理論上,這等同於”漢明距離”(Hamming distance,在信息論中,兩個等長字符串之間的漢明距離是兩個字符串對應位置的不同字符的個數)。如果不相同的數據位數不超過5,就說明兩張圖像很相似;如果大於10,就說明這是兩張不同的圖像。
代碼
int aHash(Mat matSrc1, Mat matSrc2)
{
Mat matDst1, matDst2;
cv::resize(matSrc1, matDst1, cv::Size(8, 8), 0, 0, cv::INTER_CUBIC);
cv::resize(matSrc2, matDst2, cv::Size(8, 8), 0, 0, cv::INTER_CUBIC);
cv::cvtColor(matDst1, matDst1, CV_BGR2GRAY);
cv::cvtColor(matDst2, matDst2, CV_BGR2GRAY);
int iAvg1 = 0, iAvg2 = 0;
int arr1[64], arr2[64];
for (int i = 0; i < 8; i++)
{
uchar* data1 = matDst1.ptr<uchar>(i);
uchar* data2 = matDst2.ptr<uchar>(i);
int tmp = i * 8;
for (int j = 0; j < 8; j++)
{
int tmp1 = tmp + j;
arr1[tmp1] = data1[j] / 4 * 4;
arr2[tmp1] = data2[j] / 4 * 4;
iAvg1 += arr1[tmp1];
iAvg2 += arr2[tmp1];
}
}
iAvg1 /= 64;
iAvg2 /= 64;
for (int i = 0; i < 64; i++)
{
arr1[i] = (arr1[i] >= iAvg1) ? 1 : 0;
arr2[i] = (arr2[i] >= iAvg2) ? 1 : 0;
}
int iDiffNum = 0;
for (int i = 0; i < 64; i++)
if (arr1[i] != arr2[i])
++iDiffNum;
return iDiffNum;
}
感知哈希算法
平均哈希算法過於嚴格,不夠精確,更適合搜索縮略圖,為了獲得更精確的結果可以選擇感知哈希算法,它采用的是DCT(離散余弦變換)來降低頻率的方法
一般步驟
- 縮小圖片:32 * 32是一個較好的大小,這樣方便DCT計算
- 轉化為灰度圖
- 計算DCT:利用Opencv中提供的dct()方法,注意輸入的圖像必須是32位浮點型
- 縮小DCT:DCT計算后的矩陣是32 * 32,保留左上角的8 * 8,這些代表的圖片的最低頻率
- 計算平均值:計算縮小DCT后的所有像素點的平均值
- 大於平均值記錄為1,反之記錄為0
- 得到信息指紋
代碼
int pHash(Mat matSrc1, Mat matSrc2)
{
Mat matDst1, matDst2;
cv::resize(matSrc1, matDst1, cv::Size(32, 32), 0, 0, cv::INTER_CUBIC);
cv::resize(matSrc2, matDst2, cv::Size(32, 32), 0, 0, cv::INTER_CUBIC);
cv::cvtColor(matDst1, matDst1, CV_BGR2GRAY);
cv::cvtColor(matDst2, matDst2, CV_BGR2GRAY);
matDst1.convertTo(matDst1, CV_32F);
matDst2.convertTo(matDst2, CV_32F);
dct(matDst1, matDst1);
dct(matDst2, matDst2);
int iAvg1 = 0, iAvg2 = 0;
int arr1[64], arr2[64];
for (int i = 0; i < 8; i++)
{
uchar* data1 = matDst1.ptr<uchar>(i);
uchar* data2 = matDst2.ptr<uchar>(i);
int tmp = i * 8;
for (int j = 0; j < 8; j++)
{
int tmp1 = tmp + j;
arr1[tmp1] = data1[j];
arr2[tmp1] = data2[j];
iAvg1 += arr1[tmp1];
iAvg2 += arr2[tmp1];
}
}
iAvg1 /= 64;
iAvg2 /= 64;
for (int i = 0; i < 64; i++)
{
arr1[i] = (arr1[i] >= iAvg1) ? 1 : 0;
arr2[i] = (arr2[i] >= iAvg2) ? 1 : 0;
}
int iDiffNum = 0;
for (int i = 0; i < 64; i++)
if (arr1[i] != arr2[i])
++iDiffNum;
return iDiffNum;
}
dHash算法
- 縮小圖片:收縮到8*9的大小,以便它有72的像素點
- 轉化為灰度圖
- 計算差異值:dHash算法工作在相鄰像素之間,這樣每行9個像素之間產生了8個不同的差異,一共8行,則產生了64個差異值
- 獲得指紋:如果左邊的像素比右邊的更亮,則記錄為1,否則為0
平均結構相似性 MSSIM 算法
代碼
double getMSSIM(const Mat& i1, const Mat& i2)
{
const double C1 = 6.5025, C2 = 58.5225;
/***************************** INITS **********************************/
int d = CV_32F;
Mat I1, I2;
i1.convertTo(I1, d); // cannot calculate on one byte large values
i2.convertTo(I2, d);
Mat I2_2 = I2.mul(I2); // I2^2
Mat I1_2 = I1.mul(I1); // I1^2
Mat I1_I2 = I1.mul(I2); // I1 * I2
/*************************** END INITS **********************************/
Mat mu1, mu2; // PRELIMINARY COMPUTING
GaussianBlur(I1, mu1, Size(11, 11), 1.5);
GaussianBlur(I2, mu2, Size(11, 11), 1.5);
Mat mu1_2 = mu1.mul(mu1);
Mat mu2_2 = mu2.mul(mu2);
Mat mu1_mu2 = mu1.mul(mu2);
Mat sigma1_2, sigma2_2, sigma12;
GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
sigma1_2 -= mu1_2;
GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
sigma2_2 -= mu2_2;
GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
sigma12 -= mu1_mu2;
///////////////////////////////// FORMULA ////////////////////////////////
Mat t1, t2, t3;
t1 = 2 * mu1_mu2 + C1;
t2 = 2 * sigma12 + C2;
t3 = t1.mul(t2); // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigma2_2 + C2;
t1 = t1.mul(t2); // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
Mat ssim_map;
divide(t3, t1, ssim_map); // ssim_map = t3./t1;
Scalar mssim = mean(ssim_map); // mssim = average of ssim map
return (mssim[0] + mssim[1] + mssim[2])/3;
}
由於項目需要使用了上述算法,測試后發現平均哈希算法進行兩張圖像相似度對比的效果是最好的(有些奇怪)。測試的樣本大多是完全相同的兩個圖片(只是明暗度不一樣,比如陰影),iDiffNum 的值一般在10左右。對於明顯不同的兩張圖片,上述算法都存在錯誤判斷的情況(返回的iDiffNum在10左右,正常的應該在20以外)。
reference:
[1] http://blog.csdn.net/wangyaninglm/article/details/43853435
[2] http://www.jb51.net/article/83315.htm
[3] http://blog.csdn.net/ecnu18918079120/article/details/60149864