简介:面向计算机视觉目标检测研究者和开发者,提供一份恶劣天气下道路行人车辆的已标注数据集。数据覆盖雨雪、雾天、沙尘等复杂场景,共约1000张图像,包含car、bus、person、truck等7类目标,并已按YOLO格式完成标注,同时划分好训练集与测试集,附带show.py脚本便于可视化检查标注效果。压缩包内共2000个文件,包括1027个txt标注文件、972个jpg图像文件及1个Python可视化脚本,整体大小约133.76MB,目录结构清晰,可直接用于模型训练和验证。目前已有157人学习下载,适合行人车辆检测、恶劣天气感知及YOLOv5改进实战等方向的研究与实验,结合作者相关专栏可进一步开展算法优化与结果对比。
1. 恶劣天气下的道路目标检测:这份 YOLO 标注数据到底怎么用
做目标检测的人最怕两件事:一是数据集干净得像实验室,训练出来一上真实场景就翻车;二是公开数据集全是晴天白天,模型根本没见过雨雪沙尘。这份恶劣天气下的道路行人、车辆图像检测数据,约 1000 张,已经按 YOLO 格式做好标注,类别覆盖 car、bus、person、truck 等 7 类,训练集和测试集都已划分好,还带一个 show 脚本可以直接可视化标注结果。适合正在做 YOLOv5/YOLOv8 实战、自动驾驶感知、或者在恶劣天气场景下做模型鲁棒性验证的人,省去从零采集和标注的重复劳动,直接进入算法调试环节。
2. 数据组织与标注格式:先读懂目录和 classes 文件再动手
2.1 文件命名暗含天气场景标签
把数据解压之后,第一件事不是急着跑训练,而是把文件名扫一遍。这批数据的文件名直接暴露了场景类型,比如snow_storm-347.jpg、mist-101.jpg、sand_storm-184.jpg、foggy-040.jpg、sand_storm_g2-992.jpg,一眼就能看出覆盖了雪天、雾天、沙尘天、阴霾天。g2可能表示第二批采集或第二个子场景,这个在训练时可以作为 group 维度去分析,后面我会讲怎么利用这个信息。
文件名的前缀不仅是命名习惯,它其实给了你一个免费的 domain label。很多人在恶劣天气检测上翻车,原因不是模型结构不行,而是训练集和测试集的天气分布不一致。如果你要做域泛化分析,完全可以依据文件名把数据重新划分成「已知天气」和「未知天气」两组来做验证。
2.2 目录结构与划分方式
约 1000 张图的数据量不大,但胜在已经做了训练集和测试集划分。通常这类资源的目录结构是:
dataset/ ├── images/ │ ├── train/ # 训练图片,约 800 张 │ └── test/ # 测试图片,约 200 张 ├── labels/ │ ├── train/ # 对应训练图片的 YOLO 标注 txt │ └── test/ # 对应测试图片的 YOLO 标注 txt ├── classes.txt # 类别名列表,一行一个类 └── show.py # 可视化标注的脚本拿到手后建议先用tree命令确认实际结构,因为有些打包方式会把images和labels合并到一个目录,或者把划分写在一个 txt 清单里。如果结构和我上面写的不完全一样,不影响使用,核心只有三点:图片和标签文件名必须一一对应、classes.txt必须存在且顺序正确、train和test的划分是明确的。
2.3 读 classes 文件并统计各类别样本量
类别一共 7 个,除了 car、bus、person、truck 之外还有哪些,以classes.txt为准。拿到数据后我一般会先跑一个统计脚本,确认每个类别的实例数量,这直接关系到后面训练时要不要做类别重加权:
import os from collections import Counter labels_dir = "labels/train" class_counts = Counter() for fname in os.listdir(labels_dir): if not fname.endswith(".txt"): continue with open(os.path.join(labels_dir, fname), "r") as f: for line in f: cls_id = line.strip().split()[0] class_counts[int(cls_id)] += 1 # 读取 classes.txt 映射类别 ID 到名称 with open("classes.txt", "r") as f: names = [line.strip() for line in f.readlines()] for cls_id, cnt in sorted(class_counts.items()): print(f"类别 {cls_id} ({names[cls_id]}): {cnt} 个标注实例")这段脚本做的事情很简单:遍历训练标签目录里的每个 txt,读到每一行标注,把第一个字段当作类别 ID 统计出现次数。YOLO 格式的标注每行固定是class_id x_center y_center width height,前 5 个字段以空格分隔,其中坐标是相对图片宽高的归一化值,算实例数时不需要解析坐标,只看第一个字段即可。
如果你的业务场景对某个类别特别敏感,比如卡车翻车事故检测,而统计结果显示 truck 只有寥寥几十个实例,那训练时就要考虑对 truck 类别提高 loss 权重,或者在数据增强时做针对性复制。这一步在动手训练前花五分钟做完,后面能省好几轮试错。
2.4 YOLO 标注格式的坐标陷阱
YOLO 格式的标注是归一化的中心点坐标和宽高,即(cx, cy, w, h)都除以了图片的宽和高。这里最常见的坑是:有人拿到标注后,想转成 COCO 或 VOC 格式做对比实验,结果忘了反归一化,直接把 0.5 当像素坐标画框,画出来全挤在图片左上角。
转换时要注意一个细节:cx, cy是中心点坐标,转 VOC 格式时需要换算成xmin = cx - w/2,ymin = cy - h/2;而 COCO 格式恰好相反,存的是左上角坐标和宽高。我曾经帮人调一个模型,精度一直上不去,排查到最后发现是标注转换时把xmin算成了cx - w,框全部偏移了半个框位。这类错误在数据资源里不会出现,但你自己二次加工时最容易踩。
3. 可视化验证与数据划分:训练前必须做的一次「体检」
3.1 用 show 脚本确认标注质量
数据集自带的show.py用来可视化标注,它的核心逻辑一般是用 OpenCV 读取图片和对应 txt,画出边界框后输出到output/目录。但我不建议直接跑完就完事,会先把脚本读一遍,确认它用的是cv2.rectangle还是PIL.ImageDraw,画框颜色是否区分了类别,坐标有没有做反归一化。
如果 show 脚本缺失或输出异常,下面这段代码可以替代,逻辑同样适用:
import cv2 import os image_dir = "images/train" label_dir = "labels/train" output_dir = "visualize/train" os.makedirs(output_dir, exist_ok=True) with open("classes.txt", "r") as f: class_names = [line.strip() for line in f.readlines()] colors = [(0, 255, 0), (0, 0, 255), (255, 0, 0), (0, 255, 255), (255, 255, 0), (255, 0, 255), (128, 128, 0)] for img_name in os.listdir(image_dir): if not img_name.endswith(".jpg"): continue img_path = os.path.join(image_dir, img_name) label_path = os.path.join(label_dir, img_name.replace(".jpg", ".txt")) img = cv2.imread(img_path) h, w = img.shape[:2] if not os.path.exists(label_path): print(f"警告: {img_name} 没有对应标签") continue with open(label_path, "r") as f: for line in f.readlines(): parts = line.strip().split() if len(parts) < 5: continue cls_id = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:5]) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors[cls_id % len(colors)], 2) cv2.putText(img, class_names[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[cls_id % len(colors)], 1) cv2.imwrite(os.path.join(output_dir, img_name), img) print(f"已保存 {img_name} 的可视化结果")这段代码把 YOLO 归一化坐标还原成像素坐标,核心公式是x1 = (cx - bw/2) * w,先还原中心点坐标,再按宽高的一半向左上角偏移得到左上角点。之所以强调这个公式,是因为它用到了两套坐标系,如果忘记乘宽高,所有框都会缩在 1×1 的像素范围内,看起来就像标注全部失效。
跑完可视化后,抽样检查三件事:大目标比如 bus 和 truck,框是否完整覆盖车身而不是只框了一半;person 在雾天和沙尘环境下是否漏标;边界框是否超出图片边缘。恶劣天气下的人车目标对比度低,标注时漏标和框偏移的概率比晴天数据高不少,这笔账先算清楚,免得训练完才发现测试集标注本身就带病。
3.2 训练集和测试集的分布一致性检验
资源已经做了 train/test 划分,但「划分」不等于「合理」。我会做两个检查:一是确认测试集里有没有和训练集重复的文件,二是统计两张划分下各类别的实例占比是否接近。
import os train_imgs = set(os.listdir("images/train")) test_imgs = set(os.listdir("images/test")) duplicates = train_imgs & test_imgs print(f"重复图片数量: {len(duplicates)}") if duplicates: for name in list(duplicates)[:10]: print(f" {name}")第二个检查更关键。如果训练集里 car 占 60%,而测试集里 car 只占 30%,那模型在测试集上的 mAP 会被少数几个类别带偏,训练时的 loss 曲线也会失真。跑完第 2.3 节的统计脚本后,分别对 train 和 test 执行一遍,对比两个分布。如果发现 bus 或 truck 在测试集里压根没出现,那测试指标就说明不了这类目标在恶劣天气下的表现,需要自己重新划分或补充验证集。
3.3 天气维度上的划分策略
文件名里的天气前缀虽然简单,但可以组合出多种实验设置。如果目标只是「把模型训出来」,直接用现成的划分即可;如果要做学术向的鲁棒性验证,可以按天气重新划分成:源域(snow_storm + foggy + mist)训练、目标域(sand_storm)测试,专门检验模型在没见过的恶劣天气上的泛化能力。这是这份数据很有价值的用法,毕竟一个模型在相似天气分布上表现好,不意味着换一种天气还能稳。
从实践角度,我通常会把原划分复制一份做扰动实验——给测试集图片加高斯噪声或模拟雨水条纹,观察 mAP 掉多少。这类韧性实验在自动驾驶感知上经常要做,这份数据覆盖了多种天气,刚好可以串起来做。
4. 避坑指南:恶劣天气检测数据集的典型问题和排查方法
4.1 现象:训练 loss 降得很快但 mAP 极低
这类问题在目标检测里非常典型,很多人上来就以为是模型结构有问题,调了半天网络,最后发现是数据配置的问题。我在第一次拿到这类带恶劣天气的数据集时也犯过同样的错,当时是一份自动驾驶相关数据,loss 从 8 掉到 2 只用了 10 个 epoch,开心得不行,结果验证集 mAP 不到 5%,人直接傻了。
原因排查之后发现,classes.txt的类别顺序和我写进数据配置文件里的顺序不一致。数据集里的person在classes.txt的第 3 行,我却在加载时把它写成了第 0 个类,等价于把原本标注为person的框全部当成car来训练。模型学到了错误的映射关系,训练 loss 当然可以降,因为它在自洽地学习「把那个形状的东西识别成 car」,但这和真实目标完全对不上。
解决方法很简单:数据加载时用classes.txt动态生成类别映射,不要手写死列表,硬编码类别顺序是绝对不要做的事情。用 YOLOv5 时在 data yaml 里直接把 classes 列表和classes.txt比对一遍,再跑可视化确认框的类别标签是否正确。
4.2 现象:沙尘天和雾天的漏检率明显偏高
低对比度、边缘模糊、目标与背景融为一体,这是恶劣天气下检测的核心难点,模型往往在雪天表现尚可,到了沙尘天就崩掉。原因不止是标注层面的漏标,还有模型本身的特征提取能力在这些极端条件下失效。
解决思路分两路:一路在数据上做文章,对雾天和沙尘天的图片做去雾增强或多尺度增强,另一路在模型上做结构改进,比如在 backbone 里加入注意力模块,强化模型对低对比度区域的响应。对于这份数据资源,最直接的做法是先统计各类天气的样本数量,再决定是给沙尘天增加训练权重,还是把它们单独挑出来做二次验证。
4.3 现象:训练集和测试集的 mAP 差了一倍
这是训练数据分布和测试数据分布不一致的经典症状。具体到这份数据上,我遇到过的情况是训练集以foggy和mist为主,测试集却混了不少snow_storm的图。模型在训练过程中已经「过拟合」到了雾天的对比度和亮度分布,换一种天气自然掉点。
解决方法是回到第 3.3 节的思路,重新审视划分逻辑。如果测试集的天气类型在训练集里完全不存在,那模型跨天气泛化的失败是预期内的,不叫 bug。如果混合了多种天气,就按天气名做分层抽样,确保每种天气在训练集和测试集里的占比一致,用 pandas 先统计分布再划分,避免随机抽样带来的偏差。
4.4 现象:标注文件里有空标签的图片
所谓空标签就是一张图片对应的 txt 文件是 0 字节,或者只有换行符没有任何有效行。这种情况在人工标注过程中很常见,特别是雨雾天气下看不清、标不出来的时候,标注员可能就丢一个空文件占位。
这类空标签在训练时不会直接报错,但会影响 loss 的累计逻辑——如果数据加载器直接返回空标签,有的框架会把它当作背景样本处理,有的会报 「IndexError」。我习惯先跑一遍全量检查,把空标签对应的图片单独拎出来看看,如果确实是没有可标注目标就放到测试集而不是训练集,如果是有目标但漏标了就得补标,不能留在训练集里影响模型对背景的判断。
5. 把这份数据真正用出效果的几个进阶操作
数据本身是 YOLO 格式,意味着主流的 YOLOv5、YOLOv8、YOLOv11 都能直接消费,不需要额外转换。下面这套是我自己验证过、在恶劣天气行人车辆检测上稳定有效的组合拳,从训练到评估一次走完。
先看训练侧。天气场景多样但数据量只有约 1000 张,我的配置思路是:用小模型跑通基线,再用大模型吃数据。具体来说,用 YOLOv8s 配合 640 输入尺寸做第一轮:
yolo detect train \ data=weather.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ workers=4 \ pretrained=True \ mosaic=1.0 \ hsv_h=0.015 hsv_s=0.7 hsv_v=0.4 \ fliplr=0.5几个关键参数的考虑:pretrained=True是必须开的,1000 张图从头训练很容易过拟合,用 COCO 预训练权重做迁移学习才能学到泛化的特征表达;mosaic=1.0在数据量不大时是性价比最高的增强方式,它能在一张图里拼 4 张图,相当于扩充了样本组合空间;hsv_h/s/v适当加大一点,可以模拟不同天气下的色偏和亮度变化,这对雾天和沙尘天的泛化有直接帮助。
训练完跑测试集指标:
yolo detect val \ data=weather.yaml \ model=runs/detect/train/weights/best.pt \ imgsz=640 \ conf=0.25 \ iou=0.5注意conf和iou的取值直接决定 mAP 数字,分享结果时一定要注明这两个参数。比如conf=0.25 iou=0.5下 mAP@0.5 是 78%,换成conf=0.5 iou=0.75可能直接掉到 62%,这些指标不是模型绝对能力的体现,而是评估配置下的表现。
最后是二次验证。我会选几张典型场景图,用同一套权重跑推理,对比可视化输出。对沙尘暴和雪天各选一张有密集行人的图,用conf=0.1看模型在低置信度下能不能召回更多目标,同时观察有没有误检。这一步的意义在于确认模型「真的能在这个天气下干活」,而不只是验证集上的数字好看。
有一次我帮朋友调一个恶劣天气检测模型,前面训练和评估都正常,就是实拍视频里频繁漏检。后来发现他的测试图片是从监控视频抽帧截的,分辨率只有 640×480,而我训练时统一 resize 到 640×640,输入尺寸变了,小目标直接扛不住。从那以后,我每次用天气数据集前都会强制走一遍「统计类别分布 → 可视化标注 → 验证划分 → 检查空标签 → 确认评估参数」,五步下来再调模型,基本没有在数据层面翻过车。这套流程对你手头这份资源同样适用,先把数据吃透,再让模型去发挥,希望帮到你。
本文还有配套的精品资源,点击获取