开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第17天,点击查看活动详情
前言
在本节中,我们将学习如何通过使用图像哈希查找与给定图像相似的图像,我们使用感知哈希函数( Perceptual Hash function, pHash)来实现此目的。
感知哈希函数
传统加密哈希算法(例如 MD5 )的关键特征抗碰撞性,即使输入有一点变化也会显着改变输出结果。使用加密哈希,哈希值是随机的(通常也可以认为是伪随机的),用于生成哈希值的数据可以视为随机种子,因此,相同的输入数据可以生成相同哈希值,但是不同的数据将会产生完全不同的哈希值。
因此,如果对图像执行了某些操作(例如压缩、裁剪和缩放),哈希值将变得完全不同,即使这些图像在视觉上具有相似的显示效果。为了查找内容相似的的图像,图像哈希函数应考虑图像视觉变化,并根据图像的视觉效果生成哈希值,这种类型的哈希函数可以用于识别集合中是否存在与给定图像相似的的图像。
感知哈希算法是一类可比的哈希函数,使用图像中的特征生成不同的(但不唯一的)指纹,这些指纹具有可比性。换句话说,如果特征相似,则感知哈希函数值也是类似的,而密码哈希函数对于输入值的微小变化极为敏感(会导致输出值的剧烈变化)。pHash 使用一种鲁棒性算法,即离散余弦变换来降低频率。
使用感知哈希函数查找相似图像
在本节中,使用 imagehash 库的 pHash 算法计算相似图像,我们使用数据集 caltech-101。
为了使用 pHash 函数,需要首先安装第三方库 imagehash:
$ pip install imagehash
(1) 首先导入所需的库:
from PIL import Image
import imagehash
from time import time
import os
from glob import glob
import matplotlib.pylab as plt
import numpy as np
(2) 实现函数 plot_images_to_compare(),其接受两个输入图像和一个哈希函数(默认为 pHash )作为参数,使用哈希函数计算每个输入图像的 64 位指纹,并绘制相似图像;最后,计算图像的指纹之间的汉明距离:
def plot_images_to_compare(imfile1, imfile2, hashfunc = imagehash.phash):
img1, img2 = Image.open(imfile1), Image.open(imfile2)
print('sizes of images = {}, {}'.format(img1.size, img2.size))
hash1 = hashfunc(img1)
hash2 = hashfunc(img2)
plt.figure(figsize=(20,10))
plt.subplots_adjust(0,0,1,0.95,0.01,0.01)
plt.subplot(121), plt.imshow(img1), plt.title(str(hash1), size=10), plt.axis('off')
plt.subplot(122), plt.imshow(img2), plt.title(str(hash2), size=10), plt.axis('off')
plt.show()
print('hash1 = {} ({}), length = {} bits'.format(format(int(str(hash1), 16), "040b"), str(hash1), len(format(int(str(hash1), 16), "040b"))))
print('hash2 = {} ({}), length = {} bits'.format(format(int(str(hash2), 16), "040b"), str(hash2), len(format(int(str(hash2), 16), "040b"))))
print('hamming distance =', hash1 - hash2)
(3) 调用函数 plot_images_to_compare() 比较两个不同的图像,其中第二张图像可以通过在第一个中添加一些随机笔画创建:
plot_images_to_compare('bird_01.jpg', 'bird_02.jpg')
# sizes of images = (300, 258), (300, 258)
# hash1 = 1001101101001000011001110110011010010100100110011011001101100011 (9b4867669499b363), length = 64 bits
# hash2 = 1001101101001000011001110110011010010100100110011011001101100011 (9b4867669499b363), length = 64 bits
# hamming distance = 0
根据输出可以看出图像非常相似,两者会返回完全相同的 pHash指纹,它们之间的汉明距离为 0。
(4) 接下来,我们计算原始输入图像与使用图像增强后的 pHash 值:
plot_images_to_compare('bird_01.jpg', 'bird_03.png')
# sizes of images = (300, 258), (300, 258)
# hash1 = 1001101101001000011001110110011010010100100110011011001101100011 (9b4867669499b363), length = 64 bits
# hash2 = 1001101101001000011001110110011010010100100110011101001101100011 (9b4867669499d363), length = 64 bits
# hamming distance = 2
可以看出,两张图像同样非常相似,汉明距离为 2,即 pHash 指纹仅有 2 位不同。
(5) 对比原始图像与添加水印后图像(图像的大小尺寸不同)的 pHash 指纹:
plot_images_to_compare('img_with_logo.jpg', 'img_with_no_logo.png')
# sizes of images = (1024, 683), (574, 383)
# hash1 = 1001010110000001011010111101001010010000111100100111001001111110 (95816bd290f2727e), length = 64 bits
# hash2 = 1001010110000001011010101101001010010010111100100111001001111110 (95816ad292f2727e), length = 64 bits
# hamming distance = 2
从以上输出可以看出中,指纹同样仅有 2 位不同。
(6) 最后,我们比较两个完全不同图像的 pHash 指纹:
plot_images_to_compare('bird_01.jpg', 'img_with_no_logo.png')
# sizes of images = (300, 258), (574, 383)
# hash1 = 1001101101001000011001110110011010010100100110011011001101100011 (9b4867669499b363), length = 64 bits
# hash2 = 1001010110000001011010101101001010010010111100100111001001111110 (95816ad292f2727e), length = 64 bits
# hamming distance = 28
可以看到,两张图像的指纹差异较大,大约有一半左右的位上值并不相同。