Python 图像哈希库 imagehash——从原理到实践


一、前言:为什么需要图像哈希?

在当下的互联网环境中,图像数据以爆炸式速度增长。从社交平台的图片分发,到电商平台的商品图采集,再到内容审核、重复图像检测、盗图追踪,如何高效比较两个图像是否相同或相似成为一个核心问题。

我们可以使用深度学习模型如 CNN、ViT 提取图像特征,但这需要 GPU,代价高、复杂度大。而传统图像处理领域提供了一个简单高效的解决方案。

图像感知哈希(Perceptual Hash, pHash)及其系列算法

Python 的 imagehash 库正是目前应用最广、最稳定的一套图像哈希计算工具。

它具有以下特点:

  • 速度极快(毫秒级)
  • 对缩放、旋转、亮度变化不敏感
  • 哈希值可用于相似度比较
  • 包含多个算法:aHash、pHash、dHash、wHash

无论你是做:内容去重、图像相似搜索、爬虫去重、图库管理、数字资产管理、图形数据库、以图搜图系统,imagehash 都能胜任。

接下来,我们将从安装、算法原理、使用方法、进阶技巧,逐步完整展开。


二、imagehash 库简介

imagehash 是基于 PIL(即 pillow)的图像感知哈希算法工具包。

安装方式非常简单:

pip install imagehash pillow 

常用导入方式:

from PIL import Image import imagehash 

它支持四大经典图像哈希算法:

哈希算法全称优点使用场景
aHashaverage hash简单快速基础相似检测、轻量级应用
pHashperceptual hash最稳健、常用内容审查、重复图检测
dHashdifference hash相当稳定图像去重、图像聚类
wHashwavelet hash去噪效果强细节损失较多场景

除此之外,它还支持 colorhash、crop-resistant hash 等进阶算法。


三、图像感知哈希的核心思想

传统哈希(如 MD5、SHA256)的特点是:

输入只要有一点点变化,输出就完全不同

所以不能用来比较图像相似度。

感知哈希的思想正好相反:

当两张图片“看起来相似”时,它们的哈希应当相近

因此,感知哈希的目标:

  • 忽略图像的噪声
  • 忽略尺寸变化
  • 忽略亮度变化
  • 忽略轻微裁剪
  • 保留整体结构和视觉内容

最终得到一个通常使用 64 bit(8x8)长度的哈希值:

例:

ff00aa55cc33ee99 

两个哈希值之间的差异(Hamming Distance)即可判断图像相似度。

例如:

hash1 = imagehash.phash(Image.open("img1.jpg")) hash2 = imagehash.phash(Image.open("img2.jpg")) similarity = hash1 - hash2 # 汉明距离

通常距离 <5 可以认为是同一张图片或极其相似。


四、四大常用哈希算法全面解析

接下来我们分别讲解算法原理、特点和代码使用。


1. aHash —— 平均哈希(Average Hash)

算法流程

  1. 把图像缩小到 8x8
  2. 转成灰度
  3. 求平均值
  4. 灰度 > 平均值 → 1,否则 → 0
  5. 得到 64 bit 哈希

特点

  • 速度最快
  • 简单,可用于海量图片快速预处理
  • 对亮度变换不敏感
  • 但对轻微变化敏感(裁剪、噪声)

代码示例

hash_val = imagehash.average_hash(Image.open('x.jpg'))print(hash_val)

2. pHash —— 感知哈希(最经典)

算法流程

  1. 图像缩放为 32x32
  2. 转灰度
  3. 使用 DCT 变换(离散余弦变换)
  4. 取左上角 8x8 低频区块
  5. 比较低频分量与均值

特点

  • 对缩放、亮度变化非常稳健
  • 效果最好、最常用
  • 比 aHash 略慢(仍然非常快)

代码示例

hash_val = imagehash.phash(Image.open('x.jpg'))print(hash_val)

3. dHash —— 差值哈希

算法流程

  1. 缩放为 9x8
  2. 比较相邻像素
  3. 灰度左 > 右 → 1,否则 → 0

特点

  • 对结构变化敏感
  • 比 pHash 快
  • 效果稳定,广泛用于图像去重

代码

hash_val = imagehash.dhash(Image.open('x.jpg'))

4. wHash —— 小波变换哈希(Wavelet Hash)

特点

  • 抗噪声能力最强
  • 对模糊图、压缩图也稳定
  • 稍微比 pHash 慢

使用

hash_val = imagehash.whash(Image.open('x.jpg'))

五、哈希值比较:判断两图是否相似

