ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从COCO到YOLO:雨雪路面数据集训练全流程与避坑指南

从COCO到YOLO:雨雪路面数据集训练全流程与避坑指南 简介雨雪天气路面状况识别是自动驾驶与智能交通中的常见难点这份数据集专门面向结冰路面、雪地、下雨湿滑、干燥路面四类场景图片均为原始拍摄图像并使用COCO格式进行目标标记可直接用于目标检测、语义分割等模型的训练与验证。压缩包共包含651个文件其中646张jpg原图、3个json标注文件以及2个txt说明文件整体大小约26.92MB结构简洁适合目标检测入门练习或路面状态识别项目快速调用。目前已有1010人学习下载数据规模虽不大但四类典型天气路面均覆盖标注风格统一能够帮助开发者省去自行采集与清洗数据的环节专注模型调优。对于需要构建雨雪天气路面分类或结冰预警原型的研究者、算法工程师及学生而言是一份实用且易上手的基础资源。1. 拿到雨雪天气路面状况数据集之后先别急着训练这份压缩包标题写着雨雪天气路面状况数据集里面大概率是coco标记格式的道路图片干燥、湿滑、雪地、结冰路面一个 zip 包全装完。这类数据主要服务自动驾驶感知、道路结冰预警、冬季养护巡检等场景图片没有做裁剪或压缩预处理基本保留了拍摄时的光照、镜头上水珠和反光等原始信息这对模型训练来说既是优点也是坑。适合谁用呢正在做路面目标检测、路面状态分类或者冬季道路监测的工程团队拿它做预训练、做验证集补充都很划算。但先提醒一句coco标记不等于能直接喂给 YOLO哪怕你手里有现成的训练脚本也要先完成一次格式转换和数据清洗否则后续所有指标都是空中楼阁。2. 解压与情报收集先确认 ZIP 里的 coco 标记值得信赖2.1 linux解压缩命令zip与 Windows 解压乱码拿到数据集的第一反应是解压但解压这一步就有一半人的做法不严谨。Windows 用户右键选“全部解压缩”通常没问题问题出在你把 zip 传到 Linux 服务器去训练训练机多半是 Ubuntu 或者 CentOS这时候解压命令选错了后面全是乱码文件。我一般在 Linux 下先用 unzip 解压并指定字符集mkdir -p road_weather_dataset unzip -O UTF-8 雨雪天气路面状况数据集.zip -d road_weather_dataset cd road_weather_dataset find . -maxdepth 2 -type f | head -30这个-O UTF-8参数是很多人容易漏掉的。Windows 下打包的 zip 文件名是 GBK 编码直接裸敲unzip会让“结冰路面”这类目录名变成一堆乱码后续脚本读取路径时直接抛 FileNotFoundError。如果你的 Linux 发行版自带 unzip 版本比较老不支持-O参数macOS 自带的 unzip 就没有这个选项那就用 7-Zip 的命令行版7z x 雨雪天气路面状况数据集.zip -oroad_weather_dataset7z 在解压时会尝试识别原文件名编码乱码概率低很多。还有个更稳的做法不直接在 Linux 解压而是在 Windows 上先用 Bandizip 或 7-Zip 解压一次确认目录结构无误后再整体打包成一个新的 ASCII 文件名 zip 传到服务器。解压完成后不要急着复制到训练目录先看一下目录结构。这类数据集通常是 images 和 annotations 两个目录images 里是原始图片annotations 里是 coco 标准的 JSON 文件。如果只有 JSON 和散落的图片但没有按训练集、验证集分目录那说明标注文件里只有图片 id 和标注框训练集划分要自己做这一点后面会专门讲。2.2 ZIP伪加密与密码移除先判断是真密码还是伪加密解压时如果提示输入密码而数据来源没有告诉你密码别急着找暴力破解工具。路况数据集在流传过程中经常被二次打包有人习惯性给 zip 加了层密码或者用某些压缩工具打包时把“加密标志”误置为开启。这类情况里有相当一部分是 zip 伪加密并不是真的加密。判断是不是伪加密Linux 下用 zipinfozipinfo -v 雨雪天气路面状况数据集.zip | grep -i encrypt如果输出显示 “encryption: 3DES” 或者 “AES-256”那是真加密没有密码基本拿不到内容。但如果只是 General purpose bit 显示异常或者用 Python 的 zipfile 去读时报RuntimeError: File is encrypted但 README 里明明没提密码那大概率是伪加密。伪加密的本质是压缩包头部的加密标志位被置为 1文件内容并没有真正用密码算法处理过。处理办法是把 local file header 里的通用标志位改回 0。用十六进制工具找到标志位所在字节把 0x0001 那一位清掉保存后再解压。嫌麻烦的话直接用 7-Zip 打开 zip能直接看到文件名列表并且内容可以预览说明文件本身没加密认准这一点就不会被误导。关于 zip 密码移除多说一句真正的 AES 加密在没密码的情况下没有后悔药什么软件所谓“移除密码”对现代加密算法基本都是无效的别花时间去试。正确路径是找数据提供方要密码或者重下一份未加密的版本。你真正需要警惕的是伪加密这属于打包工具抽风导致的玄学问题改完标志位就能正常解压修改前记得先备份原始 zip改坏了还能重来。2.3 读 COCO JSON 统计类别一个脚本看清家底解压完成打开 annotations 目录里面通常是instances_train.json或instances_default.json这类文件这就是 coco 标记的主文件。COCO 格式的 JSON 顶层有五个字段info、licenses、images、annotations、categories。你最该关心的是后三个。images 列表里每项包含 id、width、height、file_namecategories 列表里是类别名和类别 idannotations 列表里就是真正的标注框每项包含 image_id、category_id、bbox、area 和 segmentation。这个数据集的 segmentation 可能是多边形也可能直接用 bbox 表示但目标检测训练只依赖 bbox。拿到 JSON 后我先跑一个统计脚本看清这个数据集到底有多少张图、多少个框、每个类别有多少实例import json import collections from pathlib import Path ann_path Path(road_weather_dataset/annotations/instances_default.json) data json.loads(ann_path.read_text(encodingutf-8)) imgs data[images] anns data[annotations] cats {c[id]: c[name] for c in data[categories]} print(image count:, len(imgs)) print(annotation count:, len(anns)) print(categories:, cats) cat_counter collections.Counter() for ann in anns: cat_counter[cats[ann[category_id]]] 1 print(per-category annotation count:, dict(cat_counter)) # 检查bbox是否超出图片边界 bad 0 for ann in anns: img next(i for i in imgs if i[id] ann[image_id]) w, h img[width], img[height] x, y, bw, bh ann[bbox] if x 0 or y 0 or x bw w 1e-5 or y bh h 1e-5: bad 1 print(out-of-bound bbox count:, bad)统计结果直接决定了后面的训练策略。比如干燥路面可能占了一半以上的标注框而下雨湿滑只有两三百个框这时候训练时就要考虑类别不平衡。bbox 越界的数量也很关键这类数据集的标注不少是半自动生成的边界框偶尔会比图片还大或者坐标有负值。少量越界可以在转换脚本里过滤掉但如果越界比例超过 5%说明标注坐标基准和图片尺寸不是一套体系那就得重新评估这套数据能不能直接用。我见过最典型的翻车案例是img[width] 是 1920图片实际尺寸是 1280标注是按 1920 宽做的。这可能是因为标注前统一 resize 过或者标注软件读取了被 EXIF 旋转后的尺寸而图片文件本身尺寸不对。所以统计脚本里加一个“实际读图尺寸 vs JSON 里 width/height”的校验会更有把握。2.4 原始图片不等于“直接用”先统一 EXIF 方向标题里写着“图片均采用原始图片”这句话听起来像卖点但实际训练时反而是个要注意的点。手机、无人机、行车记录仪拍摄的 JPG 会写入 EXIF 信息其中 Orientation 字段记录了拍摄时的设备方向比如 1 表示正常6 表示需要顺时针旋转 90 度才能看到正像。OpenCV 的cv2.imread读图时不会自动应用 EXIF 旋转PIL 的Image.open默认也不转只有ImageOps.exif_transpose会处理。结果就是你训练时模型看到的图片可能是“歪着”的而 COCO 标注里的 bbox 坐标是按旋转后的正像标注的。两者一错位损失函数根本没法收敛表现出来就是训练 loss 降得很慢验证集 mAP 在 0.3 以下怎么调都上不去。统一做法是训练前先对全部图片做一次 EXIF 转正并且转正后重新确认 bbox 坐标。具体操作放在第 5 章避坑部分一起说这里你先记住接手任何带 EXIF 的“原始图片”数据集第一件事不是复制到 GPU 服务器而是先转正图片并核对标注。3. 把 COCO 标记转成 YOLO 训练集转换脚本与四个边界坑3.1 确定类别映射结冰路面、雪地、下雨湿滑、干燥路面这个数据集的识别对象是路面状态不是车辆或行人所以类别大概率是四类干燥路面、下雨湿滑路面、结冰路面、雪地。COCO 的 categories 里 id 可能是 1、2、3、4但 YOLO 训练要求的类别 id 必须从 0 开始连续排列。如果 JSON 里类别 id 是 1 到 4转换时要把它们映射成 0 到 3。这里有一个很多人会犯的错直接用ann[category_id]写进 YOLO 的 txt 标签文件于是类别 id 变成了 1、2、3、4训练时 YOLO 会认为有 5 个类别最后一个类别永远没有正样本。所以类别映射表一定要单独建一个字典label_map { dry: 0, wet: 1, ice: 2, snow: 3, }如果 COCO JSON 里的类别名更随意比如写成rainy、icy、dry、snowy那就先把 categories 打印出来再做一次名字到 id 的映射。转换脚本里对不在映射表内的类别直接跳过不报错这样即使 JSON 里混入了一个“其他”类别也不会中断转换。3.2 COCO 转 YOLO txt 的转换脚本COCO 的 bbox 给的是左上角坐标和宽高[x, y, width, height]。YOLO 要求的是归一化后的中心点坐标和宽高比例class_id center_x center_y width height。这个单位换算相当基础但因为坐标来源不同导致坐标飞出去的情况反而更容易出现在“宽高”上因为如果漏了除以图片尺寸中心点和宽高混在一起训练出的框全部偏到图片角落。转换脚本如下import json import shutil from collections import defaultdict from pathlib import Path ROOT Path(road_weather_dataset) IMAGES ROOT / images OUT Path(yolo_dataset) label_map {dry: 0, wet: 1, ice: 2, snow: 3} data json.loads((ROOT / annotations / instances_default.json).read_text(encodingutf-8)) cat_id_to_label {} for c in data[categories]: if c[name] in label_map: cat_id_to_label[c[id]] label_map[c[name]] img_info {img[id]: img for img in data[images]} ann_by_img defaultdict(list) for ann in data[annotations]: ann_by_img[ann[image_id]].append(ann) MIN_W 4 MIN_H 4 for img_id, anns in ann_by_img.items(): img img_info[img_id] w, h img[width], img[height] file_name img[file_name] if not Path(file_name).is_absolute(): file_name Path(file_name).name src_path IMAGES / file_name if not src_path.exists(): print(missing:, src_path) continue dst_img_dir OUT / images / all dst_img_dir.mkdir(parentsTrue, exist_okTrue) shutil.copy(src_path, dst_img_dir / src_path.name) lines [] for ann in anns: cat_id ann[category_id] if cat_id not in cat_id_to_label: continue x, y, bw, bh ann[bbox] if bw MIN_W or bh MIN_H: continue cx x bw / 2.0 cy y bh / 2.0 line f{cat_id_to_label[cat_id]} {cx / w:.6f} {cy / h:.6f} {bw / w:.6f} {bh / h:.6f} lines.append(line) if lines: dst_txt_dir OUT / labels / all dst_txt_dir.mkdir(parentsTrue, exist_okTrue) txt_path dst_txt_dir / (src_path.stem .txt) txt_path.write_text(\n.join(lines), encodingutf-8) print(conversion done)这段脚本有几个参数值得说明。file_name可能带或多或少的路径前缀有的标注软件会写成images/0001.jpg有的直接写0001.jpg直接取Path(file_name).name是为了只拿文件短名避免拼接路径时重复目录。MIN_W和MIN_H是用来过滤过小框的4×4 像素以下的目标连人眼都难分辨保留进去只会给损失函数增加噪声。如果你要做密集小目标检测这种任务把这两个值调成 0 再看效果。cx / w和bw / w必须有括号吗不需要但建议写成((cx) / w)这种显式方式避免后续有人改成别的公式时把除号后续的计算顺序搞错。归一化值保留 6 位小数足够YOLO 训练精度不会因此受影响文件体积还小。3.3 四个边界坑从 COCO 到 YOLO 必踩的规则第一个坑是 iscrowd 字段。COCO 原始标注里有iscrowd1的标注代表该区域是一群目标聚集在一起这类标注通常没有精确的 bbox 边界。转换脚本里我没有过滤iscrowd你需要根据数据集中该类标注的数量决定。如果它就几条直接丢弃如果数量多建议单独统计这些图的分布因为它们往往是雨滴镜头、积雪反光这类难样本。第二个坑是同一张图里有多个同类别框。比如一张雪地照片里有多个积雪区域每个区域都标注了一个框这不存在问题。但有时候同一目标被标注了两个高度重叠的框转换后 YOLO 训练时会出现重复计算 loss 的情况。我一般会在转换脚本里加一个简单的重复框检测两个框的 IoU 大于 0.9保留面积大的那个。第三个坑是文件名冲突。原始图片叫001.jpg但 images 目录下可能有两个001.jpg一个在train子目录一个在val子目录。COCO JSON 里 file_name 区分了路径但Path().name之后两者同名转换时后写的 txt 会覆盖先写的。解决方法是改用自己的文件名规则比如前缀加上原始目录名或者用 image_id 作为文件名。第四个坑是图片尺寸不一致。这个数据集标题强调“原始图片”不同设备拍出来的图可能是 1920×1080、1280×720、4000×3000 混着来。转换脚本按每张图的 width 和 height 做归一化所以本身是对的。但你想在训练时给 YOLO 设固定的 imgsz比如 640那大图被 resize 后小目标会变得非常小。经验做法是先按短边统一缩放到 1280存储后再转换这样既保留了小目标信息又避免训练时每次 resize 的随机裁切把目标切没。3.4 训练集划分让结冰和湿滑出现在验证集里COCO 标注文件通常只给一个全集没有官方划分。自己切分时如果按随机抽样很可能把占比极小的结冰路面全部塞进训练集验证集里一条结冰样本都没有。这时候 mAP 看着还行但全是干燥路面的贡献模型面对真实冰雪场景等于裸奔。划分的原则是保证每个类别在验证集里至少出现一次最好保持和全集相同的比例。这里最粗暴但好用的做法是用 sklearn 的train_test_split加 stratify 参数但 stratify 必须按“图级别的主类别”传不是按标注框级别因为同一张图可能有多类别标签。如果你不想引入 sklearn也可以用简单的随机种子加类别检查或者干脆做 K 折交叉验证。我一般先按 8:1:1 切成训练、验证、测试然后单独打印验证集里每个类别的框数量如果发现某个类别数量为 0就把训练集里包含该类别的一部分图挪进验证集宁可验证集变大也不能缺类别。划分完目录结构按 YOLO 的习惯组织yolo_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/images 和 labels 保持完全相同的子目录结构YOLO 训练时默认根据图片路径推导标签路径。如果你图省事把标注和图片混在一个目录里训练也能跑但后续做数据版本管理会非常混乱。4. yolov8训练自己的数据集参数调教要从路况特性出发4.1 准备 dataset.yaml路径和 names 是唯一关键YOLOv8 训练自己的数据集时最难的部分不是调模型结构而是把 dataset.yaml 写对。这个配置文件决定了 YOLO 去哪里找图、去哪里找标签。path: /home/yourname/road_weather/yolo_dataset train: images/train val: images/val names: 0: dry 1: wet 2: ice 3: snowpath必须写成绝对路径YOLOv8 对相对路径的解析经常出现奇怪问题你明明把 yaml 放到项目目录里了它还是到当前工作目录去找。train和val是相对path的目录路径指向的是图片目录YOLO 会自动去同名labels目录下找 txt 标注文件。names要和你转换脚本里的 label_map 完全一致顺序不能乱。这里最容易翻车的点在于YOLOv8 的names支持从 0 开始但如果你写了类似names: [dry, wet]这种纯列表训练脚本会把类别序号自动从 0 开始对应没问题但如果你写成字典形式{0: dry, 1: wet}那必须保证字典 key 连续且从 0 开始。4.2 训练命令与适合雨雪场景的数据增强参数准备好 yaml 后命令行启动训练yolo detect train \ modelyolov8s.pt \ dataroad_weather.yaml \ epochs120 \ batch16 \ imgsz640 \ lr00.005 \ hsv_h0.015 \ hsv_s0.6 \ hsv_v0.4 \ fliplr0.5 \ scale0.4 \ close_mosaic10这些参数不是随便抄来的。lr0我特意调到 0.005 而不是默认的 0.01因为路况数据集对比物体检测的公开数据集来说规模往往小一个数量级学习率过大会在几十轮后把损失震荡到难以收敛。hsv_h只给了 0.015 而不是常见的 0.05是因为冰雪路面本身就是高亮低饱和场景色调抖动太强会让雪地的白变成灰紫色反而干扰模型对“雪地”的判别。hsv_s0.6和hsv_v0.4可以相对大胆路况在不同光线下确实会有饱和度与明暗差异这个增强是模拟自然变化的。scale0.4控制训练时图片随机缩放的幅度雨雪天气中目标距离差异大0.4 的缩放能带来一定的尺度扰动。close_mosaic10表示训练最后 10 个 epoch 关闭 mosaic 增强。Mosaic 在前期能把四张图拼成一张对小样本类别非常有帮助但最后一阶段如果还开着模型会持续看到大量拼接边界特征实际推理时这种特征不会出现导致 mAP 虚高。这也是一个参数细节很多人不调模型最后验证集表现还行实际部署就露馅。如果显存有限比如只有 8Gbatch 从 16 降到 8同时把 imgsz 从 640 降到 512。但注意冰雪路面的裂缝、结冰纹理都依赖分辨率imgsz 降低会让结冰和湿滑的区分变得更模糊所以优先保 batch不要优先降 imgsz除非你的 GPU 实在跑不动。4.3 验证指标怎么读别被干燥路面的高 mAP 骗了训练结束后YOLO 会在 runs/detect/train 目录下生成 results.csv、confusion_matrix.png、val_batch*.jpg 等文件。大多数人只看那一行 mAP50但如果这个数据集里干燥路面占了 80% 以上整体 mAP 会被拉得很高掩盖结冰和湿滑类别表现极差的事实。我一般会做两件事。第一看results.csv里每个类别的 mAPYOLOv8 在验证阶段会输出 per-class AP 列表直接用 Python 读取import pandas as pd from pathlib import Path csv_path Path(runs/detect/train/results.csv) df pd.read_csv(csv_path) print(df.columns.tolist()) print(df.iloc[-1])第二打开混淆矩阵图重点看“结冰”和“湿滑”这两行。这两个类别在视觉上非常接近都是深色路面上有反光区域标注本身的交界也很模糊。如果混淆矩阵里这两类互相错分的比例超过 30%说明模型在学“反光”而不是在学“路面状态”你去数据集里抽几张图看一眼就明白了。这时候与其继续调模型不如回去审视标注质量把那些边框覆盖面积过大、同时包含两种路况的样本重新处理。对于这种语义边界模糊的多分类一个有效策略是把任务从“四分类检测”降级成“干燥 vs 非干燥”二分类先把非干燥路面全体检出再在检测框内做细分类。这样检测模型只负责找“目标区域”分类模型负责区分湿滑、结冰、雪地两个模型各自压力小很多这是路况识别项目里很常见的工程妥协。5. 常见问题与避坑COCO zip 数据集的高发事故清单5.1 解压时报 invalid zip archive: could not find EOCD现象用 unzip 或 7z 解析这个 zip 时直接报错错误信息类似invalid zip archive: could not find EOCD或者压缩包能打开但里面文件不全。原因EOCD 是 zip 中央目录结束标记位于文件末尾。报这个错基本说明 zip 文件损坏了通常是下载工具中断、磁盘空间不足或者文件从网盘传输时被强制改名导致尾部数据缺失。它不是伪加密伪加密一般还能列出文件名这个错是彻底读不出来。解决最可靠的办法是重新下载整个 zip。中间有一次你是怎么“修复”的不要用 Windows 的“压缩为 zip”功能去重新打包损坏文件那样只会把损坏状态固化到新包里。下载后先做完整性测试unzip -t 雨雪天气路面状况数据集.zip测试输出末尾有No errors detected in compressed data才算真正完整。如果你从网盘下载建议用客户端而不是浏览器直接下载浏览器断点续传对这种大 zip 经常出问题。以后再拿这类数据集我习惯在解压前先记录 zip 的大小解压后对比所有文件总大小防止静默缺失。5.2 中文文件名在 Linux 解压后乱码现象在服务器上解压后图片文件名或目录名显示成一堆结冰这类乱码Python 脚本遍历目录时直接崩。原因Windows 下打包 zip 时文件名编码用的 GBKLinux 的 unzip 默认按 UTF-8 解码两套编码系统不匹配。目录名和解压后文件名都会受影响但只要图片内容本身没损坏文件名乱码不影响图片数据。解决回到第 2 章用unzip -O UTF-8或-O GBK解压。如果已经解压完了可以写一个批量重命名脚本把乱码名称映射回正确的中文名。更省事的方法是一开始就把 zip 里全部内容路径改成 ASCII解压后立即把目录重命名为road_weather后续所有训练脚本、YAML 配置、路径引用都用这个名字彻底绕开中文路径。这里有个隐藏坑Windows 上解压正常Linux 上乱码你用 PyTorch 的ImageFolder读的时候也会跟着乱因为 Python 在 Linux 下默认编码也是 UTF-8。不要尝试用sys.setrecursionlimit或者文件编码强制转换去绕过直接把文件名改 ASCII 是唯一省心的方向。5.3 COCO 的 file_name 与 images 目录结构对不上现象转换脚本运行完发现只有部分图片生成了对应的 txt 标注文件另一些图明明在 images 目录里却始终没有标签。打印日志是missing: ...但路径看着完全正确。原因COCO JSON 里的file_name字段可能是以下几种情况之一纯文件名0001.jpg、相对路径images/0001.jpg、绝对路径/data/dataset/images/0001.jpg甚至可能是 Windows 路径风格images\\0001.jpg。你用IMAGES / file_name拼接时如果 file_name 自带路径拼接结果的中间层目录就是错的。解决转换脚本里统一取Path(file_name).name前先打印前 10 个 file_name 看一眼格式再决定拼接策略。最稳的处理是直接以image_id为基准重命名图片假设 image_id 是 100就把图片复制为100.jpg标注 txt 命名为100.txt完全忽略原始文件名。这样做还有一个好处如果一个文件名非常复杂且带空格和括号YOLO 的 dataloader 读取时会吃瘪而数字文件名不存在这个问题。5.4 训练时验证集框位偏移预览全是歪框现象训练结束后画 validation 图片上的预测框发现框的整体位置偏移几个像素到几十个像素尤其在图片边缘更明显。干燥路面框还好结冰路面框偏得离谱。原因这是 EXIF 旋转问题前面第 2 章提过。图片本身存的是横着的但 EXIF Orientation 告诉看图软件“你应该转 90 度”Windows 自带看图软件会转OpenCV 的imread不转于是模型读到了未旋转的图像而标注框是标注软件在旋转后的图像上画的两者基准不一致。解决先把图片全部转正再根据是否重新标注决定 bbox 是否需要重算。如果你的标注本来就是按原图坐标打的框那么转正后必须将四个坐标点做相应的旋转映射不是简单改 bbox 的 w/h。这里最容易踩的坑是只转图片不改 bbox然后发现框偏移得更离谱。稳妥做法是训练前把图片和标注一起在 Python 里做几何变换from PIL import Image, ImageOps img Image.open(original.jpg) img ImageOps.exif_transpose(img) img.save(fixed.jpg)exif_transpose已经帮你把方向置为 1然后你重新打开图片检查长宽是否和 COCO JSON 里的 width、height 一致。不一致就说明标注是在转正前做的需要重新标注或放弃这部分对齐的样本。在雨雪天气路面数据里行车记录仪拍的图很多是横屏带旋转角度的这个问题尤其常见。5.5 结冰和雪地类别样本过少导致训练不收敛现象训练 loss 能下降但验证集里结冰路面的 mAP 始终在 0.1 以下雪地类别根本不输出框或者把所有浅色路面全判成雪地。原因这是典型的小样本类别问题不是模型问题。数据集的收集本身偏干燥路面结冰和雪地场景受季节限制数量可能只有干燥路面的十分之一。模型在训练时每批次抽到结冰样本的概率本身就低梯度贡献被干燥样本淹没。解决第一个手段是类别重加权给结冰和雪地类别更高的 loss 权重常见做法是在 YOLOv8 中通过cls参数绕开更直接的方式是用样本复制把结冰样本重复采样几倍让模型在每个 batch 里稳定看到它们。但不要直接把结冰图片复制成 5 份放进数据集这会造成过拟合模型会把特定光线下的某几帧图背下来。更好的做法是同图多次小幅增强比如改变亮度、对比度、轻微模糊后各存一份。第二个手段是往这种 Zip 数据集里补充外部数据从公开路况任务、行车记录仪视频里抽帧重新用 COCO 格式标注后合入训练集。数量不要求多每类新增两三百张就能显著改善。第三个手段是降低任务难度把结冰和湿滑合成成一类“非干燥”等模型能稳定区分干燥和非干燥之后再细拆。这个前面说过工程上非常有效尤其当你的最终产品只要求“发出结冰预警”而不是精确判断是冰还是水时。6. 验证模型到底行不行三个更接近落地的检查习惯6.1 用混淆矩阵替代 mAP 成为第一标准我每次训练完必做的一步是单独加载模型跑一遍完整验证集并导出每个类别的混淆矩阵。YOLOv8 的model.val()返回的 metrics 对象里有box.map这个总体值但你要把 per-class 的值打出来from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.val(dataroad_weather.yaml, conf0.25, iou0.45, plotsTrue) print(mAP50:, results.box.map) print(mAP50-95:, results.box.map50_95)conf和iou是推理时的两个关键参数。conf是置信度阈值路面检测这种场景我建议从 0.25 开始调雨雪天气下目标边界模糊模型输出的置信度普遍比晴天低 0.1 左右你把阈值调到 0.4 以上虽然能减少误检但会把真正需要预警的结冰区域滤掉。iou是 NMS 的 IoU 阈值0.45 是正常值如果你发现同一块结冰区域被预测出好几个重叠框可以调低到 0.3 试一下。6.2 留一组“真实困难样本”做压测验证集是随机划分的它无法覆盖雨雪场景里最困难的分布。我会从全部图片里手工挑出三个子集夜间远光灯下的湿滑路面、被车轮碾过后半融化状态的雪地、覆盖薄冰的沥青路面。这三个子集不进训练也不进验证只在最后做压测用。压测时模型如果在这些图上能稳定输出正确类别且边界框覆盖住主要路况区域那基本可以进入试点部署阶段。如果模型在这组图上翻车先看结果图往往不是模型的问题而是训练数据里根本没有对应的光照状态这已经超出了调参能解决的范畴需要补数据。6.3 保存训练配置形成可复现的工程资产训练完不是关掉终端就结束了我会把 dataset.yaml、转换脚本、训练命令、以及每个类别的 AP 值一起存到项目目录里用日期命名归档。路况数据集会随着季节更新下次新增雪地数据时直接在原有配置基础上增量训练就不需要翻历史记录重新摸索参数组合。这个项目我做得多了以后发现真正决定模型上线质量的往往不是网络结构选 yolov8s 还是 yolov8m而是数据清洗和验证方法。一次天气数据集的整理花的精力有六成在解压、转换、划分、验证这些“看不见”的环节。希望这套处理流程能帮你少走弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表