图片相似度去重:批量出图如何筛查并人工保留
批量出图后,不同文件可能画面近似,同一主体也可能包含值得保留的不同方案。先检索候选,再对照原图作人工判断,比直接按分数清理更稳妥。本文提供索引字段、特征提取接口示意、候选筛查代码和复核模板;资料包只有官方博客索引,具体模型与提取接口仍需补充…
先准备只读素材库与索引字段
这套流程解决的是:如何从一批图片中找出值得成对比较的素材,而不是让模型决定哪些文件应被删除。建议先用素材副本建立小规模样本库,原始目录保持只读,筛查结果写到单独目录。将完全相同、视觉近似和同主体不同方案分开记录,不要合并成一个含糊的重复标签。
现有 Hugging Face Blog 索引收录了 Image Similarity with Hugging Face Datasets and Transformers,日期为 2023 年 1 月 16 日。资料包没有提供该文章正文、模型标识、输出字段或当前检索接口,不能据此给出已核验的安装命令与模型调用代码。下面的字段与流程属于本文建议的原型设计,不是官方接口说明。
| 字段 | 填写内容 | 用途 |
|---|---|---|
| image_id | 入库时分配且不随排序变化的唯一编号 | 关联候选和保留记录 |
| relative_path | 相对素材根目录的文件路径 | 找回具体文件,不能只写文件名 |
| file_sha256 | 文件内容摘要 | 登记字节级一致的候选与文件变化 |
| width、height、format | 原图规格 | 辅助判断可用版本 |
| project_id、batch_id | 所属项目与出图批次 | 限制比较范围 |
| feature_version | 模型修订、预处理、输出层与汇聚方式的组合版本 | 防止不同特征混用 |
| embedding | 一张图片对应的一维有限数值向量 | 生成视觉候选 |
| feature_status、error | 成功、失败及失败原因 | 避免把漏处理当作无重复 |
同内容文件也应有各自的 image_id,摘要不要代替文件实例编号。只按文件摘要查找无法完成视觉近似筛查;而向量分数也不能代替对原始图片、用途和细节的判断。
用成对样本定义哪些差异必须保留
先由设计负责人定义“可互相替代”的范围,再准备阈值测试样本。以下是样本构造方案,不是已经得到的模型结果,表内不预填相似分数。
| 样本对 | 如何准备 | 人工预期 |
|---|---|---|
| A 与 A_copy | 同一原文件的副本 | 完全重复对照 |
| A 与 A_export | 同一画面的另一次导出 | 按用途判断是否可替代,记录规格差异 |
| A 与 A_crop | 原画面与局部取景版本 | 若承担不同版式用途,保留两张 |
| B_front 与 B_side | 同一产品的正面与侧面方案 | 非重复难例,保留两张 |
| C_red 与 C_green | 构图相近但关键配色不同的方案 | 若颜色是交付要求,保留两张 |
| D 与 E | 主体与构图都不同的图片 | 非重复对照 |
标注表建议使用 pair_id、left_id、right_id、label、reason;label 取 duplicate、distinct 或 uncertain。拿不准的样本留给复核,不要为了方便统计强行标为重复。阈值调节样本与最终验收样本应分开,同一原图衍生的多张导出图不要跨到两组里。
从索引定位资料,再接入特征提取
- 在官方博客索引中查找上述 Image Similarity 条目,随后补充读取文章正文。先确认示例采用的模型、预处理和相似度定义,不能只看标题选择代码。
- 对照拟使用版本的说明,登记模型修订、依赖版本、输入图片要求、图像输出字段与汇聚方式。确认得到的是每张图片的向量,而不是类别标签或一串未处理的局部特征。
- 固定图像方向、颜色通道和透明背景处理规则,预处理只作用于读取后的输入副本,不覆盖原文件。
- 先处理样本库,每个向量与 image_id 一起保存;读取失败、输出为空或存在非有限数值时,将该图记为失败并保留错误原因。
- 同一索引仅接收相同 feature_version 的特征。改变模型或预处理后重建对应索引,再重新标定阈值。
下面只展示业务适配关系。read_image、prepare、encode_image 与 pool_to_vector 都是待实现的业务适配函数,不是 Datasets 或 Transformers 的已确认函数名;资料补齐前不能把它当作可直接运行的提取程序。
FOR each item IN image_index:
image = read_image(item.relative_path)
prepared = prepare(image, fixed_preprocess_config)
output = encode_image(prepared, verified_model_revision)
vector = pool_to_vector(output, verified_output_contract)
validate_finite_nonzero_vector(vector)
save_feature(item.image_id, feature_version, vector)素材量较大时,可在接口核对后再选用数据集容器与向量索引工具。先用小样本确认编号、向量与原图对应关系,比一开始追求全库吞吐更重要。
建立只输出候选的最小筛查原型
下面是独立于模型接口的 Python 标准库示例,只读取已有向量,不提取图片特征,也不移动、覆盖或删除原图。它按余弦相似度从高到低输出每张图的前 k 个候选,排除自身并合并反向重复对。分数表示本例定义的向量相近程度,不是“重复概率”。代码未执行验证,只适合小规模核对,计算量随图片数近似平方增长。
输入文件 features.json 是对象数组,每项必须包含 image_id、feature_version、embedding。这里不提供虚构的图像向量;请使用核对过的提取流程产出数据。先在单一项目范围运行,避免不同项目素材混在一起。
import csv
import json
import math
import sys
input_path, output_path = sys.argv[1:3]
k = int(sys.argv[3])
threshold = float(sys.argv[4])
if k < 1 or not math.isfinite(threshold) or not -1 <= threshold <= 1:
raise ValueError('Invalid k or threshold')
with open(input_path, encoding='utf-8') as f:
rows = json.load(f)
if not rows:
raise ValueError('No features')
ids = [r['image_id'] for r in rows]
if len(set(ids)) != len(ids):
raise ValueError('Duplicate image_id')
versions = {r['feature_version'] for r in rows}
if len(versions) != 1 or not next(iter(versions)):
raise ValueError('Mixed or missing feature_version')
vectors = []
dim = None
for row in rows:
v = [float(x) for x in row['embedding']]
if not v or not all(math.isfinite(x) for x in v):
raise ValueError('Invalid vector')
if dim is None:
dim = len(v)
if len(v) != dim:
raise ValueError('Dimension mismatch')
norm = math.hypot(*v)
if not math.isfinite(norm) or norm == 0:
raise ValueError('Invalid vector norm')
vectors.append([x / norm for x in v])
pairs = {}
for i, v in enumerate(vectors):
ranked = []
for j, w in enumerate(vectors):
if i == j:
continue
score = sum(a * b for a, b in zip(v, w))
ranked.append((j, max(-1.0, min(1.0, score))))
ranked.sort(key=lambda item: (-item[1], item[0]))
for j, score in ranked[:k]:
if score >= threshold:
pairs[tuple(sorted((i, j)))] = score
with open(output_path, 'x', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['left_id', 'right_id', 'score',
'feature_version', 'review_status'])
for (i, j), score in sorted(pairs.items()):
writer.writerow([ids[i], ids[j], score,
rows[i]['feature_version'], 'pending'])调用格式为 python screen_pairs.py features.json candidates.csv 10 -1。这里的 10 只是候选数量示例,-1 用于先观察前 k 个候选,不是生产阈值。输出文件采用排他创建,已存在时会报错,避免覆盖已有复核结果。后续用选定阈值和新的输出文件名重新运行。
top-k 之外的图片对不会输出,即使分数也超过阈值。因此,空候选不能证明全库没有重复;在小样本标定时,可把 k 设置为样本图片数减一,检查全部非自身图片对。不要把 A 接近 B、B 接近 C 自动推断为 A 与 C 可互相替代。
标定阈值后填写人工保留记录
- 先在标注样本上生成候选,逐对记录实际分数与人工标签。确认样本是否出现在候选中,未被召回的重复对也要登记。
- 围绕已观察到的分数分布比较多个候选阈值,分别统计命中的重复对、误报的非重复对和漏掉的重复对。不要借用别的模型阈值。
- 同时调整 k 并记录变化。只比较已命中的候选会漏看召回失败,应以全部已标注重复对为分母检查召回情况。
- 锁定特征版本、k 和阈值后,用独立验收样本复核;结果不满足团队约定标准时,继续保持人工小范围使用。
- 并排打开原图,比较画面、规格、授权、交付用途与已引用情况,然后选择保留两张、保留其中一张或暂缓。
保留理由可参考图片方案成对对比的记录方法,但“更喜欢某张”不能单独证明另一张是重复素材。用于不同画幅、渠道或设计方案的文件,即使画面相近,也可能都要保留。
pair_id,left_id,right_id,decision,keep_id,cleanup_candidate_id,reason,reviewer,reviewed_at,feature_version,run_id
P001,IMG001,IMG002,keep_both,NA,NA,different_delivery_use,reviewer_a,TO_FILL,TO_FILL,TO_FILL
P002,IMG003,IMG004,pending,NA,NA,needs_original_comparison,TO_FILL,TO_FILL,TO_FILL,TO_FILL以上仅为记录格式示例。确认近重复且允许清理时,decision 可填 confirmed_duplicate,同时填写 keep_id、cleanup_candidate_id、人员、时间和理由。导出前必须检查保留编号不在任何待清理项中,冲突、缺字段及 pending 项一律暂停导出。待清理清单仅供后续授权处理,不附带自动删除动作;还应关联原路径和文件摘要,防止文件后来被替换。
候选异常时按原因修正
| 常见故障 | 可能原因 | 修正动作 |
|---|---|---|
| 同主体的不同方案大量命中 | 特征偏向主体语义,或阈值不适合当前素材 | 补入不同视角、配色和版式难例,重新标定;保留人工判断 |
| 重复导出图没有进入候选 | 图片漏处理、k 太小或预处理不一致 | 先核对成功入库数量,再扩大 k,并检查同一原图的输入处理记录 |
| 分数排序与预期相反 | 把距离当成相似度,或混用不同度量 | 登记接口返回语义;本例仅对余弦相似度降序排列,不直接套用到距离接口 |
| 更换模型后结果跳变 | 旧向量、旧阈值仍在使用 | 按 feature_version 重建索引,并重新运行标注样本 |
| 候选重复或总是出现自身 | 未排除相同编号,未统一无序图片对 | 按 image_id 排除自身,并将反向对合并,不能按文件名去重 |
| 输出文件已存在 | 原型拒绝覆盖旧结果 | 使用新的批次文件名,保留旧复核记录,不盲目覆盖 |
| 同一文件既被保留又被标为待清理 | 多组复核决定互相冲突 | 暂停该批导出,重新确认最终保留版本及全部关联图片对 |
发布筛查工具前逐项验收
- 已补充核对模型正文与当前接口;仅有索引时,提取部分明确标为待接入,不能标注完整模型流程已验证。
- 索引总数、特征成功数和失败数能对应,每个编号都能找回原图。
- 同一运行仅使用一致的特征版本,向量维度、有限数值和非零长度检查全部通过。
- 完全重复、近似导出、同主体不同方案和明显不同图片均有样本,另有独立验收集。
- 阈值、k、样本版本与误报漏报记录齐全,没有把候选分数当成重复概率。
- 候选排除了自身与反向重复对,没有使用相似关系自动传播清理决定。
- 每个待清理项均有人工确认、保留版本、理由与文件摘要,不含未决项及冲突项。
- 原图目录未被写入,原型没有删除、移动或覆盖图片的功能。
现阶段可先完成索引表、样本标注和复核约定;补齐模型与接口资料后,再接入特征提取并验证候选结果。最终交付应包括素材索引、特征版本记录、候选表、阈值对照表和人工确认的待清理清单,而不只是一个分数阈值。
下一步
把这篇的方法练一遍
提示词和步骤可以带到创作里直接试做一版。