imagehash 的对象支持直接做减法:

hash1 = imagehash.phash(Image.open('a.jpg')) hash2 = imagehash.phash(Image.open('b.jpg')) distance = hash1 - hash2 # 汉明距离

一般参考阈值:

汉明距离相似度判断
0完全相同
1-5同一张图片、压缩尺寸不同
6-10内容相似(例如同一物体)
10 以上基本不相似

可自定义阈值:

if distance <5:print("图片相似")

六、在实战中使用 imagehash:10 个典型场景

1. 大规模图片去重(电商、图库)

适用于:

  • 商品图片采集
  • 抖音/小红书去重
  • 内容审核系统

使用 pHash 或 dHash 最稳定。

2. 内容审核(版权、盗图识别)

可用于发现:

  • 相似照片
  • 水印图
  • 裁剪、调整过的图像

3. 搜索引擎:以图搜图

流程:

  1. 给每个图库图片生成哈希
  2. 用户上传图片计算哈希
  3. 计算差值 <5 的返回

4. 图形数据库(如 Elasticsearch + imagehash)

将哈希存储为字符串即可快速检索。

5. 图像聚类

可用于:

  • 人像去重
  • 事件聚类
  • 相册管理

借助 k-means/DBSCAN 可聚类哈希向量。

6. 监控场景:帧相似度检测

可判断:

  • 场景变化
  • 异常事件
  • 镜头切换

7. 视频抽帧去重

imagehash + ffmpeg:

  • 抽帧
  • 计算哈希
  • 剔除重复

8. 防止重复提交(如合同照片)

可根据哈希查重。

9. 对抗恶搞图片(meme 分析)

字符变换颜色后仍可检测相似。

10. 软件安全与内容完整性验证

判断图标、UI素材是否被篡改。


七、进阶使用:colorhash、crop-resistant-hash 等

imagehash 还提供高级功能:


1. ColorHash(颜色直方图哈希)

适合风景图、背景色分类。

hash_val = imagehash.colorhash(Image.open('x.jpg'))

2. Crop-Resistant Hash(最强算法)

对裁剪、长宽变化非常稳健。

适用于:

  • 社交平台裁剪图
  • 盗图识别

八、与 OpenCV 和 deep learning 的对比

1. 与 OpenCV 的 difference hash 对比

OpenCV 也能写 dHash,但 imagehash 更完整。

2. 与深度学习特征(CNN、CLIP)相比

imagehash 优点:

  • 更快(毫秒级)
  • 无需 GPU
  • 部署简单
  • 结果可解释(哈希差)

深度学习优点:

  • 支持复杂语义相似度
  • 更多场景(如动物、物体检测)

结论:

imagehash 适合结构相似检测,深度学习适合语义相似检测。

在工程中常常两种结合使用。


九、性能优化方案(适合海量数据)

如果要处理百万级图片,可采取:

1. 批处理哈希计算

多线程:

from concurrent.futures import ThreadPoolExecutor defcalc(path):return imagehash.phash(Image.open(path))with ThreadPoolExecutor(20)as pool: results =list(pool.map(calc, images))

2. 哈希存储结构优化

存储为:

  • 64-bit int
  • binary bit array

可加速比较。

3. 使用 Annoy / FAISS 做哈希近似搜索

imagehash 返回的是 64bit bit-array,可作为向量存入 FAISS。


十、完整示例:构建一个图片重复检测系统

以下代码展示一个从目录中自动检测重复图片的完整程序:

from PIL import Image import imagehash import os deffind_duplicates(folder, threshold=5): hash_dict ={} duplicates =[]for fname in os.listdir(folder):ifnot fname.lower().endswith(('.jpg','.png','.jpeg')):continue path = os.path.join(folder, fname) img = Image.open(path) h = imagehash.phash(img)for oh, opath in hash_dict.values():if h - oh < threshold: duplicates.append((path, opath))break hash_dict[fname]=(h, path)return duplicates dups = find_duplicates("images/")for a, b in dups:print("相似图片:", a,"<==>", b)

十一、常见问题 FAQ

Q1:不同算法的差异如何选择?

建议:

  • 宽松去重:pHash
  • 快速去重:dHash
  • 抗噪声:wHash
  • 色彩特征:colorhash

Q2:为什么同一图片的哈希值有时不同?

可能原因:

  • EXIF 旋转信息不同
  • 图像压缩/分辨率变化
  • 使用了不同算法

Q3:汉明距离阈值如何设定?

经验值:

  • 0~5:同一张图
  • 5~10:内容相似
10:不同

