简介:自动驾驶场景道路异常检测数据集面向目标检测与自动驾驶视觉项目研发,整合8000张真实道路图片,类别覆盖轻型机动车、重型机动车、道路损坏、未铺面道路、行人、减速带六大类,可支撑坑洼、裂缝、井盖、减速带等异常目标识别,适合作为自动驾驶通用道路检测的补充数据。资源包共1个文件,为约5.08MB的PDF文档,内含数据集详细介绍与百度网盘获取方式,下载后即可取得完整图像数据及对应标签。标注采用labelimg工具完成,质量较高,并同时提供VOC(xml)、COCO(json)、YOLO(txt)三种常见格式,可直接用于YOLO等主流算法训练;另附YOLO11一键训练脚本及博主训练结果日志,便于快速复现实验、对比不同参数效果。该资源已有203人学习,对于正在构建自动驾驶道路异常检测数据集的开发者,能省去大量数据整理、格式转换与训练配置时间。
1. 自动驾驶道路异常检测:8000张图、三种标签格式、一套YOLO11训练脚本的落地玩法
做自动驾驶感知的人,最头疼的往往不是模型结构,而是数据。常规目标检测数据集里车、人、红绿灯一抓一大把,可真正让驾驶系统在路测时“翻车”的,往往是路面上的坑槽、散落的障碍物、施工区域和事故现场这类异常场景。这个标题给出的方案,是把8000张图和对应VOC/COCO/YOLO三种格式的标签一次性打包,再配一个YOLO11一键训练脚本,让“自动驾驶场景道路异常检测数据集”能直接拉起来训练。8000张图的规模在通用检测里不算大,但放在“道路异常”这个垂直子任务上,已经足够撑起一个能出效果的初版模型。这篇文章就顺着“数据长什么样 → 标签格式怎么转换 → YOLO11脚本怎么跑 → 参数怎么调 → 踩了哪些坑”这条线,把这个数据集方案讲透。
2. 数据集定位与标签格式:三种标注格式各解决什么问题
2.1 8000张图的规模定位:为什么这个量级对道路异常检测够用
先泼一盆冷水:8000张图做通用目标检测,像COCO那种80类、几十万张的规模,完全没法比。但道路异常检测是强垂直场景,类别少、背景相对固定,模型要学的是“正常的道路长什么样,哪里不正常”,而不是理解千变万化的开放世界。我一般拿到数据先看两个关键数字,一个是类别数,一个是单类别的平均样本量。用8000张图去分,单类别样本量通常是1000张以上,这对YOLO11这种带了预训练权重的模型来说,已经能学出稳定的特征了。
另一个角度是“异常”本身的高价值。真正有价值的异常图往往是大雾天气下模糊的障碍物、逆光时看不清的坑槽、雨天反光的路面。这类图在通用数据集里是长尾中的长尾,百度、谷歌上很难批量搜到,自己采集又需要真车配上传感器和标注人力。所以这个数据集最大的价值不是“8000张”,而是“8000张自动驾驶真实场景下的异常样本”,这里的稀缺性远比数量重要。
2.2 VOC格式:从JPEGImages和Annotations目录开始认识标注
VOC格式是历史最悠久、兼容性最好的标注格式。它的目录结构非常直观,一般分为JPEGImages存放原始图像,Annotations存放每个图对应的XML标注文件,另外还有ImageSets/Main目录存放训练集和验证集的txt划分文件。每个XML文件里记录的是图片文件名、路径、尺寸、通道数,以及图中的每个目标。
<annotation> <folder>JPEGImages</folder> <filename>road_anomaly_0001.jpg</filename> <path>/data/road_anomaly/JPEGImages/road_anomaly_0001.jpg</path> <source> <database>Road Anomaly Dataset</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>pothole</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>632</xmin> <ymin>514</ymin> <xmax>1047</xmax> <ymax>789</ymax> </bndbox> </object> </annotation>这段XML结构是所有后续转换操作的起点。<filename>字段是整个流程里最容易出问题的地方,因为有的数据集会写相对路径,有的只写文件名,有的甚至带中文,解析时稍不注意就找不到图片。还有一个最常见的问题:有的VOC标注文件里没有<path>字段,只剩文件名,但你用脚本处理的时候代码却按绝对路径去拼接,这里就需要在做格式转换前先统一。
2.3 COCO格式:一个JSON文件撑起的数据集
COCO格式把所有标注数据放在一个JSON文件里,文件结构比我第一次接触时想象的要复杂。顶层包含images、annotations、categories三个核心数组,所有标注信息通过一个整数id互相引用。跑模型时需要先写一段代码把JSON的嵌套结构解析成数据和标签,新手很容易被这段代码挡住。
{ "images": [ { "id": 1, "file_name": "road_anomaly_0001.jpg", "width": 1920, "height": 1080 } ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 2, "bbox": [632, 514, 415, 275], "area": 114125.0, "segmentation": [], "iscrowd": 0 } ], "categories": [ {"id": 1, "name": "construction_zone"}, {"id": 2, "name": "pothole"} ] }JSON文件里有两个非常容易搞错的点。第一是COCO的bbox格式,这个非常关键,它是“左上角坐标+宽+高”,这个格式跟后面YOLO格式的“中心点坐标+宽+高”完全不同,转换时必须彻底换算,不是偷懒能做好的事,很多翻车事故就出在这一步;第二是category_id的连续性,COCO的类别ID可以是非连续的,比如类别1和类别3存在、类别2不存在,这在训练时会让统一处理类别的mAP计算变得非常麻烦。精过做过的数据集会保证类别ID从1开始连续编号,但这并不是标准强制要求的。
2.4 YOLO格式:每张图一个TXT,直接喂给YOLO11训练的最简格式
YOLO格式是整个生态里最简单粗暴的标注方式,每张图片对应一个同名txt文件,文件中每一行代表一个目标对象,一行包含5列,分别是类别ID、归一化后的中心点x坐标、中心点y坐标、宽度、高度。归一化指的是把坐标值除以图片的原始宽高,让所有值落在0到1之间。
2 0.4372 0.6032 0.2161 0.2546 1 0.6904 0.5103 0.1886 0.2012因为类别顺序是标注者在转换脚本里定义的,不同数据集的YOLO格式TXT文件行首数字含义可能完全不同,所以拿到数据的第一件事必须是读取data.yaml文件,确认类别顺序是否与TXT文件对应,这个顺序在YOLO训练里是绝对的主线,一定要最先确认。
这个简洁格式带来一个隐藏问题:图片的EXIF信息里的旋转角度不会自动校正。手机拍摄或无人机拍摄的图片可能自带方向信息,但YOLO格式的TXT注释是基于被读入内存时的原始像素坐标算的,如果YOLO11在加载图片时对旋转角度做了处理,长宽对不上,就会出现一个耗时很长的排查过程——最后发现只是图片被旋转了一下。因此拿到数据后第一件事就是检查图片长宽比和TXT里的坐标值是否在合法范围内。
3. 三种格式互转:坐标映射与类别文件的边界坑
3.1 VOC转YOLO:从XML解析到生成TXT的完整脚本
三种格式的转换是这套方案的核心环节,也是新手最容易卡住的地方。VOC转YOLO的流程分为四步:第一步读取XML文件,第二步提取每个object的name和bndbox坐标,第三步把VOC格式的左上角坐标加宽高转换成YOLO格式的中心点坐标加宽高,第四步把类别名称映射成整数ID并写入TXT文件。
import xml.etree.ElementTree as ET import os from pathlib import Path # 类别映射表:名称到ID的映射,顺序决定训练时类别ID class_mapping = { 'normal': 0, 'pothole': 1, 'construction_zone': 2, 'debris': 3, 'accident_site': 4, 'flooded_road': 5 } def voc_to_yolo(xml_path, output_dir, image_width, image_height): tree = ET.parse(xml_path) root = tree.getroot() txt_name = Path(xml_path).stem + '.txt' txt_path = os.path.join(output_dir, txt_name) with open(txt_path, 'w') as f: for obj in root.findall('object'): name = obj.find('name').text if name not in class_mapping: continue class_id = class_mapping[name] bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 核心转换公式:VOC坐标系 -> YOLO归一化坐标系 x_center = (xmin + xmax) / 2 / image_width y_center = (ymin + ymax) / 2 / image_height width = (xmax - xmin) / image_width height = (ymax - ymin) / image_height # 限制边界值,防止坐标越界 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) width = min(width, 1.0) height = min(height, 1.0) f.write(f'{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n')转换公式的实质是把绝对的像素坐标换算成相对图片宽高的比例,这个比例必须是小于1的小数。主脚本里需要注意的三件事:第一是image_width和image_height这两个参数不能直接用XML里的<size>字段的值代替,因为它们可能被裁剪过而存在误差,严谨的做法是用PIL或cv2直接读取真实图片尺寸;第二是类别映射表,这个决定了输出TXT第一列的数字含义,必须和后续YOLO11训练时的data.yaml保持一致;第三是越界处理,标注时手滑把坐标标出了图片范围,生成的TXT里会出现大于1的值,训练时会被YOLO11直接跳过,所以要在脚本里加一个数值钳制。
3.2 COCO转YOLO:JSON逐行解析的排查清单
COCO转YOLO的脚本逻辑要多一步,因为需要建立image_id到文件名的映射,以及category_id到类别映射的映射。存到字典里,然后遍历annotations数组找到每个对象。
import json import os def coco_to_yolo(json_path, output_dir, image_base_dir): with open(json_path, 'r') as f: coco = json.load(f) # 建立id -> 文件名的映射 image_id_to_name = {} for img in coco['images']: image_id_to_name[img['id']] = img['file_name'] # 建立category_id -> 类别的映射,注意COCO的id不一定是连续的 id_to_category = {} for cat in coco['categories']: id_to_category[cat['id']] = cat['name'] # 建立image_id -> 类别ID列表的映射 image_annotations = {} for ann in coco['annotations']: image_id = ann['image_id'] image_annotations.setdefault(image_id, []).append(ann) for image_id, anns in image_annotations.items(): file_name = image_id_to_name[image_id] txt_path = os.path.join(output_dir, file_name.replace('.jpg', '.txt')) img = get_image_size(os.path.join(image_base_dir, file_name)) w, h = img[0], img[1] with open(txt_path, 'w') as f: for ann in anns: category_id = ann['category_id'] bbox = ann['bbox'] x, y, bw, bh = bbox # COCO的bbox是左上角坐标,YOLO需要中心点坐标 x_center = (x + bw / 2) / w y_center = (y + bh / 2) / h width = bw / w height = bh / h f.write(f'{category_id - 1} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n')这个脚本里最需要注意的一点是,COCO的category_id通常是1开始,而YOLO的类别ID是从0开始的,转换时先统一减去1或建一个新映射字典。另一个隐藏很深的坑是:COCO的images数组里的id字段不一定是从1开始的,有的数据集是0,有的甚至是字符串,所以一定要用字典映射而不是数组索引。如果你发现生成的TXT文件数量少于图片数量,最可能的情况是数据集里存在没有标注对象的图片,COCO完全允许这种图片存在,但YOLO格式没有“空TXT”的概念,处理时要么为空图片新建一个空文件,要么在训练时跳过这些图片。
3.3 三种格式对比与验证方法
格式转换完成后,验证结果的质量比转换本身更重要。我一般用两个办法来验证转换是否正确:一个是抽样检查,把TXT里还原出的坐标画回原图上,肉眼确认框的位置是否吻合;另一个是统计检查,批量扫描所有TXT文件,确认不存在超出图像范围的值,也不存在类别ID超出类别总数的值。
| 格式 | 标注文件形态 | 坐标体系 | 适合的框架 | 主要坑点 |
|---|---|---|---|---|
| VOC | 每图一个XML | 左上角xmin/ymin + 右下角xmax/ymax | Faster R-CNN、SSD | XML字段缺失、路径不一致 |
| COCO | 整个数据集一个JSON | 左上角x/y + 宽w + 高h | Detectron2、MMDetection | category_id不连续、bbox格式易混 |
| YOLO | 每图一个TXT | 中心点x/y + 宽w + 高h(归一化) | YOLO系列、Ultralytics | 类别ID从0开始、坐标越界无警告 |
三者之间最关键的差异在坐标体系。如果只有一份格式的标签,通过转换脚本生成其他两种,那转换后一定要回到图上去目检至少20张图,特别是那些有多个小目标、目标互相重叠的图。一个小目标转换时少了一个小数点,框可能就偏了十几个像素,在1280分辨率下可能还能接受,但训练出来的模型AP至少掉个两三个点。
4. YOLO11一键训练脚本:从环境配置到损失曲线判读
4.1 环境配置与目录结构:跑通之前先别急着装CUDA
用YOLO11(Ultralytics版本)训练之前,环境配置是劝退很多新手的第一个坎。常见做法是创建独立的conda环境,然后安装ultralytics包。这里有一个重要体验:装CUDA之前可以先装CPU版的PyTorch把一套代码跑通,之后再换GPU版,这样可以避免在一开始就卡在CUDA版本不匹配的问题上卡很久。
conda create -n yolov11 python=3.10 -y conda activate yolov11 # 先装CPU版本验证代码流程,后续再换GPU版 pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu环境装完后,数据目录的摆放方式也会直接影响脚本能否跑通。YOLO11训练时通过data.yaml文件可以指定数据集路径,无论是绝对路径还是相对路径都支持。
datasets/ ├── images/ │ ├── train/ │ │ └── road_anomaly_0001.jpg │ └── val/ │ └── road_anomaly_0101.jpg ├── labels/ │ ├── train/ │ │ └── road_anomaly_0001.txt │ └── val/ │ └── road_anomaly_0101.txt └── data.yaml这个结构是YOLO训练的标准结构,train和val两个文件夹分别放图片与对应的TXT标签,同一个文件需要保持相同的名字。8000张图的常见划分方式是6400张训练、1600张验证,验证集存在多个类别不均衡的问题,因此需要优先保证每个类别在验证集中至少有20-30个实例,便于评估AP值。
# data.yaml path: /Users/yourname/datasets/road_anomaly train: images/train val: images/val nc: 6 names: 0: normal 1: pothole 2: construction_zone 3: debris 4: accident_site 5: flooded_roaddata.yaml里最不起眼却最容易导致训练崩掉的字段是nc和names。如果数据集中实际有7个类别的标注但nc: 6,训练时模型输出维度是6,遇到第7类标签时会直接报索引越界错误,损失函数计算出NaN,整个训练过程在第一个epoch就没法继续。所以拿到本标题数据集的第一件事就是用脚本统计所有TXT文件里的最大类别ID,再把data.yaml里的nc对应调整,确保类别数量严格一致。
4.2 YOLO11训练命令:从最小可用到完整调参
环境就绪、data.yaml就位之后,就可以用一行命令拉起训练了。YOLO11的Python API是最直接的打开方式,既能做推理也能做训练。我这里给出一个适合道路异常检测场景、兼顾显存和精度的常见参数组合。
from ultralytics import YOLO # 加载预训练权重,imgsz=640是做迁移学习的标准输入尺寸 model = YOLO('yolo11n.pt') results = model.train( data='datasets/road_anomaly/data.yaml', epochs=100, imgsz=640, batch=16, device=0, workers=8, lr0=0.001, cache=True, patience=20, project='runs/road_anomaly', name='yolo11n_640_b16', seed=42, )yolo11n.pt是YOLO11系列里参数量最小的预训练权重,显存占用少且收敛速度最快。在自动驾驶道路异常这个场景下,我用这个配置跑下来一般300到500张验证图就能逼近稳定收敛。几个参数按顺序解释一下:batch直接影响显存,16的batch在单张12GB显存显卡上配合yolo11n没有问题;imgsz是个可以做文章的参数,标注数据如果是1080p的大图,训练时提升到960或1280能加强小目标检测能力,但显存占用会显著上升;patience是早停的耐心值,连续20轮验证集mAP不涨就自动停止,这个参数能有效避免过拟合;cache设为True可以把图片预加载进内存,如果数据集在机械硬盘上能大幅缩短每轮训练的时间。
4.3 训练时的动态观察
YOLO11训练过程中会在终端打印每一轮的训练损失box_loss、分类损失cls_loss、验证集mAP50和mAP50-95等指标。我给一个基于实操经验的判断标准:训练开始时box_loss下降比较快,前20个epoch从0.1量级降到0.05左右,后续增速放缓;分类损失在小类别数据集上容易震荡,如果连续多个epoch不降反而上升,就要考虑学习率过大或数据本身存在标注噪声。有一个隐蔽的“翻车”现象:训练损失一直在下降,但验证集mAP不动,说明过拟合了,此时不是加大数据增强而是减少训练轮数或调整早停数值。
另一个值得关注的指标是mAP50-95。对道路异常场景来说,检测目标是路面上的坑槽这类物体,边界模糊且与背景高度相似,mAP50-95的值通常比通用目标检测要低,这个现象说明当前模型的定位能力和边界回归还有提升空间,可以通过提高输入分辨率、尝试更大的模型(比如把yolo11n换成yolo11s或yolo11m)来改善。
4.4 YOLO11导出ONNX:从PyTorch权重到可部署模型
训练完成后,需要把训练好的权重转为ONNX格式用于部署,或者继续在PyTorch框架内实验。YOLO11的导出过程非常像把Python训练好的模型“固化”下来,只需要一行命令。
from ultralytics import YOLO model = YOLO('runs/road_anomaly/yolo11n_640_b16/weights/best.pt') model.export(format='onnx', imgsz=640, half=True)导出时有个关键参数,half=True代表把模型权重转为FP16半精度存储。这个做法能让模型体积减半并显著提升推理速度,特别是部署到NVIDIA GPU上时收益比较明显。需要注意的是,half=True训练时如果显存不够可以先关闭,等模型收敛后再用半精度导出,推理精度并不会因此有太大损失。
5. 避坑指南:道路异常检测数据集最常见的5个坑
5.1 坑一:类别不均衡导致模型完全忽略某些异常类
- 现象:训练完后检测一个包含施工区域的图片,模型只输出了路面上的障碍物类别,施工区域这个类别完全没有框。
- 原因:数据集里6个类别的样本量差距悬殊,最多的类别有2500张图,最少的类别只有200张图,训练时大类别主导了损失函数的梯度,小类别被“淹没”了。
- 解决:先统计每类样本量,对样本少的类别做数据增强,比如随机裁剪、旋转、亮度变化;把YOLO11损失函数里的
cls_pw参数按类别频率逆向设置,给小类别更高权重。在Ultralytics里可以在训练参数中用cls=0.7适当调高分类损失的比重,配合cos_lr=True让学习率衰减更平滑。
5.2 坑二:图片分辨率不一致导致TXT坐标错位
- 现象:验证集里某些图的mAP计算出来特别低,画出来发现所有的框都偏移了大约三分之一的图像区域。
- 原因:数据集中混有1920×1080的大图和几分之一的缩略图,转换脚本用每张图片的实际尺寸做了归一化,但训练时检查到部分图片尺寸对不上,坐标换算发生错位。
- 解决:定义一组统一尺寸(比如1280×720),加载时把所有图片缩放到统一尺寸,TXT坐标基于这个统一尺寸重新归一化。更省事的做法是训练时保留原尺寸,靠YOLO11的
rect=True按比例批量加载图片,但需要确认坐标标注确实与原始图片匹配。
5.3 坑三:背景空洞导致的结果是“输出空白框”
- 现象:验证集输出的框里大量目标没有经过细分,输出的是一整个大框,把多个不同类别的目标包含在内。
- 原因:道路异常数据集中很多小目标紧挨着,转换标注时两个相邻框在坐标上重叠了超过IOU阈值,YOLO11训练时做了非极大值抑制,把两个目标压成一个大框。
- 解决:标注阶段就要避免重叠框,数据清洗时用脚本检测同类别的重叠框,重叠面积超过30%则删除其中一个或重新标注。训练完成后调低
conf阈值(比如从0.25降到0.15),看输出框能否重新分裂回两个独立目标。
5.4 坑四:训练损失正常但验证集mAP为0
- 现象:训练损失曲线平滑下降,但所有类别的mAP都是0,验证集推断结果一个目标都没有。
- 原因:data.yaml里的类别顺序和TXT文件第一列的类别ID完全不对应。比如TXT里第一列是0代表pothole坑洞,但data.yaml里names[0]是normal正常路面,模型把整个目标都当背景忽略了。
- 解决:写一个验证脚本,读取validation图片对应的TXT文件,按坐标画框并用data.yaml的names索引类别名,人工抽查10张图,确认没有出现“0号是坑洞但names[0]是正常路面”的错位。
5.5 坑五:训练中爆显存但不是在batch_size上踩坑
- 现象:
batch=32时显存占用稳定在11GB左右,把batch调到64后程序直接崩溃报CUDA out of memory。 - 原因:这个数据集里有几张特别大的图,加载时为了保持宽高比,统一尺寸后实际送入网络的像素总量比一般图多出近一倍,显存占用并不像参数看起来那样线性增长。
- 解决:先用小batch跑一个epoch看峰值显存,再逐步提升batch。设置
cache=False避免把整图缓存打进显存,也可以用imgsz=640减少单张图的显存开销。遇到个别超大图导致的爆显存,直接把它们从训练集中剔除,这类样本在道路异常数据里往往是无人机拍摄的航拍图,比例失调,统计价值有限。
6. 进阶玩法:用YOLOv11做道路异常检测的小模型蒸馏与多模态协同
这个数据集的进阶方向是“部署落地”,而不是在训练脚本里反复调参。自动驾驶场景的异常检测,最终要跑在车载计算平台上,算力有限。我一般会做两步:先把大模型蒸馏到小模型,再用多模态方式过滤误报。
第一步是蒸馏。训练一个大模型yolo11xl作为教师模型,输出soft label(即每个类别带真实概率而非硬标签),然后把教师的中间特征图同时作为学生模型yolo11n的监督信号。操作上,用Ultralytics提供的蒸馏接口或自己写一个损失函数,把学生模型的输出对教师输出做KL散度对齐,loss_weight设为0.5左右比较稳妥。蒸馏后的小模型精度通常能追上大模型的90%以上,而推理帧率从80FPS提升到200FPS以上(以T4为例)。
第二步是多模态过滤。YOLO11输出的是检测框和类别,不能回答“这个坑槽有多深”或者“这个障碍物是否在车道线内”。更稳的方案是把YOLO11当作“候选区域生成器”,把检测到异常的图像裁剪区域交给分类模型或多模态模型做二次判定。当检测输出框位置偏离道路区域时,二次校验会给出低置信度,将其降级为无效告警,这样能大幅减少自动驾驶系统误刹车。
第三步是评估检测延迟与准确率的平衡。把导出后的ONNX模型用TensorRT加速,只做FP16精度,在T4上640分辨率下推理延迟基本能控制在5ms左右。需要观测的是CPU-GPU的传输延迟,实测这类延迟有时比推理本身还高,这时就需要批量推理或异步流式处理来掩盖传输开销。
我在实际交付中有一个教训:一开始把精力全放在调高mAP数字上,后来才发现跑在嵌入式设备上时,年薪最多的其实是推理延迟和数据预处理耗时。8000张图的数据集也许不够冲上论文级别的好成绩,但够用,能快速把项目跑通并出结果——自动驾驶场景里的道路异常检测,绝大多数时候要的是快速迭代、稳定可靠,而不是绝对精度那零点几个百分点。希望这个方案能帮你在自己的数据上少走一点弯路。
本文还有配套的精品资源,点击获取