简介:这份雨雪天气路面状况数据集面向自动驾驶感知、道路安全监测与计算机视觉方向的学习者和开发者,用于训练模型区分结冰路面、雪地、下雨湿滑与干燥路面四类典型场景,适合作为目标检测入门到进阶的实战素材。压缩包共1293个文件,包含646张原始jpg图像、646个同名txt标注文件以及1个yaml配置文件,整体约26.98MB;txt与yaml可直接对接YOLOv5训练流程,省去格式转换环节。目前已有708人学习下载,说明其在同类数据中具备一定参考价值。图像均采用原始图片,未做过度裁剪或增强,保留了真实雨雪环境下的光照、反光与纹理细节,有助于模型学习结冰与湿滑路面的视觉特征。借助该数据集,读者可快速搭建路面状况识别实验,验证YOLOv5在恶劣天气下的检测效果,并在此基础上调整类别、扩充样本或迁移到自有场景,为道路安全预警与自动驾驶决策提供数据支撑。
1. 雨雪路面识别数据集:从原始图片到 YOLOv5 可训练标签的完整链路
冬季道路感知是自动驾驶和辅助驾驶里最容易翻车的场景之一。干燥路面、下雨湿滑、积雪覆盖、结冰反光这四种状态,在图像上的纹理、反照率、边缘特征差异极大,普通目标检测模型直接拿来用,往往把结冰误判成湿滑,把压实雪误判成干燥水泥。这个标题给出的方案很直接:一套覆盖四类路面状态的原始图片数据集,配套 YOLOv5 标注文件,解压后就能进入训练流程。它解决的不是"有没有数据"的问题,而是"数据能不能直接喂给 YOLOv5、标签格式对不对、类别定义清不清楚"的问题。适合正在做路面状态分类检测、冬季道路预警、车载感知原型验证的工程师,也适合想用 YOLOv5 跑通自己第一个垂直场景数据集的新手。下面按数据检查、格式转换、训练配置、踩坑排查、进阶验证的顺序,把这条链路讲透。
2. 拿到数据集先别急着训练:四类路面状态的标注体系与目录结构
2.1 结冰、雪地、湿滑、干燥四类的视觉边界怎么定
路面状态识别最容易出问题的地方,不是模型结构,而是类别定义本身。结冰路面和下雨湿滑在夜间都表现为高反光,雪地和干燥路面在强光下都可能偏白。如果标注时没有统一的判定标准,同一个画面不同人标出来的结果能差出三成。我一般会按下面的优先级来定:
- 结冰路面:表面有明显镜面反射或半透明冰层,轮胎痕迹处呈现深色条纹,整体反照率高且纹理平滑。
- 雪地:有可见积雪覆盖,包括压实雪和松散雪,表面有颗粒感或车辙压痕,颜色偏白但不透明。
- 下雨湿滑:路面无积雪但整体湿润,有连续水膜反光,颜色比干燥状态深,边缘有积水痕迹。
- 干燥路面:无冰、无雪、无水膜,沥青或水泥纹理清晰,颜色均匀。
这四类在标注文件里对应四个 class id,顺序一旦定下来就不要改。常见做法是把结冰设为 0、雪地设为 1、湿滑设为 2、干燥设为 3,因为结冰是最高风险类别,放在前面便于后续做类别加权。
提示:如果原始图片里存在"半冰半雪"的过渡状态,标注时按主要覆盖面积归类,不要新增类别。YOLOv5 对类别数量敏感,四类已经够用,加第五类会让小样本类别更难收敛。
2.2 原始图片目录和标签目录的对应关系
标题里说的是"图片均采用原始图片",意思是没做过裁剪、缩放、增强,保留原始分辨率和 EXIF 信息。这对训练是好事,因为你可以自己决定输入尺寸,但也意味着图片尺寸可能不统一。YOLOv5 的 dataloader 会自动 resize,但标签必须同步缩放,所以标签文件里的坐标必须是归一化后的相对值。
标准目录结构应该是这样:
dataset/ ├── images/ │ ├── train/ │ │ ├── ice_001.jpg │ │ ├── snow_014.jpg │ │ └── ... │ └── val/ │ ├── ice_101.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── ice_001.txt │ │ ├── snow_014.txt │ │ └── ... │ └── val/ │ ├── ice_101.txt │ └── ... └── data.yaml图片和标签必须同名,只是扩展名不同。YOLOv5 在训练时会按图片路径去推导标签路径,如果 labels 目录下找不到同名 txt,那张图就会被当成负样本跳过,不会报错,但你的有效训练数据会悄悄变少。这个坑后面还会细说。
2.3 用脚本检查图片与标签是否一一对应
拿到数据集后第一件事不是写训练命令,而是跑一遍完整性检查。下面这个脚本会统计图片数、标签数、空标签数、以及标签里的类别分布:
import os from pathlib import Path from collections import Counter img_dir = Path("dataset/images/train") lbl_dir = Path("dataset/labels/train") img_files = {p.stem for p in img_dir.glob("*.jpg")} lbl_files = {p.stem for p in lbl_dir.glob("*.txt")} missing_lbl = img_files - lbl_files missing_img = lbl_files - img_files empty_lbl = [] class_counter = Counter() for stem in lbl_files: txt = lbl_dir / f"{stem}.txt" lines = [l.strip() for l in txt.read_text().splitlines() if l.strip()] if not lines: empty_lbl.append(stem) continue for line in lines: cls_id = int(line.split()[0]) class_counter[cls_id] += 1 print(f"图片总数: {len(img_files)}") print(f"标签总数: {len(lbl_files)}") print(f"缺标签的图片: {len(missing_lbl)} -> {list(missing_lbl)[:5]}") print(f"缺图片的标签: {len(missing_img)} -> {list(missing_img)[:5]}") print(f"空标签文件: {len(empty_lbl)} -> {empty_lbl[:5]}") print(f"类别分布: {dict(class_counter)}")逻辑说明:用 set 做差集能快速定位缺失项,空标签文件单独统计是因为它们会被 YOLOv5 当作背景图处理,如果数量太多说明标注质量有问题。类别分布用来判断是否存在严重不均衡,比如结冰样本只有几十张而干燥样本上千张,后面训练时就要做类别加权或过采样。
参数说明:img_dir和lbl_dir按实际路径改,扩展名如果是 png 或 jpeg 也要同步改 glob 模式。class_counter的 key 是 class id,输出后对照 data.yaml 里的 names 顺序确认。
3. 把原始图片和标注转成 YOLOv5 能吃的格式:data.yaml 与标签归一化
3.1 data.yaml 里四个字段的取值逻辑
YOLOv5 训练时靠 data.yaml 定位数据和类别,字段不多但每个都关键:
path: /home/user/dataset train: images/train val: images/val nc: 4 names: 0: ice 1: snow 2: wet 3: drypath是数据集根目录,train和val是相对 path 的子路径。nc是类别数,必须和 names 的长度一致。names 的顺序就是标签文件里 class id 的顺序,一旦训练开始就不能改,改了要重新训练。
常见错误是把train写成绝对路径,或者写成images/train/带尾部斜杠。YOLOv5 对路径拼接比较敏感,尾部斜杠有时会导致找不到文件。我一般写成不带斜杠的相对路径,跑之前用ls确认一下路径存在。
3.2 标签格式:class_id x_center y_center width height 的归一化
YOLOv5 的标签格式是每行一个目标:
0 0.453125 0.612500 0.320312 0.275000五个值分别是类别 id、中心点 x 相对坐标、中心点 y 相对坐标、宽度相对坐标、高度相对坐标。所有坐标都除以图片的宽和高,范围在 0 到 1 之间。如果原始标注是 VOC 的 xml 或 COCO 的 json,需要转换。
下面这个脚本把 VOC 格式的 xml 转成 YOLOv5 的 txt:
import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image def voc_to_yolo(xml_path, img_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_map: continue cls_id = class_map[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.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}") return lines class_map = {"ice": 0, "snow": 1, "wet": 2, "dry": 3} xml_dir = Path("raw_annotations") img_dir = Path("dataset/images/train") lbl_dir = Path("dataset/labels/train") lbl_dir.mkdir(parents=True, exist_ok=True) for xml_file in xml_dir.glob("*.xml"): stem = xml_file.stem img_file = img_dir / f"{stem}.jpg" if not img_file.exists(): print(f"跳过,找不到图片: {stem}") continue lines = voc_to_yolo(xml_file, img_file, class_map) (lbl_dir / f"{stem}.txt").write_text("\n".join(lines))逻辑说明:先读 xml 里的图片宽高,再把 VOC 的左上右下坐标转成中心点加宽高的归一化格式。class_map把类别名映射到 id,不在映射里的类别直接跳过,避免引入未知类别。写入时用六位小数,精度足够且文件不会太大。
参数说明:class_map的 id 必须和 data.yaml 的 names 顺序一致。xml_dir和img_dir按实际路径改。如果图片是 png,img_file的扩展名也要改。转换完成后建议随机抽几张用可视化脚本画框确认,不要直接开训。
3.3 训练集和验证集怎么切:7:3 还是 8:2
路面状态数据集的类别不均衡往往比较严重,结冰和雪地样本通常少于干燥样本。切分时不能简单随机,要保证每个类别在验证集里都有足够样本。我一般按类别分层抽样,每个类别至少留 15% 进验证集,总体比例控制在 7:3 到 8:2 之间。
如果某个类别总数少于 100 张,验证集里至少留 20 张,否则验证指标波动会很大,mAP 一会儿 0.6 一会儿 0.8,根本没法判断模型好坏。切分脚本按类别分组后各自 shuffle 再按比例取,这里不展开代码,核心是别用全局随机。
4. YOLOv5 训练配置:从 conda 环境到超参数的实际取值
4.1 环境配置:conda 建环境加依赖安装的稳定路径
YOLOv5 的环境配置本身不复杂,但版本冲突是新手最容易卡住的地方。我一般用 conda 建一个 Python 3.8 或 3.9 的环境,然后按官方 requirements 装依赖:
conda create -n yolov5_road python=3.9 -y conda activate yolov5_road git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt逻辑说明:Python 3.9 对 PyTorch 和 OpenCV 的兼容性比较稳,3.10 以上有时会遇到 numpy 版本冲突。先装 requirements 再装 torch 的顺序不要反,否则 pip 可能解析出旧版本。
参数说明:如果机器有 CUDA,装完 requirements 后单独装对应版本的 torch,比如pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 --extra-index-url https://download.pytorch.org/whl/cu117。没有 GPU 就用 CPU 版,但训练会慢很多,四类路面数据集建议至少 8GB 显存的卡。
注意:不要混用 conda install 和 pip install 装同一个包,容易出现动态库冲突。YOLOv5 的依赖统一用 pip 管理。
4.2 超参数怎么设:batch size、img size、epochs 的取值依据
YOLOv5 的训练命令看起来简单,但几个关键参数直接决定能不能收敛:
python train.py \ --data dataset/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name road_state_v1--img 640是输入尺寸,原始图片如果分辨率很高,比如 1920x1080,缩到 640 后结冰的细纹理可能丢失。如果显存够,可以试 960 或 1280,但 batch 要相应减小。--batch 16是常见起点,显存不够就降到 8 或 4,但 batch 太小会让 BN 层统计不稳定,低于 4 建议改用--accumulate梯度累积。
--epochs 100对四类数据集通常够用,但如果结冰样本很少,可能需要 150 到 200 轮。判断依据是看验证集 mAP 是否还在涨,如果连续 20 轮不涨就可以停。--hyp用 low 增强配置,因为路面状态对颜色和纹理敏感,太强的 HSV 增强会把湿滑和干燥的差异抹掉。
4.3 训练过程看什么:loss 曲线、mAP 和混淆矩阵
训练启动后不要只盯着 loss。YOLOv5 会输出三个 loss:box_loss、obj_loss、cls_loss。box_loss 管定位,obj_loss 管有没有目标,cls_loss 管类别对不对。路面状态检测里,obj_loss 通常很快降下去,因为每张图基本都有路面;cls_loss 才是关键,如果 cls_loss 震荡不降,说明类别边界模糊,要回去检查标注一致性。
验证阶段看 mAP@0.5 和 mAP@0.5:0.95。路面状态这种大目标场景,mAP@0.5 上 0.85 算正常,低于 0.7 就要排查。混淆矩阵在runs/train/road_state_v1/下,重点看结冰和湿滑之间有没有大量互判,如果有,说明这两类的视觉特征在模型看来太接近,要么加样本,要么在标注时把边界定得更严。
5. 避坑与排查:路面状态数据集训练中最容易翻车的五件事
5.1 现象:训练 loss 正常下降但 mAP 一直是 0
原因:标签路径不对,YOLOv5 把图片当成了负样本。它不会报错,只会静默跳过没有对应 txt 的图片。如果 labels 目录结构和 data.yaml 里写的对不上,所有标签都找不到,模型就在学"整张图没有目标"。
解决:跑一遍 2.3 节的检查脚本,确认图片和标签数量一致。再确认 data.yaml 里train和val指向的是 images 目录而不是 labels 目录。YOLOv5 会自动把路径里的images替换成labels去找标签,如果目录名不叫 images,替换逻辑会失效。
5.2 现象:结冰和湿滑大量互判,混淆矩阵对角线不干净
原因:这两类在夜间和阴天场景下视觉差异极小,都是高反光加低纹理。如果标注时没有严格按"有无冰层"来分,而是按"看起来亮不亮"来分,模型学到的就是亮度特征而不是材质特征。
解决:回去抽查互判样本,把边界模糊的图片重新标注或直接剔除。训练时在 data.yaml 里给结冰类别加权重,或者用--balance做类别平衡采样。如果样本量够,可以试更大的输入尺寸,让冰层纹理更清晰。
5.3 现象:验证集 mAP 波动超过 0.1,每次跑结果都不一样
原因:验证集太小,或者类别分布不均。如果验证集里结冰只有十几张,随机种子一变,mAP 就跳。另外 YOLOv5 默认开启 mosaic 增强,训练集分布每次 epoch 都在变,小验证集扛不住这种波动。
解决:验证集每个类别至少 30 张,最好 50 张以上。训练时固定--seed 42,减少随机性。如果还是波动大,把--hyp里的 mosaic 概率调低,比如从 1.0 降到 0.5。
5.4 现象:模型在训练集上表现很好,一到真实雪地就漏检
原因:原始图片虽然没做增强,但采集场景可能集中在某几种天气或某几个路段。模型学到了场景偏差,比如把某条路的护栏当成结冰的线索。真实场景一换,线索没了,检测就崩。
解决:检查训练集里是否有明显的场景聚集,比如大部分结冰图片都来自同一座桥。如果有,尽量补充不同路段、不同时段的样本。训练时开启--multi-scale,让模型对尺度变化更鲁棒。验证时不要只用同分布数据,留一组不同场景的图片做交叉验证。
5.5 现象:推理时框的位置对但类别全错,或者置信度普遍偏低
原因:推理时的预处理和训练时不一致。比如训练用了 640 输入,推理时直接喂原图,YOLOv5 虽然会自动 resize,但 letterbox 的填充方式如果和训练不一致,坐标映射会偏。另外类别全错往往是 data.yaml 的 names 顺序和训练时不一致,比如训练时 ice 是 0,推理时加载的 yaml 里 ice 变成了 2。
解决:推理时用和训练相同的--img尺寸,并且确认加载的 data.yaml 就是训练时那份。YOLOv5 的 detect.py 默认会用模型里保存的 names,但如果手动指定了 data.yaml,以指定的为准。置信度低可以调--conf-thres,但根本原因通常是训练不充分或类别不均衡。
6. 进阶验证:用混淆矩阵和单类 AP 判断数据集到底值不值得继续投入
训练跑通只是第一步,真正决定这个数据集能不能支撑业务的是单类表现。YOLOv5 训练完会在runs/val/下输出每个类别的 AP,我一般会拉一张表对比:
| 类别 | 样本数 | AP@0.5 | AP@0.5:0.95 | 主要误判方向 |
|---|---|---|---|---|
| 结冰 | 320 | 0.82 | 0.61 | 湿滑 |
| 雪地 | 450 | 0.91 | 0.74 | 干燥 |
| 湿滑 | 380 | 0.78 | 0.55 | 结冰、干燥 |
| 干燥 | 600 | 0.95 | 0.81 | 湿滑 |
如果结冰和湿滑的 AP 都低于 0.8,说明这两类的特征区分度不够,要么补样本,要么考虑把两类合并成"低摩擦路面"再分二级子类。如果干燥和雪地互判严重,通常是过曝或欠曝导致,可以在预处理里加自适应直方图均衡,但要注意别把湿滑的水膜反光也均衡掉。
另一个验证方法是拿一批完全没有标注的新图片,用训练好的模型跑推理,人工看 top-20 高置信度误检和漏检。如果误检集中在某一种纹理或某一个路段,说明数据集覆盖有盲区。我自己的习惯是每次训练完都留 50 张"没见过"的图做盲测,不看 mAP,只看实际画面里模型有没有把结冰认成湿滑。这个习惯帮我省过好几次返工——有一次 mAP 0.88 看着不错,盲测却发现夜间结冰全漏,原因是训练集里夜间样本不到 5%。数据集的价值不在标注数量,而在覆盖的场景能不能撑住你真正要跑的那条路。希望帮到你。
本文还有配套的精品资源,点击获取