图像指纹技术 - 使用 pHash 和 dHash 检测相似图像

· 9 分钟阅读

什么是图像指纹 - 感知哈希基础

图像指纹(感知哈希)是将图像转换为紧凑的固定长度哈希值的技术。与加密哈希(MD5、SHA)不同,感知哈希对视觉上相似的图像产生相似的哈希值,即使图像经过缩放、压缩或轻微修改。

与加密哈希的区别:

  • 加密哈希:1 位变化导致完全不同的哈希。用于验证文件完整性
  • 感知哈希:视觉上相似的图像产生相近的哈希。用于相似性检测

应用场景:

  • 重复图像检测:在大型图库中找出重复或近似重复的图像
  • 版权保护:检测未授权使用的图像(即使经过裁剪、缩放、滤镜处理)
  • 内容审核:检测已知违规图像的变体
  • 反向图像搜索:根据图像内容查找来源
  • 缓存去重:避免存储视觉上相同的图像的多个副本

术语与原理:图像指纹 (Image Fingerprinting) 是把图像的视觉特征转换为短的固定长度哈希值的技术。普通的密码学哈希 (如 SHA-256) 只要输入变化 1 比特就会输出完全不同的值,而感知哈希 (Perceptual Hash) 会为看起来相似的图像生成相似的哈希值。

典型用途:反向图像搜索是像 Google Images 那样「查找与这张图像相似的图像」功能的基础技术;内容审核则是与已知违规图像的哈希库比对,阻止其再次上传。感知哈希的核心在于「让人类觉得相同的图像得到相同的哈希,让人类觉得不同的图像得到不同的哈希」。常见的哈希长度为 64 比特,2 个哈希之间的汉明距离 (不同的比特数) 就是相似度指标。

aHash(平均哈希)- 最简单的图像哈希

aHash 是最简单的感知哈希算法,通过比较每个像素与平均亮度的关系生成哈希值。

算法步骤:

  • 将图像缩小到 8x8(64 像素),消除尺寸差异
  • 转为灰度图
  • 计算 64 个像素的平均亮度值
  • 每个像素与平均值比较:大于平均值为 1,否则为 0
  • 生成 64 位哈希值

优点:

  • 实现极其简单,计算速度最快
  • 对缩放和轻微亮度变化有一定鲁棒性

缺点:

  • 对伽马校正和直方图调整敏感
  • 区分度较低,容易产生误匹配
  • 无法处理局部修改(如添加水印)

适用场景:快速预筛选,作为更精确方法的第一步过滤。对精度要求不高的简单去重。

算法步骤:aHash (Average Hash) 是最简单的感知哈希算法,以图像的平均亮度为基准生成位串。Step 1 缩放:把图像缩小到 8x8 像素 (共 64 个像素),这一步会去除高频成分 (细节),只保留图像的大致结构。Step 2 灰度化:把彩色图像转为灰度,去掉颜色信息、只用亮度比较。Step 3 求平均:计算 64 个像素亮度值的平均值。Step 4 生成位:每个像素的亮度大于等于平均值记为 1,小于则记为 0,得到 64 比特哈希。

实现示例 (Python):from PIL import Image; img = Image.open('photo.jpg').resize((8,8)).convert('L'); pixels = list(img.getdata()); avg = sum(pixels)/len(pixels); hash_bits = ''.join('1' if p >= avg else '0' for p in pixels)

速度与误检:aHash 的优势是计算速度,单张的计算成本很低,因此对较大规模的库做全量扫描也是现实可行的选择。另一方面,缩小到 8x8 会丢失空间结构信息,因此容易把只是构图相似的无关图像误判为相同 (false positive)。

dHash(差异哈希)- 基于梯度的快速哈希

dHash 通过比较相邻像素的亮度差异(梯度)生成哈希,比 aHash 更能捕获图像的结构信息。

算法步骤:

  • 将图像缩小到 9x8(72 像素,宽度多 1 列用于计算差异)
  • 转为灰度图
  • 对每行,比较相邻像素:右侧像素比左侧亮为 1,否则为 0
  • 8 行 x 8 列差异 = 64 位哈希值

为什么比 aHash 好:

  • 捕获的是相对亮度变化(梯度)而非绝对亮度,对整体亮度/对比度调整更鲁棒
  • 保留了图像的结构信息(边缘方向)
  • 计算速度与 aHash 相当

实现(Python):

  • from PIL import Image
  • img = Image.open(path).resize((9, 8)).convert("L")
  • 比较相邻像素生成位串

适用场景:需要快速且比 aHash 更准确的场景。大规模图像去重的首选方法。

