Advertisement

python 相似图片 dhash_相似图片检测:感知哈希算法之dHash的Python实现

阅读量:

在某些情况下, 当遇到, 我们需要识别图片间的相似度, 执行我们的操作: 去除重复出现的照片以及识别侵权内容并做标记。

如何鉴别是否为同一张图片?最直接的方式就是利用加密哈希算法(如MD5、SHA-1)来进行比较。然而这种方法存在明显的局限性:举个例子来说吧——如果一个txt文档是一个完全复制版的话(即没有被修改过),那么它们计算出来的MD5值会一模一样;但如果只是对副本做了一点微小改动——比如仅仅更改了一点格式或者其他细节部分——这时候它们的MD5值就会天壤之别了!所以由此可见,在处理经过任何修改或变形的数据时(尤其是图像),这种方法就不再适用了

如何鉴别一张被PS处理过的图片与另一张图片在本质上是否相同?一种简便实用的方法是使用感知哈希算法(Perceptual Hash Algorithm)

perceptual hashing algorithm falls under the category of algorithms, encompassing aHash, pHash, and dHash. By definition, perceptual hashing operates in a more relative manner to compute hash values rather than using strict calculations. Since similarity is inherently a relative concept.

aHash:平均值哈希。速度比较快,但是常常不太精确。

pHash:感知哈希。精确度比较高,但是速度方面较差一些。

dHash:差异值哈希。令人惊叹的是该算法在准确度上表现优异,并且运算速度极快。经过一番考虑与评估后我发现dHash是一个非常适合用于图片识别任务的算法。

一、 相似图片检测步骤:

分别计算两张图片的dHash值

基于dHash特征值计算两张图像间的汉明距离Hamming\ Distance,并根据计算所得的汉明距离数值大小来评估两者之间的视觉相似性

二、dHash计算

3b357fbf704de2ba8f0587b760afc83f.png

需要计算dHash值的图片

Step1. 缩放图片

为了计算上图的dHash值,第一步是将其缩小到适当程度。缩放的原因是什么?由于原始图像通常具有很高的分辨率,在这种情况下处理起来会非常繁琐。一张分辨率高达200×200像素的图片会产生4万个RGB数据点(每个像素点都有一个RGB值),这带来了巨大的信息量和处理复杂度。因此有必要将图像缩小至较小尺寸以便于处理,并且在这一过程中需注意避免过多细节信息丢失以保证后续处理的有效性。通常建议将其缩小至9×8像素并在此基础上进行后续操作这一步骤能够有效降低计算复杂度同时又能较好地保留图像的主要特征信息

resize_width = 9

resize_height = 8

1. resize to (9,8)

smaller_image = image.resize((resize_width, resize_height))

a8ce84beeea5c5c17dd191a5c76de5ae.png

缩放为9*8分辨率后

Step2. 灰度化

The full name of dHash is differential hash. It calculates the difference in intensity between adjacent pixels. However, it still has its limitations. The method represents each color component using three numerical values that define red, green, and blue channels. White is represented as (255, 255, 255) and black as (0, 0, 0). Higher values correspond to brighter colors while lower values are darker. Directly comparing RGB values for color intensity differences is computationally intensive. Therefore we convert them to grayscale by using a single integer value ranging from 0 to 255 to represent luminance.

2. 灰度化 Grayscale

grayscale_image = smaller_image.convert("L")

610746ded1e3bc529cd3cf25f4c5f026.png

灰度化后

Step3. 差异计算

差异值是由对图像中相邻像素强度差值得出的一系列数值参数。我们采用一张分辨率设置为9×8 pixels的画面来进行分析工作。具体来说,则分为8 rows来进行逐pixel差异数字统计工作。在这一过程中,则会针对每一 row单独进行差异数字统计工作。值得注意的是,在这种情况下,则意味着第二 row的第一个 pixel无法与其他所有上一行中的 pixels进行比较操作处理。而每一 row共有9个 pixel的位置点,则会产生对应数量减一的数量即8个 difference values输出结果参数集合。这样做的原因在于,在计算机存储空间有限的情况下,默认选择 bit位数越多则存储精度越高这一原则下所作出的最佳折衷方案决定因素考量结果输出时则会选择将数值范围限定在0到255之间便于后续转换处理过程中的统一表示方式需求满足条件

当前一个像素的亮度超过第二个像素时, 差异值将被赋值为True(即1)。否则, 将差异值赋值为False(即0)。

3. 比较相邻像素

pixels = list(grayscale_image.getdata())

difference = []

for row in range(resize_height):

row_start_index = row * resize_width

for col in range(resize_width - 1):

left_pixel_index = row_start_index + col

difference.append(pixels[left_pixel_index] > pixels[left_pixel_index + 1])

Step4. 转换为hash值

将差异值数组中的每个元素视为一个二进制位,并将每八个连续的二进制位构成一个十六进制数值;然后将这些十六进制数值串联起来生成字符串;从而计算出dHash算法对应的哈希码字。

转化为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

三、 计算汉明距离(Hamming Distance)

汉明距离这一理论不仅在图像对比领域得到了广泛应用,也被广泛应用于其他多个领域.详细的说明可以在Wikipedia上找到.

汉明距离定义为将字符串A转换成字符串B所需的具体步骤数量,并且作为一个衡量两者差异程度的重要量化指标。例如,在比较字符串'abc'与'ab3'时,我们发现它们在第三个字符上存在差异(将'c'替换为'3'),因此它们之间的汉明距离计算结果是1。

在dHash算法中, 汉明距离是通过统计差异值中发生更改的位置数量来确定的. 我们采用的是基于二进制数值系统的方法来表示差异值. 例如, 在比较二进制字符串0 ¹ ¹ ⁰ 和 ¹ ¹ ¹ ¹时可以看到它们在第一位和第四位有所不同.

通过将两张图片的dHash值转换为二进制difference并进行异或运算,我们能够得到它们之间的汉明距离

difference = (int(dhash1, 16)) ^ (int(dhash2, 16))

return bin(difference).count("1")

如果输入的参数不是基于两张图像的dHash codes,则无需计算dHash codes,在Step3中的difference数组中统计不同位的数量即可得到汉明距离。

hamming_distance = 0

for index, img1_pix in enumerate(image1_difference):

img2_pix = image2_difference[index]

if img1_pix != img2_pix:

hamming_distance += 1

通常情况下,当汉明距离低于5时,基本上属于同一类别。根据实际情况,可以判定其阈值是多少。

Github:

参考文档:

全部评论 (0)

还没有任何评论哟~