十二、总结:imagehash 仍是图像相似性计算中最值得使用的工具

即使在深度学习时代,imagehash 仍然是工程中最常用的图像相似工具之一,因为它:

  • 速度快
  • 精度稳定
  • 轻量级
  • 部署简单
  • 跨领域通用

适合作为图像比对的第一道过滤器,再结合深度学习做更高精度分析。


Read more

Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系

Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系 前言 在 OpenHarmony 鸿蒙应用全场景覆盖、特别是适配鸿蒙桌面模式(Desktop Mode)、折叠屏大屏交互及鸿蒙 Web 版推送的工程实战中,“文件拖拽(Drag and Drop)”已成为提升生产力效率的标配功能。用户希望能够像在 PC 上一样,直接将图片或文档拖入应用窗口即可完成上传。如何实现这种跨越边界的直观交互?flutter_dropzone 作为一个专注于“拖放区域感知与文件流提取”的库,旨在为鸿蒙开发者提供一套标准的拖放治理方案。本文将详述其在鸿蒙端的实战技法。 一、原原理分析 / 概念介绍 1.1 基础原理 flutter_dropzone

By Ne0inhk
【数据结构指南】深入二叉树遍历

【数据结构指南】深入二叉树遍历

前言:         在前文中,我们探讨了完全二叉树和满二叉树的概念与性质,并基于完全二叉树实现了堆这一数据结构。然而,对于普通二叉树的认识仍有待深入,本文将系统性地介绍普通二叉树的遍历相关内容。                   一、前置说明          一般而言,对于一棵普通二叉树是通过链式结构定义,即每个节点包含三个部分:          1.数据域(data):用于存储节点的值          2.左指针(left):用于指向左子节点          3.右指针(right):用于指向右子节点                  typedef int BTDataType;//此处将 (int)整形 作为节点存储的内容 typedef struct BinaryTree { BTDataType data; struct BinaryTree* left; struct BinaryTree* right; }BTNode;                  在学习二叉树的基本操作前,需先要先创建一棵二叉树,然后才能学习

By Ne0inhk
【数据结构】长幼有序:树、二叉树、堆排序与TOP-K问题的层次解析(含源码)

【数据结构】长幼有序:树、二叉树、堆排序与TOP-K问题的层次解析(含源码)

为什么我们要学那么多的数据结构?这是因为没有一种数据结构能够去应对所有场景。我们在不同的场景需要选择不同的数据结构,所以数据结构没有好坏之分,而评估数据结构的好坏要针对场景,就如我们已经学习的结构而言,如果在一种场景下我们需要频繁地对头部进行插入删除操作,那么这个时候我们用链表;但是如果对尾部进行插入删除操作比较频繁,那我们用顺序表比较好。 因此,不同的场景我们选择不同的数据结构 文章目录 * 一、树 * 1.树的基本概念 * 2.树相关术语 * 3.树的表示 * 4.树形结构实际运用场景 * 二、二叉树 * 1. 概念与结构 * 现实中的二叉树 * 特殊的二叉树 * 二叉树的性质 * 二叉树存储结构 * 三、手动模拟实现顺序二叉树——堆 * 1.堆的结构 * 2.初始化 * 3.销毁 * 4.向上调整算法 * 5.插入数据 * 6.判空 * 7.求size * 8.向下调整算法

By Ne0inhk
【数据结构】排序算法(下篇·开端)·深剖数据难点

【数据结构】排序算法(下篇·开端)·深剖数据难点

前引:前面我们通过层层学习,了解了Hoare大佬的排序精髓,今天我们学习的东西可能稍微有点难度,因此我们必须学会思想,我很受感慨,借此分享一下:【用1520分钟去调试】,如果我们遇到了任何问题,必须先学会自己能不能解决,调试是每次代码找错的一个途径。通过每次调试,看它的数据变化是否达到了目前应该的预期,然后我们找到了错误,应该如何改进!下面小编通过拆分思想,一步步带你深解这些算法思想的奥妙! 目录  快排(双指针·递归) 算法思想: 实现步骤: 复杂度分析: 代码实现: 单趟实现: 递归实现:  代码优化: 优缺点分析: 快排(双指针·非递归) 算法思想: 实现步骤: 代码实现: 左区间: 右区间:   整体代码展示: 优缺点分析: 小编寄语  快排(双指针·递归) 大家在写部分题目的时候,是否有在评论区看见这样的评论:“双指针秒了”,小编当时见的很多,当时我不知道双指针是哈,但是现在领悟了!双指针是通过两个指针完成!

By Ne0inhk