简介:面向智能交通与自动驾驶场景的雨雪天气路面状况数据集,覆盖结冰路面、雪地、雨天湿滑及干燥路面等常见路况,可用于训练路面状态识别目标检测模型。压缩包共1293个文件,包含646张原始jpg路面图片、646个对应txt标注文件及1个yaml配置文件,整体大小约26.98MB;txt文件记录YOLOv5格式的目标框坐标,yaml用于配置数据集类别与路径。目前已有707人学习下载,适合自动驾驶、道路养护、智慧安防等方向的算法工程师与研究者,也可作为计算机视觉初学者的项目练习数据。所有图片均为原始路面图片,文件名与标签一一对应,便于按场景筛选和整理;结合yaml配置即可快速接入YOLOv5训练流程,省去手动格式转换和标注整理环节。相较于自行采集标注,这份数据能显著降低雨雪天气样本的获取成本,为模型在恶劣天气下的鲁棒性提升提供直接支撑。
1. 结冰路面识别不是配色题:这个YOLOv5数据集怎么用
下雨天跑在路上,判断路面是湿滑还是结冰,靠的不是颜色,而是纹理和反光。这组数据集的特别之处在于把四类路面状态——结冰路面、雪地、下雨湿滑、干燥路面——用原始图片配YOLOv5格式的txt标签打包,拿到手就能直接送进YOLOv5训练管线训练一个路面状态检测器。和网上那些分好类的图片库不同,这份数据保留了原始分辨率和YOLOv5的标记格式,适合用来做自动驾驶感知、冬季道路养护巡检、结冰预警等场景。如果你对YOLOv5网络结构和训练自己的数据集还没完全跑通,拿这个场景练手正好。
2. 数据集结构与YOLOv5标注格式:从原始图片到txt标签
2.1 四类路面状态怎么定义与区分
一个常见误解是结冰路面看颜色,实际上黑冰和湿滑路面在视觉上非常接近。冰面反光更均匀,雪地纹理更粗糙,干燥路面有颗粒感,下雨湿滑路面则有明显的镜面反光。这份数据集用YOLOv5标记时,类别定的是这四类,但标注的时候容易出现边界模糊,尤其是"结冰路面"和"下雨湿滑"在夜间灯光下几乎难以区分。所以拿到数据后第一件事不是直接训练,而是把标签过一遍,看有没有标错的框。
从项目正文里的文件名可以看出,图片命名类似ice-road31_jpg.rf.647734b2a0c45eadc7006abc92df2e1c.jpg,其中的.rf.是Roboflow导出时常见的命名标记。这类文件名的数据集通常已经按YOLOv5的标准目录结构整理过:images/和labels/两个大类,下面再分train/和val/。如果手头只有图片和同名txt,需要自己补成这个结构。另一种情况是下载包只有图片和标注文本,没有划分训练集和验证集,这时我会用下面的方式按 8:2 随机划分,避免同类图片全堆在一侧。
import os import random import shutil image_dir = "ice_road/images" label_dir = "ice_road/labels" for split in ["train", "val"]: os.makedirs(f"ice_road/images/{split}", exist_ok=True) os.makedirs(f"ice_road/labels/{split}", exist_ok=True) images = [f for f in os.listdir(image_dir) if f.endswith(".jpg")] random.seed(42) random.shuffle(images) val_cnt = int(len(images) * 0.2) for i, img_name in enumerate(images): split = "val" if i < val_cnt else "train" label_name = img_name.rsplit(".", 1)[0] + ".txt" shutil.move(os.path.join(image_dir, img_name), f"ice_road/images/{split}/{img_name}") shutil.move(os.path.join(label_dir, label_name), f"ice_road/labels/{split}/{label_name}")这段代码的意义在于,训练集和验证集不能只按文件序号硬切。雨雪天气有很强的场景连续性,比如同一路段连续几帧画面,如果全落在训练集或验证集,评估结果会虚高。先随机打乱再划分,至少能避免连续帧都进同一侧。val_cnt用图片总数乘以0.2取整,种子固定为42,保证每次复现的结果一致。
2.1.1 类别边界容易混淆的地方
四类路面状态并不是天然可分的。干燥路面在逆光下会局部反光,看起来像湿滑;薄冰刚形成时透明度高,完全能看到沥青纹理,更像"下雨湿滑"而不是"结冰"。标注时如果只看单张图,很容易写错class_id。我的习惯是训练前用图片查看器把同一文件名对应的txt渲染出来,重点检查ice和wet两类框是否覆盖了相同区域。
2.2 YOLOv5标注文件解析
YOLOv5的标签不是用矩形坐标,而是归一化后的中心点加宽高。每行对应一个目标框,格式如下:
<cls_id> <x_center> <y_center> <width> <height>五个值都用空格分隔,x_center、y_center、width、height都是0到1之间的浮点数,除以图片宽高得到归一化坐标。cls_id从0开始,对应yaml里的names列表顺序。
下面这段代码可以快速扫描一个标签目录,统计四类目标数量,同时检查有没有越界框:
import os from collections import Counter label_dir = "labels/train" counter = Counter() broken = [] for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue with open(os.path.join(label_dir, fname), "r") as f: for line in f: parts = line.strip().split() if len(parts) != 5: broken.append(fname) continue cls = int(parts[0]) x_center = float(parts[1]) y_center = float(parts[2]) width = float(parts[3]) height = float(parts[4]) if not (0 < x_center < 1 and 0 < y_center < 1): broken.append(fname) if not (0 < width < 1 and 0 < height < 1): broken.append(fname) counter[cls] += 1 print("类别统计:", sorted(counter.items())) print("异常文件:", set(broken))这段代码的逻辑是逐行读取txt,按空格拆出5个字段,把类别ID取整、坐标转浮点,然后检查中心点坐标和宽高是否落在0到1区间内。YOLOv5训练时如果标注框越界,不一定报错,但会影响anchor匹配,导致loss不下降。我一般在跑训练前都会做一次这个检查,尤其从网上下载的数据集,标签经常有坐标越界或格式不完整的行。
2.3 数据分布与训练前检查
以下是一份路面状态数据集的分布示意,实际数字要根据你的标签统计:
| 类别ID | 名称 | 视觉特征 | 易混淆对象 |
|---|---|---|---|
| 0 | 干燥路面 | 纹理清晰,色调偏灰 | 阴影下的雪地 |
| 1 | 下雨湿滑 | 镜面反光,颜色加深 | 薄冰覆盖路面 |
| 2 | 雪地 | 白色带纹理,边界模糊 | 亮光下的干燥路面 |
| 3 | 结冰路面 | 反光均匀,半透明 | 湿滑路面、玻璃质感 |
如果是这种分布,训练时优先让模型学会区分干燥路面和湿滑路面,因为这两类在雨天夜间光照下最容易误判。另外还要检查图片的EXIF方向信息,手机拍摄的原图有时带旋转标记,YOLOv5在LoadImages阶段默认用OpenCV读取,不会自动处理EXIF旋转,导致标签坐标和实际画面错位。遇到这种情况,先用以下命令把所有图片转正再训练。
echo "no exif orientation issue"但这条命令并不能解决EXIF旋转,真正可靠的做法是用PIL里的ImageOps.exif_transpose批量转正:
from PIL import Image, ImageOps import os for fname in os.listdir("ice_road/images"): if not fname.lower().endswith(".jpg"): continue path = os.path.join("ice_road/images", fname) img = Image.open(path) img = ImageOps.exif_transpose(img) img.save(path, quality=95)这段代码把每张原图按EXIF里的方向字段转正后覆盖保存。YOLOv5的标签坐标是按转正后的图像标注的,如果图片没有转正,模型看到的物体和框的位置会整体偏移,mAP表现会非常奇怪。
3. 用YOLOv5训练自己的数据集:环境、yaml配置与训练命令
3.1 数据集yaml怎么写
YOLOv5官方仓库训练前要准备一个数据集配置文件,指向图片目录并声明类别。路面状态这个任务可以直接写成这样:
train: ./datasets/ice_road/images/train val: ./datasets/ice_road/images/val nc: 4 names: 0: dry 1: wet 2: snow 3: icetrain和val指向图片目录,YOLOv5会自动把路径中的images替换成labels去加载标注。nc是类别数量,必须和names长度一致。names的顺序要和标签txt里的 class_id 严格对应。我见过有人把names写成了['ice', 'snow', 'wet', 'dry'],但标签里类别0是干燥路面,模型训练全程loss表现很奇怪,最后才发现是顺序错了。
3.2 训练命令与关键超参数
数据准备好后,训练命令我一般直接用官方仓库的train.py:
python train.py \ --data ice_road.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --cache \ --device 0几个关键参数的解释:--data指向刚才写的yaml;--weights用COCO预训练权重,路面状态识别属于底层外观特征分类,迁移学习比从头训练收敛快得多;--img是输入分辨率,路面场景不需要识别极小的障碍物,640足够,原图如果很大可以试1280,但显存占用会翻倍;--batch根据GPU显存调整,显存不够就减半;--cache在训练前把图片一次性加载进内存,IO慢的机器上能明显提升训练速度。
以下是YOLOv5默认超参数和推荐调整范围,可以直接当作参照:
| 超参数 | 默认值 | 推荐范围 | 说明 |
|---|---|---|---|
| lr0 | 0.01 | 0.005-0.01 | 初始学习率,数据量少时建议偏低 |
| lrf | 0.01 | 0.01-0.1 | 最终学习率因子,决定后期微调幅度 |
| momentum | 0.937 | 0.8-0.95 | SGD动量,太大会震荡 |
| cls | 0.5 | 0.3-1.0 | 分类损失权重,样本不均衡时调高 |
| box | 0.05 | 0.02-0.1 | 边框回归损失权重 |
这些超参数在data/hyps/hyp.scratch-low.yaml里修改。对道路场景,我一般会把cls提到0.7,因为路面类别之间非常容易混淆,分类损失权重太低,模型会偏向框回归而忽略类别判断。训练时也可以加--multi-scale,让YOLOv5在训练中随机变换输入分辨率,对雨雪天气下远近不同的路面目标泛化更好。
如果显存不够,常见做法是--batch 8 --img 416先跑一版基线,确认流程没问题后,再换--img 640 --batch 16正式训练。还可以加--freeze 10冻结前10层主干,只训练检测头,适合数据量只有几百张的情况。冻结层数越多,训练越快,但特征提取能力越弱,冻结超过10层一般不值得。
3.3 训练过程中的loss观察
训练启动后不要只盯着终端,要看runs/train/exp*/results.csv里的train/box_loss、train/cls_loss和val/cls_loss。正常情况是前10轮下降很快,后面慢慢收敛。如果val损失在20轮后开始反弹,说明过拟合了,先把epochs降下来,或者加大数据增强。路面状态数据的增强和普通目标检测不完全一样,hsv_h、hsv_s这类颜色扰动可以适当调低,因为雨雪和冰面本身靠颜色纹理区分,颜色被随机改过后,模型反而学不到真实特征。
3.3.1 最容易踩的路径错误
YOLOv5对路径很敏感。很多人把标签和图片放在不同命名的目录里,比如图片在images/train,标签却叫label/train。YOLOv5只认labels这个目录名,差一个字母会直接零训练,因为找不到标签文件。训练前用下面这条命令验证一下目录是否匹配:
ls datasets/ice_road/images/train | head -3 ls datasets/ice_road/labels/train | head -3两条命令输出的文件名应该一一对应。如果发现一个目录有jpg而另一个没有对应的txt,说明标注文件缺失,需要回到第2章的脚本检查。
4. 训练结果评估与雨雪天气的坑:mAP、混淆矩阵与失败样本
4.1 从results看收敛质量
训练结束后,runs/train/exp/下会生成results.png和confusion_matrix.png。不要只看mAP,还要看验证集的P和R。路面状态数据里,如果P高而R低,说明模型宁可少报也不误报,这对结冰预警来说很危险——漏报比误报更致命。如果R高而P低,误报太多,比如雨天把干燥路面标成湿滑,下游系统会被频繁告警打扰。我一般优先保证R。
下面这条命令用于在验证集上重新评估权重:
python val.py \ --weights runs/train/exp/weights/best.pt \ --data ice_road.yaml \ --task val \ --conf-thres 0.25 \ --iou-thres 0.45--conf-thres是置信度阈值,低于它的框会被过滤掉。日志输出里会给出每个类别的mAP@0.5。此时如果某个类别的AP明显低于其他类别,先别急着加数据,去看混淆矩阵,大概率是视觉特征相近导致分类错误。
4.2 混淆矩阵里看易混类别
路面状态检测的混淆矩阵通常会出现这样的情况(示意数据):
| 实际\预测 | dry | wet | snow | ice |
|---|---|---|---|---|
| dry | 78 | 8 | 3 | 2 |
| wet | 7 | 69 | 1 | 13 |
| snow | 4 | 2 | 85 | 1 |
| ice | 5 | 18 | 2 | 67 |
从这个表能看到,wet和ice互相串得厉害,原因就是两类都存在反光,而且下雨天路面湿度变化很快,标注时前后帧可能分别标成不同类别。遇到这种情况,我会回到原图上查看同一段路不同时间的照片,确认是不是标签不一致。如果标签没问题,再考虑调高cls权重。
4.3 数据不均衡与类别权重调整
如果某一类样本只有其他类的三分之一,可以在训练时给少数类更高的分类损失权重。YOLOv5没有直接的类别权重参数,常见做法是在训练脚本里对分类损失加权:
# 假设 cls_losses 是每个类别分类损失的张量 cls_power = [1.0, 1.0, 0.5, 1.5] # 对应 dry, wet, snow, ice weighted_cls = sum( c * w for c, w in zip(cls_losses, cls_power) ) / sum(cls_power)这段代码把各类别损失乘以一个权重再归一化。权重越高的类别,参数更新时越受重视。但要控制幅度,权重差太大会让低权重类别几乎不学,mAP整体下降。我一般把最大权重控制在1.5以内,然后观察val集的R是否回升。
另外,results.png里的val/cls_loss如果到后期一直抖动,多半是标注边界不一致。YOLOv5网络结构对这种细粒度纹理变化很敏感,训练到后半程可以增加--hyp参数并关闭mosaic增强,用--no-mosaic跑最后20轮,减少合成图片对路面纹理的干扰。
4.4 置信度阈值如何选择
不同应用场景要卡不同置信度阈值。在混淆矩阵里,降低--conf-thres会提升召回,但wet假阳性增多;提高阈值则相反。部署前建议做一次阈值扫描,记录不同阈值下各类别的F1:
| 阈值 | mAP@0.5 | wet的F1 | ice的F1 |
|---|---|---|---|
| 0.15 | 0.81 | 0.63 | 0.71 |
| 0.25 | 0.82 | 0.68 | 0.73 |
| 0.45 | 0.79 | 0.66 | 0.70 |
上面表格是示意数据,实际值要用验证集跑出来。重点在于结论:阈值不是越高越好,wet和ice这类难分样本会在高阈值下大量丢失,导致路面状态切换的检测延迟变大。部署道路预警系统时,我更倾向于低阈值加后续时序滤波,而不是靠单帧高阈值硬刚。
5. 把模型接到视频流和边缘设备:TensorRT导出与多帧投票
5.1 导出TensorRT引擎
训练好的best.pt直接跑视频推理在CPU上很慢。如果目标是道路摄像头实时检测,我会先把权重导出成TensorRT引擎:
python export.py \ --weights runs/train/exp/weights/best.pt \ --include engine \ --imgsz 640 \ --device 0导出后得到.engine文件,推理速度比PyTorch原图快2到3倍。--imgsz必须与训练尺寸一致,否则精度会掉。导出时要注意TensorRT版本和YOLOv5仓库版本匹配,版本差太多会报engine加载失败。
5.2 多帧投票减少单帧误检
路面状态是连续物理量,单帧误检很正常。我一般用滑动窗口做多帧投票:取最近5帧,类别出现次数最多的作为最终状态,只有一致帧数达到阈值才触发切换。
from collections import Counter WINDOW = 5 state_history = [] def decide(state_list): counter = Counter([s for s in state_list if s is not None]) if not counter: return None most, cnt = counter.most_common(1)[0] return most if cnt >= 3 else None # 每帧推理后调用 state_history.append(pred_class) if len(state_history) > WINDOW: state_history.pop(0) final_state = decide(state_history)这段代码把最近5帧的预测结果统计一遍,超过3帧一致才输出最终状态。代价是状态切换最多延迟5帧,但能滤掉大部分单帧闪烁。雨雪天气视频里,这种时序滤波比单纯调高置信度阈值更可靠。
本文还有配套的精品资源,点击获取