pHash 图片去重

用感知哈希(pHash/dHash/aHash)判断两张图片是否相似,用于视频关键帧去重。

#type / concept #status / growing #tech / ai #media / video

[!info] related notes

pHash 图片去重

一句话定义

感知哈希(Perceptual Hash)把图片缩放、灰度化后生成一个短指纹,两张图片的指纹越接近,画面就越相似。用于快速判断视频中相邻帧是否重复。

为什么需要图片去重

视频关键帧提取 中,无论用固定间隔抽帧还是 FFmpeg 场景检测,都可能抽出大量视觉上几乎相同的帧:

  • 手部轻微移动,背景不变
  • 镜头轻微晃动
  • 字幕跳动但画面主体相同
  • 快速转场中连续几帧变化都很大但语义上是同一镜头

去重的目标:从候选帧中去掉看起来太像的,只保留画面变化明显的关键帧

核心机制

感知哈希的三种变体

算法原理速度精度
aHash(平均哈希)缩放后计算像素平均值,大于平均为 1,否则为 0最快较低
dHash(差分哈希)缩放后比较相邻像素的差值,差值大于 0 为 1中等
pHash(感知哈希)缩放后做 DCT 变换,取低频部分生成哈希较慢最高

推荐用 dHash:速度和精度平衡最好,适合视频帧去重场景。

算法流程(以 dHash 为例)

原图

缩放到 9×8 像素

转灰度

逐行比较相邻像素:左 > 右 则为 1,否则为 0

得到 64 位哈希值

相似度判断:汉明距离

两张图的哈希做异或(XOR),统计不同位的数量:

hash_a = 10110100 01001011 ...
hash_b = 10110100 01001111 ...
XOR    = 00000000 00000110 ...
汉明距离 = 2

距离越小越相似:

汉明距离含义
0完全相同
1~5非常相似
6~10有些差异
11+明显不同

在视频关键帧去重中,通常用 距离 > 8 作为”画面变化明显”的阈值。

Python 实现

使用 imagehash 库

from PIL import Image
import imagehash

def compute_dhash(image_path):
    img = Image.open(image_path)
    return imagehash.dhash(img)

def are_similar(hash1, hash2, threshold=8):
    distance = hash1 - hash2  # 汉明距离
    return distance < threshold

关键帧去重流程

import imagehash
from PIL import Image

def deduplicate_frames(frame_paths, threshold=8, max_keyframes=8):
    keyframes = []
    last_hash = None

    for path in frame_paths:
        current_hash = imagehash.dhash(Image.open(path))

        if last_hash is None:
            keyframes.append(path)
            last_hash = current_hash
            continue

        distance = current_hash - last_hash
        if distance > threshold:
            keyframes.append(path)
            last_hash = current_hash

        if len(keyframes) >= max_keyframes:
            break

    return keyframes

不用第三方库的极简实现

from PIL import Image

def dhash(image_path, hash_size=8):
    img = Image.open(image_path).convert("L").resize(
        (hash_size + 1, hash_size), Image.LANCZOS
    )
    pixels = list(img.getdata())
    bits = []
    for row in range(hash_size):
        for col in range(hash_size):
            idx = row * (hash_size + 1) + col
            bits.append(pixels[idx] > pixels[idx + 1])
    return int("".join("1" if b else "0" for b in bits), 2)

def hamming_distance(hash1, hash2):
    return bin(hash1 ^ hash2).count("1")

阈值选择

阈值效果
5更敏感,保留更多帧,适合内容变化缓慢的视频
8平衡值,适合大多数短视频
10更严格,只保留变化较大的帧
15只保留明显的镜头切换

边界与易混淆点

  • pHash 不是 AI 语义理解:它只判断像素级相似度,不知道画面内容是什么
  • pHash 不能替代视觉模型:去重后仍需 AI 分析每张关键帧的内容
  • pHash 对旋转和裁剪敏感:同一画面旋转 90 度后哈希完全不同
  • 汉明距离不是百分比:64 位哈希中距离 8 表示 12.5% 的位不同

适用场景

  • 视频关键帧去重(本文主要场景)
  • 相册去重
  • 图片检索(以图搜图)
  • 版权检测(初筛)
创建于 2026/6/8 更新于 2026/6/8