AI 训练图片怎么找近似重复?用感知哈希生成复核清单

用 ImageHash pHash 筛出训练图片近似重复,保留哈希库存、候选图片对与错误清单;通过合成图片核对距离,人工校准阈值并避免训练测试同源泄漏。

训练图片中,同一张图可能保存成 PNG、重新压成 JPEG,或者经过轻微调整。文件名不同、文件字节不同,并不代表它们能作为彼此独立的样本。用 ImageHash 的 pHash 可以先筛出外观接近的图片对,再逐对复核;它不能替你判断两张图的标签是否相同,也不应按距离直接删除训练数据。

先分清字节重复、外观相近和内容相同

  • SHA-256 相同:本例按文件字节完全重复处理。
  • pHash 距离小:低频亮度结构接近,只是候选相似对。不同颜色的相似构图也可能被筛入。
  • 语义相同:例如两张不同角度的猫,pHash 不保证把它们识别为同一内容;这需要另选语义检索方法。

ImageHash 项目说明介绍了 pHash、差分哈希等方法,并说明 pHash 等结构哈希主要分析亮度,不保留颜色信息。两个哈希相减得到汉明距离,即不同位的数量。下面固定 hash_size=8,得到 64 位哈希;阈值 6 是本教程的演示参数,不是所有图片集通用的标准。

AI 训练图片怎么找近似重复?用感知哈希生成复核清单

准备环境和可核对的图片

本例在 Windows 11、Python 3.13.14、Pillow 12.3.0、ImageHash 4.3.2、NumPy 2.5.3 下运行。先在独立目录安装依赖;如果使用虚拟环境,请让安装命令和脚本使用同一个 Python 解释器。

python -m pip install Pillow ImageHash
python -c "import PIL, imagehash; print(PIL.__version__, imagehash.__version__)"

保存下面代码为 demo_images.py,执行 python demo_images.py。它生成的是合成几何图和随机噪声图,不是实际训练数据:原图、字节副本、JPEG 版本、噪声图,以及一个故意损坏的文件,用来确认错误不会被悄悄忽略。

from pathlib import Path
import random
from PIL import Image, ImageDraw

folder = Path('demo_images')
folder.mkdir(exist_ok=True)
image = Image.new('RGB', (160, 160), 'white')
draw = ImageDraw.Draw(image)
draw.rectangle((15, 30, 85, 130), fill=(25, 100, 190))
draw.ellipse((75, 15, 145, 85), fill=(220, 70, 30))
draw.line((10, 145, 150, 100), fill='black', width=5)
image.save(folder / 'shape.png')
(folder / 'shape_copy.png').write_bytes((folder / 'shape.png').read_bytes())
image.save(folder / 'shape_jpeg.jpg', quality=90)
rng = random.Random(42)
noise = Image.new('RGB', (160, 160))
noise.putdata([(rng.randrange(256), rng.randrange(256), rng.randrange(256))
               for _ in range(160 * 160)])
noise.save(folder / 'noise.png')
(folder / 'broken.png').write_bytes(b'not an image')
print('created 4 synthetic images and 1 invalid file')

生成哈希清单、候选对和错误表

把下方代码保存为 image_review.py。初次运行保持目录为 demo_images;换成自己的图片目录时,先复制一个小样本目录。脚本只读取静态 PNG、JPEG、WebP,先纠正 EXIF 方向再转 RGB。Pillow 的 exif_transpose 文档说明该操作会按方向信息转正图片并移除相关方向标记。

from pathlib import Path
import csv, hashlib
from PIL import Image, ImageOps
import imagehash

folder = Path('demo_images')  # 替换为静态图片目录
if not folder.is_dir():
    raise FileNotFoundError(f'image directory does not exist: {folder}')
