news 2026/9/11 19:15:04

YOLOv5路面状态检测实战:结冰/雪地/湿滑数据集训练与部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5路面状态检测实战:结冰/雪地/湿滑数据集训练与部署

简介:面向智能交通与自动驾驶场景的雨雪天气路面状况数据集,覆盖结冰路面、雪地、雨天湿滑及干燥路面等常见路况,可用于训练路面状态识别目标检测模型。压缩包共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渲染出来,重点检查icewet两类框是否覆盖了相同区域。

2.2 YOLOv5标注文件解析

YOLOv5的标签不是用矩形坐标,而是归一化后的中心点加宽高。每行对应一个目标框,格式如下:

<cls_id> <x_center> <y_center> <width> <height>

五个值都用空格分隔,x_centery_centerwidthheight都是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: ice

trainval指向图片目录,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默认超参数和推荐调整范围,可以直接当作参照:

超参数默认值推荐范围说明
lr00.010.005-0.01初始学习率,数据量少时建议偏低
lrf0.010.01-0.1最终学习率因子,决定后期微调幅度
momentum0.9370.8-0.95SGD动量,太大会震荡
cls0.50.3-1.0分类损失权重,样本不均衡时调高
box0.050.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_losstrain/cls_lossval/cls_loss。正常情况是前10轮下降很快,后面慢慢收敛。如果val损失在20轮后开始反弹,说明过拟合了,先把epochs降下来,或者加大数据增强。路面状态数据的增强和普通目标检测不完全一样,hsv_hhsv_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.pngconfusion_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 混淆矩阵里看易混类别

路面状态检测的混淆矩阵通常会出现这样的情况(示意数据):

实际\预测drywetsnowice
dry78832
wet769113
snow42851
ice518267

从这个表能看到,wetice互相串得厉害,原因就是两类都存在反光,而且下雨天路面湿度变化很快,标注时前后帧可能分别标成不同类别。遇到这种情况,我会回到原图上查看同一段路不同时间的照片,确认是不是标签不一致。如果标签没问题,再考虑调高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.5wet的F1ice的F1
0.150.810.630.71
0.250.820.680.73
0.450.790.660.70

上面表格是示意数据,实际值要用验证集跑出来。重点在于结论:阈值不是越高越好,wetice这类难分样本会在高阈值下大量丢失,导致路面状态切换的检测延迟变大。部署道路预警系统时,我更倾向于低阈值加后续时序滤波,而不是靠单帧高阈值硬刚。

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帧,但能滤掉大部分单帧闪烁。雨雪天气视频里,这种时序滤波比单纯调高置信度阈值更可靠。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 19:11:45

2026毕业季终极防线:一文看懂你的“AI率”到底有没有超标?

在2026年毕业答辩的“深水区”&#xff0c;除了传统的查重红线&#xff0c;悬在无数学子头顶的另一把利剑&#xff0c;便是“AI生成内容检测&#xff08;AIGC率&#xff09;”。 许多同学明明是自己一字一句写出的论文&#xff0c;却屡屡被检测系统判定为“高AI率”&#xff0…

作者头像 李华
网站建设 2026/9/11 19:10:13

离网光伏系统仿真建模与MPPT控制:Matlab/Simulink工程实践

简介&#xff1a;离网太阳能发电系统采用光伏板独立供电&#xff0c;适用于偏远地区、应急电源及小型户用等场景。配套的MATLAB程序覆盖光伏组件选型、储能配置与控制策略等核心环节&#xff0c;利用Simulink建立光伏与电池模型&#xff0c;并借助遗传算法、粒子群等优化算法求…

作者头像 李华
网站建设 2026/9/11 19:10:10

【Prometheus + Alertmanager + Grafana 监控平台部署】

Prometheus Alertmanager Grafana 监控平台部署说明&#xff1a;本文只记录部署操作步骤与配置要点。安装包内已提供的文件&#xff08;docker-compose.yaml、prometheus.yml、alerts/*.yml、node_targets.json、alertmanager/config.yml、install_node_exporter.sh、node_ex…

作者头像 李华
网站建设 2026/9/11 19:09:19

三种文本分类模型对比与Python复现:TextGCN、TextING与LEAM解析

简介&#xff1a;面向自然语言处理课程设计与期末项目&#xff0c;这份资源复现了TextGCN、TextING、LEAM三种经典文本分类方法&#xff0c;提供完整Python源码与详细注释。TextGCN基于图卷积网络&#xff0c;TextING基于图神经网络&#xff0c;LEAM则利用标签嵌入注意力机制&a…

作者头像 李华
网站建设 2026/9/11 19:09:16

共享单车租赁预测:从数据清洗到随机森林与SVM建模全流程

简介&#xff1a;共享单车租赁数量预测是交通出行类数据挖掘的典型场景&#xff0c;项目使用Python完成数据分析和可视化&#xff0c;并以随机森林、支持向量机两种模型进行租赁量回归预测。代码约300行&#xff0c;既涵盖Pandas、NumPy的数据清洗与特征预处理&#xff0c;也包…

作者头像 李华