算法步骤:dHash (Difference Hash) 是基于相邻像素之间亮度差 (梯度) 生成哈希的算法。Step 1 缩放:把图像缩小到 9x8 像素 (宽度多 1 像素是为了取水平方向的差分)。Step 2 灰度化:从彩色转为灰度。Step 3 计算差分:在每一行比较左右相邻的像素,右侧更大记为 1,否则记为 0。

性能对比 (与 aHash 的一般倾向):计算速度与 aHash 基本相当;精度方面通常优于 aHash,尤其对经过亮度校正的图像检出率更高;误检也比 aHash 更少。具体数值会随图像种类、数据集与阈值设置而大幅变化。不过它无法应对图像的左右翻转和 90 度旋转。

pHash(感知哈希)- 基于 DCT 的高精度哈希

pHash 使用离散余弦变换(DCT)提取图像的频率特征,精度最高但计算量也最大。

算法步骤:

  • 将图像缩小到 32x32,转为灰度
  • 对 32x32 图像执行 DCT 变换
  • 取左上角 8x8 的低频系数(代表图像的整体结构,忽略高频细节)
  • 计算 64 个系数的中位数
  • 每个系数与中位数比较:大于中位数为 1,否则为 0
  • 生成 64 位哈希值

为什么精度最高:

  • DCT 将图像从空间域转换到频率域,低频系数代表图像的本质结构
  • 对高频噪声(压缩伪影、轻微模糊)天然不敏感
  • 使用中位数而非平均值,对异常值更鲁棒

与 dHash 的对比:

  • pHash 对 JPEG 压缩、轻微旋转、颜色调整的鲁棒性更好
  • dHash 速度更快(无需 DCT),适合大规模预筛选
  • 实践中常组合使用:dHash 快速过滤 + pHash 精确验证

算法步骤:pHash (Perceptual Hash) 是利用离散余弦变换 (DCT) 从图像的频率特性生成哈希的高精度算法。Step 1 缩放:把图像缩小到 32x32 像素 (比 aHash/dHash 的 8x8 更大,保留更多结构信息)。Step 2 灰度化:只使用亮度通道。Step 3 应用 DCT:对 32x32 的图像数据做二维 DCT,得到频率系数矩阵。Step 4 提取低频:只取 DCT 系数矩阵左上角的 8x8 (最低的频率成分),忽略高频成分 (细节),以确保对噪声和微小改动的鲁棒性。Step 5 求中位数:计算提取出的 64 个 DCT 系数的中位数 (也有实现用去掉 DC 成分后的 63 个来计算)。Step 6 生成位:每个系数大于等于中位数记为 1,小于则记为 0,得到 64 比特哈希。

鲁棒性与代价:JPEG 再压缩耐受性方面,再压缩后汉明距离的增加通常较小;缩放耐受性方面,缩小后汉明距离的变化也较小;轻微裁剪耐受性方面,周边的少量裁剪通常仍会被判定为相似。另一方面,由于需要做 DCT,计算成本明显高于 aHash/dHash。在大规模库中,先用 dHash 缩小候选范围、再用 pHash 精确判定的 2 阶段方案很有效。

汉明距离相似度评分与阈值设计

两个哈希值之间的汉明距离(不同位的数量)衡量图像的相似程度。阈值的选择直接影响检测的精确率和召回率。

汉明距离计算:

  • 对两个 64 位哈希进行 XOR 运算,统计结果中 1 的数量
  • 距离 0:完全相同的哈希(极可能是同一图像)
  • 距离 1-10:非常相似(可能是同一图像的不同版本)
  • 距离 > 20:很可能是不同图像

阈值设计:

  • 严格匹配(距离 ≤ 5):仅检测近乎相同的图像。高精确率,低召回率
  • 宽松匹配(距离 ≤ 10):检测经过修改的相似图像。平衡精确率和召回率
  • 模糊匹配(距离 ≤ 15):检测视觉上相关的图像。高召回率,可能有误报

多哈希组合策略:

  • 同时计算 dHash 和 pHash,两者都满足阈值才判定为相似
  • 或使用加权组合:总分 = 0.4 * dHash距离 + 0.6 * pHash距离
  • 不同应用场景调整权重和阈值

汉明距离的计算:图像指纹的比较,是通过 2 个哈希值之间的汉明距离 (Hamming Distance) 进行的。计算可以用 XOR 运算加上 popcount (数出值为 1 的比特数) 高速完成:distance = bin(hash1 ^ hash2).count('1')