threshold = 6  # 64 位哈希上的演示阈值,先人工校准
records, errors = [], []
for path in sorted(folder.rglob('*')):
    if not path.is_file() or path.suffix.lower() not in {'.png', '.jpg', '.jpeg', '.webp'}:
        continue
    try:
        with Image.open(path) as raw:
            if getattr(raw, 'n_frames', 1) != 1:
                raise ValueError('animated image requires a separate frame policy')
            image = ImageOps.exif_transpose(raw).convert('RGB')
            fingerprint = imagehash.phash(image, hash_size=8)
        digest = hashlib.sha256(path.read_bytes()).hexdigest()
        records.append((str(path), fingerprint, digest))
    except Exception as exc:
        errors.append((str(path), type(exc).__name__, str(exc)))

def write_csv(name, columns, rows):
    with open(name, 'w', newline='', encoding='utf-8-sig') as file:
        writer = csv.writer(file); writer.writerow(columns); writer.writerows(rows)

pairs = []
for i, (a, ha, sa) in enumerate(records):
    for b, hb, sb in records[i + 1:]:
        distance = int(ha - hb)
        if distance <= threshold:
            pairs.append((a, b, distance, sa == sb, 'pending'))
write_csv('hash_inventory.csv', ['path', 'phash', 'sha256'],
          [(p, str(h), s) for p, h, s in records])
write_csv('review_pairs.csv', ['a', 'b', 'hamming_distance', 'byte_equal', 'review_status'], pairs)
write_csv('hash_errors.csv', ['path', 'error_type', 'message'], errors)
print(f'hashed={len(records)}, candidate_pairs={len(pairs)}, errors={len(errors)}')
for row in pairs:
    print(row)

执行 python image_review.py 后,hash_inventory.csv 保留每张成功读取图片的路径和哈希,review_pairs.csv 记录候选相似对,hash_errors.csv 记录打不开或不符合输入要求的文件。三个表使用 UTF-8 BOM,便于在表格软件中查看中文路径。

用示例验证,再校准真实数据的阈值

本机实际输出为 hashed=4, candidate_pairs=3, errors=1。三个候选分别是原图与副本、原图与 JPEG、副本与 JPEG,汉明距离都为 0;只有原图与副本的 byte_equal 为 True。噪声图没有进入候选表,坏文件进入错误表。这说明距离 0 仍不能等同于字节完全一致。

先打开候选对的两张图,核对主体、裁切范围、标签和来源。复核后另存一列决定:保留、归为同源样本、或待确认。原目录保持原样,不直接按 CSV 删除文件。

在真实样本中混入你已知的同图重压缩对和不同图片对,对不同阈值分别检查漏检与误报。两张训练图若被确认来自同一原图,应按原图或采集对象进行分组划分,避免其变体分散到训练集和测试集,造成测试结果过于乐观。已经确定标签冲突的相似图片,应先查标注来源,不能为了去重任选一个标签。

这个脚本在哪些情况下需要调整

全对比较的工作量随图片数平方增长,本例适合先用几十到几百张图校准。海量库应保存哈希并使用适合汉明距离的索引检索,再抽样检查召回情况;不要把这段双重循环直接当成百万图方案。

裁切、翻转、旋转可能让普通 pHash 漏掉重复,纹理简单的不同图也可能误报。动画文件在本例中列为错误,不只取第一帧当成完整样本;超大图片或异常文件需要先处理读取错误,不建议关闭 Pillow 的大图保护来强行运行。图片权利与标注质量也不能由哈希距离证明。

问答:A 接近 B,B 接近 C,能把三张图全合并吗?

不能仅凭这两条距离关系合并。距离阈值关系并不保证传递,A 与 C 可能超出阈值。先核对每组成员与拟保留代表图的关系,再结合来源和标签决定;本脚本输出图片对,不自动建立删除分组。

Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30297.html

赞 (0)
AI小管家的头像AI小管家
处理好的训练数据怎么离线复用?用 Datasets 保存、重载并核对结构
上一篇 1天前
Datasets 批量 map 怎么把一行拆成多行?避免列长不一致并保留来源
下一篇 1天前

相关推荐

联系我们

联系我们

1

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部