ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLO障碍物数据集从体检到训练:标签可视化与避坑指南

YOLO障碍物数据集从体检到训练:标签可视化与避坑指南 简介面向目标检测入门与实战的YOLO格式数据集聚焦马路上常见障碍物识别包含四个类别障碍物、小动物、路障、减速带。图像均为640×640分辨率RGB图标注采用YOLO相对坐标边界框完整每张图含多个目标适合直接训练或评估目标检测模型。数据已按YOLOv5目录结构划分训练集1337张图片及对应标签验证集572张图片及对应标签另附类别txt文件与可视化脚本可随机传入图片快速绘制边界框并保存结果无需额外预处理即可上手。资源共2000个文件其中txt标签与类别文件1910个、jpg图像89个、Python可视化脚本1个压缩包大小124.8MB下载解压后即可使用。训练集与验证集划分明确方便对比实验与结果复现可视化脚本无需修改即可运行适合快速检查标注质量。已有151人学习下载推荐给需要道路交通障碍检测数据的开发者、学生或研究者使用。1. 拿到一份“已划分好”的YOLO障碍物数据集先做这三件事再谈训练一份划分好的YOLO目标检测数据集放在眼前时它通常不是一堆图片加一堆txt就完事了。路上障碍物检测这类4类别数据集常见交付形态是train/val两个目录、每张图配一个同名的标签文件外加一个写明类别顺序的class文件以及几个能直接跑的脚本。对还没接触过yolo入门学习的新手来说第一步往往不是急着训练而是先把这三样东西之间的关系搞清楚对熟手来说重点是确认标签是否越界、类别是否均衡、划分是否泄漏。这篇笔记按这个顺序从目录结构讲到可视化脚本再落到最小训练配置和常见问题排查上。2. 拆解数据集结构与标签格式4个类别如何映射到YOLO的class文件2.1 images与labels的目录配对先建立“同名同路径”的心智模型这类数据集最常见的组织方式是images和labels两个平行目录各自下面再分train和val。先别急着写训练命令把目录树打出来看一遍tree -L 2 dataset/预期看到的结构大致是这样dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ └── 0002.jpg │ └── val/ │ ├── 1001.jpg │ └── 1002.jpg ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ └── 0002.txt │ └── val/ │ ├── 1001.txt │ └── 1002.txt ├── classes.txt ├── data.yaml └── visualize_labels.py逻辑说明YOLO系列训练时读图路径由images目录给出标签路径则把images替换成labels、把.jpg替换成.txt推导出来所以两张目录树必须逐级对应。这里顺便验证两个点labels/train下的txt数量和images/train下的图片数量是不是完全一致文件名不含扩展名是不是一一配对。我用一条命令检查diff (ls dataset/images/train | sed s/\..*// | sort) \ (ls dataset/labels/train | sed s/\..*// | sort) | head -20参数说明sed s/\..*//表示只保留第一个点之前的主文件名去掉扩展名后再排序对比diff没有输出就是两边完全匹配。如果有输出多出来的行就是孤儿图片或孤儿标签这种文件在训练时会被跳过或直接报错。2.2 标签txt的五列数值class_id之外cx/cy/w/h为何必须归一化每张图的标签文件是纯文本一行一个目标。拿一个真实标签举例0 0.5123 0.4180 0.0829 0.2315 2 0.7261 0.6802 0.0473 0.1198含义拆开第一列是类别ID从0开始计数在4类别数据集里范围是0到3第二、三列是目标中心点的x、y坐标第四、五列是目标框的宽度和高度。这四列都必须除以图片原始宽高做归一化得到的值在0到1之间和图片分辨率无关。归一化带来的好处是同一份标注可以直接跑640x640、1280x1280不同输入尺寸的训练不需要改标签。注意YOLO标签用的是中心点加宽高不是VOC那种左上角加右下角。拿到txt先随便打开一行确认格式如果第一行是0 123 45 200 300这种整数坐标说明这份数据还没有转成YOLO格式。2.3 两种class文件形态data.yaml与classes.txt的适用场景标题里提到的“class文件”其实有两种形态要分清。第一种是纯文本的类别清单例如classes.txtcone barrier construction_sign stone_block它只承担“给人看”的角色训练时并不直接读它。第二种是Ultralytics YOLO用的data.yaml里面同时写了路径和类别名path: dataset train: images/train val: images/val nc: 4 names: 0: cone 1: barrier 2: construction_sign 3: stone_block参数说明path建议写相对路径这样整个数据集目录移动到别的位置也能直接跑names的顺序绝对不能乱第0个名字对应所有标签txt里类别ID为0的目标。两个文件要保持一致修改了classes.txt必须同步改data.yaml否则训练时类别名会张冠李戴。实际项目里这两份常常共存前者用于数据交换后者用于训练框架。2.4 数据划分比例与“同源样本”泄漏70/20/10之外还要看什么“划分好的数据集”听上去省事但划分质量决定了验证指标的含金量。先看一眼train和val的数量比例echo train: $(ls dataset/images/train | wc -l) echo val: $(ls dataset/images/val | wc -l)常见划分是7:3或8:2差太多就要留个心眼。比数量更重要的是检查语义泄漏即训练集和验证集里出现了同一路段、同一时刻的相似画面。场景类数据集常见做法是按采集时间段或按路段划分而不是把所有帧随机打散。我一般会抽查从val里挑两张图看场景再回train里对比是否存在视角极其接近的图片。小数据集里这种泄漏直接表现为训练loss正常但val的mAP虚高到不真实落地到新路段就露馅。拿到任何划分好的数据集先做上面这个抽查比急着调参更有价值。3. 用数据可视化脚本给数据集做“体检”画框、分布统计与坏标签拦截3.1 脚本1OpenCV按行读txt绘制标注框与类别名可视化脚本是这类数据集最实用的部分它能在一分钟内暴露标签错位、坐标错误、类别名对不上等问题。先写最核心的“画框”脚本import cv2 import os def draw_yolo_labels(image_path, label_path, class_names, out_dirvis): img cv2.imread(image_path) h, w img.shape[:2] os.makedirs(out_dir, exist_okTrue) with open(label_path, r, encodingutf-8) as f: lines f.read().strip().splitlines() for line in lines: parts line.split() if len(parts) ! 5: print(f[skip] bad line in {label_path}: {line}) continue cls_id, cx, cy, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) 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 cls_id len(class_names) else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) label class_names[cls_id] if cls_id len(class_names) else funknown_{cls_id} cv2.putText(img, label, (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) out_path os.path.join(out_dir, os.path.basename(image_path)) cv2.imwrite(out_path, img) print(f[ok] {image_path} - {out_path}, objects{len(lines)})逻辑说明脚本把归一化坐标换算回像素坐标画矩形并叠加类别名。两类输出需要特别留意一类是输出文件名带unknown_N说明标签ID越界连4类别都没对上另一类是[skip] bad line说明某一行不是5列。这两个都是必须提前处理的问题否则训练脚本读到这些文件时会报错或直接丢弃标注。读取和保存都显式指定了UTF-8编码Windows中文路径下最常用的报错就是编码问题这里先做了防御。OpenCV的putText不支持中文类别名如果是中文会显示成问号所以脚本约定class文件里的名字用英文标注和中文显示分开处理。3.2 脚本2类别分布统计脚本一眼看出4类别是否失衡可视化不只是画几张图给人看更值得做的是全量统计用数据说话。把数据集中所有标签txt扫一遍计算每个类别出现的次数和占比from collections import Counter import glob counts Counter() empty_files [] bad_files [] for label_path in glob.glob(dataset/labels/**/*.txt, recursiveTrue): with open(label_path, r, encodingutf-8) as f: lines f.read().strip().splitlines() if not lines: empty_files.append(label_path) continue for line in lines: parts line.split() if len(parts) ! 5: bad_files.append((label_path, line)) continue cls_id int(parts[0]) counts[cls_id] 1 class_names [cone, barrier, construction_sign, stone_block] for i, name in enumerate(class_names): print(f{name:20s} id{i}: {counts.get(i, 0)} instances)这份脚本跑完能看到三类关键信息总样本量、类别占比和异常文件数量。4类别检测任务里最典型的问题数据是“长尾”状态比如锥桶占了八成石墩只有零星几十个。这种失衡状态下模型会偏向高频类别对低频类别的召回率会明显偏低。遇到这种情况常见的调整策略是对低频类别做过采样或者在训练时给loss里低频类别的权重加权。注意样本量按“实例数”统计不是按“图片数”统计一张图里有多个同类目标会全部计入。统计完可以把结果存成一张柱状图配到数据集说明里后面做汇报或写博客会省很多事。3.3 脚本3标签越界与空文件检测训练前拦下一半翻车标签越界是最隐蔽又最常见的坏数据YOLO标签要求所有坐标都在0到1区间内一旦出现cx1.12或w1.05训练时解析函数会做截断或直接报错问题往往到训练中期才暴露排错成本高。写一个专门检测越界的脚本import os import cv2 def check_bounds(image_root, label_root): problems [] for sub in [train, val]: img_dir os.path.join(image_root, sub) lbl_dir os.path.join(label_root, sub) for name in os.listdir(img_dir): stem, ext os.path.splitext(name) lbl_path os.path.join(lbl_dir, stem .txt) img_path os.path.join(img_dir, name) if not os.path.exists(lbl_path): problems.append((img_path, missing label)) continue h, w cv2.imread(img_path).shape[:2] with open(lbl_path, r, encodingutf-8) as f: for line_no, line in enumerate(f): p line.split() if len(p) ! 5: problems.append((lbl_path, fline {line_no 1} not 5 cols)) continue cls_id int(p[0]) cx, cy, bw, bh float(p[1]), float(p[2]), float(p[3]), float(p[4]) if not (0 cls_id 4): problems.append((lbl_path, fline {line_no 1} class id {cls_id} out of range)) if not (0 cx 1 and 0 cy 1 and 0 bw 1 and 0 bh 1): problems.append((lbl_path, fline {line_no 1} coord out of [0,1]: {p})) return problems for item in check_bounds(dataset/images, dataset/labels): print(item)逻辑说明一张图一张图地读先确认标签文件存在再解析每一行做三类检查格式、类别ID、归一化范围。注意脚本里的cv2.imread(img_path)带了真实图片宽高后续如果需要做“超出图片边界”检查也能扩展。这里先做的是数值区间检查因为YOLO训练时对越界容忍度很低。这类脚本不用写得复杂跑一遍输出一个清单把有问题的文件单独放到fix/目录等着修比直接改源数据安全。修完再跑一遍直到零输出为止。3.4 运行可视化脚本的三个前置检查拿到别人的脚本先别急着双击运行。三个最常见的情况我列在这里第一脚本默认输出路径不存在或没有写权限。多数脚本会os.makedirs(out_dir, exist_okTrue)但老脚本可能没有手动创建一个输出目录传进去即可。第二Matplotlib在无图形界面的服务器上直接plt.show()会卡死。数据可视化脚本里如果画了统计图建议把plt.show()改成plt.savefig(stats.png, dpi150)用图片文件代替弹窗展示。第三类别名和txt的ID对不上。脚本里写的class_names列表必须和class文件顺序一致动手改脚本前先打开classes.txt逐行核对顺序一到四对应ID零到三。这一条能拦住大部分“画出来的框标注名称是乱的”的诡异问题。4. 把数据集接入YOLO训练的最小闭环data.yaml、损失函数与验证指标4.1 先写data.yaml路径、names顺序与labels中的class_id一一对应数据体检完开始接训练框架。以Ultralytics YOLO为例训练前要准备一份yaml配置文件路径关系和字段含义直接影响训练是否正常启动path: dataset train: images/train val: images/val nc: 4 names: 0: cone 1: barrier 2: construction_sign 3: stone_block参数说明path是相对路径相对于你执行训练命令所在的位置train和val在path基础上继续向下拼。这里最关键的约束是names里的顺序必须和所有标签txt第一列的ID一一对应0: cone意味着所有0开头的标签行对应cone。如果标到一半时发现类别顺序写错只能重新生成全部标签不要指望在yaml里“微调”。写完后可以先用一行代码验证配置能被正确加载from ultralytics.data import YOLODataset ds YOLODataset(yaml_pathdata.yaml, imgsz640) print(len(ds), samples)逻辑说明这一步只是确认标签解析和图片路径没有异常不需要完整加载全部数据。如果这里就报Image not found或Label index out of range说明前面的目录检查或者类别ID检查没做干净对比跑训练时在几百个epoch之后才发现问题这里成本低太多。4.2 最小训练命令与关键超参数imgsz、batch、epochs怎么设数据没问题就能直接开始训练了这是常见的yolov8训练自己的数据集入口yolo detect train \ modelyolov8s.pt \ datadata.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience20参数说明modelyolov8s.pt选择small版本4类别障碍物检测的场景复杂度不高s足够跑得快且内存占用少epochs100是起步值障碍物场景通常50到80个epoch就能趋于收敛batch16是假设单张8GB显存的情况显存低于6GB改到8或4并同步把workers调低来避免数据加载阻塞patience20是等20个epoch验证集指标都没有提升就提前停止避免干等。新手常见翻车点是batch设太大直接OutOfMemory或者device写错没用到GPU训练速度慢到像死机。先跑一个epoch确认速度正常再挂后台跑完整训练。4.3 训练中看loss三件套box_loss、cls_loss、dfl_loss各管什么训练日志里每个epoch会打出一行最后几个数分别是box_loss、cls_loss、dfl_loss。很多教程只告诉你“loss下降就是好的”但yolo损失函数这三项值得分开看。它们的对应关系如下。表格障碍物检测训练中三类loss的含义与排查倾向loss字段对应问题数值异常时的排查方向box_loss预测框和真实框的位置偏差检查标注框是否偏大偏小归一化是否错误cls_loss类别是否分对检查类别ID错位类别样本是否失衡dfl_loss边框回归的分布损失检查近景大目标和远景小目标的标注质量训练初始阶段box_loss会快速下降到后20个epoch变成缓慢下行这属于正常形态。如果cls_loss一直不降最常见的原因是前面说的类别ID错位模型学到的是“错误正确答案”。如果box_loss反复震荡先去看数据而不是去换模型结构。4.4 用验证结果确认4类别真学明白了mAP、混淆矩阵与热力图训练完后的验证一定要单独跑yolo detect val \ modelruns/detect/train/weights/best.pt \ datadata.yaml跑完会输出各类别mAP50、mAP50-95以及保存的混淆矩阵图。4类别任务里mAP50整体到0.7以上算是能用的模型单看一个整体数字不够我一般会逐个类别看指标。如果某个类别AP明显低于其他先回第3章的统计脚本查这类样本数是不是太少再回可视化脚本看标注框有没有画偏。进一步可以借助热力图观察模型关注区域。YOLO的检测头输出特征图本身就可以可视化把某张图的feature map叠在原图上能看到落在障碍物附近的响应是否强烈。这类分析对确认模型是不是学到了目标本身很有帮助尤其在锥桶密集摆放的场景里热力图能看清模型是不是只盯着单个目标而不是整片区域。5. 避坑与常见问题排查这份障碍物数据集最容易踩的5个坑5.1 现象训练loss正常下降但验证mAP一直在0.2上下抖动训练三条loss都收敛验证集指标上不去。先怀疑数据划分再怀疑预处理差异。最典型的根因是数据泄漏验证集里混进了和训练集同一路段相邻帧的图片模型在验证集上“见过”类似目标分布指标虚高如果指标反而很低通常是训练集和验证集场景差异太大比如训练集全是大晴天验证集包含了黄昏或逆光。这时先打开val里几张图对比train的场景构成如果差异明显可以按“光照/路段/时段”重新切分而不是随机再分一遍。5.2 现象可视化脚本把框画到了图片外面或者在边界被截断原因就一个标签里某个坐标越界例如cx0.98, bw0.12换算成像素后右边框超出了图像宽度。YOLO在推理时会把这个框当成小目标框严重偏离真实位置直接压低AP。解决方式是按第3章的越界检测脚本把所有问题标签找出来逐个修正。如果越界目标是贴在图片边缘的物体常见做法是把中心点坐标和宽高做clamp重新写入txt。修复前先备份原始标签目录这个行为不费事但非常保命。5.3 现象验证集mAP很高换了一段新路采集的图片就崩这是最容易让项目返工的问题根子在划分逻辑。某些“划分好的数据集”只是把整段视频的帧随机打散再切train/val同一秒内的相邻帧会进不同集合模型无形中在验证集上做过“开卷考试”。解决方法是按时间或路段来做划分比如前半段视频全部进train、后半段进val如果数据集不是按序列组织的至少按文件名前缀分组后再切确保同一个场景id只落在一个集合里。5.4 现象改过classes.txt的类别顺序后训练报标签类别不匹配示意图是一种情况手动修改class文件后忘了同步修改标签txt里的class_id导致训练时报Label class is out of range。反过来的情况也一样标签ID改了但class文件没改模型名字全乱。解决的核心是“class文件和标签必须同步重生成”先把类别顺序定死用脚本统一替换标签第一列的数字再跑第3章的检测脚本兜底。别手工改txt数据量一大必然出漏。5.5 现象可视化脚本在Windows中文路径下报UnicodeDecodeError原样代码读txt用的是系统默认编码中文Windows下是GBK而多数标签生成工具写的是UTF-8导致打开就炸。代码里读取文件时显式指定编码with open(label_path, r, encodingutf-8) as f: ...逻辑说明不管脚本跑在Windows还是Linux读取和写入统一UTF-8是最省心的习惯。如果已经存在一批GBK编码的标签可以用iconv批量转换。此类问题在数据预处理阶段处理成本最低等训练脚本报错再回头排查耗时可观。6. 让这份数据集产生更大价值统计报告、困难样本与README沉淀数据集训练完成后真正拉开项目差距的往往不是模型结构而是对数据本身的挖掘。我习惯把训练前的统计结果和训练后的验证结果汇总到同一个文档里正面回答三个问题类别分布是什么、每个类别的AP是多少、模型最容易在哪类图上出错。统计报告可以直接用第3章的脚本生成跑完输出一个文本块加上混淆矩阵图片这就是一份合格的数据集验收记录。模型训练好后把置信度阈值调到0.5以上跑一遍val从低置信度结果里挑出“模型预测很弱但真实框存在”的图这些就是困难样本。障碍物场景里它们通常是远景小目标、强逆光下的锥桶、半遮挡的施工标志。把这些图挑出来单独归类后续补标注和增广都有明确方向。数据增广也要针对场景取舍。马赛克增强对密集小目标有帮助但在障碍物场景里要小心它把真实遮挡关系抹掉水平翻转对锥桶和护栏这类对称物体安全遇到带方向性的施工标志就要谨慎翻转后语义可能变成“另一侧”的含义。常见的做法是先做弱增广训练一轮看基线再逐步加mosaic和翻转对比每一轮的mAP再定。最后我还会把数据集的基础说明写进README类别顺序、标签格式、划分逻辑、脚本用法、每个类别的实例数。这份文档写细致一点三个月后回来接着用同一份数据集时会感谢当时的自己。这套流程多跑几个数据集后处理速度和排错效率都会明显提升。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进