详解相似图片匹配算法:差异值哈希算法 + 颜色直方图

9次阅读

共计 2766 个字符,预计需要花费 7 分钟才能阅读完成。

由于最近涉及到匹配相似图片的问题,所以在此记录下解决办法:差异值哈希算法 + 颜色直方图
环境要求:Python cv2 库 math 库
差异值哈希算法
检索相似图片,第一个想到的就是差异值哈希算法。这个算法的步骤是:

缩小尺寸 一般将图片缩放为 8 8 的尺寸大小,共 64 个像素的图片。但是由于 64 个像素对于我来说,损失的细节太多所以我选择了缩放到 33 32 的尺寸大小
彩色图像灰度化 由于我们现有的图片是由 RGB 三原色构成,每个像素点是一个由这三个颜色组成的一个 list。而 RGB 三个颜色中每个颜色值都是用 8 个比特来表示,大小范围是 0 ~ 255(2^8 – 1),就一共有 256 256 256 种颜色。并且作为一个像素类似于这样的数值:[253 255 255] 是不利于简单比较的,肉眼看着类似的颜色,但是它的三个颜色分布可能相差很多。所以将它灰度化,用 256 个不同的灰色表示现有的图片。由于现在用一种灰色表示三种颜色,原来每个像素是一个 list 现在就降维成一个数值,数值的大小还是比较容易比较的。
比较像素的灰度值 比较图片灰度化的每行相邻像素之间的大小,每行后面像素值大于前面一个像素值那么记为 1,如果不大于则记为 0
计算哈希值 根据上一步得到了由 0 和 1 构成的数组合在一起就构成了 1024 位的整数
对比不同图片的汉明距离 对比两个图片生成的整数有多少位不一样。一般汉明距离小于 5,两张图片的相似度就很高了。

差异值哈希算法的 Python 代码

import cv2

# 差异值哈希算法
def dhash(image):
resize_height, resized_width = 32, 33
# 缩放到 (resized_width, resize_height) 尺寸的大小
resized_img = cv2.resize(image, (resized_width, resize_height))
# 图片灰度化
grey_resized_img = cv2.cvtColor(resized_img, cv2.COLOR_RGB2GRAY)
# 差异值计算
hash_list = []
for row in range(resize_height):
for col in range(resized_width – 1):
# 每行前一个颜色强度大于后一个,值为 1,否则值为 0
if grey_resized_img[row, col] > grey_resized_img[row, col + 1]:
hash_list.append(‘1’)
else:
hash_list.append(‘0’)

return ” . join(hash_list)

# 比较汉明距离
def hamming_distance(dhash1, dhash2):
return bin(int(dhash1, base = 2) ^ int(dhash2, base = 2)).count(‘1’)

# 读取图片内容
img1 = cv2.imread(img1_path)
# 读取图片内容
img2 = cv2.imread(img2_path)
if hamming_distance(dhash(img1), dhash(img2)) <= 5:
print(‘ 相似图片 ’)

颜色直方图
由于差异值哈希失去了太多的细节,适合比较原图或者缩略图。所以我再加上颜色直方图的比较计算图片间的接近程度,用以排除部分像素的微小差异。

缩小尺寸 一般将图片缩放为 8 8 的尺寸大小,共 64 个像素的图片。但是由于 64 个像素对于我来说,损失的细节太多所以我选择了缩放到 32 32 的尺寸大小
降低位深 原来 RGB 每个颜色都有 256 种变化,现在做一个映射,将原来的 256 分为 8(3 个比特表示) 个颜色区间。类似旧的 0 – 31 对应新的颜色 0,以达到降低计算的效果
计算像素值 由于降低了位深,图片颜色值变小,那么在计算方面就可以简化到一维运算。每个颜色值不大于 8,然后我们给三元素不同的权重,分别为 8 * 8,8,1 以达到不会相互影响干扰的目的
计算相似度 用余弦相似度去计算相似度,越是相似的图片最后值越接近于 1。截图来自于 WiKi

颜色直方图的 Python 代码
import cv2
from math import sqrt

# 颜色映射
def bgr_mapping(img_val):
# 将 bgr 颜色分成 8 个区间做映射
if img_val >= 0 and img_val <= 31: return 0
if img_val >= 32 and img_val <= 63: return 1
if img_val >= 64 and img_val <= 95: return 2
if img_val >= 96 and img_val <= 127: return 3
if img_val >= 128 and img_val <= 159: return 4
if img_val >= 160 and img_val <= 191: return 5
if img_val >= 192 and img_val <= 223: return 6
if img_val >= 224: return 7

# 颜色直方图的数值计算
def calc_bgr_hist(image):
if not image.size: return False
hist = {}
# 缩放尺寸减小计算量
image = cv2.resize(image, (32, 32))
for bgr_list in image:
for bgr in bgr_list:
# 颜色按照顺序映射
maped_b = bgr_mapping(bgr[0])
maped_g = bgr_mapping(bgr[1])
maped_r = bgr_mapping(bgr[2])
index = maped_b * 8 * 8 + maped_g * 8 + maped_r
hist[index] = hist.get(index, 0) + 1

return hist

# 计算两张图片的相似度
def compare_similar_hist(h1, h2):
if not h1 or not h2: return False
sum1, sum2, sum_mixd = 0, 0, 0
for i in range(512):
sum1 = sum1 + (h1.get(i, 0) * h1.get(i, 0))
sum2 = sum2 + (h2.get(i, 0) * h2.get(i, 0))
sum_mixd = sum_mixd + (h1.get(i, 0) * h2.get(i, 0))

return sum_mixd / (sqrt(sum1) * sqrt(sum2))

# 读取图片内容
img1 = cv2.imread(img1_path)
# 读取图片内容
img2 = cv2.imread(img2_path)
if compare_similar_hist(calc_bgr_hist(img1), calc_bgr_hist(img2)) < 0.9999:
print(‘ 相似图片 ’)

总结
总的来说:差异值哈希算法 + 颜色直方图 解决了我的问题。
参考资料
相似图片搜索的原理一相似图片搜索的原理二

正文完
 0