之前介绍了许多图像比对的算法,这里再介绍一个算法,叫感知哈希算法:perceptual hash algorithm, 它的作用是对每张图像生成一个“指纹”字符串,然后比较不同图像的指纹。结构越接近,说明图像越相似。
哈希算法步骤图解
这个算法常用于图像整理,归类上,比如快速整理手机中的照片文件夹子。该算法的优点明显,相对于其它算法实现起来比较简单,且速度快,准确率高。
图像哈希或者叫感知哈希包括以下两个过程:
1)检查图像内容
2)构造基于图像内容唯一标识,即输入图像的哈希值
最典型的应用比如TinEye,就是一种采用反向图像搜索引擎。用户可以:
1)上传图像
2)然后TinEye给出类似图像的网址
接下来我们将讨论:
1)图像散列(为什么传统散列不起作用)
2)实现图像散列,特别是差异散列(dHash)
3)将图像散列应用于实际问题和数据集
为什么我们不能使用md5,sha-1等?
图2:在此示例中,我获取输入图像并计算md5哈希值。然后我将图像调整为宽度为250像素而不是500像素,然后再次计算md5哈希值。即使图像的内容没有改变,哈希也是如此。
具有密码学或文件验证背景的读者可能想知道为什么不能使用md5或sha-1等。这里的问题在于加密散列算法的本质:更改文件中的单个位将导致不同的散列。这意味着如果我们改变输入图像中单个像素的颜色,最终会得到一个不同的校验和,而事实上对于我们在图像感知层面上根本不觉得有什么不同。也就是说这个微小的改变我们是无法觉察的。
图2中的例子就说明这个问题,只是改变图像的大小,其它不变,计算图像的md5哈希,结构哈希值发生变化。在图像哈希情况下,我们实际上希望类似的图像具有类似的哈希值。
该项目的测试图像哈希数据集
该项目的目标是:
1)两个图像目录,dataset和query
2)确定query目录中的图像哪些已经在dataset中,哪些没有
使用图像哈希,我们可以快速完成这个任务。
了解图像哈希和差异哈希
dHash算法只有四个步骤
步骤一:转换图像为灰度
图3:通过差异散列算法进行图像散列的第一步是将输入图像(左)转换 为灰度(右)。
图像散列算法的第一步把输入图像转换成灰度并丢弃任何颜色信息。这样我们只需要检查一个通道,从而更快地散列图像,另外匹配相同但颜色空间略有变化的图像。无论出于何种原因,如果你对于颜色感兴趣,可以独立在每个通道上应用散列算法,然后在最后进行组合。
步骤二:调整大小
灰度图像后,把图像压缩到9 x 8像素,忽略纵横比。对于大多数图像和数据集,调整大小、插值步骤是算法中最慢的部分。
为什么在调整大小期间忽略图像的宽高比呢?将图像压缩到9x8并忽略纵横比,以确保生成的图像哈希与相似的照片匹配,无论其初始空间维度如何。
为什么是 9 x 8这么一个奇怪的大小呢?后面会对这个问题详细解释。
步骤三:计算差异
我们最终的目标是计算64位散列 - 因为8 x 8 = 64,我们非常接近这个目标。
那么回答上面第二个问题,为什么调整大小到9 x 8?请记住我们正在实现的算法是dHash差异哈希。差异散列算法通过计算相邻像素之间的差异(即,相对梯度)来工作的。
如果我们采用每行9个像素的输入图像并计算相邻列像素之间的差异,我们最终得到8个差异。八行八个差异(即8x8)是64,这将成为我们的64位散列。
在实践中,我们实际上不必计算差异:我们可以应用“大于”测试(或“小于”,只要始终使用相同的操作,它就不重要,我们将在下面的步骤中看到)。
步骤四:构建哈希
最后一步是分配位并构建结果哈希。为此,我们使用简单的二进制测试。
给定差异图像D和对应的像素组P,我们应用以下测试:P[x]>P[x+1] = 1 否则 0。
在这种情况下,我们测试左像素是否比右像素更亮。如果左侧像素更亮,我们将输出值设置为1,否则,如果左侧像素较暗,我们将输出值设置为0。
如果我们假设这个差异映射是 8×8 像素,则该测试的输出产生一组64个二进制值,然后将它们组合成单个64位整数(即实际图像散列)。
dHash的好处
使用差异散列(dHash)有很多好处,但主要的好处包括:
1)如果我们的输入图像的宽高比发生变化(因为我们忽略了宽高比),我们的图像哈希不会改变。
2)调整亮度或对比度要么不改变我们的哈希值,要么仅稍微改变它,确保哈希值接近。
3)差异散列非常快。
比较差异哈希值
通常我们使用汉明距离来比较哈希值。汉明距离测量两个不同哈希值中的比特数。
汉明距离为零的两个散列意味着两个散列是相同的(因为没有不同的位)并且两个图像在相同/感知上相似。
通常情况,差异散列 >10位图像最有可能不同而汉明距离在1和10之间的图像很可能相同或类似。实际上,你可能需要为自己的应用程序和相应的数据集调整这些阈值。
使用OpenCV和Python实现图像散列
首先,请确保您已经安装了我的imutils软件包,新建一个文件hash_and_search.py,先导入必要的包:
# import the necessary packagesfrom imutils import pathsimport argparseimport timeimport sysimport cv2import os#定义dhash函数,包含我们的差异哈希实现:def dhash(image, hashSize=8):# resize the input image, adding a single column (width) so we# can compute the horizontal gradientresized = cv2.resize(image, (hashSize + 1, hashSize)) #调整图像大小位9x8 # compute the (relative) horizontal gradient between adjacent# column pixelsdiff = resized[:, 1:] > resized[:, :-1] #计算二进制值测试相邻像素明暗 # convert the difference image to a hashreturn sum([2 ** i for (i, v) in enumerate(diff.flatten()) if v]) #转换64位整数来构建哈希值
我们的dhash函数需要一个输入image以及一个可选的hashSize。设置hashSize = 8 以指示输出散列将是8 x 8 = 64位。然后将得到的整数返回给调用函数。
接下来输入以下代码解析命令行参数:
# construct the argument parse and parse the argumentsap = argparse.ArgumentParser()ap.add_argument("-a