首页Python【Python计算生态】I...

【Python计算生态】ImageHash——图像哈希库

Python受欢迎的原因之一就是其计算生态丰富,据不完全统计,Python 目前为止有约13万+的第三方库。

本系列将会陆续整理分享一些有趣、有用的第三方库。

文章配套代码获取有以下两种途径:
  • 通过百度网盘获取:
链接:https://pan.baidu.com/s/1FSGLd7aI_UQlCQuovVHc_Q?pwd=mnsj 提取码:mnsj
  • 前往GitHub获取
https://github.com/returu/Python_Ecosystem





01
简介

ImageHash 是一个用于计算图像哈希值的 Python 第三方库,它提供了多种不同的哈希算法,可用于图像相似度比较、图像去重等任务。支持以下哈希算法:
  • 平均哈希(Average Hash):
  • 原理:
首先将图像缩放到固定大小(通常是 8×8),并将其转换为灰度图像。
计算图像中所有像素的平均灰度值。
对于图像中的每个像素,若其灰度值大于平均灰度值,则将对应的哈希位设为 1;否则设为 0。
  • 特点:
计算速度快,实现简单。适用于快速判断图像是否大致相同。
但是精度不高,鲁棒性较差。
  • 感知哈希(Perceptual Hash):
  • 原理:
将图像缩放到固定大小(通常是 32×32),并转换为灰度图像。
对图像进行离散余弦变换(DCT),DCT 可以将图像从空间域转换到频域,突出图像的低频部分(包含图像的主要结构信息)。
选取 DCT 系数的低频部分(例如左上角 8×8 的系数),计算这些系数的中位数。
根据每个系数与中位数的比较结果生成哈希值,大于中位数的设为 1,小于的设为 0。
  • 特点:
对图像的视觉内容更敏感,能够更好地捕捉图像的结构特征。
对图像的缩放、旋转和压缩有一定的鲁棒性。
  • 差异哈希(Difference Hash):
  • 原理:
将图像缩放到固定大小(通常是 9×8),并转换为灰度图像。
比较相邻像素的灰度值,从左到右逐行比较。如果右边像素的灰度值大于左边像素的灰度值,则将对应的哈希位设为 1;否则设为 0。
  • 特点:
计算速度快,对图像的亮度变化不敏感。
  • 小波哈希(Wavelet Hash):
  • 原理:
对图像进行小波变换,将图像分解为不同尺度和方向的子带,小波变换可以将图像的能量集中到少数系数上。
选择部分低频子带的系数,计算这些系数的中位数。
根据每个系数与中位数的比较结果生成哈希值。
  • 特点:
能够更好地捕捉图像的多尺度特征,对图像的细节变化更敏感。
对图像的缩放、旋转和噪声有一定的鲁棒性。
  • HSV 颜色哈希(Color Hash):
  • 原理:
将图像从 RGB 颜色空间转换到 HSV(色调、饱和度、亮度)颜色空间。
将 HSV 空间划分为若干个区间,统计每个区间内像素的数量。
根据每个区间的像素数量生成哈希值,例如可以根据像素数量的大小将区间标记为不同的二进制位。
  • 特点:
对图像的颜色变化敏感。对图像的亮度和结构变化不敏感。
  • 抗裁剪哈希(Crop-Resistant Hashing):
  • 原理:
将图像分割为多个区域。
对每个区域分别计算哈希值。
将所有区域的哈希值组合成最终的哈希值。
  • 特点:
对图像的裁剪、缩放和部分遮挡具有很强的鲁棒性。计算复杂度较高,但能够更好地处理局部变化。
直接使用pip安装,该库基于PIL/Pillow Imagenumpyscipy.fftpack(用于感知哈希)实现:
pip install imagehash
GitHub页面:
https://github.com/Zulko/moviepy

02
使用

