简单介绍一下哈希感知算法:
“感知哈希算法”(Perceptual hash algorithm),它的作用是对每张图片生成一个"指纹"(fingerprint)字符串,然后比较不同图片的指纹。结果越接近,就说明图片越相似。
算法步骤:
第一步,缩小尺寸。
最快速的去除高频和细节,只保留结构明暗的方法就是缩小尺寸。
将图片缩小到8x8的尺寸,总共64个像素。摒弃不同尺寸、比例带来的图片差异。
如这张图片:
处理前:
处理后(为方便查看,这里将图片放大了):
第二步,简化色彩。
将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。
第三步,计算DCT(离散余弦变换)。
DCT是把图片分解频率聚集和梯状形,虽然JPEG使用8x8的DCT变换,在这里使用32x32的DCT变换。
第四步,缩小DCT。
虽然DCT的结果是32x32大小的矩阵,但我们只要保留左上角的8x8的矩阵,这部分呈现了图片中的最低频率。
第五步,计算平均值。
计算所有64个值的平均值。
第六步,进一步减小DCT。
这是最主要的一步,根据8x8的DCT矩阵,设置0或1的64位的hash值,大于等于DCT均值的设为”1”,小于DCT均值的设为“0”。结果并不能告诉我们真实性的低频率,只能粗略地告诉我们相对于平均值频率的相对比例。只要图片的整体结构保持不变,hash结果值就不变。能够避免伽马校正或颜色直方图被调整带来的影响。
第七步,计算哈希值。
将64bit设置成64位的长整型,组合的次序并不重要,只要保证所有图片都采用同样次序就行了。将32x32的DCT转换成32x32的图像。
将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了(例如,自左到右、自顶向下、big-endian)。
得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。在理论上,这等同于计算“汉明距离”(Hammingdistance)。如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。
下面附上代码:
//
// main.cpp
// 图像对比
//
// Created by 王泽一 on 2021/3/29.
// Copyright © 2021 nan. All rights reserved.
//
//导入所需的库
#include </Users/wangzeyi/Downloads/stdafx.h>//我的自己的文件所在的位置,需要自己自行修改
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <stdio.h>
#include "opencv2/imgcodecs/legacy/constants_c.h"
#pragma comment(lib,"opencv_core2410d.lib")
#pragma comment(lib,"opencv_highgui2410d.lib")
#pragma comment(lib,"opencv_imgproc2410d.lib")
using namespace std;
using namespace cv;
int main()
{
string strSrcImageName1 = "/Users/wangzeyi/Downloads/IMG_4808.JPG";//读取照片1
string strSrcImageName2 = "/Users/wangzeyi/Downloads/IMG_4806 2.JPG";//读取照片2
Mat matSrca, matSrcb, matSrc1, matSrc2;
//将两张图片读取为彩色
matSrca = imread(strSrcImageName1, IMREAD_COLOR);
matSrcb = imread(strSrcImageName2, IMREAD_COLOR);
CV_Assert(matSrca.channels() == 3);
CV_Assert(matSrcb.channels() == 3);
cv::resize(matSrca, matSrc1, cv::Size(357, 419), 0, 0, cv::INTER_NEAREST);
//cv::flip(matSrc1, matSrc1, 1);
cv::resize(matSrcb, matSrc2, cv::Size(2177, 3233), 0, 0, cv::INTER_LANCZOS4);
cv::Mat matDst1, matDst2;
//调整jpg图片大小
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);
//update 20181206 for the bug cvtColor
cv::Mat temp1 = matDst1;
cv::Mat temp2 = matDst2;
cv::cvtColor(temp1 , matDst1, COLOR_BGR2GRAY);
cv::cvtColor(temp2 , matDst2, COLOR_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;
cout<<"iDiffNum = "<<iDiffNum<<endl;
if (iDiffNum <= 5)
cout<<"two images are very similar!"<<endl;
else if (iDiffNum > 10)
cout<<"they are two different images!"<<endl;
else
cout<<"two image are somewhat similar!"<<endl;
imshow("123",matSrca);
imshow("12",matSrcb);
waitKey(0);
getchar();
return 0;
}
运行结果:
相似:
相同:
不同
参考资料:
相似图片搜索的原理
http://www.ruanyifeng.com/blog/2011/07/principle_of_similar_image_search.html