ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

自动驾驶道路异常检测数据集:8000张图+YOLO11一键训练

自动驾驶道路异常检测数据集:8000张图+YOLO11一键训练 简介一份面向自动驾驶场景道路异常检测的目标检测数据集资料包收录8000张真实道路场景图片覆盖轻型机动车、公交车/卡车/挖掘机等重型机动车、道路损坏坑洼/裂缝/凸起/井盖、未铺面道路、行人、减速带六大类别适合算法工程师与科研人员用于YOLO等模型的训练与验证也可作为通用道路检测数据集的补充。数据采用labelimg标注同时提供VOC、COCO、YOLO三种常用标签格式可直接接入主流检测框架另附YOLO11一键训练脚本及博主训练结果日志便于对比调参和快速复现。资源共1个文件为PDF格式约5.08MB内附数据集的详细介绍、标签样例与百度网盘获取链接。目前已有203人浏览学习对于正在积累自动驾驶路况检测数据或需要补充异常路面样本的开发者而言具有不错的实用参考价值。 做自动驾驶感知的人十有八九被同一个问题卡过车辆、行人、红绿灯这些常规目标公开数据集一大把模型调到 90% 以上 mAP 不稀奇。可一到路测真正让系统翻车的往往是那些长尾事件——前车压过的塑料桶、路面上突然出现的坑洼、施工区散落的锥桶、夜里的遗撒物。这类目标样本少、形态杂、场景极端想训练又凑不齐数据。这个「目标检测-自动驾驶场景道路异常检测数据集-8000张图-对应VOC/COCO/YOLO三种格式标签YOLO11一键训练脚本」就是冲这个痛点来的。8000 张带道路异常标注的图配好 VOC、COCO、YOLO 三种通用格式标签外加一个 YOLO11 一键训练脚本适合谁想快速搭起道路异常检测基线、又不想从零标数据的人。下面把怎么验证数据、怎么转格式、怎么调参、坑在哪一次性讲清楚。2. 先看懂自动驾驶场景下的道路异常检测类别体系、数据规模与标签质量2.1 道路异常检测和通用目标检测差在哪异常不是类别是“不该出现”通用目标检测的类别体系是“稳定的物体”人、车、猫、狗、红绿灯它们在语义上是闭环的特征相对稳定模型学的是“这个东西长什么样”。而道路异常检测的类别体系完全不同——坑洼不是一个物种而是路面的一种破损状态遗撒物是什么都可能一个纸箱、一块轮胎皮、一袋垃圾、掉落的零件形态千变万化。模型在这里真正要学的不是“这个东西是什么”而是“这个路面状态是不是不该出现在这里”这个边界决定了标注和模型的难度。这里还要先厘清一个概念标题里的“异常检测”不是无监督的 anomaly detection离群点检测而是有监督的目标检测。类别是明确定义好的比如 pothole坑洼、crack裂缝、debris遗撒物、traffic_cone锥桶、water_accumulation积水每一类都有盒子。别一看到“异常”两个字就去做无监督或者开放词汇目标检测那是另一条技术路线对自动驾驶落地来说固定类别的监督检测才是当前工程上最稳定的做法。另外提一句自动驾驶端到端方案。现在很多团队在推端到端感知直接融进一个大模型里但道路异常检测在实操中还是倾向做成独立模块。原因很简单端到端模型对长尾异常的召回率不好控制出了问题也不好定位是感知错了还是规划错了。独立模块可以单独调阈值、单独换模型、单独做数据补充这对路测迭代来说很重要。2.2 8000 张图够不够用按类别分布和场景分布判断数据价值8000 张图从数量上看不算多但对道路异常检测这个场景来说关键是看两个维度实例数和场景分布。假设平均每张图标注 2-4 个目标8000 张大约是 1.6 万到 3.2 万个实例。对坑洼、裂缝这类单类别形态相对统一的目标这个量级配合 YOLO11 的 COCO 预训练权重完全能跑出一个实用基线。因为迁移学习已经把通用特征学好了你需要模型学习的是“异常”这个概念的差异化特征而不是从头学怎么识别边缘和纹理。比张数更重要的是场景分布。拿到数据先别急着训练把夜间、雨天、逆光、高速、城市道路这几类场景的占比列出来。夜间样本如果不到 10%模型白天能跑一到夜间漏检率会吓你一跳雨天样本少路面积水的反光会让模型把正常路面也判成异常。这不是模型玄学是成像特征变了——白天的坑洼阴影纹理清晰夜间只有车灯照亮的那一小块区域能看到路面特征完全不同。提示衡量一个目标检测数据集值不值得用张数是最次要的指标。优先看类别实例数是否均衡再看场景分布是否覆盖你的部署环境最后才是总量。2.3 拿到数据先做的三件事标注质量抽检、类别分布统计、空图检查我一般拿到任何数据集第一件事不是训练而是跑一个脚本把底摸清。这个脚本读 VOC 格式的 XML标题里三格式都有VOC 通常是最原始的统计类别实例数、图片尺寸范围顺便检查有没有只有 XML 没有对应图片的孤儿文件。import os from collections import defaultdict import xml.etree.ElementTree as ET voc_dir VOC # 数据集里 VOC 标签所在目录 counts defaultdict(int) img_sizes [] orphan_xml [] for xml_name in os.listdir(voc_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(voc_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() # 读取图片尺寸 size root.find(size) w int(size.find(width).text) h int(size.find(height).text) img_sizes.append((w, h)) # 检查对应的图片文件是否存在 filename root.find(filename).text img_path os.path.join(JPEGImages, filename) if not os.path.exists(img_path): orphan_xml.append(xml_name) # 统计每个类别的实例数 for obj in root.iter(object): name obj.find(name).text counts[name] 1 print(类别实例数:, dict(counts)) print(图片尺寸范围:, min(img_sizes), 到, max(img_sizes)) print(孤儿 XML 数量:, len(orphan_xml))这段脚本的输出直接决定后面怎么走。如果类别实例数相差超过一个数量级比如井盖 5000 个、遗撒物只有 200 个训练时就要做类别平衡处理不然少数类基本被多数类淹没。如果图片尺寸不统一训练时要让 YOLO11 的 imgsz 参数适配最小边否则缩放到 640 时小目标直接消失。孤儿 XML 必须清理否则转换脚本会在中途崩掉。夜间样本占比怎么查看文件名约定很多数据集会在文件名里带 night、rain 这类标识没有的话把图片转灰度取平均亮度亮度低于阈值的归为夜间。阈值不用很精确抽样看 20 张确认一下就行。这一步不花多少时间但能避免你把一个带严重场景偏置的数据集当成通用数据直接训。3. 把 VOC/COCO/YOLO 三格式打通转换脚本与四个边界坑3.1 三格式的底层差异XML 的像素框、JSON 的 COCO 结构、TXT 的归一化坐标标题里标注了 VOC/COCO/YOLO 三种格式这意味着数据集已经不是原始标注而是被加工成三份。但工程上你很可能只需要其中一份或者需要把它们转成自己团队的标准格式。转换的前提是先搞清楚三者的存储方式。VOC 格式是 Pascal VOC 的 XML 文件一个目标一个object块里面有name类别字符串和bndbox的 xmin、ymin、xmax、ymax单位是像素坐标是左上角和右下角。COCO 格式是一个大 JSON 文件包含 images、annotations、categories 三个数组annotations 里的 bbox 是[x, y, width, height]同样是像素坐标但语义从“左上右下”变成了“左上宽高”。YOLO 格式最简单也最容易出错每张图对应一个同名的 TXT 文件每行一个目标class_id x_center y_center width height全部归一化到 0-1。这里要先立一个原则转换时永远以像素坐标VOC 或 COCO作为唯一真值去推导其他格式不要在 YOLO 和 COCO 之间互相推导。归一化坐标一旦经过浮点截断再反转回像素框的位置会偏移小目标尤其明显。你先用 VOC 的 XML 作为源一次性生成 YOLO 和 COCO就避免了二次转换的精度损失。3.2 转换脚本从 VOC XML 到 YOLO TXT 和 COCO JSON 的一键转换下面给一套我常用的转换脚本。它从一个 VOC 目录读 XML生成 YOLO 格式的标签目录再单独生成 COCO 的 JSON。import os import xml.etree.ElementTree as ET CLASSES [pothole, crack, debris, traffic_cone, water] # 按数据集的names实际填 def voc2yolo(xml_path, label_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # YOLO需要中心点坐标和宽高全部除以图片宽高做归一化 x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) with open(label_path, w) as f: f.write(\n.join(lines)) # 批量转换把 VOC 目录下所有 xml 转成同名 txt voc_dir VOC label_dir labels/yolo os.makedirs(label_dir, exist_okTrue) for xml_name in os.listdir(voc_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(voc_dir, xml_name) txt_name xml_name.replace(.xml, .txt) voc2yolo(xml_path, os.path.join(label_dir, txt_name))这里的逻辑要点是类别字符串先映射到 CLASSES 列表的索引这个索引就是 YOLO 的 class_id从 0 开始。坐标转换的核心公式是(xmin xmax) / 2.0 / w注意2.0不能写成2虽然 Python 3 里两者都是浮点除法但如果你复制的是 Python 2 的老代码整除会直接把所有 x_center 变成 0。这也是坐标归一化最经典的翻车点。COCO 的转换稍微复杂一点因为要维护 image_id 和 annotation_id 的递增关系import json import xml.etree.ElementTree as ET def voc2coco(xml_files, json_path, classes): images, annotations [], [] ann_id 1 for img_id, xml_path in enumerate(xml_files): tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) w int(size.find(width).text) h int(size.find(height).text) images.append({ id: img_id, file_name: filename, width: w, height: h }) for obj in root.iter(object): name obj.find(name).text if name not in classes: continue cat_id classes.index(name) 1 # COCO的category_id从1开始 bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) annotations.append({ id: ann_id, image_id: img_id, category_id: cat_id, bbox: [xmin, ymin, xmax - xmin, ymax - ymin], area: (xmax - xmin) * (ymax - ymin), iscrowd: 0 }) ann_id 1 categories [{id: i 1, name: name} for i, name in enumerate(classes)] with open(json_path, w) as f: json.dump({images: images, annotations: annotations, categories: categories}, f)这段代码里最容易错的是 bbox 的四个值。VOC 给的是左上右下xmin、ymin、xmax、ymaxCOCO 要的是左上宽高x, y, width, height所以xmax - xmin和ymax - ymin一定要算对。另外 COCO 的 category_id 从 1 开始和 YOLO 的 class_id 从 0 开始不一样转换后要保留一份类别映射表后面训练写 data.yaml 时要用同一个顺序。3.3 四个边界坑类别 ID 映射、坐标归一化、空标注、划分方式第一个坑是类别 ID 映射。VOC 里类别是字符串COCO 里 category_id 从 1 开始YOLO 里 class_id 从 0 开始。如果你直接拿枚举顺序当 ID前面加了新类别后面所有标签就全错位了。我的习惯是固定一个 CLASSES 列表所有转换都从它取索引然后把这个列表原样写进 data.yaml 的 names一步对齐。第二个坑是坐标归一化。上面提到过除法的坑还有一个隐蔽版本有些标注框会超出图片边界比如 xmax 算出来是 1300但图片宽度只有 1280。VOC 的标注不规范时常见这种情况。YOLO 格式允许坐标略大于 1 吗不训练时会警告甚至报错。转换时要做 clamp把坐标限制到 [0, 1] 区间。第三个坑是空标注。道路异常数据集里一定会有“这张图没有异常目标”的负样本这是故意的用来让模型学会“正常路面什么都不输出”。YOLO 格式里空标注就是空的 TXT 文件一行都没有COCO 里是该图片在 annotations 数组中没有对应项但 images 数组里要保留它。如果你在转换时把空图删掉模型没见过正常路面推理时会把什么路面都判成异常误检直接爆炸。第四个坑是数据划分方式。道路数据通常是采集视频后抽帧得到的相邻帧高度相似。如果随机 shuffle 划分训练集和验证集同一段路的连续帧会同时出现在两边验证指标虚高——模型记住的是具体帧的纹理不是抽象的“坑洼”概念。正确做法是按采集批次或路段划分先给数据按文件名前缀分组再按组分训练/验证集。4. 用 YOLO11 一键训练脚本跑通道路异常检测最小命令与必调参数4.1 先把数据按 YOLO 的目录布局排好train/val 分家data.yaml 对齐类别名不管数据集自带的是 VOC 还是 COCO 格式训练 YOLO11 前都建议统一转成 YOLO 的 TXT 标签然后按下述目录结构排布。这个结构是 ultralytics 的默认约定目录名不能改改了训练脚本找不到标签。datasets/road_abnormal/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml 是训练的关键配置文件内容很简单path: datasets/road_abnormal train: images/train val: images/val nc: 5 names: [pothole, crack, debris, traffic_cone, water]这里最容易踩的坑是path字段。如果你把数据集放在项目根目录下path写相对路径datasets/road_abnormal即可如果放在别的盘符下写绝对路径会让脚本换机器后失效。我一般把数据集和训练脚本放在同一个目录下path永远写相对路径。另外names的顺序必须和转换脚本里 CLASSES 的顺序完全一致这个顺序就是 YOLO 标签里数字 class_id 的对应关系。4.2 一键训练脚本做了什么从环境校验到启动训练的最小闭环标题里的“一键训练脚本”本质是把环境准备、数据校验、启动训练三步打包成一个 shell 脚本。下面这个版本我实际用过逻辑是先装依赖再校验数据目录和标签数量最后启动训练。每一步失败都会直接退出不让你在数据路径错的情况下白跑几个小时。#!/bin/bash set -e # 1. 安装依赖固定一个大版本避免API变动 pip install ultralytics torch2.0 # 2. 校验数据目录和标签数量 python - EOF import os for split in [train, val]: img_dir fdatasets/road_abnormal/images/{split} lbl_dir fdatasets/road_abnormal/labels/{split} assert os.path.exists(img_dir), f缺少 {img_dir} assert os.path.exists(lbl_dir), f缺少 {lbl_dir} n_img len(os.listdir(img_dir)) n_lbl len(os.listdir(lbl_dir)) print(f{split}: images{n_img}, labels{n_lbl}) # 图片数和标签数不必完全相等空标注图只有图片没有标签但标签数不能大于图片数 assert n_lbl n_img, f{split} 标签数大于图片数标签有误 EOF # 3. 启动训练 yolo detect train \ modelyolo11n.pt \ datadatasets/road_abnormal/data.yaml \ epochs120 \ imgsz640 \ batch16 \ patience20 \ device0 \ projectruns/road_abnormal \ nameexp_baseline脚本里的set -e很关键它让脚本在任意一条命令返回非零状态时立即退出。训练前校验那一段也不可省——我就遇到过 labels 目录里混进了 .json 文件训练脚本读标签时报错排查了半天。校验脚本把图片数和标签数打印出来一眼就能看出问题。yolo detect train是 ultralytics 的 CLI 入口modelyolo11n.pt指定 nano 版本的预训练权重显存不够就先用它把流程跑通。4.3 必调参数imgsz、batch、epochs 加一个 workers别只看默认值ultralytics 的默认参数能跑通但跑不出最优效果。道路异常检测这个场景下面四个参数必须动脑子调。imgsz默认 640对大多数情况够用但道路异常里有大量远距离小目标——50 米外的锥桶在 1080p 图上可能只有 30x30 像素缩到 640 后只剩十几像素。如果验证集里小目标多把 imgsz 调到 960 或 1280小目标的召回率会明显提升。代价是显存和训练时间翻倍。我的习惯是先用 nano 模型跑一遍 640 和 1280对比验证集 mAP 再决定。batch跟着显存走。12G 显存配 imgsz640 时 batch16 是安全的训练中如果 OOM 就降到 8。要注意 batch 低于 8 时BN 层的统计量会很不稳定容易出 NaN 或者精度骤降后面避坑章节细说。epochs起步设 120配patience20早停。道路异常类别少模型通常 60-80 轮就收敛不用硬跑 300 轮早停到了自然停。workers是数据加载的并行进程数。Linux 上设 4-8 可以明显加快数据读取Windows 上设大于 0 极大概率卡死这是 multiprocessing 的实现差异Windows 下老老实实设 0。4.4 YOLO11 和 yolov26 传闻选现役稳态版本做落地网上总能看到 yolov26 目标检测的各种源码和效果截图这里说句实在话实操里不要追新版本。YOLO11 是 ultralytics 当前主推的稳定版本对 VOC/COCO/YOLO 转好的数据是原生支持改完 data.yaml 直接就能训。所谓 yolov26 大多是自媒体根据 ultralytics 的 roadmap 推断的源码要么不完整要么是第三方魔改拿来落地风险很高。做自动驾驶项目模型版本的选择逻辑是“生态成熟度优先于指标领先”。YOLO11 的部署工具链、TensorRT 转换、量化支持都是现成的团队里随便一个人都能接手。等新版本真出稳定版再迁移成本也不高——无非是改一下 model 参数。5. 道路异常检测训练的 5 条血泪踩坑记录现象、原因、解决5.1 白天 mAP 不错夜间直接摆烂场景分布不均衡的后果现象训练时 loss 正常收敛白天验证集 mAP 有 0.78一跑夜间视频检测框寥寥无几坑洼和遗撒物基本全漏。原因数据里夜间样本占比太低。模型学到的“道路”语义是白天晴天的——坑洼有清晰阴影纹理遗撒物颜色与环境对比度高而夜间只有车灯照亮的一小块区域路面纹理信息少特征分布和白天的差异远大于类别差异。目标检测模型对成像条件的过拟合就是对场景分布的过拟合。解决第一步先统计夜间样本占比低于 15% 就得补数或者做增强。ultralytics 训练时自带 HSV 扰动hsv_v参数默认 0.4可以调到 0.6 增强亮度变化也可以对夜间样本做 Mosaic 增强时提高其采样权重。但增强只是缓解真实夜间样本不够的话模型白天再强夜间照翻车。5.2 井盖检得好、遗撒物全漏类别不平衡怎么处理现象验证集整体 mAP 还行但一看每类 AP井盖类 0.9遗撒物类只有 0.2。混淆矩阵里遗撒物的框几乎全部被漏检。原因类别实例数差 10 倍以上。YOLO 的损失是所有类别平均贡献的多数类在梯度里占绝对主导模型倾向于把不确定性高的框判成多数类或者对少数类特征学习不充分。解决先跑 2.3 的统计脚本算清每个类别的实例数。对少数类做过采样——重复把少数类图片放进训练集或者对少数类图片单独做复制增强旋转、缩放、亮度扰动。不要简单粗暴删多数类样本路上井盖就是多删了训练分布就失真了。5.3 远距离小目标看不见imgsz 该调高就调高现象锥桶在 20 米内能检50 米外就漏小目标的召回率远低于大目标。原因imgsz640 时远处的目标在缩放后可能只剩 15x15 像素特征少到无法区分是噪声还是真实目标。YOLO11 的小目标能力比前几代强但物理极限摆在那里。这类问题在移动小目标检测、遥感图像目标检测里都很常见解法思路一致。解决把 imgsz 调到 960 或 1280 重新训练小目标特征会被放大召回率明显改善。推理时如果还是不够可以用 SAHI 做切片推理——把大图切成小块分别预测再合并结果。SAHI 适合离线分析场景实时推理慎用速度跟不上。5.4 训练中途 loss 变 NaNBN 在 batch 太小时的经典崩溃现象训练到第 40 轮左右loss 突然变成 NaN或者 mAP 瞬间归零。重启训练后过几轮又复现。原因batch 设太小。BN 层的统计量需要在一个 batch 内计算均值和方差batch4 时统计量方差巨大极易导致数值不稳定。这类问题在 YOLO 训练中很经典网上搜“yolo 训练 bn 崩溃”就能看到大量同类求助。解决先把 batch 加到 16显存不够就把 imgsz 降到 480。确认 batch 没问题后如果还出 NaN降低lr0到 0.001。不要一上来就怀疑是数据问题绝大多数 NaN 是优化器参数和 batch 的搭配问题。5.5 一键脚本在 Windows 上卡死路径分隔符与 workers 的坑现象训练脚本跑起来进度条卡在 0%CPU 占用 100%GPU 显存占用为 0。进程像是死锁但没有任何报错。原因Windows 下 multiprocessing 用的是 spawn 方式启动子进程data loader 的 workers0 时子进程会递归导入主脚本。如果训练入口没有if __name__ __main__保护子进程会重新执行整个脚本创建新的数据加载器最终死锁。另外 Windows 的路径分隔符是反斜杠脚本里硬编码/路径时也可能找不到文件。解决Windows 上把workers0单进程加载数据慢一点但稳定。训练入口必须写成if __name__ __main__:包裹。路径统一用os.path.join拼接不要硬编码/或\。6. 从能检到能用验证指标之外的一个检查技巧6.1 用帧级检出率替代 mAP 看效果mAP 是类别平均指标几个类别表现好就能把整体拉上去少数类翻车完全看不出来。我现在的习惯是挑一段连续视频逐帧跑预测然后把预测结果按类别统计“被检出的帧数 / 总帧数”这个就是帧级检出率。道路异常检测的落地场景里帧级检出率比 mAP 直观得多——夜间视频里坑洼被检出 10% 的帧还是 80% 的帧路测团队一眼就能看懂。6.2 把预测结果落盘逐帧人工扫一遍脚本跑完只是第一步我会把预测结果带框渲染到图上按夜间、逆光、雨天、高速四类分组人眼过一遍。这个步骤没有技术含量但能发现 mAP 看不出的事框抖动、误检成正常路沿、同一个目标被重复检出好几个框。这些在真实路测里都会被当成系统故障比漏检还麻烦。6.3 一个值得长期保留的检查习惯我现在拿到任何新数据集第一件事永远是跑类别分布统计和夜间占比检查这个习惯救过我很多次——有次一个看起来很好的数据集夜间样本只有 3%要是直接训练测完才知道翻车就太晚了。做目标检测就是这样模型能跑不代表能用数据和场景的匹配程度才是决定项目成败的隐藏变量。希望这两千字的经验能帮你少走一段弯路。本文还有配套的精品资源点击获取
返回列表