感知哈希算法:pHash算法实现图像相似度比较(附完整c++代码)
数字内容安全课实验
感知哈希算法
在浏览器的图片搜索中,用户可以上传一张图片,浏览器显示因特网中与此图片相同或者相似的图,实现这种功能的关键技术叫做"感知哈希算法"(Perceptual Hash Algorithm), 意思是为图片生成一个指纹(字符串格式), 两张图片的指纹越相似, 说明两张图片就越相似。感知哈希算法是一类算法的总称,包括
- aHash:平均值哈希
- pHash:感知哈希
- dHash:差异值哈希
在这里我使用的是pHash算法,即对图像进行DCT变换,获取DCT系数均值,基于其变换域特征来实现图片相似度计算。
实现原理
-
第一步 缩小图片尺寸
将图片缩小到32x32的尺寸, 这一步的作用是去除各种图片尺寸和图片比例的差异, 只保留结构、明暗等基本信息
(我一开始缩小尺寸到8x8,但检测后发现性能很差,于是改成32x32,性能得到大幅提高) -
第二步 转为灰度图片
将缩小后的图片转为灰度图片 -
第三步 计算DCT
计算DCT,但只取左上角8*8的矩阵,这部分呈现了图片中的最低频率 -
第四步 计算平均值
计算DCT系数平均值 -
第五步 计算哈希值
将每个像素的灰度与DCT系数平均值进行比较, 如果大于或等于平均值记为1, 小于平均值记为0,结果组合在一起就构成了一个64位的二进制整数, 这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了 -
第六步 对比图片指纹
得到图片的指纹后, 就可以对比不同的图片的指纹, 计算出64位中有多少位是不一样的. 如果不相同的数据位数不超过5, 就说明两张图片很相似, 如果大于10, 说明它们是两张不同的图片
实验环境
- vs2017
- opencv库
代码实现
#include <opencv2\opencv.hpp>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\objdetect\objdetect.hpp>
#include <opencv2\imgproc\types_c.h>
#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;
using namespace cv;
string pHashValue(Mat &srcImg);//pHash方法计算图片哈希值
int hanmingDist(string &str1, string &str2);//计算哈希值字符串的汉明距离
int main()
{
Mat orgImg = imread("图片1路径");
Mat img = imread("图片2路径");
imshow("OrgImg", orgImg);
imshow("Img", img);
string str1 = pHashValue(orgImg);
string str2 = pHashValue(img);
int distance = hanmingDist(str1, str2);
cout << "两张图片的汉明距离:" << distance << endl;
if (distance < 5) //若汉明距离小于5,则两张图片相似
{
cout << "两张图片相似" << endl;
}
else
{
cout << "两张图片不相似" << endl;
}
waitKey(0);
return 0;
}
string pHashValue(Mat &srcImg)//pHash方法计算图片哈希值
{
Mat img, dstImg;
string rst(64, '\0');
double dIndex[64];
double mean = 0.0;
int k = 0;
if (srcImg.channels() == 3)//若为彩色图像则转换为灰度图像
{
cvtColor(srcImg, srcImg, CV_BGR2GRAY);
img = Mat_<double>(srcImg);
}
else
{
img = Mat_<double>(srcImg);
}
//缩放尺寸
resize(img, img, Size(32, 32));
//离散余弦变换 DCT
dct(img, dstImg);
//获取dct系数均值
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
dIndex[k] = dstImg.at<double>(i, j);
//计算每个像素的均值
mean += dstImg.at<double>(i, j) / 64;
++k;
}
}
//计算hash
for (int i = 0; i < 64; ++i)
{
if (dIndex[i] >= mean)
{
rst[i] = '1';
}
else {
rst[i] = '0';
}
}
return rst;
}
int hanmingDist(string &str1, string &str2)//计算哈希值字符串的汉明距离
{
if ((str1.size() != 64) || (str2.size() != 64))
{
return -1;
}
int distValue = 0;
for (int i = 0; i < 64; i++)
{
if (str1[i] != str2[i])
{
distValue++;
}
}
return distValue;
}
实验材料
使用以下两张图片,并对其做翻转、旋转、调节亮度饱和度等操作
Zelda
peppers
实验结果
-
Zelda vs Zelda
汉明距离:0 -
peppers vs Zelda
汉明距离:28 -
peppers vs 翻转后的peppers
汉明距离:29 -
peppers vs 旋转后的peppers
汉明距离:28 -
peppers vs 调节亮度色彩饱和度的peppers
汉明距离:4 -
peppers vs 调节大小后的peppers
汉明距离:1
算法性能分析
由实验结果可以看出,pHash对于图像的旋转、翻转不具有鲁棒性,但由于经过DCT变换后提取了其低频成分(包含了图像主要内容信息),所以pHash算法对于图像比例大小、亮度饱和度等变化的抵抗力较强。我还测试了同一个视频截取的两张连续帧,但算法结果显示其相似度并不高,所以此基本算法的性能和应用还有局限性,例如无法根据图片颜色进行相似度的比对,因此可以根据具体的应用要求来对算法作出改进。
参考资料
openCV中利用感知哈希算法实现图片相似度计算https://jingyan.baidu.com/article/915fc414a461b451394b20f9.html