简介:面向自动驾驶感知与车道级高精地图应用的路面信息数据集,图像涵盖慕尼黑、柏林、美因茨等德国城市街道场景,适合目标检测、车道线识别及路面要素分割等视觉任务。压缩包大小约209.49MB,内置JPEGImages与Annotations两个文件夹,分别存放2684张JPG图像和2684个XML标注文件,文件一一对应,可直接用于YOLO、Faster R-CNN等检测框架。标注体系覆盖18类路面要素,包括车辆、行人、路牙、多类实线与虚线(白实线、双白线、黄实线、双黄线、黄虚线、白虚线等)、线端位置以及压线/未压线状态,能为模型训练提供细致的监督信号。已有1195人浏览学习。下载即可获得完整图像与标注目录,省去自行收集和整理的流程,适合作为路面感知算法验证与训练的基础数据集。
1. 拿到 2684 张路面图后,先别急着训模型
一个包含 JPEGImages 和 Annotations 两个文件夹、各 2684 项的数据集,是典型的 PASCAL VOC 布局。JPEGImages 放原图,Annotations 放同名 XML 标注,每张图对应一个标注文件。路面信息数据集和通用目标检测数据集的最大差异在于:路面场景里小目标多、类别不平衡严重、光照和天气变化大,直接把通用检测框架套上去,mAP 往往很难看。这一篇就围绕 VOC 格式的路面数据集,讲清楚从目录校验、标注解析到训练配置和结果验证的完整链路。适合刚拿到路面数据集、准备跑检测或分割任务的工程师,也适合想搞清楚 Annotations 里到底存了什么的人。
2. 数据集的目录结构和标注格式,决定了后面所有代码
2.1 JPEGImages 和 Annotations 的命名对应关系
VOC 格式的核心约束是:JPEGImages 里的图片文件名和 Annotations 里的 XML 文件名必须完全一致(不含扩展名)。路面数据集常见的命名有 frame_000001.jpg、road_001.xml 这类带序号的形式,也可能直接是时间戳。拿到数据集后第一件事不是看图片,而是确认两边文件是否一一对应。
import os img_dir = "JPEGImages" ann_dir = "Annotations" imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".jpeg", ".png"))} anns = {os.path.splitext(f)[0] for f in os.listdir(ann_dir) if f.endswith(".xml")} print("图片数量:", len(imgs)) print("标注数量:", len(anns)) print("缺标注的图片:", len(imgs - anns)) print("缺图片的标注:", len(anns - imgs))这段代码用集合差集快速找出不配对的文件名。路面数据集经常从行车记录仪或监控视频抽帧而来,抽帧过程中偶尔会丢帧或有重复帧,导致 XML 和 JPG 对不上。如果差集不为空,不要删文件,先看文件名规律,通常补一个对齐脚本就能解决。
2.2 Annotations 里到底有什么:XML 字段逐个拆解
一个标准的 VOC XML 标注文件,结构上是固定的四层:folder / filename / source,然后是 object 列表。路面数据集的 object 类别通常包含 person、car、truck、bicycle、motorcycle,以及路面特有的类别如 lane_line(车道线)或 pothole(坑洼)。下面是一个典型的标注结构:
<annotation> <folder>JPEGImages</folder> <filename>frame_000001.jpg</filename> <source> <database>Road Dataset</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>car</name> <bndbox> <xmin>320</xmin> <ymin>540</ymin> <xmax>780</xmax> <ymax>860</ymax> </bndbox> </object> </annotation>size里的宽高必须和 JPEGImages 里实际图片的尺寸一致——不一致时,很多检测框架(尤其是用固定输入尺寸的)会在 resize 后产生坐标偏移。bndbox的四个值是像素坐标,xmin/ymin 是左上角,xmax/ymax 是右下角。路面数据里一个常见问题是标注框的坐标系原点在图片左上角还是左下角,PASCAL VOC 明确是左上角,但如果数据来自某些自动驾驶采集工具,可能混入左下角坐标系,需要抽样验证。
2.3 类别分布统计:路面数据集的第一个坑
路面场景中 car 和 person 数量可能上万,而 pothole 或 traffic_cone 可能只有几百个。这种不平衡会让模型偏向多数类。写一个脚本统计每个类别的框数量:
import xml.etree.ElementTree as ET import glob from collections import Counter counter = Counter() size_mismatch = [] for xml_file in glob.glob("Annotations/*.xml"): tree = ET.parse(xml_file) root = tree.getroot() for obj in root.findall("object"): name = obj.find("name").text counter[name] += 1 print("类别统计:", counter.most_common())如果发现某个类别占比低于 5%,训练时就要考虑类别权重或者使用 Focal Loss。另外,统计框的宽高比也值得做——路面数据里车辆大多是横向框(宽大于高),行人则是纵向框,如果标注时混入了大量正方形框,说明标注质量有问题。
3. 从 VOC 格式转换到训练格式:三种路径和代码实现
3.1 不转换直接用:哪些框架原生支持 VOC
YOLOv5 和 YOLOv8 的官方仓库里都提供了datasets/VOC.yaml配置文件,理论上把数据集按 VOC 目录结构放好,改一下 yaml 里的路径就能训练。但实际跑起来会踩不少坑。
# VOC.yaml train: /path/to/road_dataset/images/train val: /path/to/road_dataset/images/val nc: 6 names: ['person', 'car', 'truck', 'bicycle', 'motorcycle', 'pothole']YOLO 系列要求图片和标注文件分开放在 images 和 labels 目录下,而且标注格式是归一化的 txt(每行class x_center y_center width height)。直接把 VOC 的 XML 塞进去是不行的,官方虽然提供了voc2yolo脚本,但那个脚本默认处理的是 VOC 官方数据集,类别名写死在代码里,路面数据集必须自己改类别映射。
3.2 自己写一个 VOC 转 YOLO 的转换脚本
这是把路面数据集用起来最实用的一步。核心逻辑是:读 XML → 提取每一个 object 的类别名和 bndbox → 按图片宽高归一化 → 写入 txt 文件。
import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, output_dir, class_names): 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) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_names: continue class_id = class_names.index(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) # 归一化 x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 裁剪到 [0,1] 范围,防止标注越界 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_name = os.path.splitext(os.path.basename(xml_path))[0] + ".txt" with open(os.path.join(output_dir, out_name), "w") as f: f.write("\n".join(lines)) # 使用示例 class_names = ["person", "car", "truck", "bicycle", "motorcycle", "pothole"] os.makedirs("labels_train", exist_ok=True) for xml in os.listdir("Annotations"): if xml.endswith(".xml"): voc_to_yolo(os.path.join("Annotations", xml), "labels_train", class_names)归一化坐标是 YOLO 格式的核心,x_center 和 y_center 是相对于图片宽高的比例。越界裁剪这一步不能省。路面数据里经常有目标被截断在画面边缘的情况,标注工具可能把 bndbox 的 xmax 标到 1920 以上(超出图片实际宽度),不裁剪的话训练时损失函数会计算出 NaN。
3.3 路面数据集特有的目录划分策略
统一 VOC 数据集通常按 7:2:1 划分 train/val/test,但路面数据不是随机划分这么简单。行车记录仪视频抽帧得到的数据,连续帧之间高度相似——第 100 帧和第 101 帧几乎一样。如果随机划分,训练集里出现第 100 帧、验证集里出现第 101 帧,相当于把几乎重复的数据同时放进训练和验证,val mAP 会虚高,实际部署时掉点严重。
处理方式是按视频片段划分:
import random # 假设文件名格式是 video_id_frame_number.jpg files = os.listdir("JPEGImages") video_map = {} for f in files: video_id = f.split("_")[0] # 提取视频 ID video_map.setdefault(video_id, []).append(f) random.seed(42) video_ids = list(video_map.keys()) random.shuffle(video_ids) split_point = int(len(video_ids) * 0.8) train_videos = video_ids[:split_point] val_videos = video_ids[split_point:] train_files = [] val_files = [] for vid in train_videos: train_files.extend(video_map[vid]) for vid in val_videos: val_files.extend(video_map[vid]) print(f"训练集: {len(train_files)} 张, 验证集: {len(val_files)} 张")3.4 转换后必须做的三个校验
转换脚本跑完不算完。检查结果:
# 每行必须是 5 个数字 awk '{print NF}' labels_train/000001.txt | sort | uniq -c # 统计每个类别出现次数 cat labels_train/*.txt | awk '{print $1}' | sort | uniq -c # 检查是否有空标注文件 find labels_train -name "*.txt" -size 0 | wc -l空标注文件在 YOLO 训练时会直接报错或跳过,如果路面数据集里大量图片没有任何目标,需要单独处理。如果空标注超过总量 20%,先别训检测模型,考虑跑分割或异常检测更合适。
4. 用 YOLOv8 训练路面检测模型:配置与参数调优
4.1 数据配置文件:YAML 的每个字段怎么填
YOLOv8 用 Ultralytics 框架,配置文件是一个 YAML。路面数据集的配置比标准 VOC 多几个注意点:
# road.yaml path: /data/road_dataset # 数据集根目录 train: images/train # 训练图片目录(相对 path) val: images/val # 验证图片目录 nc: 6 # 类别总数 names: 0: person 1: car 2: truck 3: bicycle 4: motorcycle 5: pothole这里path是绝对路径,train和val是相对 path 的相对路径。目录结构应该提前准备好,和前面转换脚本输出的 labels 目录对应。YOLOv8 要求 images 和 labels 在同一级目录下,即images/train/xxx.jpg对应labels/train/xxx.txt,否则训练时会找不到标注。
4.2 路面场景的 5 个关键训练参数
路面数据集和 COCO 那种通用场景的训练参数差异主要在 imgsz、batch、epochs 和增强策略上。
| 参数 | 推荐值 | 说明 |
|---|---|---|
imgsz | 1280 或 1536 | 路面小目标多(远处行人、锥桶),原图 1920x1080,缩放太小会丢失细节 |
batch | 8-16 | 取决于 GPU 显存,imgsz 大,batch 要相应减小 |
epochs | 100-150 | 路面数据集一般 2k-10k 张,100 轮左右足够收敛 |
lr0 | 0.005-0.01 | 比 COCO 默认的 0.01 略低,防止小数据集过拟合 |
mosaic | 0.5 或 0 | 路面图片有强空间连续性,mosaic 过大破坏目标尺度分布 |
训练命令:
yolo detect train \ model=yolov8s.pt \ data=road.yaml \ imgsz=1280 \ batch=8 \ epochs=120 \ lr0=0.007 \ mosaic=0.5 \ device=0 \ project=road_experiment \ name=run1model=yolov8s.pt是预训练权重,会在 COCO 基础上微调。路面检测场景里,预训练权重帮助很大,因为 COCO 里有 person、car 这些类别,迁移学习能让模型在早期就收敛得更快。
4.3 训练过程中的显存监控和日志解读
训练启动后不要只盯着进度条。YOLOv8 会输出 box_loss、cls_loss、dfl_loss 三个指标,还有一个 P(精度)和 R(召回)。
Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 50/120 9.35G 1.023 1.452 1.198 12 1280box_loss 持续下降说明定位在变好;cls_loss 如果震荡不降,大概率是类别不平衡,pothole 类别的样本太少。看验证集 mAP 别只看最后一个 epoch,选最佳模型是根据 val mAP 最高点保存,YOLOv8 默认会保存best.pt和last.pt,best 是验证集表现最好的权重。
如果显存溢出(OOM),优先把 batch 除以 2 而不是降低 imgsz。降低 imgsz 会让小目标直接消失,召回率断崖式下降。
4.4 类别不平衡:给 pothole 单独加权重
路面数据集的类别不平衡几乎必然存在。处理方式有两种:一是过采样少样本类别的图片,二是修改损失函数权重。YOLOv8 里没有直接给类别加 loss 权重的参数,但可以通过数据增强和采样器间接控制。
常见做法是复制少样本类别的图片加入训练集:
import shutil # 找出包含 pothole 的所有图片 import xml.etree.ElementTree as ET import glob pothole_images = [] for xml in glob.glob("Annotations/*.xml"): tree = ET.parse(xml) root = tree.getroot() for obj in root.findall("object"): if obj.find("name").text == "pothole": pothole_images.append(os.path.splitext(os.path.basename(xml))[0]) break # 在 images/train 和 labels/train 中复制两份 for img in pothole_images: src_img = f"images/train/{img}.jpg" dst_img = f"images/train/{img}_copy1.jpg" shutil.copy(src_img, dst_img) shutil.copy(src_img, dst_img.replace("_copy1", "_copy2"))复制时要注意标注文件也要跟着复制,并且重命名一致。过采样到少数类占比 15%-20% 就停,复制太狠会让模型过拟合到 pothole 的特定位置和光照条件。正确复现实验结果、在验证集上分别看每个类别的 AP 才能确认不平衡是否真的改善了。
5. 验证集评估和可视化:怎么判断模型真的能用
5.1 按类别看 mAP,而不是只看整体 mAP
训练完成后,YOLOv8 会在runs/detect/run1/下生成results.csv,里面有每个类别的 AP 数据。用 pandas 读出来分析:
import pandas as pd df = pd.read_csv("runs/detect/run1/results.csv") # 取最后一个 epoch 的数据 last = df.iloc[-1] print("整体 mAP50:", last["metrics/mAP50(B)"]) print("整体 mAP50-95:", last["metrics/mAP50-95(B)"]) # 查看 results.png 中各类别 AP 曲线如果整体 mAP50 有 0.7,但 pothole 的 AP 只有 0.2,这个模型在实际路面场景里基本不可用。路面上真正困扰工程的是小目标和遮挡目标,不是整体 mAP。
5.2 可视化预测结果,重点看漏检和误检
验证集预测图像输出:
yolo predict \ model=runs/detect/run1/weights/best.pt \ source=images/val \ imgsz=1280 \ conf=0.25 \ save=True \ project=validation_output输出的图片会保存在validation_output/下,每个预测框带置信度和类别标签。手动翻图的时候,主要看两类错误:远处的车辆有没有被漏检,路面阴影和井盖有没有被误检成 pothole。如果大量误检出现在树影和路面接缝处,说明类别特征学习得不够,单纯提高 conf 阈值治标不治本,需要补充更多负样本。
5.3 用混淆矩阵定位错误的来源
YOLOv8 训练完成后会生成confusion_matrix.png。这张图对角线越亮越好。看非对角线的亮点:如果background列有大量预测,说明模型产生了大量误检;如果car行上有不少truck的预测,说明这两个类别在路面视角下区分度不够——卡车和厢式货车在很多角度下看起来确实像。处理方式是检查标注数据里是否有类别错误,偶尔是标注员把小型卡车标成了 car,修正后重新训练,比调参更有效。
6. 最后一公里:把模型导出和部署踩坑一次说清
训练好 best.pt 后,导出成部署格式:
yolo export model=runs/detect/run1/weights/best.pt format=onnx imgsz=1280 opset=12导出成 ONNX 后,用 ONNX Runtime 推理的关键代码:
import onnxruntime as ort import cv2 import numpy as np session = ort.InferenceSession("best.onnx") input_name = session.get_inputs()[0].name input_shape = session.get_inputs()[0].shape # [1, 3, 1280, 1280] img = cv2.imread("frame.jpg") img_resized = cv2.resize(img, (1280, 1280)) img_blob = img_resized[:, :, ::-1].transpose(2, 0, 1) # BGR->RGB, HWC->CHW img_blob = np.expand_dims(img_blob, axis=0).astype(np.float32) / 255.0 outputs = session.run(None, {input_name: img_blob}) # outputs[0] 形状是 [1, 8, 25200],8 = 4(bbox) + 1(conf) + 6(类别数)推理时 imgsz 必须和导出时的输入尺寸一致,否则计算出来的框坐标全部偏移。25200 是 1280x1280 输入下三个尺度的 anchor 总数(20x20 + 40x40 + 80x80,乘以 3 个 anchor)。
如果部署端是 Jetson 或边缘盒子,优先导出 TensorRT 格式,在 GPU 上能把延迟从 30ms 降到 8ms 以下。注意 TensorRT 的 int8 量化在路面小目标上掉点明显,尽量用 FP16。模型压缩到 int8 后,pothole 这类小目标的检测率可能下降 30% 以上,这个代价值得在选型前明确知道。
本文还有配套的精品资源,点击获取