简介:面向目标检测算法训练与恶劣天气场景研究的行人车辆数据集,涵盖了雾天、大雪、沙尘暴、薄雾等不同天气下的道路图像,共标注7类目标(car、bus、person、truck等),使用YOLO格式txt标注,能直接喂给YOLOv5、YOLOv8等主流检测框架训练,也可作为本科毕设、课程设计或算法改进实验的验证数据。资源共2000个文件,主体为972张jpg原图和1027个txt标注文件,另附1个py可视化脚本,压缩包整体约133.76MB;数据已经按训练集与测试集划分,运行show脚本可随机展示图片及对应标注框,便于快速检查标签质量与样本分布。目前已有157人学习下载。与常规晴天数据集相比,这份数据包聚焦低可见度场景,类别覆盖行人、轿车、客车、卡车等常见道路目标,能够帮助读者积累雨雪沙尘天气下的标注规范与模型调参经验,也可为后续做恶劣环境检测算法优化提供可靠基准。1. 恶劣天气下的目标检测:一份让行人车辆「看得见」的 YOLO 数据集一个在晴天数据集上 mAP 能跑到 0.87 的 YOLO 模型,遇到暴雪天气可能直接掉到 0.5——不是网络结构不行,是它没见过雪天里的行人长什么样。恶劣天气目标检测项目最缺的往往不是更花哨的网络,而是带高质量标注的恶劣天气图像数据。这份资源提供约 1000 张已标注图片,覆盖暴雪、薄雾、沙尘、大雾四类道路场景,全部按 YOLO 标注格式做好标签,类别包含 car、bus、person、truck 等 7 个道路目标,并已完成训练集与测试集划分。它天然适合正在做 YOLOv5/v8 检测却被天气泛化问题卡住的人,也适合需要真实天气数据做改进实验、组合数据增强的开发者。拿到手可以直接丢进训练管道,省掉最脏最累的洗数据环节。2. 数据全景:从文件名到标注格式,1000 张图里藏着什么数据集的质量直接决定模型上线后会不会翻车。这份资源我拆开看过,先说结论:它的文件名本身就是天气标签。snow_storm-347.jpg是暴雪,mist-101.jpg是薄雾,sand_storm-184.jpg是沙尘,foggy-040.jpg是大雾。这意味着你不需要额外写分类脚本,光靠文件名前缀就能把测试集按天气场景拆分,做分组评估非常方便。2.1 天气场景构成:四种恶劣条件的图像特征四种天气对图像的影响结构完全不同。暴雪在画面里形成大块高光区域,地面和车顶被积雪覆盖,目标和背景的对比度被压得很低,远处行人几乎融入雪面;薄雾是全局对比度下降,目标轮廓还在但细节丢失;沙尘暴最特殊,整张画面偏黄偏暗,白平衡被彻底打乱,颜色信息失真严重,连 bus 的红色标识都可能变成土黄色;大雾则把远处的目标「吞」掉,近处目标边缘也变得模糊。天气类型文件名前缀典型图像特征检测难点暴雪snow_storm-高光区域大、对比度低远处目标融入雪面薄雾mist-整体偏灰、细节丢失轮廓模糊难定位沙尘sand_storm-偏色严重、光照不均颜色信息失真大雾foggy-对比度大幅下降目标边缘被吞掉真实的雪不是高斯噪声,而是成块的高光白色区域;沙尘不是简单的亮度变化,而是全局色偏。模型在混合天气数据上训练,比在单一晴天数据上训练学到的特征鲁棒得多——这正是这份数据必须存在的理由:网上随手扒来的晴天数据集,永远补不上雪天的高光噪声和沙尘的色偏结构。2.2 类别与标注格式:classes 文件与归一化坐标数据一共标注了 7 个类别,明确包含 car、bus、person、truck,其余类别的完整名单以你拿到的 classes 文件为准。拿到数据第一件事就是打开 classes.txt 核对类别顺序:YOLO 的类别编号是写死在 txt 标签第一列的,classes.txt 第 0 行对应 id 0,所有 car 的标签第一列都必须是 0。这个顺序一旦错位,训练出来的模型会把所有类别名张冠李戴。每个图片同名 txt 里的标注行格式固定为五列:0 0.512345 0.448912 0.210234 0.318765 2 0.710521 0.230192 0.123456 0.245678 3 0.392488 0.618945 0.189723 0.250123第一列是类别 id,后面四个数是归一化坐标,分别代表 bbox 中心点的 x、y 和宽、高。这些值全部除以了原图尺寸,范围在 0 到 1 之间。反算像素坐标的公式很简单:x_pixel = x_center * img_width,y_pixel = y_center * img_height,宽高同理。归一化的好处在于图片最终要 resize 到 640 或者 960,坐标在缩放后依然有效,不需要重新换算——这是 YOLO 系标注格式的通用约定。另外建议可视化时留意雾天和暴雪天气下的大目标框:如果框普遍比目标小半圈,说明标注边界和真实目标边缘存在系统性偏差,这类脏标签会在训练时把边框回归带偏,后面避坑章节会专门讲怎么处理。2.3 目录结构:训练测试划分与 show 脚本的位置这份数据已经做好训练集和测试集划分,常见目录布局如下:weather_dataset/ ├── classes.txt ├── images/ │ ├── train/ │ │ ├── snow_storm-347.jpg │ │ ├── mist-101.jpg │ │ └── ... │ └── test/ │ ├── sand_storm-184.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── snow_storm-347.txt │ │ └── ... │ └── test/ │ └── ... └── show.pylabels 目录下的每个 txt 与 images 下同名 jpg 一一对应,这是 YOLO 数据的基本约定。如果你拿到的包里有 val 目录就直接用,没有的话建议从训练集里按天气比例抽 10% 出来做验证集,只在 test 上选最优权重容易过拟合。show.py 脚本做的事情是遍历 images 找 jpg,去 labels 找同名 txt,把框和类名画回图上,核心代码下一章直接展开讲。拿到手后的标准动作是三步:先打开 classes.txt 核准类别,再跑 show.py 看标注质量,最后用脚本统计每个类别的样本数量。前两步两分钟就能做完,能帮你判断这批数据值不值得投入训练时间。3. 快速跑通:环境、可视化脚本与 YOLOv5 训练首跑这一章完成从零到训练起来的全过程。先搭好 Python 环境,跑通可视化确认数据没问题,再组织成 YAML 配置丢给 YOLOv5 训练。整个过程不涉及改模型结构,属于「先把管道跑通」的基准操作。3.1 环境准备:Python 版本与依赖清单可视化只需要三个库:opencv-python、matplotlib、numpy,加上读取 YAML 配置用的 pyyaml。如果要训练,再补 torch 和 torchvision。建议用 conda 建一个独立环境,避免污染其他项目。conda create -n yolo-weather python=3.8 -y conda activate yolo-weather pip install opencv-python matplotlib numpy pyyaml # 训练才需要 torch,装 CPU 版跑可视化也不需要 pip install torch torchvisionPython 3.8 对 YOLOv5 和 YOLOv8 都兼容,属于最不容易出版本冲突的选择。如果你有 NVIDIA 显卡且装了 CUDA,torch 会自动启用 GPU 加速;显存低于 6G 的话建议先用 CPU 跑完 show 脚本,把数据质量确认好再考虑训练,否则训练中显存溢出会浪费很多时间排查。torch 安装失败最常见的原因是 CUDA 版本和 wheel 包不匹配,官方源按默认装一般不会出问题。3.2 可视化原图与标签:show 脚本的核心逻辑show.py 脚本是这个数据集自带的,核心逻辑用 OpenCV 把标签画回原图,逐张按回车翻页查看。下面是等价实现,也方便你按自己的需求改:import cv2 from pathlib import Path img_dir = Path("images/train") # 先看训练集,确认后再看 test label_dir = Path("labels/train") class_file = Path("classes.txt") with open(class_file, "r", encoding="utf-8") as f: names = [line.strip() for line in f if line.strip()] for img_path in sorted(img_dir.glob("*.jpg")): label_path = label_dir / (img_path.stem + ".txt") img = cv2.imread(str(img_path)) if img is None: print("跳过无法读取的图片:", img_path) continue h, w = img.shape[:2] if label_path.exists(): for line in label_path.read_text(encoding="utf-8").splitlines(): parts = line.strip().split() if len(parts) != 5: continue cls_id, x_c, y_c, bw, bh = map(float, parts) cx, cy = int(x_c * w), int(y_c * h) bw_p, bh_p = int(bw * w), int(bh * h) x1, y1 = cx - bw_p // 2, cy - bh_p // 2 x2, y2 = x1 + bw_p, y1 + bh_p color = (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow("weather-label-check", img) if cv2.waitKey(0) & 0xFF == ord("q"): break cv2.destroyAllWindows()这段脚本的关键点有几个:img_path.stem取文件名不带后缀,用来拼同名 txt;shape[:2]拿的是原始图像的高和宽,后续像素坐标换算全部以它为准;标签每行用split()而不是split(" "),是为了兼容多个空格或制表符分隔。按 q 键退出,按任意其他键看下一张。跑完可视化后重点看三类图:暴雪里远处的 person 有没有被标出来、沙尘图里 bus 的框是不是偏了、雾天边缘模糊的 truck 框是否贴近目标边缘。这一步发现的标注质量问题,越早处理对训练越有利。3.3 组织数据并启动训练:一条 YOLOv5 训练命令可视化确认没问题后,需要写一个 data.yaml 告诉 YOLOv5 数据在哪、有几类。注意 names 的顺序必须和 classes.txt 完全一致,这里给出一个示例结构,实际类别以你的 classes.txt 为准:path: ../weather_dataset train: images/train val: images/train # 没有 val 时先拿 train 顶一下,正式训练建议单独分 test: images/test nc: 7 names: 0: car 1: bus 2: person 3: truck 4: motorcycle 5: bicycle 6: ridercd yolov5 python train.py --img 640 --batch 16 --epochs 100 \ --data ../weather_dataset/weather.yaml \ --weights yolov5s.pt --name weather-exp1--img 640是输入网络的边长,640 是显存和效果之间的平衡点;--batch 16在 8G 显存左右比较稳,显存不够就降到 8 而不是降分辨率;--weights yolov5s.pt用 COCO 预训练权重做初始化。1000 张数据从零训练很容易过拟合,但用预训练权重起步时,模型已经知道车和人的基本形态,只需要学「雪天里的车长什么样」,这是迁移学习的标准用法,也是这个数据量下最不容易翻车的选择。配套的 YOLOv5 改进实战笔记里有大量针对 backbone 和训练策略的调法,和这份数据配合着看会更快上手。跑完第一个 100 epochs 后别急着下结论,先看 val 的 mAP 曲线和 PR 曲线,再决定是加 epochs 还是换更大输入尺寸。4. 核心实现拆解:标签解析、预处理增强与训练配置的完整链路数据管道跑通只是第一步,真正决定模型上限的是标签解析的正确性、天气场景的预处理策略和训练参数的选择。这一章把数据到模型的完整链路拆开讲。4.1 解析 YOLO 标注:从 txt 到像素坐标的换算无论是训练还是评估,第一步都是把归一化坐标还原成像素坐标。下面这个函数可以复用到项目的任何环节:def load_label(txt_path, img_w, img_h): """解析 YOLO txt 标签,返回绝对像素坐标框""" boxes = [] with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls_id = int(parts[0]) x_c, y_c = float(parts[1]), float(parts[2]) bw, bh = float(parts[3]), float(parts[4]) # 中心点坐标转左上角和右下角坐标 x1 = (x_c - bw / 2) * img_w y1 = (y_c - bh / 2) * img_h x2 = (x_c + bw / 2) * img_w y2 = (y_c + bh / 2) * img_h boxes.append((cls_id, x1, y1, x2, y2)) return boxes换算逻辑很直接:bbox 的存储格式是中心点和宽高,先减半宽得到左边界,再加半宽得到右边界,最后乘以图像宽高还原成像素坐标。返回的(x1, y1, x2, y2)是绝对坐标框,画图、裁剪、算 IoU 都基于这个格式。这段代码刻意没做越界裁剪,因为越界本身就是信号。如果解析出来的框有负数坐标或者超过图像宽高,说明标签里有脏数据;如果这种越界框占了 5% 以上,就要重新考虑这批数据的可信度。解析时用split()而不是split(" ")能兼容制表符和连续空格,避免因为格式细节报错。4.2 数据加载与预处理:针对天气噪声的增强策略通用目标检测的数据增强是随机裁剪、翻转、HSV 扰动,但天气数据的预处理应该针对天气噪声的结构来做。雾天图像对比度低,用 CLAHE(对比度受限自适应直方图均衡)提升局部对比度很有效;沙尘图偏色严重,灰度世界白平衡能把黄色调拉回来。下面两个函数是我处理天气数据时常用的:import cv2 import numpy as np def clahe_rgb(img, clip=2.0): """雾天/薄雾增强:CLAHE 提升局部对比度""" lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=clip, tileGridSize=(8, 8)) l = clahe.apply(l) lab = cv2.merge((l, a, b)) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) def gray_world_white_balance(img): """沙尘图增强:灰度世界白平衡校正色偏""" b, g, r = cv2.split(img) mean_b, mean_g, mean_r = b.mean(), g.mean(), r.mean() gray = (mean_b + mean_g + mean_r) / 3 b = np.clip(b * (gray / max(mean_b, 1e-5)), 0, 255).astype(np.uint8) g = np.clip(g * (gray / max(mean_g, 1e-5)), 0, 255).astype(np.uint8) r = np.clip(r * (gray / max(mean_r, 1e-5)), 0, 255).astype(np.uint8) return cv2.merge((b, g, r))CLAHE 的两个参数值得说明:clipLimit 是裁剪限制,值越大对比度增强越强,2.0 左右比较安全,超过 3.0 容易把阴影噪声也放大;tileGridSize 是分块大小,(8,8) 表示把图像分成 8x8 的小块分别做直方图均衡,能避免全局均衡造成的过度曝光。灰度世界白平衡的假设是画面的平均颜色趋向中性灰,所以把每个通道的平均值拉平——这个假设在沙尘场景下基本成立,但对大面积单色路面可能失效,用之前先看一眼效果。预处理变换不要太激进,否则会把阴影误判成目标。更重要的原则是:增强变换应该在训练时随机应用,让模型看到不同强度的天气退化,而不是只在推理时做一次固定预处理。YOLOv5 自带的 mosaic 增强对天气数据尤其有效——它把雪天、雾天、沙尘天的目标拼到同一张训练图上,缓解不同天气样本量不均带来的偏见。4.3 训练配置:模型、输入尺寸与类不平衡的调法天气场景里小目标占比高,被困在雪地里的行人可能只有几十个像素。这种情况下输入尺寸比 backbone 网络的选择更影响最终效果,参考配置如下:配置项推荐值说明img640起步基线,显存友好img960小目标多时提升明显,显存约为 640 的 1.5 倍batch168G 显存左右;显存不足先降 batch 再降 imgepochs100基线值,看曲线收敛情况再追加optimizerSGDYOLOv5 默认,Adam 收敛快但更容易过拟合weightsyolov5s.pt迁移学习初始化,别从零训练我一般先用 640 跑 100 epochs 做基线,记录 val mAP,再开一个 960 的训练微调 30 epochs,看 mAP 提升幅度是否值得增加的训练时间。天气数据样本只有 1000 张,训练时间不会太长,多跑几个配置对比成本可控。类别不平衡是这类道路数据的高频问题:person 的标注数量可能只有 car 的五分之一,模型训练时会天然偏向高频类别,结果就是车检得不错、人漏检严重。处理办法有两个:一是在 YOLOv5 的 hyp 超参数里调cls_pw,把它从默认的 1.0 改成类别数量的反比,增大少样本类别在分类损失里的权重;二是在数据层面把 person 的样本复制几份混进训练集,等价于做类别加权。第一个方法更干净,血泪经验是两种都试一下,在 val 集上对比 person 类别的 mAP 再决定留哪个。5. 避坑指南:恶劣天气数据最容易踩的 5 个坑天气数据的坑比普通数据集多一个维度——图像本身被天气「污染」,标注质量、颜色分布、场景划分都会出问题。以下是实际使用中最常见的 5 个坑,每条都按「现象 → 原因 → 解决」给出处理方案。5.1 雾天边界框「差半截」跑 show 可视化时会发现:雾天背景下远处的车,标注框明显比目标小一圈,框的边界落在目标的轮廓内部,没有贴住边缘。原因不难理解,雾中目标边缘不可见,标注员只能凭经验补框,不同人补出来的尺度不统一,甚至同一个人在不同雾浓度下也会画偏。这个坑不解决会直接影响边框回归的质量,模型学到的框尺寸系统性地偏小。解决思路是宁缺毋滥:写个脚本按「框面积占整图比例小于 0.001 且文件名前缀为 foggy」把可疑目标筛出来,人工逐张确认,模棱两可的直接从训练集中剔除。1000 张数据的体量经不起脏标签的污染,删掉几十个问题框对样本量的影响很小,但对 mAP 的改善很明显。5.2 类别严重不均衡:person 远少于 car训练完看 val 结果,car 的 mAP 到 0.9,person 只有 0.3,甚至更低。根因是样本数量差距超过 5 倍,模型把 person 当成稀有类,学了但没学好,代价函数里 person 的分类误差占比太小。这种情况下继续加 epochs 也没用,损失函数根本不会把采样频率低的类别当回事。解决路径有两条:改cls_pw增大 person 的损失权重,或者对 person 样本做过采样,把含 person 的图片复制两份放进训练集。复制样本是最直接的手段,虽然会带来一定的过拟合风险,但在 1000 张数据量下收益大于风险。改完重新训练,盯着 person 类别的 mAP 看是否抬升,别只看整体 mAP 的平均值。5.3 沙尘图整体偏色导致漏检和误检沙尘场景下模型表现不稳定:truck 经常检不出来,bus 被误检成 car。原因是沙尘图的黄褐色调把目标的颜色信息淹没了,模型在训练时把「黄色调」当成了背景特征,目标本身的边缘和纹理反而被忽略。解决思路是在数据增强里加入色调扰动,模拟沙尘天的颜色偏移,让模型学会忽略色偏的影响。HSV 增强里的 H(色调)和 S(饱和度)扰动范围可以比晴天数据调大一些,比如 H 扰动从默认的 ±0.015 加到 ±0.03。同时可以配合上一章的灰度世界白平衡做预处理,双管齐下,注意增强力度别大到把暗色目标洗成全黑。5.4 同源帧进了测试集,评估结果虚高这是最隐蔽的一个坑。现象是训练 mAP 0.85、测试 mAP 0.82,看起来表现不错,但模型一上线到真实雪天路段就崩到 0.5。原因是同一段路连续拍摄的帧图像高度相似,随机划分数据集时,测试集里出现了大量和训练集几乎同源的图片,评估结果虚高。正确做法是按天气场景分组划分,而不是随机打散文件。这份数据的文件名前缀天然提供了分组依据:把snow_storm-、mist-、sand_storm-、foggy-四种前缀的图片整组划分,保证同一天气场景的所有帧只出现在训练集或测试集的一侧。如果数据量允许,还可以按拍摄时段分组,进一步降低相关性。5.5 Windows 上跑通、Linux 上翻车的路径分隔符在 Windows 本地跑 show 脚本一切正常,把数据打包上传到 Linux 服务器训练,报 FileNotFoundError,找不到图片文件。原因是索引文件或脚本里写了反斜杠路径,Windows 认、Linux 不认,换到服务器就变成无效路径。解决方式是统一用 pathlib 处理路径,一行代码搞定:路径字符串用str(Path(p).resolve())转成绝对路径,或者用.as_posix()把分隔符统一转成正斜杠。养成这个习惯后,换机器再也不会因为路径问题浪费半小时排查。这是我每次换环境都会踩的坑,也建议大家拿到任何数据集,先把所有路径操作改成 pathlib 再开始跑。6. 改造落地:按天气分组评估与数据增强的实战路径拿到这份数据,最常见的三个改造方向:扩展类别、混入晴天数据增强、按天气分组评估。扩展类别的做法是改 classes.txt 追加新类名,同步更新 weather.yaml 里的 nc 和 names,再用半自动标注工具补新类别的框——注意新类别的编号只能追加在末尾,不能插在中间,否则所有已有标签的类别 id 全部错位,这是 YOLO 格式最容易忽略的连环坑。如果你的项目本来就有大量晴天道路数据,把这份天气数据当作「增强样本」以 1:2 的比例混入训练,比单独训练天气模型更稳。模型先在大规模晴天数据上学会目标的基本形态,再用天气数据微调适应退化场景,泛化能力会好不少。这也是最省力的改造路径,不用改动任何网络结构,只调 data.yaml 里的训练集路径。最后是我的核心建议:按天气分组评估,而不是看整体 mAP。写一个脚本按文件名前缀把 test 集拆成 snow_storm、mist、sand_storm、foggy 四组,逐组跑 val 统计 mAP,结果往往能颠覆你对模型的判断。出现过一次让我印象很深的事:整体 mAP 看起来有 0.78,按天气拆分后发现雪天只有 0.41,其他三组都在 0.8 以上——问题被平均值完美掩盖了。从那以后我每次训练天气模型,评估阶段都强制走一遍分组统计流程,先看各组分数再决定改预处理方向和增强策略。希望帮到你。本文还有配套的精品资源,点击获取
简介:面向目标检测算法训练与恶劣天气场景研究的行人车辆数据集,涵盖了雾天、大雪、沙尘暴、薄雾等不同天气下的道路图像,共标注7类目标(car、bus、person、truck等),使用YOLO格式txt标注,能直接喂给YOLOv5、YOLOv8等主流检测框架训练,也可作为本科毕设、课程设计或算法改进实验的验证数据。资源共2000个文件,主体为972张jpg原图和1027个txt标注文件,另附1个py可视化脚本,压缩包整体约133.76MB;数据已经按训练集与测试集划分,运行show脚本可随机展示图片及对应标注框,便于快速检查标签质量与样本分布。目前已有157人学习下载。与常规晴天数据集相比,这份数据包聚焦低可见度场景,类别覆盖行人、轿车、客车、卡车等常见道路目标,能够帮助读者积累雨雪沙尘天气下的标注规范与模型调参经验,也可为后续做恶劣环境检测算法优化提供可靠基准。1. 恶劣天气下的目标检测:一份让行人车辆「看得见」的 YOLO 数据集一个在晴天数据集上 mAP 能跑到 0.87 的 YOLO 模型,遇到暴雪天气可能直接掉到 0.5——不是网络结构不行,是它没见过雪天里的行人长什么样。恶劣天气目标检测项目最缺的往往不是更花哨的网络,而是带高质量标注的恶劣天气图像数据。这份资源提供约 1000 张已标注图片,覆盖暴雪、薄雾、沙尘、大雾四类道路场景,全部按 YOLO 标注格式做好标签,类别包含 car、bus、person、truck 等 7 个道路目标,并已完成训练集与测试集划分。它天然适合正在做 YOLOv5/v8 检测却被天气泛化问题卡住的人,也适合需要真实天气数据做改进实验、组合数据增强的开发者。拿到手可以直接丢进训练管道,省掉最脏最累的洗数据环节。2. 数据全景:从文件名到标注格式,1000 张图里藏着什么数据集的质量直接决定模型上线后会不会翻车。这份资源我拆开看过,先说结论:它的文件名本身就是天气标签。snow_storm-347.jpg是暴雪,mist-101.jpg是薄雾,sand_storm-184.jpg是沙尘,foggy-040.jpg是大雾。这意味着你不需要额外写分类脚本,光靠文件名前缀就能把测试集按天气场景拆分,做分组评估非常方便。2.1 天气场景构成:四种恶劣条件的图像特征四种天气对图像的影响结构完全不同。暴雪在画面里形成大块高光区域,地面和车顶被积雪覆盖,目标和背景的对比度被压得很低,远处行人几乎融入雪面;薄雾是全局对比度下降,目标轮廓还在但细节丢失;沙尘暴最特殊,整张画面偏黄偏暗,白平衡被彻底打乱,颜色信息失真严重,连 bus 的红色标识都可能变成土黄色;大雾则把远处的目标「吞」掉,近处目标边缘也变得模糊。天气类型文件名前缀典型图像特征检测难点暴雪snow_storm-高光区域大、对比度低远处目标融入雪面薄雾mist-整体偏灰、细节丢失轮廓模糊难定位沙尘sand_storm-偏色严重、光照不均颜色信息失真大雾foggy-对比度大幅下降目标边缘被吞掉真实的雪不是高斯噪声,而是成块的高光白色区域;沙尘不是简单的亮度变化,而是全局色偏。模型在混合天气数据上训练,比在单一晴天数据上训练学到的特征鲁棒得多——这正是这份数据必须存在的理由:网上随手扒来的晴天数据集,永远补不上雪天的高光噪声和沙尘的色偏结构。2.2 类别与标注格式:classes 文件与归一化坐标数据一共标注了 7 个类别,明确包含 car、bus、person、truck,其余类别的完整名单以你拿到的 classes 文件为准。拿到数据第一件事就是打开 classes.txt 核对类别顺序:YOLO 的类别编号是写死在 txt 标签第一列的,classes.txt 第 0 行对应 id 0,所有 car 的标签第一列都必须是 0。这个顺序一旦错位,训练出来的模型会把所有类别名张冠李戴。每个图片同名 txt 里的标注行格式固定为五列:0 0.512345 0.448912 0.210234 0.318765 2 0.710521 0.230192 0.123456 0.245678 3 0.392488 0.618945 0.189723 0.250123第一列是类别 id,后面四个数是归一化坐标,分别代表 bbox 中心点的 x、y 和宽、高。这些值全部除以了原图尺寸,范围在 0 到 1 之间。反算像素坐标的公式很简单:x_pixel = x_center * img_width,y_pixel = y_center * img_height,宽高同理。归一化的好处在于图片最终要 resize 到 640 或者 960,坐标在缩放后依然有效,不需要重新换算——这是 YOLO 系标注格式的通用约定。另外建议可视化时留意雾天和暴雪天气下的大目标框:如果框普遍比目标小半圈,说明标注边界和真实目标边缘存在系统性偏差,这类脏标签会在训练时把边框回归带偏,后面避坑章节会专门讲怎么处理。2.3 目录结构:训练测试划分与 show 脚本的位置这份数据已经做好训练集和测试集划分,常见目录布局如下:weather_dataset/ ├── classes.txt ├── images/ │ ├── train/ │ │ ├── snow_storm-347.jpg │ │ ├── mist-101.jpg │ │ └── ... │ └── test/ │ ├── sand_storm-184.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── snow_storm-347.txt │ │ └── ... │ └── test/ │ └── ... └── show.pylabels 目录下的每个 txt 与 images 下同名 jpg 一一对应,这是 YOLO 数据的基本约定。如果你拿到的包里有 val 目录就直接用,没有的话建议从训练集里按天气比例抽 10% 出来做验证集,只在 test 上选最优权重容易过拟合。show.py 脚本做的事情是遍历 images 找 jpg,去 labels 找同名 txt,把框和类名画回图上,核心代码下一章直接展开讲。拿到手后的标准动作是三步:先打开 classes.txt 核准类别,再跑 show.py 看标注质量,最后用脚本统计每个类别的样本数量。前两步两分钟就能做完,能帮你判断这批数据值不值得投入训练时间。3. 快速跑通:环境、可视化脚本与 YOLOv5 训练首跑这一章完成从零到训练起来的全过程。先搭好 Python 环境,跑通可视化确认数据没问题,再组织成 YAML 配置丢给 YOLOv5 训练。整个过程不涉及改模型结构,属于「先把管道跑通」的基准操作。3.1 环境准备:Python 版本与依赖清单可视化只需要三个库:opencv-python、matplotlib、numpy,加上读取 YAML 配置用的 pyyaml。如果要训练,再补 torch 和 torchvision。建议用 conda 建一个独立环境,避免污染其他项目。conda create -n yolo-weather python=3.8 -y conda activate yolo-weather pip install opencv-python matplotlib numpy pyyaml # 训练才需要 torch,装 CPU 版跑可视化也不需要 pip install torch torchvisionPython 3.8 对 YOLOv5 和 YOLOv8 都兼容,属于最不容易出版本冲突的选择。如果你有 NVIDIA 显卡且装了 CUDA,torch 会自动启用 GPU 加速;显存低于 6G 的话建议先用 CPU 跑完 show 脚本,把数据质量确认好再考虑训练,否则训练中显存溢出会浪费很多时间排查。torch 安装失败最常见的原因是 CUDA 版本和 wheel 包不匹配,官方源按默认装一般不会出问题。3.2 可视化原图与标签:show 脚本的核心逻辑show.py 脚本是这个数据集自带的,核心逻辑用 OpenCV 把标签画回原图,逐张按回车翻页查看。下面是等价实现,也方便你按自己的需求改:import cv2 from pathlib import Path img_dir = Path("images/train") # 先看训练集,确认后再看 test label_dir = Path("labels/train") class_file = Path("classes.txt") with open(class_file, "r", encoding="utf-8") as f: names = [line.strip() for line in f if line.strip()] for img_path in sorted(img_dir.glob("*.jpg")): label_path = label_dir / (img_path.stem + ".txt") img = cv2.imread(str(img_path)) if img is None: print("跳过无法读取的图片:", img_path) continue h, w = img.shape[:2] if label_path.exists(): for line in label_path.read_text(encoding="utf-8").splitlines(): parts = line.strip().split() if len(parts) != 5: continue cls_id, x_c, y_c, bw, bh = map(float, parts) cx, cy = int(x_c * w), int(y_c * h) bw_p, bh_p = int(bw * w), int(bh * h) x1, y1 = cx - bw_p // 2, cy - bh_p // 2 x2, y2 = x1 + bw_p, y1 + bh_p color = (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow("weather-label-check", img) if cv2.waitKey(0) & 0xFF == ord("q"): break cv2.destroyAllWindows()这段脚本的关键点有几个:img_path.stem取文件名不带后缀,用来拼同名 txt;shape[:2]拿的是原始图像的高和宽,后续像素坐标换算全部以它为准;标签每行用split()而不是split(" "),是为了兼容多个空格或制表符分隔。按 q 键退出,按任意其他键看下一张。跑完可视化后重点看三类图:暴雪里远处的 person 有没有被标出来、沙尘图里 bus 的框是不是偏了、雾天边缘模糊的 truck 框是否贴近目标边缘。这一步发现的标注质量问题,越早处理对训练越有利。3.3 组织数据并启动训练:一条 YOLOv5 训练命令可视化确认没问题后,需要写一个 data.yaml 告诉 YOLOv5 数据在哪、有几类。注意 names 的顺序必须和 classes.txt 完全一致,这里给出一个示例结构,实际类别以你的 classes.txt 为准:path: ../weather_dataset train: images/train val: images/train # 没有 val 时先拿 train 顶一下,正式训练建议单独分 test: images/test nc: 7 names: 0: car 1: bus 2: person 3: truck 4: motorcycle 5: bicycle 6: ridercd yolov5 python train.py --img 640 --batch 16 --epochs 100 \ --data ../weather_dataset/weather.yaml \ --weights yolov5s.pt --name weather-exp1--img 640是输入网络的边长,640 是显存和效果之间的平衡点;--batch 16在 8G 显存左右比较稳,显存不够就降到 8 而不是降分辨率;--weights yolov5s.pt用 COCO 预训练权重做初始化。1000 张数据从零训练很容易过拟合,但用预训练权重起步时,模型已经知道车和人的基本形态,只需要学「雪天里的车长什么样」,这是迁移学习的标准用法,也是这个数据量下最不容易翻车的选择。配套的 YOLOv5 改进实战笔记里有大量针对 backbone 和训练策略的调法,和这份数据配合着看会更快上手。跑完第一个 100 epochs 后别急着下结论,先看 val 的 mAP 曲线和 PR 曲线,再决定是加 epochs 还是换更大输入尺寸。4. 核心实现拆解:标签解析、预处理增强与训练配置的完整链路数据管道跑通只是第一步,真正决定模型上限的是标签解析的正确性、天气场景的预处理策略和训练参数的选择。这一章把数据到模型的完整链路拆开讲。4.1 解析 YOLO 标注:从 txt 到像素坐标的换算无论是训练还是评估,第一步都是把归一化坐标还原成像素坐标。下面这个函数可以复用到项目的任何环节:def load_label(txt_path, img_w, img_h): """解析 YOLO txt 标签,返回绝对像素坐标框""" boxes = [] with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue cls_id = int(parts[0]) x_c, y_c = float(parts[1]), float(parts[2]) bw, bh = float(parts[3]), float(parts[4]) # 中心点坐标转左上角和右下角坐标 x1 = (x_c - bw / 2) * img_w y1 = (y_c - bh / 2) * img_h x2 = (x_c + bw / 2) * img_w y2 = (y_c + bh / 2) * img_h boxes.append((cls_id, x1, y1, x2, y2)) return boxes换算逻辑很直接:bbox 的存储格式是中心点和宽高,先减半宽得到左边界,再加半宽得到右边界,最后乘以图像宽高还原成像素坐标。返回的(x1, y1, x2, y2)是绝对坐标框,画图、裁剪、算 IoU 都基于这个格式。这段代码刻意没做越界裁剪,因为越界本身就是信号。如果解析出来的框有负数坐标或者超过图像宽高,说明标签里有脏数据;如果这种越界框占了 5% 以上,就要重新考虑这批数据的可信度。解析时用split()而不是split(" ")能兼容制表符和连续空格,避免因为格式细节报错。4.2 数据加载与预处理:针对天气噪声的增强策略通用目标检测的数据增强是随机裁剪、翻转、HSV 扰动,但天气数据的预处理应该针对天气噪声的结构来做。雾天图像对比度低,用 CLAHE(对比度受限自适应直方图均衡)提升局部对比度很有效;沙尘图偏色严重,灰度世界白平衡能把黄色调拉回来。下面两个函数是我处理天气数据时常用的:import cv2 import numpy as np def clahe_rgb(img, clip=2.0): """雾天/薄雾增强:CLAHE 提升局部对比度""" lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=clip, tileGridSize=(8, 8)) l = clahe.apply(l) lab = cv2.merge((l, a, b)) return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) def gray_world_white_balance(img): """沙尘图增强:灰度世界白平衡校正色偏""" b, g, r = cv2.split(img) mean_b, mean_g, mean_r = b.mean(), g.mean(), r.mean() gray = (mean_b + mean_g + mean_r) / 3 b = np.clip(b * (gray / max(mean_b, 1e-5)), 0, 255).astype(np.uint8) g = np.clip(g * (gray / max(mean_g, 1e-5)), 0, 255).astype(np.uint8) r = np.clip(r * (gray / max(mean_r, 1e-5)), 0, 255).astype(np.uint8) return cv2.merge((b, g, r))CLAHE 的两个参数值得说明:clipLimit 是裁剪限制,值越大对比度增强越强,2.0 左右比较安全,超过 3.0 容易把阴影噪声也放大;tileGridSize 是分块大小,(8,8) 表示把图像分成 8x8 的小块分别做直方图均衡,能避免全局均衡造成的过度曝光。灰度世界白平衡的假设是画面的平均颜色趋向中性灰,所以把每个通道的平均值拉平——这个假设在沙尘场景下基本成立,但对大面积单色路面可能失效,用之前先看一眼效果。预处理变换不要太激进,否则会把阴影误判成目标。更重要的原则是:增强变换应该在训练时随机应用,让模型看到不同强度的天气退化,而不是只在推理时做一次固定预处理。YOLOv5 自带的 mosaic 增强对天气数据尤其有效——它把雪天、雾天、沙尘天的目标拼到同一张训练图上,缓解不同天气样本量不均带来的偏见。4.3 训练配置:模型、输入尺寸与类不平衡的调法天气场景里小目标占比高,被困在雪地里的行人可能只有几十个像素。这种情况下输入尺寸比 backbone 网络的选择更影响最终效果,参考配置如下:配置项推荐值说明img640起步基线,显存友好img960小目标多时提升明显,显存约为 640 的 1.5 倍batch168G 显存左右;显存不足先降 batch 再降 imgepochs100基线值,看曲线收敛情况再追加optimizerSGDYOLOv5 默认,Adam 收敛快但更容易过拟合weightsyolov5s.pt迁移学习初始化,别从零训练我一般先用 640 跑 100 epochs 做基线,记录 val mAP,再开一个 960 的训练微调 30 epochs,看 mAP 提升幅度是否值得增加的训练时间。天气数据样本只有 1000 张,训练时间不会太长,多跑几个配置对比成本可控。类别不平衡是这类道路数据的高频问题:person 的标注数量可能只有 car 的五分之一,模型训练时会天然偏向高频类别,结果就是车检得不错、人漏检严重。处理办法有两个:一是在 YOLOv5 的 hyp 超参数里调cls_pw,把它从默认的 1.0 改成类别数量的反比,增大少样本类别在分类损失里的权重;二是在数据层面把 person 的样本复制几份混进训练集,等价于做类别加权。第一个方法更干净,血泪经验是两种都试一下,在 val 集上对比 person 类别的 mAP 再决定留哪个。5. 避坑指南:恶劣天气数据最容易踩的 5 个坑天气数据的坑比普通数据集多一个维度——图像本身被天气「污染」,标注质量、颜色分布、场景划分都会出问题。以下是实际使用中最常见的 5 个坑,每条都按「现象 → 原因 → 解决」给出处理方案。5.1 雾天边界框「差半截」跑 show 可视化时会发现:雾天背景下远处的车,标注框明显比目标小一圈,框的边界落在目标的轮廓内部,没有贴住边缘。原因不难理解,雾中目标边缘不可见,标注员只能凭经验补框,不同人补出来的尺度不统一,甚至同一个人在不同雾浓度下也会画偏。这个坑不解决会直接影响边框回归的质量,模型学到的框尺寸系统性地偏小。解决思路是宁缺毋滥:写个脚本按「框面积占整图比例小于 0.001 且文件名前缀为 foggy」把可疑目标筛出来,人工逐张确认,模棱两可的直接从训练集中剔除。1000 张数据的体量经不起脏标签的污染,删掉几十个问题框对样本量的影响很小,但对 mAP 的改善很明显。5.2 类别严重不均衡:person 远少于 car训练完看 val 结果,car 的 mAP 到 0.9,person 只有 0.3,甚至更低。根因是样本数量差距超过 5 倍,模型把 person 当成稀有类,学了但没学好,代价函数里 person 的分类误差占比太小。这种情况下继续加 epochs 也没用,损失函数根本不会把采样频率低的类别当回事。解决路径有两条:改cls_pw增大 person 的损失权重,或者对 person 样本做过采样,把含 person 的图片复制两份放进训练集。复制样本是最直接的手段,虽然会带来一定的过拟合风险,但在 1000 张数据量下收益大于风险。改完重新训练,盯着 person 类别的 mAP 看是否抬升,别只看整体 mAP 的平均值。5.3 沙尘图整体偏色导致漏检和误检沙尘场景下模型表现不稳定:truck 经常检不出来,bus 被误检成 car。原因是沙尘图的黄褐色调把目标的颜色信息淹没了,模型在训练时把「黄色调」当成了背景特征,目标本身的边缘和纹理反而被忽略。解决思路是在数据增强里加入色调扰动,模拟沙尘天的颜色偏移,让模型学会忽略色偏的影响。HSV 增强里的 H(色调)和 S(饱和度)扰动范围可以比晴天数据调大一些,比如 H 扰动从默认的 ±0.015 加到 ±0.03。同时可以配合上一章的灰度世界白平衡做预处理,双管齐下,注意增强力度别大到把暗色目标洗成全黑。5.4 同源帧进了测试集,评估结果虚高这是最隐蔽的一个坑。现象是训练 mAP 0.85、测试 mAP 0.82,看起来表现不错,但模型一上线到真实雪天路段就崩到 0.5。原因是同一段路连续拍摄的帧图像高度相似,随机划分数据集时,测试集里出现了大量和训练集几乎同源的图片,评估结果虚高。正确做法是按天气场景分组划分,而不是随机打散文件。这份数据的文件名前缀天然提供了分组依据:把snow_storm-、mist-、sand_storm-、foggy-四种前缀的图片整组划分,保证同一天气场景的所有帧只出现在训练集或测试集的一侧。如果数据量允许,还可以按拍摄时段分组,进一步降低相关性。5.5 Windows 上跑通、Linux 上翻车的路径分隔符在 Windows 本地跑 show 脚本一切正常,把数据打包上传到 Linux 服务器训练,报 FileNotFoundError,找不到图片文件。原因是索引文件或脚本里写了反斜杠路径,Windows 认、Linux 不认,换到服务器就变成无效路径。解决方式是统一用 pathlib 处理路径,一行代码搞定:路径字符串用str(Path(p).resolve())转成绝对路径,或者用.as_posix()把分隔符统一转成正斜杠。养成这个习惯后,换机器再也不会因为路径问题浪费半小时排查。这是我每次换环境都会踩的坑,也建议大家拿到任何数据集,先把所有路径操作改成 pathlib 再开始跑。6. 改造落地:按天气分组评估与数据增强的实战路径拿到这份数据,最常见的三个改造方向:扩展类别、混入晴天数据增强、按天气分组评估。扩展类别的做法是改 classes.txt 追加新类名,同步更新 weather.yaml 里的 nc 和 names,再用半自动标注工具补新类别的框——注意新类别的编号只能追加在末尾,不能插在中间,否则所有已有标签的类别 id 全部错位,这是 YOLO 格式最容易忽略的连环坑。如果你的项目本来就有大量晴天道路数据,把这份天气数据当作「增强样本」以 1:2 的比例混入训练,比单独训练天气模型更稳。模型先在大规模晴天数据上学会目标的基本形态,再用天气数据微调适应退化场景,泛化能力会好不少。这也是最省力的改造路径,不用改动任何网络结构,只调 data.yaml 里的训练集路径。最后是我的核心建议:按天气分组评估,而不是看整体 mAP。写一个脚本按文件名前缀把 test 集拆成 snow_storm、mist、sand_storm、foggy 四组,逐组跑 val 统计 mAP,结果往往能颠覆你对模型的判断。出现过一次让我印象很深的事:整体 mAP 看起来有 0.78,按天气拆分后发现雪天只有 0.41,其他三组都在 0.8 以上——问题被平均值完美掩盖了。从那以后我每次训练天气模型,评估阶段都强制走一遍分组统计流程,先看各组分数再决定改预处理方向和增强策略。希望帮到你。本文还有配套的精品资源,点击获取