简介:面向目标检测入门与实战的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.txt:
cone 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 脚本1:OpenCV按行读txt绘制标注框与类别名
可视化脚本是这类数据集最实用的部分,它能在一分钟内暴露标签错位、坐标错误、类别名对不上等问题。先写最核心的“画框”脚本:
import cv2 import os def draw_yolo_labels(image_path, label_path, class_names, out_dir='vis'): img = cv2.imread(image_path) h, w = img.shape[:2] os.makedirs(out_dir, exist_ok=True) with open(label_path, 'r', encoding='utf-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 f'unknown_{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', recursive=True): with open(label_path, 'r', encoding='utf-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区间内,一旦出现cx=1.12或w=1.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', encoding='utf-8') as f: for line_no, line in enumerate(f): p = line.split() if len(p) != 5: problems.append((lbl_path, f'line {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, f'line {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, f'line {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_ok=True),但老脚本可能没有,手动创建一个输出目录传进去即可。
第二,Matplotlib在无图形界面的服务器上直接plt.show()会卡死。数据可视化脚本里如果画了统计图,建议把plt.show()改成plt.savefig('stats.png', dpi=150),用图片文件代替弹窗展示。
第三,类别名和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_path='data.yaml', imgsz=640) print(len(ds), 'samples')逻辑说明:这一步只是确认标签解析和图片路径没有异常,不需要完整加载全部数据。如果这里就报Image not found或Label index out of range,说明前面的目录检查或者类别ID检查没做干净,对比跑训练时在几百个epoch之后才发现问题,这里成本低太多。
4.2 最小训练命令与关键超参数:imgsz、batch、epochs怎么设
数据没问题,就能直接开始训练了,这是常见的yolov8训练自己的数据集入口:
yolo detect train \ model=yolov8s.pt \ data=data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ patience=20参数说明:model=yolov8s.pt选择small版本,4类别障碍物检测的场景复杂度不高,s足够,跑得快且内存占用少;epochs=100是起步值,障碍物场景通常50到80个epoch就能趋于收敛;batch=16是假设单张8GB显存的情况,显存低于6GB改到8或4,并同步把workers调低来避免数据加载阻塞;patience=20是等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 \ model=runs/detect/train/weights/best.pt \ data=data.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 现象:可视化脚本把框画到了图片外面,或者在边界被截断
原因就一个:标签里某个坐标越界,例如cx=0.98, bw=0.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', encoding='utf-8') as f: ...逻辑说明:不管脚本跑在Windows还是Linux,读取和写入统一UTF-8是最省心的习惯。如果已经存在一批GBK编码的标签,可以用iconv批量转换。此类问题在数据预处理阶段处理成本最低,等训练脚本报错再回头排查,耗时可观。
6. 让这份数据集产生更大价值:统计报告、困难样本与README沉淀
数据集训练完成后,真正拉开项目差距的往往不是模型结构,而是对数据本身的挖掘。我习惯把训练前的统计结果和训练后的验证结果汇总到同一个文档里,正面回答三个问题:类别分布是什么、每个类别的AP是多少、模型最容易在哪类图上出错。
统计报告可以直接用第3章的脚本生成,跑完输出一个文本块,加上混淆矩阵图片,这就是一份合格的数据集验收记录。模型训练好后,把置信度阈值调到0.5以上跑一遍val,从低置信度结果里挑出“模型预测很弱但真实框存在”的图,这些就是困难样本。障碍物场景里它们通常是远景小目标、强逆光下的锥桶、半遮挡的施工标志。把这些图挑出来单独归类,后续补标注和增广都有明确方向。
数据增广也要针对场景取舍。马赛克增强对密集小目标有帮助,但在障碍物场景里要小心它把真实遮挡关系抹掉;水平翻转对锥桶和护栏这类对称物体安全,遇到带方向性的施工标志就要谨慎,翻转后语义可能变成“另一侧”的含义。常见的做法是先做弱增广训练一轮看基线,再逐步加mosaic和翻转,对比每一轮的mAP再定。
最后我还会把数据集的基础说明写进README:类别顺序、标签格式、划分逻辑、脚本用法、每个类别的实例数。这份文档写细致一点,三个月后回来接着用同一份数据集时会感谢当时的自己。这套流程多跑几个数据集后,处理速度和排错效率都会明显提升。希望帮到你。
本文还有配套的精品资源,点击获取