前提

  目前有许多算法来衡量两幅图像的相似性,本文主要介绍在工程领域最常用的图像相似性算法评价算法:图像哈希算法。
  图像哈希算法通过获取图像的哈希值并比较两幅图像的哈希值的汉明距离来衡量两幅图像是否相似。
  两幅图像越相似,其哈希值的汉明距离越小,通过这种方式就能够比较两幅图像是否相似。
  在实际应用中,图像哈希算法可以用于图片检索,重复图片剔除,以图搜图以及图片相似度比较。

汉明距离

  这里简单介绍下汉明距离。
  与欧式距离、曼哈顿距离相似的是,其使用的场合也是非常的多,最常用的场合就是相似性比较。
  汉明距离是以理查德·卫斯里·汉明的名字命名的。在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。例如:
  ·1011101 与 1001001 之间的汉明距离是 2。
  ·2143896 与 2233796 之间的汉明距离是 3。
  ·”toned” 与 “roses” 之间的汉明距离是 3。

原理介绍

  图像哈希算法在评估两幅图像的相似性时,需要借助每幅图像的哈希值。
  哈希值计算算法的本质就是对原始数据进行有损压缩,有损压缩后的固定字长能够作为唯一标识来标识原始数据,这个唯一标识就是哈希值。通常改变原始数据任意一个部分,即:不同的原始数据,都会对应不同的哈希值。(否则就发生了哈希碰撞)
  根据计算图像哈希值方式的不同,从而存在不同的图像哈希算法。
  此处主要使用到的是在OpenCV Contrib库中的img_hash模块,其提供了很多种计算图像哈希值的算法,这里主要推荐三种算法,分别是:
  ·AverageHash:基于像素均值计算哈希值,一种快速的图像哈希算法,但仅适用于简单情况。
  ·PHash:AverageHash的改进版,比AverageHash慢,但可以适应更多的情况。
  ·MarrHildrethHash:基于Marr-Hildreth边缘算子计算哈希值,速度最慢,但更具区分性。
  图像哈希的整个流程是比较统一的。以pHash为例来进行说明的话:首先将输入的图片的行列改为8*8大小,之后借助pHash函数对其进行hash值的计算,从而得到一个唯一Hash标识码。每个Hash标识码包含8个uint8大小的数值,将其组合在一起从而得到一个64位的二进制串,对不同的图像的二进制串之间进行汉明距离的计算即可。

代码实现

  这里我使用了3张图片进行对比,分别如下所示。
·inputImg:

·targetImg:

·anotherImg:

  代码如下所示:

import cv2

inputImg = cv2.imread("1.jpg")
targetImg = cv2.imread("2.jpg")
anotherImg = cv2.imread("3.jpg")

print("=== AverageHash ===")
hashFun = cv2.img_hash.AverageHash_create()
tick = cv2.TickMeter()

tick.reset()
tick.start()
# 统计图1的计算时间
hashA = hashFun.compute(inputImg)
tick.stop()
print("compute1: " + str(tick.getTimeMilli()) + " ms")
# 统计图2的计算时间
tick.reset()
tick.start()
hashB = hashFun.compute(targetImg)
tick.stop()
print("compute2: " + str(tick.getTimeMilli()) + " ms")
# 统计图3的计算时间
tick.reset()
tick.start()
hashC = hashFun.compute(anotherImg)
tick.stop()
print("compute3: " + str(tick.getTimeMilli()) + " ms")
print("=== Compare ===")
# 比较图与图间的差别
print("image1-image2: " + str(hashFun.compare(hashA, hashB)))
print("image1-image3: " + str(hashFun.compare(hashA, hashC)))
print("image2-image3: " + str(hashFun.compare(hashB, hashC)))

  此处使用AverageHash算法,最后的运行结果如下所示:

=== AverageHash ===
compute1: 0.153 ms
compute2: 0.011300000000000001 ms
compute3: 0.0089 ms
=== Compare ===
image1-image2: 25.0
image1-image3: 31.0
image2-image3: 46.0

  可以看出的是,第一张和第二张图片之间的差异最小。第二和第三张图片之间的差异最大。
  之后进行第二种算法:pHash的使用,代码与上述的相同,只需要把第8行代码替换成如下即可:

hashFun = cv2.img_hash.PHash_create()

  此处使用pHash算法,最后的运行结果如下所示:

=== pHash ===
compute1: 0.236 ms
compute2: 0.034 ms
compute3: 0.033800000000000004 ms
=== Compare ===
image1-image2: 17.0
image1-image3: 27.0
image2-image3: 20.0

  可以看出的是,第一张和第二张图片之间的差异最小。第一和第三张图片之间的差异最大。
  最后进行尝试的算法是MarrHildrethHash,与上述的一致,只需要修改原本的第8行代码即可:

hashFun = cv2.img_hash.MarrHildrethHash_create()

  此处使用MarrHildrethHash算法,最后的运行结果如下所示:

=== MarrHildrethHash ===
compute1: 6.7604 ms
compute2: 5.2183 ms
compute3: 5.3427999999999995 ms
=== Compare ===
image1-image2: 170.0
image1-image3: 314.0
image2-image3: 314.0

  可以看出的是,第一张和第二张图片之间的差异最小,且与其他两种情况有着非常明显的差异,而其他两种情况因为差异都很大。但与此同时,其消耗的时间也是最长的。

总结

  可以看到各种图像哈希算法的计算速度,在实际中PHash是个很不错的选择,快速且效果好。然而,PHash常用,并不代表其他算法没用。
  上述的三种方法都是基于局部信息来进行的计算,这就意味着如果将1张图片镜像后再与原来的图片进行hash计算,其值不一定会有一个好的结果。此处需要使用RadialVarianceHash或ColorMomentHash会更好,因为这两者是基于全局信息来计算的hash值。这两者的使用也与之前的相似,只需要更改第8行的代码如下所示即可:

hashFun = cv2.img_hash.RadialVarianceHash_create()
# 或者
hashFun = cv2.img_hash.ColorMomentHash_create()