OpenCV4入门163:计算图像hash值

索引地址:系列索引

图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅图像是否相似。两幅图像越相似,其哈希值的汉明距离越小,通过这种方式就能够比较两幅图像是否相似。在实际应用中,图像哈希算法可以用于图片检索,重复图片剔除,以图搜图以及图片相似度比较。

哈希值计算算法的本质就是对原始数据进行有损压缩,有损压缩后的固定字长能够作为唯一标识来标识原始数据,这个唯一标识就是哈希值。通常改变原始数据任意一个部分,即:不同的原始数据,都会对应不同的哈希值。(否则就发生了哈希碰撞即两个不同的文件但是hash值相同)

根据计算图像哈希值方式的不同,从而存在不同的图像哈希算法。此处主要使用到的是在OpenCV Contrib库中的img_hash模块,其提供了很多种计算图像哈希值的算法,这里主要推荐三种算法,分别是:

  • AverageHash:基于像素均值计算哈希值,一种快速的图像哈希算法,但仅适用于简单情况。
  • PHash:AverageHash的改进版,比AverageHash慢,但可以适应更多的情况。
  • MarrHildrethHash:基于Marr-Hildreth边缘算子计算哈希值,速度最慢,但更具区分性。

图像哈希的整个流程是比较统一的。以pHash为例来进行说明的话:首先将输入的图片的行列改为8*8大小,之后借助pHash函数对其进行hash值的计算,从而得到一个唯一Hash标识码。每个Hash标识码包含8个uint8大小的数值,将其组合在一起从而得到一个64位的二进制串,对不同的图像的二进制串之间进行汉明距离的计算即可。

PS:MD5是将数据计算后得出32位16进制数据,sha256会生成256位数据,这一类的算法不可逆(即无法通过值逆推出原始值)统称为摘要算法,一般验证文件是否被修改但是不用于安全性验证

汉明距离

汉明距离是以理查德·卫斯里·汉明的名字命名的。在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。例如:

  • 1011101 与 1001001 之间的汉明距离是 2。将第一个字符串第三位的1改为0,第五位的1改为0就和第二个字符串一样了,所以是2。
  • 2143896 与 2233796 之间的汉明距离是 3。
  • ”toned” 与 “roses” 之间的汉明距离是 3。

测试

测试代码来自官方示例samples/img_hash/hash_samples.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "opencv2/core.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/img_hash.hpp"

#include <iostream>

using namespace cv;
using namespace cv::img_hash;
using namespace std;

template <typename T>
inline void test_one(const std::string &title, const Mat &a, const Mat &b)
{
cout << "=== " << title << " ===" << endl;
TickMeter tick;
Mat hashA, hashB;
Ptr<ImgHashBase> func;
func = T::create();

tick.reset(); tick.start();
func->compute(a, hashA);
tick.stop();
cout << "compute1: " << tick.getTimeMilli() << " ms" << endl;

tick.reset(); tick.start();
func->compute(b, hashB);
tick.stop();
cout << "compute2: " << tick.getTimeMilli() << " ms" << endl;

cout << "compare: " << func->compare(hashA, hashB) << endl << endl;;
}

int main(int argc, char **argv)
{
if (argc != 3)
{
cerr << "must input the path of input image and target image. ex : hash_samples lena.jpg lena2.jpg" << endl;
return -1;
}
ocl::setUseOpenCL(false);

Mat input = imread(argv[1]);
Mat target = imread(argv[2]);

test_one<AverageHash>("AverageHash", input, target);
test_one<PHash>("PHash", input, target);
test_one<MarrHildrethHash>("MarrHildrethHash", input, target);
test_one<RadialVarianceHash>("RadialVarianceHash", input, target);
test_one<BlockMeanHash>("BlockMeanHash", input, target);

return 0;
}

测试图片为lena

lena

测试2号图片也是她,输出为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ ./imghash lena.jpg lena2.jpg
=== AverageHash ===
compute1: 41.0088 ms
compute2: 0.010916 ms
compare: 0

=== PHash ===
compute1: 5.08526 ms
compute2: 0.029897 ms
compare: 0

=== MarrHildrethHash ===
compute1: 15.3049 ms
compute2: 3.6342 ms
compare: 0

=== RadialVarianceHash ===
compute1: 0.379439 ms
compute2: 0.296902 ms
compare: 1

=== BlockMeanHash ===
compute1: 0.298048 ms
compute2: 0.256746 ms
compare: 0

因为两张图片是一模一样的,所以基本上所有结果都提示(接近)一样,只是处理时间不同

我们将图片换个角度

lena

输出结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ ./imghash lena.jpg lena3.jpg
=== AverageHash ===
compute1: 0.095063 ms
compute2: 0.004088 ms
compare: 27

=== PHash ===
compute1: 0.111951 ms
compute2: 0.017173 ms
compare: 39

=== MarrHildrethHash ===
compute1: 5.27253 ms
compute2: 3.76183 ms
compare: 307

=== RadialVarianceHash ===
compute1: 0.380913 ms
compute2: 0.433301 ms
compare: 0.425406

=== BlockMeanHash ===
compute1: 0.279763 ms
compute2: 0.265222 ms
compare: 132