简介:驾驶员佩戴安全带检测的YOLO格式数据集,面向目标检测初学者、课题研究者以及需要快速验证效果的开发者。数据按YOLOv5文件夹结构保存,标注采用yolo相对坐标(类别、中心点x、y、宽高),仅含seatbelt一个类别,并附有类别txt文件;已划分训练集1201张、验证集354张,每张图片均配有同名txt标签,可直接加载训练。资源包共2000个文件,以1556个txt标注文件、443个jpg图像和1个py可视化脚本为主,整体约63.76MB,压缩包类型为7z,解压后目录层级清晰,便于迁移到不同YOLO环境中。附带可视化脚本无需修改参数,随机传入一张图片即可绘制边界框并保存,便于直观核对标注质量。目前已有140人学习下载,适合需要标准划分数据集、免去整理流程的开发者,无论是入门YOLO实战还是课程设计、科研验证,都能直接套用。
1. 为什么“划分好的安全带检测数据集”能省掉你至少三天时间
做驾驶员安全带检测的人手里最不缺的就是监控截图,缺的是能直接喂给 YOLO 的标签。我见过很多人第一步就栽在数据上:从视频里抽了几千帧,标了三天,结果有人把中心坐标想当然写成像素值没归一化,有人用全角逗号分隔,还有人在没有划分的情况下直接开训,等 loss 降到 0.02 才发现验证集和训练集混着同一辆车的连续帧,指标虚高得没法看。这套“驾驶员佩戴安全带检测”数据集把最耗时也最容易出错的三道工序替你完成了:标注统一成了 YOLO 的 txt 格式,train/val/test 已经切好,类别 class 文件和可视化脚本也都放在包里,你拿到手第一件事不是标图,而是花十分钟把数据“看一遍”。适合三种人:想快速跑通 YOLO 全流程的新手,做车辆安全监管类项目的开发者,以及需要给算法选型做快速验证的技术负责人。
2. 安全带检测数据集的结构与类别约定:先花 10 分钟对齐 class 文件
拿到数据别急着解压完就跑去训练。先花十分钟把类别设计和目录结构看清,后面所有调参都建立在“我知道这个包在说什么”的基础上。这一步省不掉,因为 YOLO 训练器只认编号不认类名,类名和编号的对应关系全写在 class 文件里,对不齐的话,你连 loss 曲线和混淆矩阵都读不懂。
2.1 两种主流标注思路:检测“安全带区域”还是检测“佩戴状态”
安全带检测在工程上通常有两条路线,你先要判断手里这份数据集走的是哪一条。第一种是单类别目标检测,只把安全带当作前景目标来框选,比如肩带斜跨过胸口的那一段。这种设计的优点是任务干净、类内差异小,类别不均衡的问题不严重;缺点是它不直接回答“驾驶员有没有系”,后续还要加规则:有安全带框就认为已佩戴,没有就认为未佩戴。遇到安全带被外套遮挡、只有半截入镜的情况,规则就容易误判。
第二种是状态二分类,把目标定义成“佩戴”和“未佩戴”两类,class 文件里通常会有两个类别名,比如 wearing_seatbelt 和 no_seatbelt,有时还会额外带一个 person 类别做辅助。这种设计直接输出业务结论,部署时少一层规则转换,是我在安全监管类项目里的首选。拿到包先看一眼 classes.txt,文件里有几行,就说明这个数据集把任务定义成了几个类别。用下面的命令直接读:
cat classes.txt逻辑说明:classes.txt 每行一个类名,行号从 0 开始,对应标注 txt 里每行开头的数字编号。比如 0 对应 wearing_seatbelt,1 对应 no_seatbelt,那么一张图里标注文本写 0 的就是系了安全带,写 1 的就是没系。参数说明:类名本身只影响可读性,不影响训练过程,但会影响你后续对验证集输出的理解,建议先把编号和类名的对应关系抄在项目 README 里。
2.2 目录结构与 YOLO txt 格式:一张图上标了什么
YOLO 的数据格式不复杂:每张图片对应一个同名 txt,txt 的每一行是一个目标框,格式是 class_id x_center y_center width height,四个坐标值全部是相对图片宽高的归一化比例,范围在 0 到 1 之间。这套数据集既然说“划分好了”,目录结构通常是下面这样的:
data/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt └── data.yaml这里要注意的是“同名同路径”:图片叫 000001.jpg,标注就必须叫 000001.txt,且两个文件要放在对应的 train 或 val 目录下。images/train 和 labels/train 必须一一对应,多一张少一张都不行。data.yaml 是训练器读取数据的入口,常见内容长这样:
path: ./data train: images/train val: images/val test: images/test nc: 2 names: 0: wearing_seatbelt 1: no_seatbelt逻辑说明:path 是数据集根目录相对当前执行位置的路径;train、val、test 分别填图片目录的相对路径,训练器会自动把 images 换成 labels 去寻找标注文件;nc 是类别总数,必须和 classes.txt 的行数一致;names 是类名列表,顺序也必须和 classes.txt 一致。参数说明:实际类名以你手上的 classes.txt 为准,我这里只是为了举例。如果 nc 和 names 对不上,训练器会直接报错或者把类别错位,这是新手最容易翻车的地方。
光看目录结构还不够,我建议在训练前跑一个最小检查脚本,扫描所有 txt 里的坐标值是否都在合法范围内。标注工具导出时偶尔会产生 0 到 1 之外的异常值,这种脏数据不会让训练崩掉,但会让 loss 曲线出现莫名的尖峰。
from pathlib import Path label_dirs = [Path("data/labels/train"), Path("data/labels/val")] for ld in label_dirs: for txt in ld.glob("*.txt"): for line in txt.read_text().strip().splitlines(): parts = line.split() if len(parts) != 5: print(f"格式异常: {txt.name}: {line}") continue vals = [float(x) for x in parts[1:]] if not all(0.0 <= v <= 1.0 for v in vals): print(f"坐标越界: {txt.name}: {line}")逻辑说明:脚本遍历 train 和 val 两个标注目录,逐行检查每个 txt 是否恰好有 5 个字段,并对后四个坐标值做 0 到 1 的范围校验。凡是打印出来的文件都要人工确认。参数说明:如果你拿到的是分成 3 类的数据集,nc 和 names 相应改成 3 行即可,检查脚本不需要变,它只关心字段数量和坐标范围,不关心类别数。
2.3 划分合理性检查:train 和 val 里有没有同源帧
“划分好的数据集”不等于“划分合理的数据集”。很多从视频切帧得到的数据集,划分时只是按文件名排序后切一刀,结果同一个视频片段里相邻的几帧被拆到 train 和 val 两侧,验证集里全是训练集里见过的场景,评估结果虚高一截。最直接的检查是找出完全重复的图片:
md5sum data/images/train/*.jpg | sort > /tmp/train.md5 md5sum data/images/val/*.jpg | sort > /tmp/val.md5 comm -12 /tmp/train.md5 /tmp/val.md5 | wc -l逻辑说明:第一条命令计算训练集所有图片的 MD5 并排序,第二条对验证集做同样处理,第三条用 comm 命令取出两侧相同的行,也就是重复图片,最后 wc -l 统计数量。如果输出是 0,说明没有完全相同的图片。参数说明:注意图片扩展名,数据集里如果混着 .png 或 .JPG,需要把通配符改成对应扩展名再跑一次。这种完全重复的情况比较少见,更隐蔽的是“内容相似但文件名不同”的近似帧,这种要靠抽帧间隔感知或感知哈希来查,更详细的做法放在第 5 章排查里展开。
3. 数据可视化脚本:训练前把标注质量破绽找出来的最快路径
这类数据包自带的可视化脚本,常见功能不外乎三类:把标注框画回原图、统计类别分布、输出样本量汇总。你拿到手先别急着只跑一遍看个热闹,我一般会把这三类输出当作质量审计来用,每一类都对应一种数据缺陷。
3.1 图框叠加:先看框贴不贴近目标,再看类别贴不贴语义
图框叠加是把每个 txt 里的归一化坐标还原成像素坐标,画到原图上。这是最直观的质检手段,能发现三类问题:框把整片胸口都框进去而不是只框安全带区域;安全带被方向盘或手臂遮挡时框还在但语义已经不对;wearing 和 no_wearing 标签贴反。如果你拿到包的脚本只输出图片,那我建议你额外准备一个最小可用的绘图函数,十几行就能搞定:
import cv2 from pathlib import Path def draw_boxes(img_path: str, label_path: str, class_names: list, out_dir: str): img = cv2.imread(img_path) h, w = img.shape[:2] for line in Path(label_path).read_text().strip().splitlines(): cid, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) color = (0, 255, 0) if int(cid) == 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[int(cid)], (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) out = Path(out_dir) out.mkdir(exist_ok=True) cv2.imwrite(str(out / Path(img_path).name), img) for img_path in list(Path("data/images/train").glob("*.jpg"))[:200]: label_path = Path("data/labels/train") / img_path.with_suffix(".txt").name if label_path.exists(): draw_boxes(str(img_path), str(label_path), ["wearing_seatbelt", "no_seatbelt"], "vis/train")逻辑说明:先把归一化的中心坐标和宽高换算成像素坐标,然后按类别画不同颜色的框,绿色是已佩戴,红色是未佩戴,框上方再写类名。我建议你渲染 train 和 val 各 200 张左右,不要只盯着原始渲染图,要带着“这个框能不能帮助模型学出安全带语义”的问题去看。参数说明:颜色和类名的对应关系要和 classes.txt 一致,如果数据换成了三分类,class_names 列表要同步改,color 的选取逻辑也要跟着编号走。
看叠加图时重点看两点。第一是框的边界:框太大会把胸口、方向盘都包进去,模型学到的是“胸口附近有一个目标”而不是“斜跨的安全带”;框太小会丢掉安全带的头尾,模型学到的特征不完整。第二是类别边界:如果同一条安全带上,有的帧标为 wearing,有的帧标为 no_wearing,模型会直接学乱,训练 loss 降不下去的锅经常在这里。
3.2 类别分布与样本量统计:一眼看穿不均衡
安全带的类别不均衡问题比一般目标检测更棘手。现实监控里绝大多数驾驶员是系了安全带的,no_seatbelt 的样本天然稀少,而业务上恰恰最关心这种少数类。可视化脚本里的类别统计功能,本质就是数一数每个类别在所有标注里出现了多少次:
from pathlib import Path from collections import Counter cnt = Counter() for ld in [Path("data/labels/train"), Path("data/labels/val")]: for txt in ld.glob("*.txt"): for line in txt.read_text().strip().splitlines(): cnt[int(line.split()[0])] += 1 for cid, num in sorted(cnt.items()): print(f"class {cid}: {num} 个实例")逻辑说明:脚本遍历 train 和 val 两个标注目录,对每个 txt 的每一行取行首的类别编号,用 Counter 统计出现次数,最后按类别编号打印。这个数字代表的是“实例数”而不是“图片数”,一张图里有两个 no_seatbelt 框就会计两次,对类别不均衡的判断要按实例数来。参数说明:如果你只关心某一张图里有没有某个类别,可以把累加逻辑改成 per-image 判断,那个用于抽帧检查更合适。
看完统计数字,我的判断标准是:少数类实例数如果不到多数类的 20%,就需要在训练策略上做干预。干预手段不是删多数类样本,而是做类别加权或者对少数类做过采样,这一点会在第 4 章的进阶参数里具体给命令。可视化脚本里通常还会输出每张图的 box 数量分布,这个指标用来判断一张画面里通常有几个目标,如果 val 里大量图片是 0 目标,就要警惕验证集里空帧太多,影响 mAP 评估的参考价值。
3.3 抽帧建目录:把少数类样本单独挑出来人工复审
有时候统计数字没有问题,但你想确认少数类的样本质量,这时候用目录抽帧比一张张看图更高效。我习惯把某个类别的样本单独复制到一个目录,批量过一遍:
mkdir -p debug/no_belt python - <<'EOF' from pathlib import Path import shutil for txt in Path("data/labels/train").glob("*.txt"): for line in txt.read_text().strip().splitlines(): if line.split()[0] == "1": img = Path("data/images/train") / txt.with_suffix(".jpg").name if img.exists(): shutil.copy(img, "debug/no_belt") break EOF逻辑说明:这段脚本在 train 标注目录里找所有包含类别 1 的 txt,只要某张图里有至少一个 no_seatbelt 框,就把对应图片复制到 debug/no_belt 目录,然后 break 跳出这个小循环,避免同一张图被复制多次。参数说明:类别编号 1 对应 classes.txt 里的第二行,如果你的类名顺序不同,把这个数字换成实际的编号。复制出来之后用看图软件快速翻一遍,重点确认两件事:这些少数类样本里有没有大量重复的连续帧,以及类别标注是否稳定一致。如果少数类样本大量来自同一个车牌同一段视频,模型在真实场景里对“没系安全带”的泛化能力基本是空的。
4. 用 YOLO 把安全带检测跑起来:训练命令与参数取舍
数据看完了,接下来就是训练。这一章我只讲一件事:怎么用最少的坑把 YOLO 训练跑通,以及安全带场景下哪些参数值得你额外花时间去调。训练框架用 YOLO 官方仓库的 yolo 命令,模型文件用预训练权重起步,不要随机初始化从头训练。
4.1 环境与数据路径:先让 data.yaml 的 path 对齐到实际目录
跑训练前先把环境装好,核心是 PyTorch 和 YOLO 官方训练工具。GPU 不是必须的,但没有 GPU 就别想用大分辨率训练,CPU 跑一两个 epoch 验证流程可以,完整训练不现实。装好之后第一件事不是直接开训,而是确认 data.yaml 里的 path 能不能被正确解析。我见过太多人把数据集放在 /home/user/datasets 下,data.yaml 里却写着相对路径,训练器在错误的目录下找不到 labels,一上来就报 no labels found。
先跑一个最小验证,用一个 epoch 在 CPU 上走一遍全流程:
python -c "from pathlib import Path; print(Path('data/images/train').resolve())" yolo detect train data=data.yaml model=yolov8s.pt epochs=1 imgsz=640 batch=4 device=cpu逻辑说明:第一条命令打印训练图片目录的绝对路径,确认你当前的工作目录离数据集足够近,data.yaml 里的相对路径能正确解析。第二条命令用 1 个 epoch、CPU、batch 4 跑一个完整训练迭代,目的是验证数据读取链路、标签匹配、类别数配置是否正确,花不了几分钟。参数说明:device=cpu 只是验证用,正式训练换成 0 表示第一张显卡;batch 在验证时可以调小,CPU 内存不够就改成 2;model 指定的是预训练权重文件,yolov8s.pt 是中等体积的模型,首次先不要选最大的模型,跑通之后再换。
这一步跑完,你会看到训练日志里逐类打印目标数量和图片数量。重点看两个数字:train 和 val 的图片数是否明显区分,以及每个类别的实例数是否和你在第 3 章统计的一致。只要这里对得上,后面训练再出问题就可以放心去调模型和参数,而不是怀疑数据路径写错了。
4.2 最小可复现训练命令:先跑通再谈提点
验证通过之后,可以上正式训练命令。我用的是下面这条,项目名 s 开头表示 small 模型起步,跑通后再按需求换更大的模型:
yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ project=runs \ name=safety_belt逻辑说明:data 指向 data.yaml,model 指定预训练权重,epochs 是训练轮数,imgsz 是输入分辨率,batch 是每批次图片数,device 指定显卡,project 和 name 决定输出日志和权重的保存目录。训练完成后会生成 runs/safety_belt 目录,里面有权重文件、训练曲线和验证结果。参数说明:batch 不是越大越好,它受显存限制。以 16GB 显存为例,imgsz=640 时 batch 16 通常能跑,但如果你把 imgsz 提到 1280,batch 必须降到 4 到 8,否则直接 OOM。epochs 100 是起步值,安全带的类别差异不大,一般 100 轮以内就能收敛,重点看 val 曲线是否在后期还有上升空间。
这条命令跑通后,你会得到一个能用的基线模型。不要急着上线,先看验证集上的 mAP50 和 mAP50-95 两个指标,再结合第 6 章的坏例分析确认模型行为。如果 mAP50-95 明显偏低,说明框的定位精度不行,这会直接影响“安全带是否系好”的判断,因为业务上关心的是安全带区域的精确位置。
4.3 面向安全带场景的进阶参数:分辨率、Mosaic 关闭时机、类别权重
基线跑通之后,安全带场景有两个明显的专项优化方向:小目标和类别不均衡。小目标问题在远距离监控画面里特别突出,驾驶员的安全带在 1280×720 的原图里可能只有几十个像素,缩到 640 之后特征几乎消失。我的做法是把 imgsz 提到 1280,同时把 batch 下调,用训练时间换检测精度。另一个方向是类别不均衡,如果第 3 章统计出少数类实例占比低于 20%,我会在训练命令里显式做类别加权。
yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=1280 \ batch=8 \ mosaic=1.0 \ close_mosaic=10 \ patience=20 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4逻辑说明:imgsz 提高到 1280 后,远处的小目标在特征图里能保留更多像素;close_mosaic=10 表示训练到最后 10 个 epoch 时关闭 Mosaic 增强,避免模型在合成样本上过拟合,最后阶段的评估更贴近真实分布;patience=20 表示验证集指标连续 20 轮不提升就提前停止;hsv 三项是对色相、饱和度、明度的随机扰动,安全带颜色与衣物、座椅接近时,这类颜色增强能提升模型的抗干扰能力。参数说明:imgsz 1280 会让显存占用翻倍,batch 必须同步降下来;close_mosaic 的值不要设太大,10 到 15 比较合适,太早关掉会损失 Mosaic 带来的鲁棒性收益。类别权重的设置在 YOLO 官方训练工具里通过 class_weights 参数传入,需要你在训练前按少数类比例算好一组权重数组,权重值我一般从 2 到 5 起步,具体要看验证集上的召回率变化再调。
加了这些参数之后,训练时间会比基线长不少,但安全带检测这个任务的业务价值恰恰体现在这些难例上:近处的驾驶员大家都检得出来,远处、侧光、遮挡状态下还能稳定输出,才是这个模型真正能上线的原因。
5. 安全带数据集训练的常见坑与排查:现象、原因、解决
这一章把我在安全带检测项目里实际撞过的坑集中说一说。每一条都按“现象 → 原因 → 解决”来写,你可以直接对照自己的训练日志。
5.1 训练一上来就报 no labels found,loss 一路为 0
现象:训练日志里出现 warnings,提示某个图片目录下找不到标签文件,loss 从第一个 epoch 开始就不变化,等于模型在空数据上学了个寂寞。
原因:绝大多数是路径和文件名的匹配问题。data.yaml 里 val 写的是 images/val,但实际目录叫 validation;图片是 .JPG 大写扩展名,而标注文件生成时用的是 .jpg;或者 labels 目录下直接少了一批文件,只有图片没有 txt。
解决:先检查 data.yaml 的目录名,再检查图片和标注文件是否同名同扩展名。我习惯用一个命令统计两边文件数是否一致:
echo "train images: $(ls data/images/train | wc -l)" echo "train labels: $(ls data/labels/train | wc -l)"逻辑说明:对比两个数字,不一致就说明有图片没标注或者有标注没图片。参数说明:如果扩展名不统一,先统一扩展名再跑。这个坑在 CPU 验证那一步就能暴露,所以无论如何不要跳过 4.1 里的最小训练验证。
5.2 val 指标虚高:划分泄露比你想的更隐蔽
现象:训练还没结束,val 的 mAP50 就已经到 0.95 以上,模型看起来强得离谱,但放到真实视频流里测试,连续报错,一张没有人脸没有人身的空背景也能框出一个目标。
原因:数据集划分时把同一段视频的连续帧拆到了 train 和 val 两侧。模型记住的是场景和背景,而不是安全带的语义特征。完全重复图片查重能查到,但时间戳相邻的内容近似帧靠 md5 查不出来。
解决:先确认 val 的图片是否和 train 有内容重叠,做法是随机抽 val 里 50 张图,人眼比对有没有“同一辆车同一角度”的既视感。更规范的解法是回到源头,把划分改成按视频片段切分:同一个视频的所有帧只能进 train 或 val 中的一个集合,不能两边都占。数据集的划分通常已经按这个思路做过,但你最好自己验证一下。
5.3 模型只会输出“未系安全带”:类别不平衡的病征
现象:验证集上 no_seatbelt 的召回率很高,wearing_seatbelt 的召回率很低,每次预测几乎都在报“没系安全带”,precision 和 recall 严重失衡。
原因:数据里 no_seatbelt 的实例数远少于 wearing_seatbelt,模型在 loss 的驱动下倾向于把所有目标都判成多数类,因为这样整体 loss 最小,少数类的错误被大量多数类正确样本淹没了。可视化脚本统计出的类别分布数字,在这一步就是排查依据。
解决:优先用类别加权或过采样,不要盲目加训练轮数,加轮数只会让模型更确信自己的偏见。在训练命令里给少数类更高的权重,或者把少数类样本复制几份并配合随机增强,让模型在少数类上多走几步。权重从 2 到 5 起步,观察 val 上两个类别的 precision/recall 是否开始靠拢。
5.4 远处驾驶员的安全带只有指甲盖大小:小目标漏检
现象:近景检测正常,一旦画面里有远景车辆、后排乘客或者摄像头安装角度偏低,安全带就完全漏检。可视化叠加图里,这类目标的安全带框只有十几个像素宽。
原因:640 的输入分辨率下,小目标在 YOLO 的深层特征图里只剩几个像素,语义信息被下采样抹掉了。数据分布里远景样本占比少,模型没有机会学到这种尺度下的安全带形态。
解决:把 imgsz 提到 1280 是最直接的手段,代价是训练和推理时间增加。如果项目对耗时敏感,可以考虑在推理端对画面里的车辆区域先做一次检测,再把车辆 ROI 放大后单独跑安全带模型,这个两段式方案往往比单纯提分辨率性价比更高。另外,用可视化脚本按框面积排一次序,把面积小于图像总面积 2% 的样本单独统计比例,如果数字超过三分之一,就要认真对待小目标问题。
5.5 安全带颜色和衣物、座椅、光影融为一体:误检与漏检的情境化处理
现象:白色安全带配白色衬衫、深色安全带在逆光阴影里、安全带压在深色座椅上时,模型时有时无,一种颜色下检出来,换个座椅颜色就丢掉。
原因:安全带是低纹理的目标,可区分的特征本身就少,靠的主要是颜色对比和几何走向。数据增强里如果没做颜色扰动,模型会把某个特定颜色组合当成强特征。
解决:在训练命令里把 hsv_h、hsv_s、hsv_v 的扰动范围加大,让模型见过各种色偏下的安全带。更重要的是检查标注语义:框是否紧紧贴着安全带可见的斜段,如果标注把大片衣物也框进去,模型就会把衣物特征当成安全带特征,遇到不同颜色的衣物立刻翻车。这一类问题没有一次性根治的招数,只能在每次坏例分析后对持续出现的形态做定向补数据。
6. 用坏例分析和 mAP 门控给安全带模型验收:一个可复用的检查习惯
6.1 先看数量差,再看置信度,最后看具体图
训练结束不能只看一行 mAP 就收工。我给自己定的验收习惯是三步:先看 val 上每个类别单独的 precision 和 recall,再按“预测框数量与真实框数量的差值”把最离谱的样本挑出来,最后一张张看挑出来的图。安全带业务的漏检和误检代价不一样,漏掉一个未系安全带的司机比多报几次误警严重得多,所以我的验收优先看少数类 recall。
最简单有效的坏例抽取方式是做数量对比。预测结果和真实标签之间如果框数差很多,大概率是漏检或者误检的集中爆发点:
from pathlib import Path pred_dir = Path("runs/safety_belt/predict/labels") gt_dir = Path("data/labels/val") for pred_txt in pred_dir.glob("*.txt"): content = pred_txt.read_text().strip() n_pred = len(content.splitlines()) if content else 0 gt_txt = gt_dir / pred_txt.name n_gt = 0 if gt_txt.exists(): gt_content = gt_txt.read_text().strip() n_gt = len(gt_content.splitlines()) if gt_content else 0 if abs(n_pred - n_gt) >= 2: print(f"{pred_txt.stem} pred={n_pred} gt={n_gt}")逻辑说明:脚本遍历验证集预测结果目录里的每个 txt,统计预测框数量和真实框数量,差值大于等于 2 的样本就是怀疑对象,打印出文件名和两边数量。差值为正是疑似误检,差值为负是疑似漏检。参数说明:阈值设成 2 意味着至少差两个框才重点关注,如果你的项目对漏检极敏感,可以把阈值降到 1,代价是人工要看的图多一些。跑完之后,把打印出来的图片按文件名从 val 里找出来,用可视化脚本渲染成叠加图,逐张看模型到底犯了什么错。
我最后会把这个坏例清单交给标注方再确认一遍边界情况。很多预测错误的背后不是模型笨,而是标注本身在模糊边界上不一致,这一轮确认下来,既校准了数据也校准了模型的预期行为。以前我也为 val 指标好看就急着签验收吃过亏,后来养成这个习惯,每条新模型上线前都先跑一遍坏例分析再谈效果。希望帮到你。
本文还有配套的精品资源,点击获取