本次使用 ImageHash 库中的不同哈希方法分别计算以下三张图片的哈希值,然后根据manhattan distance (hash1 – hash2 < threshold)判断哪些图像是相似的。

img_1.png
img_2.png
img_3.png

示例代码:

每种哈希算法还可以通过hashsize参数调整哈希大小(对于颜色哈希,可以调整 binbits参数)。增加哈希大小可以让算法在哈希中存储更多细节,从而提高对细节变化的敏感性。

在 crop_resistant_hash 方法中,通过以下两个参数控制图像分割的方式和哈希计算的精度:

  • min_segment_size参数用于指定每个分割区域的最小像素数量。如果某个区域的像素数量小于该值,则会被忽略。

  • hsegmentation_image_size参数用于控制图像在进行分割时的缩放尺寸。图像会先缩放到 segmentation_image_size 的大小,然后再进行分割。

from PIL import Image
import imagehash
import os

# 图像路径
image_paths = ['img_1.png''img_2.png''img_3.png']

# 哈希方法列表
hash_methods = {
    "Average Hash": imagehash.average_hash,
    "Perceptual Hash": imagehash.phash,
    "Difference Hash": imagehash.dhash,
    "Wavelet Hash": imagehash.whash,
    "Color Hash": imagehash.colorhash,
    "Crop-Resistant Hash": imagehash.crop_resistant_hash
}

# 相似性阈值
threshold = 20  # 可根据需求调整

# 计算所有图像的哈希值
hashes = {}
for method_name, method in hash_methods.items():
    print(f"计算 {method_name}...")
    hashes[method_name] = {}
    for path in image_paths:
        try:
            img = Image.open(path)
            hash_value = method(img)
            hashes[method_name][path] = hash_value
            print(f"{path}: {hash_value}")
        except Exception as e:
            print(f"无法处理 {path}: {e}")

# 比较图像相似性
for method_name, hash_dict in hashes.items():
    print(f"n使用 {method_name} 比较图像相似性:")
    images = list(hash_dict.keys())
    for i in range(len(images)):
        for j in range(i + 1, len(images)):
            img1 = images[i]
            img2 = images[j]
            hash1 = hash_dict[img1]
            hash2 = hash_dict[img2]
            distance = hash1 - hash2  # 计算距离
            if distance <= threshold:
                print(f"'{img1}' 和 '{img2}' 相似 (距离: {distance})")

输出结果:


03
存储哈希值:

哈希值可以转换为字符串,同时字符串可以重新转换为ImageHash对象。
  • 对于单个感知哈希:

original_hash = imagehash.phash(Image.open('./img_1.png'))
# 哈希值转换为字符串
hash_as_str = str(original_hash)
print(hash_as_str)
# 输出:92a71cdc79d980e3

# 字符串重新转换为ImageHash对象
restored_hash = imagehash.hex_to_hash(hash_as_str)
print(restored_hash)
# 输出:92a71cdc79d980e3

print(restored_hash == original_hash)
# 输出:True
print(str(restored_hash) == hash_as_str)
# 输出:True


  • 对于抗裁剪哈希:


original_hash = imagehash.crop_resistant_hash(Image.open('./img_1.png'), min_segment_size=500, segmentation_image_size=1000)
# 哈希值转换为字符串
hash_as_str = str(original_hash)
print(hash_as_str)
# 输出:ccd9f3e5e1c18100,706c6e66464cb9b1,......

# 字符串重新转换为ImageHash对象
restored_hash = imagehash.hex_to_multihash(hash_as_str)
print(restored_hash)
# 输出:ccd9f3e5e1c18100,706c6e66464cb9b1,......

print(restored_hash == original_hash)
# 输出:True
print(str(restored_hash) == hash_as_str)
# 输出:True


更多内容可以前往官方文档查看:

https://zulko.github.io/moviepy/

本篇文章来源于微信公众号: 码农设计师

RELATED ARTICLES

欢迎留下您的宝贵建议

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments