资讯详情

资讯详情

恶劣天气下YOLO目标检测:数据集解析与训练调优指南

简介面向目标检测学习者和YOLO实战研究者提供一份恶劣天气下道路行人、车辆检测数据集覆盖雾天、雨天、雪天、沙尘等场景可用于模型训练与鲁棒性验证。数据约1000张已标注图像类别包括car、bus、person、truck等7类并已做好训练集、测试集划分内置show脚本可一键可视化标注效果。压缩包共2000个文件以1027个txt标注文件、972张jpg原始图像及1个Python可视化脚本组成完整包大小约133.76MB。目前已有157人学习。标注格式采用YOLO标准txt文件每个标签文件与对应jpg一一匹配便于直接接入YOLOv5等主流检测框架同时已按类别做好训练集、测试集划分可省去自行分集的麻烦自带的show脚本则方便快速核查标注质量提升数据使用效率。该数据集既能支撑算法工程师针对雨雪雾、沙尘等恶劣条件下的检测性能调优也可作为计算机视觉相关课题或毕业设计的实验数据适合作为YOLO改进实战的配套数据。1. 恶劣天气下的道路目标检测这套 YOLO 数据到底解决什么问题雨雾天气里跑目标检测模型在晴天测试集上明明有九成准确率一落到雨夜场景就断崖式跌到六成以下——这是做车载视觉、智慧交通、道路监控的团队几乎都撞过的一堵墙。难点不在模型本身而在训练数据的场景分布普通数据集里晴天占比过高模型学到的是“清晰边缘加高对比度”的捷径一旦光线、水雾、雨丝把特征打散行人和车辆就从画面里“隐身”了。我这次要说的这套数据核心价值就是补上恶劣天气这一段约 1000 张已标注图像全部使用 YOLO 标注格式目标类别落在行人和车辆上专门用来微调或验证模型在雨、雪、雾、夜等低可见度条件下的检测能力。适合谁用做车载感知的算法工程师、做交通监控的集成商、以及研究生阶段拿真实恶劣天气数据做实验的同学——它不解决“从零训练一个模型”的问题但恰好解决“晴天模型在坏天气里翻车”的痛点。下面我按数据解析、训练落地、场景调优、踩坑排查的顺序把全套做法拆开讲。2. 恶劣天气道路数据集与 YOLO 标签格式先把数据吃透再谈训练2.1 数据集的目录结构与类别定义拿到这套数据后第一件事不是急着开训练而是把目录和标签结构完整过一遍。常见做法是数据以标准 YOLO 格式组织images目录存放所有 JPEG 图像labels目录存放每张图对应的 txt 标注文件两个目录下各有train、val、test子集划分。图像文件名和标签文件名一一对应只是扩展名不同——比如rainy_001.jpg对应rainy_001.txt。类别方面行人person和车辆vehicle是两个大类其中车辆的标签可能细分为小汽车、卡车、公交车也可能统一归为 vehicle取决于数据制作者的标注规范动手前先打开任意一个 txt 文件确认class_id的取值范围这是整个流程里最容易先入为主出错的地方。# 查看数据集目录结构的常用命令 find . -type f | head -50 # 查看单个标签文件内容 cat labels/train/rainy_001.txt标签文件里每一行是一个目标格式为class_id x_center y_center width height后四个值都是归一化到 0~1 的相对坐标。比如0 0.521 0.482 0.104 0.218表示类别 0假设是行人的边界框中心点在图像宽度的 52.1%、高度的 48.2% 处框的宽度是图像宽度的 10.4%高度是图像高度的 21.8%。理解这个归一化格式非常重要因为后续所有数据增强、anchors 计算、mAP 评估都在这个坐标体系下进行任何一处在像素坐标和归一化坐标之间搞混都会导致训练时 loss 异常或推理时框的位置完全偏移。2.2 验证标签质量边界框越界与空标注检查拿到标注数据后我习惯先跑一个脚本做完整性校验而不是直接丢进训练。恶劣天气图像往往存在边界框只有一半在画面内的情况——行人被雨雾遮挡后标注者可能只框出可见部分或者框体溢出图像边界。YOLO 格式在训练时对超过边界的框会做裁剪处理但如果框的中心点落在图像外训练时会直接产生 NaN loss。我一般会检查两类问题一是标签文件中是否有空文件即图像存在但没有任何目标二是归一化坐标是否全部落在 0~1 区间内。通过一个小脚本即可快速过滤问题样本。import os label_dir labels/train invalid_files [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue fpath os.path.join(label_dir, fname) with open(fpath, r) as f: lines [line.strip() for line in f if line.strip()] if len(lines) 0: invalid_files.append((fname, empty)) continue for line in lines: parts line.split() if len(parts) ! 5: invalid_files.append((fname, format_error)) break try: vals [float(v) for v in parts[1:]] except ValueError: invalid_files.append((fname, non_numeric)) break if not all(0.0 v 1.0 for v in vals): invalid_files.append((fname, out_of_range)) break print(f检查完毕共发现 {len(invalid_files)} 个异常文件) for item in invalid_files[:20]: print(item)这段脚本的逻辑是逐行读取每个 txt先排除空文件再校验每行是否为 5 个字段、后 4 个坐标值是否为数值且落在 0~1 范围。参数说明label_dir换成你的实际标签路径out_of_range的判断条件是归一化坐标必须满足0 v 1如果出现负值或大于 1说明标注过程存在坐标换算错误这类文件继续参与训练会污染模型建议直接剔除或重新标注。另外提醒一下空标签文件不是完全没用——一张真实恶劣天气图像里可能确实没有行人也没有车辆这种“负样本”对降低误检率有贡献我的习惯是保留但单独标记不让它混进正常训练集里干扰 loss 统计。2.3 恶劣天气图像特点与样本分布预分析标签质量没问题之后我会对图像本身做一个像素级统计。恶劣天气场景的典型特征包括低亮度、低对比度、局部高光如车灯、路灯、雨丝/雪花造成的高频噪声等。通过计算每张图像的灰度均值、标准差和雾度指标可以粗略量化数据集的难度分布。import cv2 import numpy as np img_dir images/train stats [] for fname in os.listdir(img_dir): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue img cv2.imread(os.path.join(img_dir, fname)) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) mean_val gray.mean() std_val gray.std() # 雾度简单估计算子: 亮区域占比 bright_ratio (gray 200).mean() stats.append((fname, mean_val, std_val, bright_ratio)) stats.sort(keylambda x: x[1]) print(最暗的 10 张图像:) for s in stats[:10]: print(f{s[0]}: 灰度均值{s[1]:.1f}, 标准差{s[2]:.1f})这个统计有两个用途第一如果发现灰度均值低于 60 的图像占比过高训练时可以考虑配合 CLAHE 或自动白平衡做预处理再送入网络否则模型学到的特征空间会和晴天数据差异过大第二标准差低说明图像对比度差这类样本对模型的泛化能力要求更高建议在训练时对它们做加权采样。参数层面bright_ratio的阈值 200 是可以调的夜间车灯场景会把阈值拉高若你的数据以夜间为主这个值建议改为 180 或者直接用灰度直方图的 95 分位数替代。3. 用 YOLOv8 在本地跑通恶劣天气数据训练最小配置与迁移学习3.1 环境准备与数据集文件夹整理我选择 YOLOv8 作为训练框架来演示因为它的配置文件结构简单对自定义数据集友好而且是目前 YOLO 系列里对低光照、小目标检测支持相对成熟的一个版本。环境准备上PyTorch 版本建议 2.0 以上CUDA 按显卡驱动选择 11.8 或 12.1CPU 训练也可以跑但这个数据规模下不建议迭代速度会慢到影响调参节奏。数据集目录需要按照 YOLO 要求组织成train/val分离的结构并准备一个data.yaml文件声明类别信息。# 安装 ultralytics 包YOLOv8 的官方实现 pip install ultralytics# data.yaml path: /your/dataset/path train: images/train val: images/val test: images/test names: 0: person 1: vehicledata.yaml是训练入口的关键配置。path必须是绝对路径或相对于 yaml 文件位置的路径我踩过坑如果train和val写相对路径ultralytics 会以当前工作目录为基准去拼接导致“Dataset not found”报错所以建议直接用绝对路径。names里的类别顺序必须和标签 txt 里的class_id完全一致类别数不用单独写框架会根据names的键数量自动推断。注意这套数据的类别如果你发现 txt 里有class_id超过 1 的值说明是细分车辆类别此时names要改成对应的0: person, 1: car, 2: truck, 3: bus这种形式。3.2 训练命令与核心参数选择基础训练命令如下我建议第一轮不做什么花哨操作直接按照常规参数跑 100 个 epoch把硬件资源和时间成本估算出来再决定后续调优方向。yolo train datadata.yaml modelyolov8m.pt epochs100 imgsz640 batch16 lr00.01各参数的默认含义yolov8m.pt是 COCO 预训练权重在自有小数据集上微调比从头训练收敛快得多这是迁移学习的常规做法imgsz640是输入分辨率如果你的图像本身就是 1920x1080 的监控画面640 会严重缩小目标尺寸——但 YOLOv8 会在训练时做随机缩放所以实际输入是在 480 到 800 之间动态变化的batch16取决于显存大小我常用 16 或 32显存不足时降低 batch 而不是降低分辨率后者对恶劣天气小目标影响更明显。lr00.01是 SGD 初始学习率如果换 AdamW 优化器通常要降到 0.001。第一轮跑完后观察results.csv里的val_mAP50和val_mAP50-95这两个指标分别对应粗定位精度和精确定位精度恶劣天气数据上 mAP50-95 会比 mAP50 低很多这是正常现象不必焦虑。3.3 微调策略冻结骨干层还是全量微调约 1000 张的数据量属于典型的小数据集全量微调的风险是过拟合模型会记住训练集里的雨丝纹理和特定光照而不是学到“行人”这个概念。我的习惯是先做一次全量微调作为 baseline然后尝试冻结骨干层只训练检测头的方案。在 ultralytics 中冻结操作可以通过修改训练代码或使用freeze参数实现。from ultralytics import YOLO model YOLO(yolov8m.pt) # 冻结 backbone 前 10 层具体层数按模型结构调整 for name, param in model.model.named_parameters(): if name.startswith(model.0) or name.startswith(model.1): param.requires_grad False model.train(datadata.yaml, epochs100, imgsz640, batch16)冻结的逻辑是保留 ImageNet/COCO 预训练中学到的低级特征——边缘、纹理、颜色块只更新高层语义特征和检测头。这么做的好处是训练速度更快且在小数据集上不容易把骨干层的通用特征带偏。参数说明model.0到model.2通常对应 YOLOv8 的 stem 和早期 stage不同版本层号略有差异可以通过model.model打印结构确认。如果冻结后 val loss 明显上升而 train loss 下降说明检测头容量不足以拟合恶劣天气特征这时应解冻部分骨干层继续训练。1000 张数据下我通常先冻结跑 50 epoch再解冻最后两个 stage 跑 50 epoch效果比一直全量微调更稳。4. 恶劣天气场景下 YOLO 模型掉点的三个根因与针对性调优4.1 低对比度导致特征退化预处理与数据增强的组合拳雨天和雾天的共同特征是图像对比度低、细节模糊YOLO 的 C2f 模块在浅层提取的边缘特征会变得不稳定。要缓解这个问题我在数据送入网络前增加一道预处理增强流程使用 Albumentations 库实现。基础增强加上随机亮度和对比度扰动让模型见过更丰富的亮度分布。import albumentations as A from albumentations.pytorch import ToTensorV2 transform A.Compose([ A.RandomBrightnessContrast(brightness_limit0.3, contrast_limit0.3, p0.7), A.CLAHE(clip_limit2.0, tile_grid_size(8, 8), p0.5), A.GaussNoise(var_limit(20, 40), p0.3), A.RandomSizedBBoxSafeCrop(width640, height640, erosion_rate0.2, p0.5), ToTensorV2() ])这段增强配置专门针对恶劣天气设计。RandomBrightnessContrast让模型适应从清晨微光到夜间路灯的跨度CLAHE是限制对比度自适应直方图均衡能把雾天灰度集中的图像拉伸开但参数clip_limit不能设太高否则夜间图像会出现明显噪声放大GaussNoise模拟传感器在低光照下的噪点这比直接在原图上加噪声更接近真实物理过程RandomSizedBBoxSafeCrop做一个带框安全裁剪确保裁剪区域内的标注框不会被切掉一半。这套增强在验证集的 mAP50 上通常能提升 2 到 4 个百分点但如果原始图像已经包含大量雨丝不建议再加GaussNoise否则雨丝区域的高频噪声会叠加出现过拟合。4.2 小目标漏检输入尺度与 Anchor 策略监控摄像头视野下的行人往往只有 30x60 像素甚至更小而 YOLOv8 的默认 anchors 基于 COCO 目标尺寸统计对小目标召回率偏低。数据不换模型架构的前提下有两个参数立竿见影一是提升imgsz从 640 提高到 960 甚至 1280二是在训练时开启multi_scale随机尺度采样。但高分辨率训练显存消耗成倍增加batch 16 在 960 分辨率下需要至少 16GB 显存这个要提前评估。yolo train datadata.yaml modelyolov8m.pt epochs100 imgsz960 multi_scaleTrue batch8multi_scaleTrue表示每个 iteration 随机从 0.5 到 1.5 倍原始尺寸中采样一个缩放因子这种尺度抖动让模型对不同距离的目标都有感知。需要特别注意如果标签中的小目标占比高multi_scale采样到低分辨率时小目标几乎消失模型反而会被带偏。我的建议是先用固定分辨率 960 跑 50 个 epoch观察小目标类别person的 AP 曲线如果还在爬升再开启多尺度训练。另外YOLOv8 的 anchor-free 设计对锚框尺寸不再敏感所以不要沿用 YOLOv5 的手动 anchors 搜索流程这个版本里调 anchors 是无效操作属于常见误用。4.3 类别不均衡车辆多行人少时的损失函数调节交通场景里车辆数量远多于行人尤其恶劣天气下行人被雨具遮挡标注框更少。默认的BCEWithLogitsLoss在类别不均衡时会偏向多数的车辆类。YOLOv8 的 loss 里有一个cls_pw参数控制正样本权重提升行人类别对应的权重可以缓解不均衡。model.train( datadata.yaml, epochs100, imgsz640, modelyolov8m.pt, batch16, cls_pw1.5, box_pw2.0, fl_gamma1.5 )cls_pw和box_pw分别放大分类和回归损失中正样本的贡献fl_gamma是 focal loss 的调制因子gamma 越大模型越关注难分类样本——恶劣天气中被遮挡的目标自然被赋予了更高权重。这三个参数在 YOLOv5 时代就有YOLOv8 仍然保留但改动幅度不建议超过 2 倍否则 loss 震荡严重。判断参数是否有效的指标不是总的 mAP而是分别查看 person 和 vehicle 两个类别的 AP 值。如果 person 的 AP 明显低于 vehicle先把cls_pw调到 1.5 到 2.0 之间。注意要在验证集上用yolo val modelbest.pt datadata.yaml单独看每类指标而不是只看综合数字。5. 恶劣天气数据集训练避坑指南五个必看的实战踩坑记录5.1 雨天图像的标注框坐标比想象中更容易越界现象训练到第 30 个 epoch 时出现loss: nan训练进程直接崩溃。查看日志发现 loss 在直升 NaN 前有个异常跳动。原因某张雨夜图像中行人的边界框中心点落在图像右边缘之外——标注者把遮挡后的行人完整轮廓画上了但行人的一半已经出了画面的可见区域。YOLO 格式要求是归一化坐标中心点 x 值大于 1训练时计算损失时出现无效值。解决用前面的坐标校验脚本把所有越界框过滤掉。具体做法是写一个清洗脚本将每个框的中心点坐标限制在 0~1 内必要时直接删除该标注并备份原始文件。另外之后每次收到新数据都先跑一遍校验不要认为标注供应商给的格式一定正确。5.2 车辆类别下的细分标签导致维度对不上现象数据加载时一直报错提示标签类别数超限但我数了 data.yaml 里只有两个类别。原因某个 txt 文件里的class_id出现了数字 3原来标注者在车辆类别下细分了“轿车 1、卡车 2、公交车 3”但只对部分图像做了细分标注大部分图像仍然用 1 表示所有车辆。混合使用导致类别维度不一致。解决统一类别定义。我写了一个一键替换脚本将所有标注中的class_id 2和3全部映射为1把多类问题化简为二分类问题。在 1000 张数据规模下不建议保留多类别细分数据量不足会让每个子类都学不好。5.3 验证集里混入了夜间的极端长曝光样本现象训练 loss 正常下降但验证集 mAP 始终在 0.5 附近徘徊怎么调都上不去。原因验证集里包含多张长曝光夜间图像车辆尾灯拖出长长的光轨导致标注框对应的是一个“光带”而不是一辆清晰的车。人和模型都很难对这种物理伪影给出稳定的语义判断。解决把这部分图像从验证集中移除放入单独的 hard sample 集不参与精度评估。评估指标应该反映模型在“可认知目标”上的表现而不是被物理伪影拖低数值。之后如果想测试模型的鲁棒性可以单独在 hard sample 集上跑推理看哪些目标被漏检。5.4 雾天图像使用 CLAHE 增强后出现严重色偏现象使用 CLAHE 增强后雾天图像的验证集 mAP 反而下降了 3 个百分点。原因CLAHE 在 RGB 三个通道上独立做直方图均衡破坏了颜色分布的一致性。雾天本身色调偏灰白增强后部分图像变成带青色或品红的色偏图像模型学到的颜色特征偏离真实分布。解决只在灰度或亮度通道上做 CLAHE保留色度信息。在 Albumentations 里用转换到 HSV 色彩空间再应用 CLAHE 的方式替代直接 RBG 处理。另外clip_limit从 2.0 降到 1.5增强幅度更温和雾天细节恢复效果差一些但颜色失真明显减少。5.5 迁移学习时预训练权重的类别数不匹配现象加载yolov8m.pt时提示权重和模型的类别数不一致要求忽略某些权重层。原因COCO 的类别是 80 类而当前模型只输出 2 类检测头的权重维度不同加载器会自动丢弃这些层只保留骨干层权重。这个现象本身没问题但如果日志里显示只有骨干层被加载而检测头随机初始化说明你没有使用 COCO 预训练的全部分层缺点是检测头得从头学收敛速度变慢。解决这不算错误但要意识到行为。对于 1000 张数据建议先用yolov8m.pt加载然后单独训练检测头 20 个 epoch再解冻整个网络微调。另一种做法是用yolov8n.pt这种更小的模型因为小模型的检测头参数少随机初始化带来的影响更小。没有绝对优劣我通常两个版本各跑一轮哪个 val mAP 高用哪个。6. 用域迁移补足恶劣天气数据短板一个实用的后续动作1000 张已标注数据作为起点是够用的但如果目标是部署到真实监控环境中你会发现训练集仍然覆盖不了多种雨强、雾浓度和雪天反光。一个实用思路是用合成雨雾数据做域迁移预训练再回到真实数据上微调。具体做法是用现有的干净道路图像比如 BDD100K 或 Cityscapes 的晴天子集合成雨滴纹理和雾效生成 5000 到 10000 张伪恶劣天气图像在合成数据上只训练骨干层的域适应让特征提取器学会在退化图像中保持响应。# 合成雨雾增强的最小示例基于 OpenCV 直方图平移模拟薄雾 import cv2 import numpy as np def simulate_fog(img, intensity0.3): intensity: 0~1 之间的雾浓度越大雾越重 fog np.full_like(img, 200, dtypenp.uint8) result cv2.addWeighted(img, 1 - intensity, fog, intensity, 0) return result这个合成代码的逻辑是用一个接近白色的常数图像与原图做加权叠加intensity控制雾的浓度。0.3 对应薄雾0.6 对应大雾超过 0.8 时目标几乎不可见不需要生成。合成数据的作用不是替代真实数据而是让模型在迁移学习的第一个阶段就见过“低对比度下的车辆轮廓”这种特征模式后续在真实恶劣天气数据上微调时收敛更快、过拟合风险更低。验证阶段我有一个习惯把数据集中所有雾天图像单独归为一个子集算一次这个子集上的 mAP再和全体数据集的 mAP 对比。如果雾天子集低 5 个百分点以上说明模型仍然没有学好雾天特征回到第 4 章的增强策略里调整 CLAHE 强度和变换组合。这套流程跑完一轮大约需要一个下午但它能最大程度压榨 1000 张数据的价值也让后续补充数据时有明确的量化依据。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →