简介:面向雨雪天气下的道路路面状况识别任务,资源提供了一套完整的COCO格式图像数据集,标签覆盖结冰路面、雪地、下雨湿滑和干燥路面四类典型路况。所有图片均为原始拍摄图像,未做过度裁剪或压缩,json标注文件精确记录了目标边界框与类别信息,可直接用于目标检测、实例分割及多标签分类模型的训练与验证,既能满足自动驾驶环境感知、智能交通监控等工程研发需求,也适合高校计算机视觉课程作为教学案例。包体共651个文件,包含646张jpg原始图像、3个json标注文件与2个txt说明文档,整体仅26.92MB,资源体积适中,便于快速下载与导入主流深度学习框架,省去数据整理环节。目前已有1010人学习浏览,具备一定的参考价值。借助该数据集,开发者无需从零进行数据采集和人工标注,可大幅缩短实验准备周期;同时,图像与标注分离的目录结构也有利于数据划分、增广和跨模型对比,能为路面状态识别算法的验证提供清晰、标准化的输入,适合初学者快速上手,也方便研究者开展消融实验与性能评测。
1. 雨雪天气路面状况数据集:四类路面识别任务的COCO标注资源拆解
做自动驾驶感知和车路协同项目的人都有个共同的痛点:雨雪天气的路面样本太难攒了。晴天干燥路面的数据到处都是,可一到结冰、积雪、湿滑这类低附着场景,采集车不愿意上路,公开数据集里能用的又少得可怜。这份雨雪天气路面状况数据集解决的正是这个缺口——图片全部采用原始拍摄图像,没有做过合成增强,标注格式是COCO,可识别类别覆盖结冰路面、雪地、下雨湿滑和干燥路面四类。对做路面状态分类、车道线检测预研、或者给YOLO系模型做微调的人来说,这份资源可以直接拿来当训练底料用,省掉自己满世界找图、再人工标注的折腾。
2. 读懂这份COCO标注:先看目录结构和类别定义再动手
把zip解压之后,第一件事不是急着训练,而是先摸清数据组织方式和标注文件结构。COCO格式是个黑匣子倒不至于,但它有三个文件必须搞清楚:train.json、val.json、test.json,分别对应训练、验证、测试三个划分。
2.1 文件命名规律暗示了数据来源
文件名长这样:ice-road31_jpg.rf.647734b2a0c45eadc7006abc92df2e1c.jpg。拆开看,ice-road31是原始图片名,jpg是格式,rf后面跟一长串哈希值是Roboflow平台导出时加的标识。这说明数据集原本是在Roboflow上管理的,导出成COCO格式时保留了原始图像没有做任何像素级修改。
这一点对训练很关键。Roboflow导出时默认会做resize、增强之类的预处理,但这份数据集的说明里明确写了“图片均采用原始图片”,意味着标注框的坐标和图像尺寸是严格对应的。你用json里记录的width和height去裁剪、缩放都不用担心坐标偏移。
2.2 类别ID映射和标注字段解析
用Python读一遍标注文件,先确认类别映射关系。常见做法是我会先把categories字段打印出来,确认四类路面状态的ID编号:
import json with open("train.json", "r", encoding="utf-8") as f: coco_data = json.load(f) categories = coco_data["categories"] print("类别数量:", len(categories)) for cat in categories: print("ID:", cat["id"], "名称:", cat["name"])这段代码的作用是读取训练集的categories字段。COCO格式里,categories是一个列表,每个元素包含id和name两个关键字段。id是标注文件里annotations部分category_id引用的数字,name是类别名。我一般会先跑一遍这个,确认四类是否齐全,有没有出现ID错位的情况。
接着看单张图片的标注信息长什么样:
# 查看第一张图片的标注信息 image_info = coco_data["images"][0] annotation_count = len(coco_data["annotations"]) print("示例图片信息:", image_info) print("标注总数:", annotation_count)打印结果中,images字段给出的是图片路径、宽、高和ID;annotations字段里则是每个标注框的bbox坐标、area面积、category_id类别ID。检查bbox格式时要特别注意:COCO的bbox是[x_min, y_min, width, height],不是YOLO的归一化中心点格式。如果你之前只做过YOLO数据,很容易在这里栽跟头。
2.3 标注质量的初步体检
拿到数据后别急着开训,先做个快速体检,统计每个类别的标注数量和框的尺寸分布:
from collections import Counter cat_ids = [ann["category_id"] for ann in coco_data["annotations"]] cat_counter = Counter(cat_ids) for cat in categories: cat_id = cat["id"] cat_name = cat["name"] print(f"{cat_name}: {cat_counter.get(cat_id, 0)} 个标注框") # 统计框尺寸分布 all_boxes = [ann["bbox"] for ann in coco_data["annotations"]] widths = [box[2] for box in all_boxes] heights = [box[3] for box in all_boxes] print(f"框宽度均值: {sum(widths)/len(widths):.2f}, 最小: {min(widths)}, 最大: {max(widths)}") print(f"框高度均值: {sum(heights)/len(heights):.2f}, 最小: {min(heights)}, 最大: {max(heights)}")这一步能看出两个问题:一是类别均衡性,如果某一个类别的标注数量明显偏少,训练时就要考虑给这个类别加权;二是标注框尺寸,如果最小框只有十几个像素,说明存在大量小目标,模型学习难度会明显增大。这两项直接决定后边的训练策略和增强参数怎么设。
3. 从COCO到YOLO格式转换:写一个通用脚本并处理四个坑
COCO格式本身适合做检测和分割,但你如果打算用YOLOv8或者YOLOv5训练,就必须先把COCO的json转成YOLO的txt格式。这一步的坑比想象中多,值得单独拆开说。
3.1 转换脚本的完整实现
我自己写过一个通用转换脚本,逻辑很简单但每一步都有讲究:
import json import os from pathlib import Path def coco_to_yolo(coco_json_path, output_dir, img_dir): """ 将COCO格式标注转换为YOLO格式 Args: coco_json_path: COCO标注文件路径 output_dir: 输出YOLO标注目录 img_dir: 原始图片目录 """ with open(coco_json_path, "r", encoding="utf-8") as f: coco_data = json.load(f) Path(output_dir).mkdir(parents=True, exist_ok=True) # 建立图片ID到文件名的映射 img_id_to_info = {} for img in coco_data["images"]: img_id = img["id"] img_id_to_info[img_id] = { "file_name": img["file_name"], "width": img["width"], "height": img["height"] } # 建立类别ID到YOLO类别索引的映射 cat_id_to_index = {} for idx, cat in enumerate(coco_data["categories"]): cat_id_to_index[cat["id"]] = idx # 按图片ID分组标注 anns_by_img = {} for ann in coco_data["annotations"]: image_id = ann["image_id"] if image_id not in anns_by_img: anns_by_img[image_id] = [] anns_by_img[image_id].append(ann) # 为每张图片生成对应的txt标注文件 for img_id, anns in anns_by_img.items(): img_info = img_id_to_info[img_id] img_w = img_info["width"] img_h = img_info["height"] base_name = Path(img_info["file_name"]).stem txt_path = os.path.join(output_dir, base_name + ".txt") with open(txt_path, "w", encoding="utf-8") as f: for ann in anns: # 读取COCO的bbox: [x_min, y_min, width, height] x_min, y_min, bw, bh = ann["bbox"] # 转换为YOLO格式: [center_x, center_y, width, height] 并归一化 cx = (x_min + bw / 2) / img_w cy = (y_min + bh / 2) / img_h norm_w = bw / img_w norm_h = bh / img_h # 类别ID转换 yolo_class = cat_id_to_index[ann["category_id"]] # 裁剪到0-1范围,防止越界 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) norm_w = min(max(norm_w, 0.0), 1.0) norm_h = min(max(norm_h, 0.0), 1.0) f.write(f"{yolo_class} {cx:.6f} {cy:.6f} {norm_w:.6f} {norm_h:.6f}\n") return len(anns_by_img) # 转换训练集 converted = coco_to_yolo("train.json", "labels/train", "images/train") print(f"成功转换 {converted} 张图片的标注") # 转换验证集 converted = coco_to_yolo("val.json", "labels/val", "images/val") print(f"成功转换 {converted} 张图片的标注")这个脚本的核心逻辑分四步:先建图片ID到宽高信息的映射,再建COCO类别ID到YOLO顺序索引的映射,然后按图片ID聚合标注框,最后逐框换算坐标。换算公式本身不复杂,但有几个细节值得注意。
3.2 四个高频踩坑点和对应解法
第一个坑是类别ID索引。COCO的categories字段里,ID可能是1、2、5、7这样不连续的编号,而YOLO格式要求类别从0开始且必须连续。直接用category_id当YOLO类别号,会在训练时报类别数量不匹配。解决方式就是我脚本里写的:enumerate(coco_data["categories"])按顺序建索引,不依赖原始ID值。
第二个坑是坐标越界。Roboflow导出的标注偶尔会出现框边界超出图像范围几个像素的情况,这些框直接转成YOLO格式后,归一化值可能略大于1,训练时OpenCV的rectangle函数或者Mosaic增强都可能报错。处理办法是写一层min(max())做裁剪,我之前为了省事跳过这步,结果训练到第三个epoch就崩了。
第三个坑是图片和标注文件的同名匹配。COCO的file_name可能包含子目录前缀比如images/ice-road31_jpg.rf.xxx.jpg,而YOLO的txt文件要求放在labels目录下且与图片同BaseName。脚本里用Path(img_info["file_name"]).stem取文件名不包含扩展名,就是为了保证txt和jpg一一对应。
第四个坑是空标注文件。这个数据集里如果某张图的某个类别恰好没有目标,annotations里就不可能出现它的image_id,最终这张图片没有txt文件。YOLO训练允许图片没有对应txt,但你会损失一部分背景样本。我习惯在转换完后跑一遍检查,找出哪些图片没有标注文件,单独归到背景样本里做负样本用。
转换完成后的目录结构应该是这样的:
├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── train.json └── val.json4. 用YOLOv8训练路面识别模型:配置、参数和验证全流程
数据集格式转换完毕,就到实际训练环节。YOLOv8是目前最省心的选择,配置写对之后直接一条命令就能跑起来。
4.1 准备训练配置文件
先创建一个data.yaml。这是YOLOv8的数据集描述文件,路径、类别数量、类别名都必须准确:
# data.yaml # 训练和验证图片的绝对路径或相对路径 train: ./images/train val: ./images/val # 类别数量和类别名,顺序要跟转换脚本里的索引一致 nc: 4 names: ["icy", "snowy", "wet", "dry"]我一般会用绝对路径写train和val,因为YOLOv8在解析路径时会拼上当前工作目录,相对路径偶尔出问题。nc要和names的长度一致,这一步错了训练直接起不来。
4.2 训练命令和参数解释
接下来启动训练。我常用的训练命令长这样:
# 训练YOLOv8s模型,200个epoch,batch size看显存调 yolo train model=yolov8s.pt data=data.yaml epochs=200 imgsz=640 batch=16 device=0 patience=30 # 如果显存不够,降到yolov8n或者减小batch yolo train model=yolov8n.pt data=data.yaml epochs=150 imgsz=640 batch=8 device=0参数含义逐个说:model=yolov8s.pt是以COCO预训练权重做迁移学习,对路面识别这种小目标场景效果比从头训练好很多;imgsz=640是训练时缩放的图像尺寸,雨雪路面的纹理细节需要保留,尺寸太小容易丢失结冰和湿滑的视觉差异;patience=30是早停机制,验证集mAP连续30轮不涨就自动停止,省得死等;device=0指定用第一张显卡。
如果跑完一轮发现mAP偏低,大概率是类别不均衡的问题。我一般会加类别权重重新训练:
# 给数量少的类别加大权重,假设雪地和结冰路面样本少 yolo train model=yolov8s.pt data=data.yaml epochs=200 imgsz=640 batch=16 device=0 class_weights=[1.0, 2.0, 2.0, 1.0]class_weights参数的顺序要和names完全对应,权重值越大,对应类别的损失占比越高,模型会偏向学习这些少样本类别。没有class_weights参数的老版本就需要在loss函数里手动改,YOLOv8官方已经支持直接传。
4.3 模型训练后的评估维度
训练结束后,务必看验证集的四个指标:mAP50、mAP50-95、precision和recall。mAP50指的是IoU阈值为0.5时的平均精度均值,mAP50-95是在0.5到0.95区间内不同IoU阈值的平均结果。对路面状态识别来说,mAP50更重要,因为这个任务的标注框通常比较大,不需要追求像素级的精确框定位。
跑完看一眼confusion_matrix.png,它能直观显示哪两类最容易互相混淆。如果发现“下雨湿滑”和“结冰路面”经常被搞混,原因通常是这两类在视觉特征上太接近——都是深色、都反光。处理办法一个是增加这两类样本的增强强度,另一个是裁掉标注框以外的干扰区域重新训练。
5. 必须看的避坑清单:从数据体检到训练翻车的五条血泪经验
训练这类路面数据集,有几个坑是我反复踩过的。每一条都值得你在动手之前先对照检查一遍。
5.1 标注框和原图尺寸不匹配导致坐标错位
- 现象:训练时Loss降不下去,验证集的mAP始终在0.3以下徘徊。
- 原因:Roboflow导出的数据集有时会带一份
_mask版本,或者某些工具自动缩放过图片,但标注json里的width和height还是原始值。转换脚本用原始宽高做归一化,和实际缩放后的图片对不上。 - 解决:转换前随机挑三张图片,用脚本核对
json里的width、height和图片实际尺寸是否一致。不一致就先统一图片尺寸,再跑转换脚本。
5.2 类别不均衡导致极端漏检
- 现象:训练了200轮,晴天干燥路面的精确率高得吓人,但结冰路面的recall不到0.5。
- 原因:数据集中干燥路面占比远高于结冰和积雪路面,模型学成了一个“偏向干燥”的分类器。
- 解决:除了
class_weights,还可以对少样本类别做复制粘贴增强,或者从源数据集中手动多挑一些结冰路面图片补进来。对特别少的类别,我倾向于直接补数据而不是改权重,效果更稳定。
5.3 标注框过小导致小目标完全学不到
- 现象:预测时大目标框得准,远处的小目标直接漏掉。
- 原因:
imgsz=640的训练尺寸对小目标不友好。路面状态识别往往关注的是远处一小块路面区域。 - 解决:把
imgsz提到960或者1280,代价是显存翻倍、训练时间变长。另一个做法是输入尺寸不变,在数据增强里开启mosaic=1.0,让小目标在拼接图中有更多出现机会。
5.4 数据集划分不合理导致评估虚高
- 现象:训练集mAP很高,测试集一把就掉下来。
- 原因:同一位置的连续帧图片可能同时出现在train和val里,模型等于见过“答案”。Roboflow导出的划分有时是按文件序列顺序切的,不是完全随机。
- 解决:自己重新划分数据集,按时间戳或位置分组,确保同一场景的图片不跨划分。代码实现很简单,把文件名单打乱后按比例切分即可,但很多人会忽略这一步。
5.5 预训练权重版本不匹配
- 现象:
yolov8s.pt加载时报shape不匹配或者直接警告。 - 原因:网络结构、类别数量变了,预训练权重最后一层无法复用。某些渠道下载的权重文件本身就有问题。
- 解决:直接从YOLO官方源拉权重,确认是
v8系列的.pt文件,不要用别人二次转换过的权重。如果类别数量不一致,官方训练代码会自动跳过不匹配的层,不用手动处理,但要注意看启动日志里的警告信息。
6. 验证模型是否真的可靠:场景复现测试和故障样本分析技巧
模型训练完,最后的验证不能只看mAP数字。对路面识别这类安全相关任务,我的习惯是额外跑一遍场景复现测试。
先做一个交叉验证的脚本,把val里每张图的预测结果叠加原始图片输出,人工过目一遍。这个操作的作用有两个:一是看标注框是否贴合路面区域,二是看有没有把车身上反光、路牌反光错判成湿滑路面的情况。写起来很快:
from ultralytics import YOLO import cv2 model = YOLO("runs/detect/train/weights/best.pt") results = model.predict(source="images/val", save=True, conf=0.25, imgsz=640) # 分析预测结果中各类别的置信度分布 for result in results: boxes = result.boxes for box in boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) print(f"类别: {result.names[cls_id]}, 置信度: {conf:.4f}")参数里conf=0.25是置信度阈值,低于这个值的框会被过滤掉。对路面识别我习惯设到0.25而不是默认的0.25低一档,因为漏检比误检更致命——你可以容许模型把湿滑误判成干燥,但不能容许把结冰漏掉。
最后做一个故障样本收集。把预测错误的图片单独整理到一个文件夹,按错误类型分类:结冰被预测成干燥、雪地被预测成湿滑、标注框定位偏移。这些故障样本是你下一个版本迭代的直接素材。我一般会保留至少50张错误样本,作为下一轮训练的额外负样本补充进去。
有个细节值得提:雨雪天气的视觉特征受光照影响极大,同一个结冰路面在逆光和顺光下拍摄,模型输出可能完全不同。如果验证时恰好赶上天晴,别急着下结论说模型不行,把阴天、夜间、逆光场景的样本单独测一轮再判断。
从那以后我每次跑完路面识别模型,都强制走一遍这个流程:先看混淆矩阵找容易混淆的类别对,再人工过目至少50张预测结果图,最后留下错误样本做下一轮的输入。这套流程不复杂,但能避免你被一个虚假的mAP数字骗过去。希望帮到你。
本文还有配套的精品资源,点击获取