阈值设计指引 (64 比特哈希):0-2 为几乎相同的图像,差异仅相当于 JPEG 再压缩或轻微缩放;3-5 为高相似度,包含轻微色调校正、滤镜、添加文字等;6-10 为中等相似度,包含裁剪、局部编辑、添加水印;11-15 为低相似度。若要把 false positive 降到最低、只确实检出同一图像,就要收紧阈值;版权侵权检测取阈值 10 以下,为了也能检出被编辑过的图像而设置得稍宽;相似图像推荐取阈值 12-15,广泛呈现视觉上相关的图像。

大规模检索:在 100 万张以上的大规模库中做高速检索时,使用 BK-Tree (Burkhard-Keller Tree) 或 VP-Tree (Vantage-Point Tree) 等度量空间索引。BK-Tree 是针对汉明距离的树结构,检索阈值 d 以内的哈希时需要比较的候选远少于全量扫描。此外,把哈希切成多个块并构建倒排索引的 Multi-Index Hashing,也是在大规模库上加速近邻检索的常用手法。

实现模式与实际系统架构

在生产环境中部署图像指纹系统需要考虑存储、索引和查询效率。

存储与索引:

  • 数据库存储:将 64 位哈希存为 BIGINT 类型,支持位运算查询
  • 分桶索引:将哈希分为多个子段(如 4 个 16 位段),对每段建立倒排索引。查询时在各段分别搜索,取交集
  • VP-Tree:Vantage-Point Tree,专为汉明距离设计的空间索引结构。支持高效的范围查询

大规模系统架构:

  • 入库流程:图像上传 → 计算多种哈希(dHash + pHash)→ 存入数据库 → 建立索引
  • 查询流程:查询图像 → 计算哈希 → 索引搜索候选集 → 精确汉明距离计算 → 返回相似图像
  • 增量更新:新图像入库时与现有库对比,检测重复

性能方面的考虑:

  • 大规模图库:dHash 索引查询通常较快,但耗时取决于硬件与索引结构
  • 超大规模图库:宜采用分布式索引(如 Elasticsearch 的位向量插件)
  • 哈希计算:dHash 的开销小于 pHash,pHash 还需承担 DCT 的计算成本

推荐架构 (重复检测流水线):接入层在图像上传时用 Lambda/Cloud Functions 计算哈希,与元数据一起保存到 DynamoDB/Redis,并同时算好 dHash 和 pHash 这 2 种。检索层把新图像的哈希与既有库比对:先用 dHash 通过 BK-Tree 高速检索汉明距离 8 以内的候选,再对候选用 pHash 做精确判定 (阈值 5)。判定层对通过 pHash 阈值的图像对,按需追加像素级的 SSIM 比较,做出最终判定。

主要库与工具:imagehash (Python) 是实现了 aHash、dHash、pHash、wHash 的经典库,用 pip install imagehash 即可引入;blockhash-js (JavaScript) 是可在浏览器/Node.js 上运行的分块哈希实现;phash.org (C++) 是高性能的 pHash 实现,也支持视频指纹。

实际服务中的应用与局限:Google Images 在反向图像搜索中把感知哈希与深度学习特征量结合使用;Facebook/Instagram 用 PhotoDNA 和自研哈希技术检测并阻止 CSAM (儿童性虐待素材) 的扩散;Pinterest 则用于相似图像的聚类与推荐。需要注意的是,感知哈希并非万能:对大幅裁剪 (50% 以上)、旋转、宽高比变更、大量叠加文字等较弱,这些情况下需要 SIFT/ORB 等基于局部特征量的方法,或用 CNN (卷积神经网络) 提取特征。

相关文章

图像差异对比方法 - 从像素级到语义级比较

检测和可视化图像差异的系统指南。涵盖像素比较、结构相似性、感知差异检测及实际实现。

图像篡改检测 - 取证分析技术及其局限性

系统讲解图像篡改检测的取证分析技术。涵盖 ELA、元数据分析、克隆检测、深度学习方法及各技术的局限性。

图像缓存策略完全指南 - Cache-Control、ETag 与 CDN 配置

全面了解 Web 图像缓存策略,包括 Cache-Control 头设计、ETag 条件请求、CDN 缓存配置和 Service Worker 离线支持。

图像阈值化的类型与最优阈值确定 - 从大津法到自适应方法

系统讲解图像二值化处理。涵盖固定阈值、大津法、自适应阈值的原理与应用场景,以及文档图像和传感器图像中的实用阈值设计方法。

Web 图像文件大小优化策略 - 在保持质量的同时减小体积

系统讲解 Web 图像文件大小优化技术。涵盖格式选择、分辨率优化、元数据清理、CDN 分发优化和自动化质量管理。

工业检测中的图像处理 - 从外观检查到尺寸测量

系统讲解图像处理在工业检测中的应用。涵盖缺陷检测、尺寸测量、表面检查和深度学习方法。

相关术语