适用场景:多来源目标检测数据集、训练集/验证集/测试集划分、数据泄漏排查
一、为什么要做全局哈希去重
我们的数据来源比较复杂,包括公开数据集、爬虫素材、人工标注数据、Roboflow 导出数据、LabelMe 标注数据、旧版合并数据、新增数据,以及直方图均衡化增强数据。
这些数据源之间很容易出现一个问题:
同一张图片被不同来源重复收集、重新命名、重新导出,最后同时出现在 train、val、test 中。
如果同一张图片同时出现在训练集和测试集中,模型训练时已经见过这张图,测试时自然会表现得更好。这会导致验证和测试指标偏高,也就是常说的数据泄漏。
因此,全局哈希去重的核心目标是:
在所有来源、所有 split 之间查找完全重复图片,避免同一张图既参与训练又参与验证或测试。
二、全局哈希去重的基本原理
全局哈希去重可以理解成给每张图片计算一个内容指纹。
它不是看文件名,也不是看文件路径,而是读取图片文件本身的二进制内容,再计算哈希值。常见算法包括 `SHA1`、`SHA256`、`MD5` 等。
逻辑可以简化为:
image_bytes = read_file(image_path)
fingerprint = sha1(image_bytes)
如果两张图片的文件内容完全一致,那么它们算出来的哈希值也会一致。
train/images/safety_001.jpg
test/images/worker_renamed_abc.jpg
虽然文件名、目录和来源都不同,但只要图片二进制内容完全一样,它们的哈希值就会相同,可以被识别为重复图。
三、"全局"的含义
这里的"全局"不是只在单个目录内去重,而是在整个数据集构建过程中统一去重。
检查范围包括:
-
`train`
-
`val`
-
`test`
-
不同数据源
-
不同批次新增数据
-
不同命名规则
-
不同导出工具生成的数据
目标是保证:
同一张图片不会同时出现在 train、val、test 任意两个 split 中。
尤其要避免:
train 里有一张图,test 里也有同一张图。
这种情况会直接让测试集失去独立性。
#四、它能解决的问题
全局文件哈希去重主要解决以下几类问题。
1. 同图不同名
001.jpg
helmet_worker_202605.jpg
文件名不同,但内容完全一样。
2. 同图不同目录
source_a/train/images/xxx.jpg
source_b/test/images/yyy.jpg
同一张图在不同数据源、不同 split 中重复出现。
3. 重复导出
Roboflow、Kaggle、人工整理或脚本合并时,可能把同一批图多次导出,文件名发生变化但内容不变。
4. 跨来源撞图
公开数据集、爬虫素材和人工整理数据可能收集到同一张原图。全局哈希可以识别这些完全相同的文件。
##五、它不能解决的问题
普通文件哈希去重只适合识别完全相同的文件内容。只要图片经过轻微处理,哈希值就会改变。
例如以下情况,普通文件哈希通常无法识别为重复:
-
重新压缩
-
改变尺寸
-
裁剪
-
加水印
-
改亮度或对比度
-
直方图均衡化
-
JPEG 转 PNG
-
图片内容几乎一样但编码不同
如果要识别"视觉上很像"的近似重复图,需要使用感知哈希,例如:
aHash / dHash / pHash
但感知哈希也有风险,可能把相似但有价值的不同样本误删。因此在当前阶段,我们优先采用更稳妥的文件哈希去重,先解决完全重复和跨 split 泄漏问题。
## 六、本项目旧版数据集中的实际问题
审计结果显示,标签格式整体干净:
| split | images | labels | objects | missing labels | invalid rows |
|---|---:|---:|---:|---:|---:|
| train | 16262 | 16262 | 33746 | 0 | 0 |
| val | 3393 | 3393 | 6912 | 0 | 0 |
| test | 2990 | 2990 | 6958 | 0 | 0 |
也就是说,旧版数据集的问题不是标签坏,而是重复图和数据泄漏风险。
重复审计结果:
| 项目 | 数量 |
|---|---:|
| duplicate groups | 824 |
| cross-split duplicate groups | 353 |
其中 `cross-split duplicate groups` 尤其重要,表示同一张图片出现在了不同 split 中。
典型例子包括:
-
`safety-helmet-kaggle` 中同一张安全帽图片同时出现在 `train` 和 `test`。
-
`slipper-siteadd` 与 `slipper-v3` 中同一张拖鞋图片跨来源重复,并落入不同 split。
这类问题会让模型测试时见到训练阶段已经见过的图片,从而抬高指标。
## 七、新版数据集中的实践做法
处理思路如下:
-
读取旧版 6 类数据集和新增数据源。
-
对所有候选图片计算文件哈希。
-
建立全局哈希表。
-
如果哈希未出现,则保留图片和对应标签。
-
如果哈希已经出现,则跳过该图片,避免重复进入数据集。
-
在清理后重新组织 `train/val/test`。
-
对新增数据也执行同样的哈希检查,避免后续增量再次引入重复。
新版数据集已经消除了旧版中发现的完全重复图片和跨 split 泄漏问题。
## 八、增量追加中的去重实践
后续又补充了两批新数据:
为了节省时间,没有重新完整合并整个大数据集,而是直接向当前主数据集增量追加。但追加前仍然做了全局哈希检查。
拖鞋新增数据
追加结果:
| 项目 | 数量 |
|---|---:|
| 保留图片 | 325 |
| 跳过重复图片 | 95 |
| 空标签图片 | 13 |
这说明新增拖鞋数据中确实存在一部分和现有主数据集重复的图片。如果不做哈希去重,这 95 张重复图就会被再次加入。
吸烟新增数据
追加结果:
| 项目 | 数量 |
|---|---:|
| 保留图片 | 2623 |
| 跳过重复图片 | 0 |
| 坏标签 | 0 |
该批吸烟数据没有发现完全重复图片,可以完整接入。
## 九、为什么不能只按文件名去重
在本项目里,很多重复图的文件名并不一样。例如同一张图可能被不同数据源导出为:
```text
safety-helmet-kaggle_yolohard_hat_workers1197_png_rf_xxx.jpg
safety-helmet-kaggle_yolohard_hat_workers1197_png_rf_yyy.jpg
``
拖鞋数据里也存在同一张图在不同来源中被重新命名的情况:
```text
slipper-siteadd_xxx.jpg
slipper-v3_sandal_xxx.jpg
```
如果只按文件名判断,这些重复都无法发现。
因此本项目采用内容哈希,而不是文件名哈希。
十、对模型指标的影响
全局哈希去重通常不会直接让训练集数量最大化,但会让验证和测试更可信。
旧版数据可能存在:
模型在 train 中见过某张图,又在 test 中测试这张图。
新版去重后,测试集更接近真正未知数据,指标更有参考价值。
这也意味着:
-
旧版指标可能偏乐观。
-
新版如果指标仍然提升,说明模型真的获得了更强泛化能力。
-
后续汇报中,新版 `test` 结果会比单纯训练过程 `val` 更重要。
十一、与直方图均衡化的关系
直方图均衡化生成的是新图像文件,虽然来自原图,但二进制内容和视觉分布都发生了变化。因此普通文件哈希不会把 HEQ 图和原图识别为重复。
这并不是错误,而是符合预期。
HEQ 图在本项目中被视为增强样本,而不是重复样本。但为了避免增强过度,我们没有采用全量 HEQ,而是采用选择性策略:
-
`slipper`:保留偏暗样本的 HEQ。
-
`smoking`:只保留低照度样本的 HEQ。
-
其他类别不做全量 HEQ。
因此,全局哈希去重解决的是"完全重复图片",HEQ 策略解决的是"小目标低照度增强",二者职责不同。
十二、实际经验总结
本次项目实践中,全局哈希去重带来了几个明确收益:
-
发现旧版数据集存在跨 split 重复。
-
避免测试指标因数据泄漏而虚高。
-
支持多来源数据继续扩充。
-
新增数据可以快速追加,同时避免重复污染。
-
数据集质量从"能训练"提升到"更适合正式对比"。
同时也要注意:
-
文件哈希不能识别近似重复。
-
HEQ、缩放、裁剪后的图不会被普通哈希当成重复。
-
是否进一步做感知哈希,需要权衡误删风险。
-
去重之后仍然需要人工关注类别语义、标注质量和场景分布。
十三、结论
全局哈希去重的本质是:
给所有图片计算内容指纹,在所有来源和所有 split 之间统一查重,避免同一张图同时参与训练和验证/测试。
在本次矿区安全检测项目中,旧版 6 类数据集虽然标签格式干净,但存在明显跨 split 重复。通过全局哈希去重和重新组织数据集,新版数据集将:
duplicate groups 从 824 降到 0
cross-split duplicate groups 从 353 降到 0
这使后续模型训练和测试结果更加可信,也为后续持续加入拖鞋、吸烟等短板类别数据提供了更可靠的数据底座。