简介:这份资源面向从事目标检测算法研发与模型训练的学习者,提供极端天气场景下的车辆交通图像数据集,可用于验证模型在雨雪、雾霾、沙尘等复杂环境中的鲁棒性。压缩包共约2000个文件,包含971张jpg图像与1027个VOC格式xml标注文件,另附1个py脚本和1个json配置,整体约133.56MB,图像与标签一一对应,按文件夹组织,可直接投入训练无需额外清洗转换。类别覆盖汽车、公交车、行人、卡车、摩托等7类,兼顾车辆与行人检测需求。内容预览显示样本涵盖snow_storm、mist、sand_storm、foggy等多种天气条件,便于构建跨域测试集。目前已有152人学习下载,适合作为目标检测课程实验、算法对比或极端天气鲁棒性研究的现成数据基础。
1. 极端天气车辆检测数据集:为什么我把它当成训练集的“压力测试集”
去年冬天跑一个路口车辆计数的小项目,晴天数据上 mAP 能到 0.87,结果一场雨夹雪直接掉到 0.6 出头,误检全糊在挡风玻璃反光和雨刷上。那次翻车之后,我养成了一个习惯:任何车辆检测模型上线前,必须拿极端天气样本过一遍。这份「极端天气下的车辆交通图像检测数据」就是干这个用的——VOC 标注格式,图像和 XML 标签配套,覆盖雨、雪、雾、夜间低照度等场景,专门用来暴露模型在恶劣成像条件下的短板。
它适合三类人:一是做车辆检测、交通监控、自动驾驶感知预研的工程师,想验证模型鲁棒性;二是学生或刚入门目标检测的从业者,需要一个真实场景、标注规范的数据集练手 VOC 转 YOLO 的完整流程;三是已经有晴天数据集、想补充困难样本做数据增强或域适应的团队。下面我按「这份数据长什么样 → 怎么转格式、怎么训 → 坑在哪 → 怎么用它做进阶验证」的顺序拆一遍。
2. VOC 标注格式拆解:目录结构、XML 字段与转 YOLO 的完整脚本
拿到一份 VOC 数据集,第一件事不是急着训练,而是先确认它的目录组织是否符合标准 VOC 约定,以及 XML 里的字段有没有缺失或越界。这一步做扎实,后面转格式、训练才不会出玄学问题。
2.1 标准 VOC 目录与这份数据的组织方式
标准 VOC2007 的目录结构是这样的:
VOCdevkit/ └── VOC2007/ ├── Annotations/ # 存放 XML 标注文件 ├── JPEGImages/ # 存放原始图像 ├── ImageSets/ │ └── Main/ # 存放 train/val/test 划分的 txt └── SegmentationClass/ (可选)这份极端天气数据集通常给到的是Annotations(XML)和JPEGImages(图像)两个核心目录,ImageSets/Main里的划分文件可能没有,需要自己按比例切分。我一般会先跑一段脚本统计图像数量、标签数量、类别分布,确认没有空标签或孤儿文件。
import os import xml.etree.ElementTree as ET from collections import Counter ann_dir = "Annotations" img_dir = "JPEGImages" # 统计类别分布和空标签 class_counter = Counter() empty_files = [] img_files = set(os.path.splitext(f)[0] for f in os.listdir(img_dir)) for xml_file in os.listdir(ann_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(ann_dir, xml_file)) root = tree.getroot() objs = root.findall("object") if len(objs) == 0: empty_files.append(xml_file) for obj in objs: name = obj.find("name").text class_counter[name] += 1 print("类别分布:", class_counter) print("空标签文件数:", len(empty_files)) print("图像总数:", len(img_files))这段脚本做三件事:遍历所有 XML,统计每个类别出现的次数,找出没有目标框的空标签文件,同时统计图像总数用于和标签数比对。如果发现某个类别样本极少(比如只有几十个),训练时就要考虑过采样或类别加权。空标签文件建议直接剔除,否则某些框架会在计算 loss 时报错。
2.2 XML 关键字段与常见越界问题
一个标准的 VOC XML 长这样:
<annotation> <folder>JPEGImages</folder> <filename>rain_0001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>car</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>234</xmin> <ymin>456</ymin> <xmax>567</xmax> <ymax>789</ymax> </bndbox> </object> </annotation>需要重点检查的字段:size里的宽高必须和实际图像一致,否则坐标归一化会错位;bndbox的 xmin/ymin 必须小于 xmax/ymax,且不能超出图像边界。极端天气图像里,雨雪遮挡容易导致标注员把框画到图像外面,这种越界框在训练时会引发 NaN loss。我一般会加一段校验:
def validate_bbox(xml_path): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) issues = [] for obj in root.findall("object"): bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) if xmin >= xmax or ymin >= ymax: issues.append(f"坐标反转: {xmin},{ymin},{xmax},{ymax}") if xmin < 0 or ymin < 0 or xmax > w or ymax > h: issues.append(f"越界: {xmin},{ymin},{xmax},{ymax} vs {w}x{h}") return issues参数说明:w、h从 XML 的 size 字段读取,如果这个值和真实图像尺寸不符,需要以真实图像为准重新校准。越界框的处理方式有两种——裁剪到边界内,或直接丢弃该目标。我倾向于裁剪,因为极端天气下目标本来就少,丢一个少一个。
2.3 VOC 转 YOLO 格式的脚本与划分策略
YOLO 格式要求每个图像对应一个 txt,每行是class_id x_center y_center width height,全部归一化到 0~1。转换脚本如下:
import os import xml.etree.ElementTree as ET import random classes = ["car", "bus", "truck", "motorbike"] # 按实际类别修改 class_to_id = {c: i for i, c in enumerate(classes)} def convert(xml_path, out_txt_path, img_w, img_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in class_to_id: continue bbox = obj.find("bndbox") xmin = max(0, int(bbox.find("xmin").text)) ymin = max(0, int(bbox.find("ymin").text)) xmax = min(img_w, int(bbox.find("xmax").text)) ymax = min(img_h, int(bbox.find("ymax").text)) xc = (xmin + xmax) / 2.0 / img_w yc = (ymin + ymax) / 2.0 / img_h bw = (xmax - xmin) / img_w bh = (ymax - ymin) / img_h lines.append(f"{class_to_id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}") with open(out_txt_path, "w") as f: f.write("\n".join(lines)) # 划分 train/val all_files = [os.path.splitext(f)[0] for f in os.listdir("Annotations") if f.endswith(".xml")] random.shuffle(all_files) split = int(len(all_files) * 0.8) train_files = all_files[:split] val_files = all_files[split:] for phase, files in [("train", train_files), ("val", val_files)]: with open(f"ImageSets/Main/{phase}.txt", "w") as f: f.write("\n".join(files))逻辑说明:先定义类别列表和映射字典,遍历 XML 时把越界坐标裁剪到图像范围内,再归一化。划分比例我一般用 8:2,极端天气样本如果本身就不多,可以调到 9:1,保证训练集有足够样本。注意classes列表必须和后续训练配置文件里的names完全一致,顺序也不能错,否则类别 ID 会错位。
3. 用 YOLOv8 训练这份数据:配置文件、参数设置与极端天气下的调参经验
转完格式,接下来就是训练。我选 YOLOv8 是因为它的配置文件结构清晰,对 VOC 转过来的数据兼容性好,而且社区里踩过的坑多,遇到问题好查。这一章从环境配置讲到训练参数,再到极端天气场景下的针对性调整。
3.1 环境配置与 data.yaml 写法
环境这块,Python 3.8~3.10 都行,PyTorch 建议 1.13 以上。安装 ultralytics 一条命令:
pip install ultralytics然后准备data.yaml:
path: /home/user/datasets/weather_vehicle train: ImageSets/Main/train.txt val: ImageSets/Main/val.txt nc: 4 names: ["car", "bus", "truck", "motorbike"]参数说明:path是数据集根目录,train和val是相对于 path 的 txt 路径,txt 里每行是图像文件名(不带扩展名)。nc是类别数,names是类别名列表,顺序必须和转换脚本里的classes一致。常见错误是 txt 里写了完整路径或带了.jpg后缀,YOLOv8 会找不到图像,报No images found。
3.2 训练命令与关键参数含义
启动训练:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ augment=True \ mosaic=1.0 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4逐个说:model=yolov8n.pt是 nano 版本,显存不够可以换yolov8s或yolov8m;imgsz=640是输入分辨率,极端天气图像如果分辨率很高(1920×1080),可以先用 640 跑一轮看效果,再考虑 1280;batch=16根据显存调,8G 显存大概能跑 16;patience=20是早停耐心值,20 轮验证集不提升就停;mosaic=1.0是 mosaic 增强概率,极端天气下我一般保持 1.0,因为拼接能增加小目标和遮挡场景;hsv_h/s/v是色调、饱和度、亮度扰动,亮度扰动hsv_v=0.4对夜间和雾天样本有帮助。
3.3 极端天气下的调参方向与验证指标
极端天气数据的核心难点是:目标对比度低、雨雪遮挡、反光干扰。针对这几点,我的调参习惯是:
第一,提高亮度扰动幅度。hsv_v从默认 0.4 提到 0.5~0.6,让模型适应不同曝光条件。第二,开启mixup增强,mixup=0.1~0.2,把两张图按透明度叠加,模拟雾天和雨幕的视觉效果。第三,如果数据里夜间样本多,考虑在预处理阶段做直方图均衡化,但这一步要在训练前离线做,不要放在在线增强里,否则会拖慢训练速度。
验证时重点看mAP50-95和mAP50的差距。如果 mAP50 高但 mAP50-95 低,说明框的位置不够准,极端天气下边界模糊导致定位误差大,这时候可以适当提高boxloss 的权重。另外,按天气类型分别统计 mAP——把验证集按雨、雪、雾、夜间分组,分别跑yolo detect val,能看出模型在哪类天气下最弱。
4. 避坑与排查:标注、格式转换、训练中常见的五个翻车点
这一章是我自己踩过的坑,按「现象 → 原因 → 解决」写,每条都是真实遇到过的。
4.1 训练 loss 出现 NaN
现象:训练几个 epoch 后 loss 突然变成 NaN,模型权重全废。原因:XML 里有越界框或宽高为 0 的框,归一化后出现除零或超出 0~1 范围。解决:跑 2.2 节的校验脚本,把所有越界框裁剪到边界内,宽高为 0 的直接丢弃。另外检查图像尺寸和 XML 里的 size 是否一致,不一致的以图像实际尺寸为准重新生成 XML。
4.2 类别 ID 错位导致全部预测成同一类
现象:训练完推理时,所有目标都被预测成car,其他类别完全不出现。原因:data.yaml里的names顺序和转换脚本里的classes顺序不一致,比如转换时是["car","bus","truck"],yaml 里写成了["bus","car","truck"],导致 ID 映射错位。解决:统一用一份类别列表,转换和训练配置都从同一个文件读取,不要手写两遍。
4.3 验证集 mAP 远低于训练集
现象:训练集 mAP 0.9,验证集只有 0.5。原因:极端天气样本分布不均,比如训练集里雨天多、验证集里雪天多,模型没学过雪天。解决:划分 train/val 时按天气类型分层采样,保证每类天气在训练集和验证集里的比例接近。如果某类天气样本太少,考虑用数据增强生成合成样本,或者调整损失函数给少数类更高权重。
4.4 图像和标签文件名不匹配
现象:训练时报No labels found或Image not found。原因:图像是.jpg,标签是.xml,转换后生成的.txt文件名和图像文件名不一致,比如图像叫rain_0001.jpg,txt 叫rain_0001.xml.txt。解决:转换脚本里用os.path.splitext去掉扩展名再拼.txt,确保文件名严格对应。另外注意大小写,Linux 下Rain_0001和rain_0001是两个文件。
4.5 显存溢出导致训练中断
现象:训练到一半报CUDA out of memory。原因:batch设太大,或者imgsz设太高,极端天气图像本身分辨率大,640 可能不够,但直接上 1280 显存翻倍。解决:先把batch降到 8 或 4,再考虑用梯度累积模拟大 batch。如果必须用高分辨率,可以开启amp=True(自动混合精度),能省不少显存。另外,mosaic增强会同时加载 4 张图,显存占用比单图高,显存紧张时可以临时关掉。
5. 进阶用法:用这份数据做域适应验证与模型鲁棒性评分
训练完一个模型只是开始,这份极端天气数据的真正价值在于做鲁棒性验证和域适应实验。我一般会用它跑一套「天气分层评测」,给模型打一个鲁棒性分数,再决定要不要上线。
5.1 按天气类型分层评测的脚本
假设验证集里图像文件名带了天气前缀,比如rain_、snow_、fog_、night_,可以按前缀分组分别评测:
import os import subprocess weather_types = ["rain", "snow", "fog", "night"] val_dir = "ImageSets/Main" for wt in weather_types: # 筛选对应天气的验证文件 with open(f"{val_dir}/val.txt") as f: files = [line.strip() for line in f if line.strip().startswith(wt)] if not files: print(f"{wt}: 无样本") continue with open(f"{val_dir}/val_{wt}.txt", "w") as f: f.write("\n".join(files)) # 调用 YOLO 验证 cmd = f"yolo detect val data=data_{wt}.yaml model=runs/detect/train/weights/best.pt" subprocess.run(cmd, shell=True)逻辑说明:从val.txt里按前缀筛选出每类天气的文件,生成独立的 txt,再为每类天气建一个data_{wt}.yaml,指向对应的 txt。跑完后对比各类天气的 mAP,差距越大说明模型对该天气越不鲁棒。参数上,data_{wt}.yaml里除了val路径不同,其他和主data.yaml保持一致。
5.2 鲁棒性评分与域适应策略
我给模型算鲁棒性分数的方式很简单:取所有天气类型里最低的 mAP50,除以最高 mAP50,得到一个 0~1 的比值。比值低于 0.6 就说明模型在某种天气下严重退化,需要针对性补数据或做域适应。
域适应的常见做法有两种:一是把极端天气样本按一定比例混入晴天训练集,做联合训练,让模型同时见过两种域;二是先用晴天数据预训练,再用极端天气数据微调最后几层,学习率调小到 1e-4 量级。我一般先用第一种,因为实现简单,效果也稳。如果联合训练后某些天气仍然差,再考虑第二种。
还有一个技巧:把极端天气验证集当成「困难样本挖掘」的来源。用训练好的模型跑一遍推理,把置信度低但实际有目标的样本挑出来,人工复核后加入训练集,迭代两三轮,鲁棒性通常能提升 5~10 个点。
从那以后我每次训练车辆检测模型,都会强制走一遍天气分层评测,不看到四类天气的 mAP 都过线,绝不往线上推。希望帮到你。
本文还有配套的精品资源,点击获取