感知哈希算法与图像指纹

在谷歌图片搜索中, 用户可以上传一张图片, 谷歌显示因特网中与此图片相同或者相似的图片.

实现这种功能的关键技术叫做”感知哈希算法”(Perceptual Hash Algorithm), 意思是为图片生成一个指纹(字符串格式), 两张图片的指纹越相似, 说明两张图片就越相似。
感知哈希算法是一类算法的总称,包括aHash、pHash、dHash。顾名思义,感知哈希不是以严格的方式计算Hash值,而是以更加相对的方式计算哈希值,因为“相似”与否,就是一种相对的判定。

  • aHash:平均值哈希。速度比较快,但是常常不太精确。
  • pHash:感知哈希。精确度比较高,但是速度方面较差一些。
  • dHash:差异值哈希。精确度较高,且速度也非常快。

差异值哈希算法dHash

1、执行步骤

step 1:缩小图像尺寸
把图片缩放到非常小。这一步的作用是去除各种图片尺寸和图片比例的差异, 只保留结构、明暗等基本信息
step 2:转为灰度图片
缩放后的图片,细节已经被隐藏,信息量已经变少。但是还不够,因为它是彩色的,由RGB值组成。白色表示为(255,255,255),黑色表示为(0,0,0),值越大颜色越亮,越小则越暗。每种颜色都由3个数值组成,也就是红、绿、蓝的值 。如果直接使用RGB值对比颜色强度差异,相当复杂,因此我们转化为灰度值——只由一个0到255的整数表示灰度。这样的话就将三维的比较简化为了一维比较。
step 3:差异计算
差异值是通过计算每行相邻像素的强度对比得出的。我们的图片为9X8的分辨率,那么就有8行,每行9个像素。差异值是每行分别计算的,也就是第二行的第一个像素不会与第一行的任何像素比较。每一行有9个像素,那么就会产生8个差异值,这也是为何我们选择9作为宽度,因为8bit刚好可以组成一个byte,方便转换为16进制值。
如果前一个像素的颜色强度大于第二个像素,那么差异值就设置为True(也就是1),如果不大于第二个像素,就设置为False(也就是0)。
step 4:转换为hash值
将差异值数组中每一个值看做一个bit,每8个bit组成为一个16进制值,将16进制值连接起来转换为字符串,就得出了最后的dHash值
step 5:对比图片指纹
得到图片的指纹后, 就可以对比不同的图片的指纹, 计算出64位中有多少位是不一样的. 如果不相同的数据位数不超过5, 就说明两张图片很相似, 如果大于10, 说明它们是两张不同的图片

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
26
def dHash(image, hash_size = 8):
'''
感知hash算法
'''
image = image.convert('L').resize((hash_size+1, hash_size))
pixels = list(image.getdata())

# 比较相邻像素
difference = []
for row in range(hash_size):
for col in range(hash_size):
pixel_left = image.getpixel((col, row))
pixel_right = image.getpixel((col + 1, row))
difference.append(pixel_left > pixel_right)

# 转化为16进制(每个差值为一个bit,每8bit转为一个16进制)
decimal_value = 0
hash_string = ""
for index, value in enumerate(difference):
if value: # value为0, 不用计算
decimal_value += value * (2 ** (index % 8))
if index % 8 == 7: # 每8位的结束
hash_string += str(hex(decimal_value)[2:].rjust(2, "0")) # 不足2位以0填充。0xf=>0x0f
decimal_value = 0

return hash_string

距离比较见距离度量中的汉明距离

0%