简介:一套面向路上障碍物检测场景的YOLO格式目标检测数据集,适用于自动驾驶、辅助驾驶与道路安全监控等方向,可直接用于训练YOLOv5等主流检测模型,免去数据清洗和格式转换的额外工作。压缩包共2000个文件,主要由1910个txt标签、89张jpg图像和1个Python可视化脚本构成,整包约124.8MB,目录按训练集和验证集划分清晰。目前已有151人学习,适合需要现成道路障碍物数据集的检测初学者或项目开发者使用。数据包含障碍物、小动物、路障、减速带4个类别,训练集1337张图像及对应txt标签,验证集572张图像及对应txt标签,全部为640×640分辨率的RGB图片,标注采用YOLO相对坐标格式,边界框完整且每图含多个目标;txt文件包含类别与坐标信息,可视化脚本可直接运行,随机传入一张图片即可绘制边界框并保存结果,便于快速检查标注质量。
1. 路上障碍物检测数据集与 YOLO 的 4 类别划分:为什么不能只下载一个 COCO 子集
想象你正在做一个园区巡检的移动机器人,摄像头对着路面,识别对象只有行人、汽车、自行车、摩托车四类,还要能离线跑。很多人直接去 COCO 里筛出这四类图,结果发现类别不平衡、分辨率参差不齐、训练验证划分还漏了同一视频的相邻帧,最终 loss 降不下去。这份标题里的「划分好的数据集 + class 文件 + 数据可视化脚本」,解决的是 YOLO 目标检测流程里最容易被低估的一步:数据进入网络之前,目录、类别顺序、坐标格式是否自洽。别急着调网络结构,先把数据集当成代码仓库一样审查一遍,下面用 4 类路上障碍物检测作为例子,把这套结构讲清楚。
2. 4 类别障碍物训练集的目录设计与 class 文件规范
2.1 YOLO 数据集目录结构与标注 txt 的坐标含义
一个能被 YOLO 直接训练的数据集,不是「一个文件夹里堆满图」。以最常用的 YOLOv8 / YOLO11 为例,目录通常长这样:
road_obstacle/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt # 类别文件,一行一个类别 └── dataset.yaml # 用于训练的配置images 和 labels 下必须同名同后缀的图片与 txt 文件,图片0001.jpg对应labels/0001.txt,txt 里每一行代表一个检测框。这里要特别强调:YOLO 的标注不是 VOC 的像素坐标,而是归一化后的中心点坐标。拿路上障碍物里的汽车举例,图片宽度 1920,盒子的左上角和右下角像素坐标为(100, 200)和(500, 600),那么x_center = (100+500)/2 / 1920 = 0.15625,y_center = (200+600)/2 / 1080 = 0.37037,width = (500-100)/1920 = 0.20833,height = (600-200)/1080 = 0.37037。对应的 txt 文件内容是:
1 0.15625 0.37037 0.20833 0.37037最后一个隐藏规则是文件里不能有空行,也不能缺少某个盒子。无论用 CVAT 标注还是把 KITTI 等数据集转成 YOLO 格式,最终落盘必须满足「一行四个空格分隔浮点数 + 换行」的严格结构。
逻辑说明:第一列1表示类别 id,后续四列依次是x_center、y_center、width、height,均除以图片宽高的归一化值。图片在训练时会被缩放,但标签坐标仍然相对原图归一化,所以 YOLO 在训练内部会按 letterbox 后的比例换算。因此千万不要在准备标签时额外乘以 1000 或保留整数,归一化到[0,1]是唯一正确写法。
参数说明:归一化后的小数精度一般保留到六位小数。过长的浮点数会增大文件体积,而四舍五入到 3 位可能在 4K 图上造成几十像素的偏移,建议统一用round(value, 6)处理。
2.2 class 文件决定了类别顺序和模型输出层的含义
标题里提到的「类别 class 文件」,很多初学者以为是纯文档,实际上它的行号就是模型输出层的索引。classes.txt 写成:
person car bicycle motorcycle意味着检测头输出的 4 个类别通道里,第 0 通道永远映射到 person,第 1 通道映射到 car,以此类推。这里有两个隐蔽问题。第一个问题是数据集里实际没有 bicycle,class 文件中依然必须保留 bicycle 那一行,否则 val 计算 mAP 时类别数对不上。第二个问题是在多来源数据合并时,A 来源按car, person顺序,B 来源按person, car顺序,合并前必须把所有 txt 第一列的 id 重映射,不然可视化脚本里 person 会画成 car 的框,训练 loss 也会一路乱走。
排查顺序和 class 文件是否一致,最省事的办法不是人眼去翻 txt,而是拿可视化脚本随机抽一批图,把预测框上显示的类名和实际物体对照。如果显示的全部错位,大概率不是标注画错,而是 class 文件顺序和标注 id 没对齐。我一般会在完成数据集准备后立刻用脚本打印一张抽样图,这张图里每个框的颜色和类名由继承关系决定,一眼就能看出顺序问题。
2.3 train/val/test 划分比例与随机种子
划分好的数据集不是随便把文件拖进三个文件夹。障碍物检测必须考虑同一条路上的连续帧,如果同一辆车出现在训练和验证里面,验证分数会被高估,跑上线后才发现泛化不行。常见做法是先按时间戳或视频片段分组,再对组做随机切分,而不是对单张图片切分。一个相对稳妥的比例是:
| 数据集 | 比例 | 典型图片量 | 用途 |
|---|---|---|---|
| train | 70% | 2800 | 训练与剪枝 |
| val | 20% | 800 | 训练中做早停和调参 |
| test | 10% | 400 | 最终指标评估,训练时不触碰 |
以上比例对应 4000 张左右的障碍物数据集。如果只有 800 张,建议把 val 压到 15%,保证 train 有足够数据。凡是用 Python 脚本划分,一定固定 random seed,最好把 seed 写进脚本并在运行日志里打印出来,否则同一份数据两次划分出来的 val 不同,后续对比实验的基准就不成立。
划分脚本逻辑不复杂,但要注意标签文件也要跟着动。只用shutil.move移动图片而忘了移动同名 txt,会造成 txt 在 labels 目录下堆积,训练脚本不报错但全部跳过,等于所有样本被当成了背景。
2.4 划分之后必须做的三项一致性检查
划分完成后,我一般跑三段 shell 命令做快速校验。第一段统计数量,第二段比对同名文件,第三段看 txt 行数是否有零标注。可以用下面这段 Bash:
# 统计 images/train 和 labels/train 中文件数量 echo "images train: $(ls images/train | wc -l)" echo "labels train: $(ls labels/train | wc -l)" # 找出 images/val 中缺少 labels 的文件 comm -23 <(ls images/train | sort) <(ls labels/train | sort)这段命令的逻辑是:前两行分别统计数量,数量不一致说明有图没标或者有标签没图;comm -23显示只在左边出现的文件名,也就是缺少标签的图。遇到这类文件可以直接删掉或补标,不能留在数据集里靠训练脚本兜底,因为训练过程中网络会把它们识别成背景,虽然也能训练但验证指标会失真。对只有几百张的小数据集,这三行命令足够把大部分低级错误暴露出来。
3. 把原始图片和标注整理成 YOLO 格式:四类障碍物的坐标转换与可视化验收
3.1 图片筛选与分辨率下限
路上障碍物检测的难点往往不在大货车,而在十几米外的人或摩托车,即便标注框正确,分辨率不够照样训不出来。数据准备阶段的第一个硬性条件是所有图片写入 images 之前统一检查宽高,低于训练输入尺寸的图片要么剔除,要么先做同分辨率填充。实际项目里常见做法是设置一个最小边长 640 的阈值,如果图片是 520×800,不直接缩小,而是保持长宽比缩放为 640×984 再补边,这样不会把远处目标压缩成不可辨识的噪点。
对 4 个类别的数据,还要检查类别数量均衡程度。理想状态下每类至少 500 个实例,少于这个数字就会在 val 里出现 mAP 方差很大,结果好坏完全取决于这一批样本里负例的占比。最简单的检验方法是把所有 txt 的第一列切出来数频次,发现某类数量太少时,优先做数据增强而不是直接复制同一张图,典型的增强组合是 HSV 饱和度扰动、随机水平翻转和 Mosaic,这些增强在 YOLO 训练参数里就能开。
3.2 坐标转换脚本:VOC/COCO 到 YOLO,以及 KITTI 转出来的坑
如果你拿到的数据是 XML 格式的 VOC 或 JSON 格式的 COCO,就必须写一次性转换脚本。我比较推荐直接写一个独立脚本而不是把转换逻辑塞进训练入口,因为转换往往只跑一次,跑完就用不上了。下面这个 Python 脚本处理 VOC 格式,假设 XML 和 jpg 同目录:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) with open(out_txt_path, 'w') as f: for obj in root.iter('object'): name = obj.find('name').text if name not in class_map: continue box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h line = f"{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}" f.write(line + '\n') # class_map 的 key 是源数据里的类名,value 是重映射后的 id class_map = {'person': 0, 'car': 1, 'bicycle': 2, 'motorcycle': 3} voc_to_yolo('0001.xml', 'labels/0001.txt', class_map)脚本逻辑很容易读:先从 XML 里读图片宽高和每个目标的bndbox,再用中心公式转成归一化坐标,最后把class_map映射好的类别 id 写进 txt。class_map里的 value 必须与 classes.txt 的行号一致,这是最容易踩的坑。另一点是if name not in class_map: continue不能漏,源数据里可能会有 truck、bus 等类别,标题限定参数为四类,遇到这些类应该跳过,而不是报错中断。
KITTI 标注转 YOLO 是另一个常见任务,坑在于 KITTI 数据集的坐标系里目标的高度用像素表示,但雷达 3D 框的投影会和 2D 框不一致,不能直接拿投影高度当 h,正确做法始终以 2D 框的四条边为准。CVAT 导出 YOLO 格式时也会附带一个obj.names文件,该文件内容就是类别顺序,导入前一定要人工比对一次。
3.3 训练数据标记与类别映射的自动化
如果多个来源的标注类名不统一,比如有人标 pedestrian,有人标 person,合并前要先做类名归一化。写一段小脚本读出所有 txt 的第一列并统计频率,再对照 class_map 生成一张映射表,比手写静态映射更可靠。映射表里没有出现的类别 id 可以打印出来人工判断,避免漏掉一个原本数量很少的类。
做完映射后,全数据集跑一次有效性校验,重点检查三类错误:负坐标、越界框、以及宽或高小于 1 像素的框。YOLO 训练时不会因为一个负坐标直接报错,但损失函数会对离谱框给出不稳定梯度,早停也可能被异常值干扰。推荐在训练前把全部 txt 用一段 OpenCV 脚本读一遍,统计边界越界比例,超过 1% 就该回头查转换逻辑。对应关系可以整理成一张排错表:
| 错误类型 | 检查方式 | 处理建议 |
|---|---|---|
| 坐标为负 | 遍历 txt 全部数值 | 回看 xml 的 bndbox,修正归一化公式 |
| 越界框 | 统计是否超出图片边距 | 做边缘裁剪,而不是直接删除 |
| 宽或高小于 1 像素 | 统计最小 bbox 尺寸 | 删除或合并到邻近框 |
3.4 用数据可视化脚本检查标注对齐效果
标题里的数据可视化脚本作用就是这时候发挥:把 image 和对应的 txt 画出来,让人眼快速确认标签是否贴边、类别名称是否正确。典型调用方式如下:
python visualize.py \ --root road_obstacle \ --split train \ --classes classes.txt \ --sample 60 \ --out output/preview参数说明:--root指向数据集根目录,--split指定要画训练集还是验证集,--classes指向类别文件,--sample是随机抽取张数,--out是保存目录。脚本通常会按每个类别抽样,确保不是 60 张全是同一类。看图的重点是宽高比是否夸张、多目标场景有没有覆盖、每张图平均目标数是否是预期水平。平均目标数不到 1 说明大量图片没有实例,这会导致背景帧太多,训练时网络更偏向预测无目标。
提示:可视化不是可选项。训练前花 15 分钟看 100 张随机抽样,能省下训练后排查 mAP 异常的半天时间。
4. 在划分好的数据集上跑通 YOLOv8:yaml 配置与损失曲线排查
4.1 准备 dataset.yaml 与数据集挂载
现在把上述数据集交给 YOLOv8 训练。先写数据集配置,路径建议用绝对路径排除相对路径带来的坑:
path: /home/user/road_obstacle train: images/train val: images/val test: images/test names: 0: person 1: car 2: bicycle 3: motorcycle这个 yaml 里的names列表顺序必须和 classes.txt 一致。path是根目录,train/val/test是相对于path的目录。YOLO 训练脚本会自动到同名 labels 目录下找 txt,无需手动写 labels 路径。配置完先跑一句yolo detect train data=road_obstacle.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16用最小模型验证数据链路完整性。
参数说明:这里强推训障碍物检测先用 nano 模型而不是直接上 large,因为目的是验证数据,不是出最好指标。nano 会把速度压到几分钟一轮,任何标签错误会更快暴露。batch 大小根据显存调整,8GB 以下建议 batch=8 或者更小。
4.2 YOLO 目标检测流程里的损失函数与训练参数
开始完整训练前,还需要理解损失函数随 epoch 的变化,训练日志里的box_loss、cls_loss、dfl_loss是三个关键指标。路上障碍物检测属于类别数很少的目标检测,正常情况下cls_loss在前 30 个 epoch 快速下降并趋于平缓,box_loss则缓慢下降。如果cls_loss跑了几十个 epoch 还是十几的水平,说明某类图片数太少或被标注错误主导。YOLO 的训练损失函数由三部分组成,其中分类损失使用 BCEWithLogitsLoss,回归部分同时考虑 IoU 和 Distribution Focal Loss,这个组合决定了数据不干净时曲线会明显抖动。
训练命令如下:
yolo detect train \ data=road_obstacle.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=30 \ project=runs/road \ name=obs_nano参数含义逐个说明:epochs控制最大训练轮数,patience表示验证集指标连续 30 个 epoch 不提升就早停;lr0是初始学习率;project和name决定输出目录。对于 4000 张的小数据集,默认的 mosaic 增强足够用,不要额外把degrees调太大,路上障碍物不存在 90 度旋转的情况,训练时给太多旋转角度反而会误导模型。
4.3 从训练曲线和数据分布定位问题
训练中要盯住验证集mAP50-95和mAP50两个指标。mAP50只要求预测框与真值框 IoU 超过 0.5 才算正确,mAP50-95则平均多个 IoU 阈值。路上有小目标,人站在 50 米外可能只有十几个像素,这时mAP50可能到 0.85,而mAP50-95只有 0.5,这种差距说明位置精度不如分类精度,可以回头检查最小目标比例,不要盲目加模型复杂度。
下面是 4 类障碍物常见的损失曲线形态与对应判断:
| 现象 | 可能原因 | 对策 |
|---|---|---|
| cls_loss 先降后升 | 验证集类别不均衡 | 重做划分,增加少数类 |
| box_loss 下降极慢 | 标签框偏移严重 | 修 xml 转 yolo 的偏移换算 |
| 训练集与验证集 mAP 差大于 0.2 | 同帧画面泄漏到两个集合 | 按视频片段重新划分 |
| 前 20 epoch mAP 为 0 | 标签 id 与 class 文件没对齐 | 打印可视化核对类别颜色 |
这张表是排查路线,实际训练时如果出现前三行,多半不是网络问题,而是数据集本身。我见过很多团队一上来就换模型,结果损失出现大断层的原因是把同一视频连续帧分到了 train 和 val,帧间高度相似导致验证指标虚高。
4.4 训练数据标记不完整导致的三个高频报错
训练中常见的两个致命错误是AssertionError: train: No labels in ...和FileNotFoundError: label。第一个表示 labels/train 下没有 txt,但 images/train 有图片,检查目录名大小写,以及是否误把压缩包解压成带层级的嵌套目录。第二个表示某张图没有对应的 txt,多数是同步脚本漏拷了文件。第三个高频问题是图片格式,YOLO 依赖 OpenCV 读取,某些渠道下载的.jpg实际是 webp 编码,虽然扩展名是 jpg 读出来也会异常,建议遍历一遍做真实格式识别。
提示:收集数据完成后直接跑
find images -type f | wc -l与find labels -type f | wc -l,两者数量一致是最低门槛,数量一致不代表内容一致,仍要靠可视化兜底。
5. 收尾技巧:给四类障碍物数据集加一张「中心点散点图」做质量审计
5.1 目标中心点分布反映视野盲区
数据可视化脚本通常画的是框,但有一个比框更有诊断价值的输出:把所有标注框的中心点画到同一张归一化坐标图上。这个图能直观反映模型在哪些位置见过的目标多,哪些位置目标完全没出现过。路上障碍物数据如果中心点图左下大片空白,说明左下角长期没有目标,训练后模型在该区域漏检率偏高,因为 Anchor 匹配和特征图采样都依赖训练样本的空间分布。散点图脚本逻辑非常简单,读取全部 txt 的第二和第三列直接plt.scatter即可。
5.2 用每图目标数找出重复帧与漏标段
第二个小技巧是统计每张图的标注目标数并输出直方图。直方图最左边那一柱如果异常高,比如超过 10% 的图目标数为 0,问题通常来自视频抽帧:视频静止时段或者长时间红灯停车产生的几乎重复的背景帧混入数据集。这类帧会拉低训练效率,还容易让模型把背景学成负样本。此时要回到抽帧脚本,加入帧间差分,连续两帧像素变化低于阈值就直接丢弃。如果直方图右端出现目标数超过 30 的图,则要检查是否把大图的密集标注重复计算了,四类路上障碍物单张图超过 30 个目标已经非常拥挤,模型训练时下采样会丢失很多小目标。
5.3 把审计结果写进数据集的构建产物
最后把这个审计脚本固定下来,每次重新生成数据集后自动跑一遍,输出一张中心点散点图、一张目标数直方图和一份每类频次的 CSV。CSV 的作用是让后来接手的人一眼看到数据集的类别分布。这个三维审计产物比让同事看 100 张可视化原图高效得多,目标检测的调优往往不在网络结构,而在于对数据分布的把握。把这段脚本放在数据集的 tools 目录下,和 classes.txt 放在同一层,方便后续任何人复验。把这三件套的输出作为数据集的构建产物,用 CI 在每次数据集变更后自动重新生成并保留历史版本,后面任何一次指标回退都能从数据版本里找到原因。
本文还有配套的精品资源,点击获取