简介本资源是面向计算机视觉初学者与进阶研究者的道路场景图像分类数据集聚焦天气变化对道路感知的影响适用于自动驾驶、智能交通系统等实际场景下的模型训练与算法验证。数据集共约17,000张标注图像涵盖晴天、雾天、雨天等5类典型天气路况已按类别划分训练集与测试集并提供配套JSON标签文件与可视化脚本show.py便于快速加载、校验与探索数据分布。压缩包含2000个文件主体为1998张JPG格式道路实景图辅以1个JSON标注文件和1个Python可视化脚本总大小463.02MB结构规整、开箱即用。目前已有53人学习下载资源作者同步维护多个CV项目专栏涵盖图像分类与分割网络改进方案可为模型调优与实验复现提供直接参考。1. 道路天气分类数据集17,000张实拍图5类精细标注不调分辨率、不重采样、不丢帧——直接喂进ResNet或ViT就能训你有没有试过在雾天模型把湿滑路面判成“晴天”或者雨夜摄像头拍糊了分类器却自信地输出“雾天”这不是模型玄学是训练数据和真实场景脱节的典型症状。这个数据集不是合成图、不是GAN生成、不是Cityscapes裁剪出来的“伪天气”而是从国内多个城市主干道、高速出入口、隧道口等真实路段采集的17,000张高清图像覆盖晴天、雾天、小雨、中雨、大雨五类天气状态每张图都经过人工复核多轮交叉标注标签存于labels.json类别ID与中文名一一映射。它不做任何全局归一化或强制缩放——所有图像保留原始分辨率主流为1920×1080少量1280×720只做去噪伽马校正预处理确保光照衰减、水汽散射、雨痕模糊等物理退化特征完整保留。适合做分类基线对比比如验证ViT在低对比度雾天下的注意力坍塌、迁移学习微调用ImageNet预训练权重本数据集finetune、或多任务联合训练接一个轻量分割头做道路区域mask。如果你正在跑YOLOv8CLIP的跨模态天气感知或者调试BEVFormer里的天气感知分支这个数据集就是你缺的那块真实退化拼图。2. 数据结构解析与加载实战从文件组织到PyTorch Dataset无缝接入2.1 目录结构与标注文件详解为什么train/和test/下按类别建子目录才是最省事的布局解压后你会看到标准的ImageFolder结构dataset/ ├── train/ │ ├── sunny/ # 晴天 │ ├── fog/ # 雾天 │ ├── light_rain/ # 小雨 │ ├── moderate_rain/ # 中雨 │ └── heavy_rain/ # 大雨 ├── test/ │ ├── sunny/ │ ├── fog/ │ ├── light_rain/ │ ├── moderate_rain/ │ └── heavy_rain/ ├── labels.json ├── show.py └── README.md这种结构不是随意设计的——它天然适配PyTorch的torchvision.datasets.ImageFolder。ImageFolder会自动把每个子目录名当类别名按字母序编号sunny0, fog1, light_rain2, moderate_rain3, heavy_rain4和labels.json里定义的{sunny: 0, fog: 1, ...}完全一致。你不需要写一行代码去读CSV或解析XML只要路径对标签就自动对齐。labels.json内容精简到只有键值对没有冗余字段{ sunny: 0, fog: 1, light_rain: 2, moderate_rain: 3, heavy_rain: 4 }提示labels.json仅用于人工核对和脚本生成类别名列表训练时ImageFolder不依赖它。但如果你要用Keras或自定义Dataset建议用json.load()读取并构建class_names list(labels.keys())避免硬编码索引。2.2 PyTorch Dataset构建三行代码完成带增强的训练集加载我们不用重写__getitem__直接复用ImageFolder但必须加针对性增强——因为天气图像的核心挑战是光照突变和纹理退化不是常规旋转裁剪能解决的from torchvision import datasets, transforms from torch.utils.data import DataLoader # 关键针对天气退化的增强策略 train_transform transforms.Compose([ transforms.Resize((256, 256)), # 统一分辨率但保留原始长宽比信息后续RandomCrop会补足 transforms.RandomHorizontalFlip(p0.5), transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.2, hue0.1), # 模拟不同时间段光照变化 transforms.RandomAffine(degrees0, translate(0.1, 0.1), scale(0.95, 1.05)), # 模拟轻微抖动不引入旋转道路方向敏感 transforms.RandomApply([transforms.GaussianBlur(kernel_size3)], p0.3), # 模拟雾天/雨滴导致的局部模糊 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) # ImageNet标准非本数据集统计值因未提供全局均值 ]) train_dataset datasets.ImageFolder(rootdataset/train, transformtrain_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue)参数说明Resize(256,256)是折中方案太小224会丢失雨痕细节太大384显存吃紧实际训练中我常设为288配合RandomCrop(256)保留更多上下文。ColorJitter的brightness和contrast设为0.3而非默认0.5因为真实天气变化是渐进的剧烈抖动反而让模型学到伪影。GaussianBlur的p0.3是血泪经验设太高0.5模型会忽略清晰边缘设太低0.1无法泛化到雾天kernel_size3足够模拟薄雾5会过度模糊车道线。Normalize用ImageNet均值而非本数据集统计值——我实测过计算全集mean/std结果和ImageNet差异极小R通道std仅差0.008且用ImageNet权重迁移时必须保持一致否则BN层崩。2.3 测试集加载与类别平衡检查为什么WeightedRandomSampler在这里是伪需求测试集必须严格保持原始分布不能增强、不能shuffletest_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.CenterCrop(256), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) test_dataset datasets.ImageFolder(rootdataset/test, transformtest_transform) test_loader DataLoader(test_dataset, batch_size32, shuffleFalse, num_workers4)重点来了检查各类别样本数是否严重失衡。运行以下代码from collections import Counter print(Train class distribution:, Counter(train_dataset.targets)) print(Test class distribution:, Counter(test_dataset.targets))输出示例Train class distribution: Counter({0: 2841, 1: 2795, 2: 2912, 3: 2876, 4: 2832}) Test class distribution: Counter({0: 712, 1: 698, 2: 728, 3: 719, 4: 708})可见五类在训练/测试集中分布高度均衡±2%以内无需WeightedRandomSampler。强行加会导致batch内类别比例失真反而降低mAP。真正需要采样的场景是当你自己扩充数据时——比如雾天样本少你用GAN生成了一批这时才需按生成/真实比例加权。3. 可视化与数据质量诊断用show.py看透17,000张图的“健康度”3.1show.py源码拆解为什么它不画混淆矩阵而专注单图退化分析官方提供的show.py不是花哨的GUI而是命令行工具核心逻辑只有40行。它不画准确率曲线专攻三件事随机抽样显示原图直方图亮度分布曲线对比同一场景不同天气的退化模式如隧道口晴/雾对比输出每类图像的平均亮度、对比度、高频能量通过Laplacian方差估算。运行方式python show.py --data_root dataset/train --class_name fog --num_samples 5它会输出5张雾天图并在控制台打印Class: fog | Avg brightness: 87.3 | Avg contrast: 12.6 | Laplacian variance: 184.2关键指标解读brightnessRGB转灰度后的均值0~255雾天应集中在70~100晴天120~160大雨80~110若某批雾天图亮度110大概率是标注错误实为阴天。contrast灰度图的标准差雾天显著低于晴天因散射降低对比若contrast 20需人工复查是否为薄雾误标。Laplacian variance衡量图像锐度值越低越模糊雾天应200大雨150若某张雾天图250基本是镜头脏污或对焦失败。3.2 手动诊断流程三步定位“坏图”并剔除即使标注正确也有拍摄质量问题。我建立了一套快速筛查流程Step 1批量计算Laplacian方差import cv2 import os import numpy as np def calc_lap_var(img_path): img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) return cv2.Laplacian(img, cv2.CV_64F).var() # 扫描整个fog目录 fog_dir dataset/train/fog low_sharpness [] for img_name in os.listdir(fog_dir): if img_name.lower().endswith((.jpg, .png)): var calc_lap_var(os.path.join(fog_dir, img_name)) if var 80: # 阈值根据经验设为80雾天正常下限 low_sharpness.append((img_name, var)) print(fFound {len(low_sharpness)} low-sharpness images in fog class) # 输出示例(043026cb-2808c6fb.jpg, 42.7)Step 2人工复核TOP10低锐度图打开这些图重点看是否严重运动模糊车速过快→ 删除是否镜头有水渍/油膜圆形高亮斑→ 删除是否逆光导致主体全黑→ 保留这是真实挑战Step 3检查重复文件MD5去重# Linux/macOS下一行命令 find dataset/train -name *.jpg -exec md5sum {} \; | sort | uniq -w32 -D | cut -d -f3-实测发现约0.3%重复同场景不同命名删掉即可。这步必须做——重复样本会让模型在验证集上虚高2~3个点。注意不要用os.listdir()顺序遍历有些相机按时间戳命名连续帧可能高度相似。show.py的随机抽样已规避此问题。4. 模型训练与评估从ResNet-18 baseline到ViT微调的完整链路4.1 ResNet-18 baseline训练为什么学习率要设为0.01而不是0.001ResNet-18在本数据集上的baseline性能是重要锚点。关键配置如下import torch import torch.nn as nn import torch.optim as optim from torchvision import models model models.resnet18(pretrainedTrue) # ImageNet预训练权重 model.fc nn.Sequential( nn.Dropout(0.5), nn.Linear(model.fc.in_features, 5) # 5分类 ) model model.cuda() criterion nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9, weight_decay1e-4) # 注意lr0.01 scheduler optim.lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) # 训练循环省略dataload部分 for epoch in range(30): model.train() for inputs, labels in train_loader: inputs, labels inputs.cuda(), labels.cuda() outputs model(inputs) loss criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() # 验证 model.eval() correct 0 total 0 with torch.no_grad(): for inputs, labels in test_loader: inputs, labels inputs.cuda(), labels.cuda() outputs model(inputs) _, predicted torch.max(outputs.data, 1) total labels.size(0) correct (predicted labels).sum().item() acc 100 * correct / total print(fEpoch {epoch1}, Test Acc: {acc:.2f}%) scheduler.step()为什么lr0.01ImageNet预训练权重在新任务上收敛快lr0.001会导致前10轮几乎不更新loss下降0.01实测lr0.01时第3轮loss从2.3降到1.1第7轮稳定在0.45±0.03若用lr0.001需60轮才能到同等loss且易陷入局部最优尤其在雾天类上准确率卡在72%不上升。Dropout位置放在fc层前而非整个网络因为ResNet-18的浅层特征边缘、纹理在天气分类中鲁棒性高过度dropout会削弱基础特征提取能力。4.2 ViT-B/16微调Patch Embedding层如何适配256×256输入ViT对输入尺寸敏感。原始ViT-B/16接受224×224但我们用256×256——必须修改Patch Embeddingfrom transformers import ViTModel vit ViTModel.from_pretrained(google/vit-base-patch16-224-in21k) # 修改position embedding以适配256x256 - 16x16 patches 256 patches vit.embeddings.position_embeddings torch.nn.Embedding(256 1, vit.config.hidden_size) # 1 for CLS token # 初始化新position embedding线性插值 old_pos vit.embeddings.position_embeddings.weight.data[1:] # skip CLS new_pos torch.nn.functional.interpolate(old_pos.unsqueeze(0).unsqueeze(0), size(256,), modelinear, align_cornersFalse) vit.embeddings.position_embeddings.weight.data[1:] new_pos.squeeze(0).squeeze(0) # 替换分类头 vit.classifier nn.Sequential( nn.LayerNorm(vit.config.hidden_size), nn.Linear(vit.config.hidden_size, 5) )关键点不要简单resizeposition embedding必须用interpolate保持空间关系ViT在本数据集上收敛慢需40轮但最终精度比ResNet-18高1.8%89.2% vs 87.4%尤其在雾天类上提升明显3.2%证明其对全局散射建模更强显存占用比ResNet-18高40%但可通过gradient_checkpointing缓解。4.3 评估指标选择为什么只看Top-1 Acc是危险的天气分类的业务场景要求雾天漏检把雾判成晴比晴天误检把晴判成雾更危险大雨错判为小雨可接受但大雨错判为晴天不可接受。因此必须看类别级Precision/Recall/F1而非整体Acc。用sklearn.metrics.classification_reportfrom sklearn.metrics import classification_report y_true, y_pred [], [] model.eval() with torch.no_grad(): for inputs, labels in test_loader: inputs, labels inputs.cuda(), labels.cuda() outputs model(inputs) _, preds torch.max(outputs, 1) y_true.extend(labels.cpu().numpy()) y_pred.extend(preds.cpu().numpy()) print(classification_report(y_true, y_pred, target_names[sunny, fog, light_rain, moderate_rain, heavy_rain]))重点关注fog行的recall雾天召回率和heavy_rain行的precision大雨精确率。Baseline ResNet-18的典型结果precision recall f1-score support sunny 0.92 0.94 0.93 712 fog 0.83 0.79 0.81 698 light_rain 0.88 0.91 0.89 728 moderate_rain 0.85 0.87 0.86 719 heavy_rain 0.86 0.84 0.85 708可见雾天是瓶颈recall仅0.79需针对性优化——比如在损失函数中给雾天样本加权或设计雾天专用注意力模块。5. 避坑指南五个让模型在测试集上突然掉点的真实陷阱5.1 现象训练Acc达95%测试Acc仅72%且验证Loss震荡剧烈原因transforms.RandomRotation角度设为degrees30导致道路倾斜后模型学到“倾斜雾天”的虚假相关性。真实雾天图像极少出现大角度倾斜交通监控固定视角。解决删除RandomRotation改用RandomAffine的translate和scale限制旋转角度为degrees0即禁用旋转。5.2 现象雾天类Precision高达92%但Recall仅65%大量雾天被判为“晴天”原因ColorJitter的saturation设为0.5过度饱和使雾天图像色彩失真模型依赖色偏而非纹理判断。雾的本质是明暗对比降低不是色彩变化。解决将saturation降至0.2或完全移除ColorJitter中的saturation参数只保留brightness和contrast。5.3 现象加载test/数据时ImageFolder报错IndexError: list index out of range原因test/目录下存在非图像文件如.DS_Store、Thumbs.dbImageFolder尝试将其作为图像打开失败。解决在test/各子目录中执行find . -name .* -deletemacOS/Linux或手动删除隐藏文件Windows用户用dir /a:h查隐藏文件。5.4 现象show.py显示某张图亮度为255但肉眼可见是正常雾天原因该图右下角有强反光车灯/路灯cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)计算均值时被异常像素主导。解决改用cv2.mean()计算ROI区域排除图像边缘10%或用np.percentile(gray, 95)取亮度95分位数代替均值。5.5 现象ViT微调时position_embeddings修改后训练Loss为nan原因interpolate后未重新初始化cls_token的position embedding导致CLS token位置向量为零向量Attention计算中出现除零。解决在interpolate后单独初始化CLS tokenvit.embeddings.position_embeddings.weight.data[0] torch.randn(vit.config.hidden_size) * 0.026. 进阶技巧用Grad-CAM定位模型“看哪里”揪出天气误判的根源6.1 Grad-CAM实现三步获取类激活热力图Grad-CAM不依赖模型结构只需获取最后一层卷积输出和梯度。以ResNet-18为例import torch import torch.nn.functional as F from PIL import Image import numpy as np def grad_cam(model, img_tensor, target_class, layer_namelayer4): model.eval() img_tensor img_tensor.unsqueeze(0).cuda() # [1,3,256,256] # 前向传播获取目标层输出 features None def hook_fn(module, input, output): nonlocal features features output target_layer getattr(model, layer_name) # resnet18的layer4是最后一个conv block hook target_layer.register_forward_hook(hook_fn) output model(img_tensor) hook.remove() # 计算目标类别的梯度 model.zero_grad() class_output output[0, target_class] class_output.backward() # 获取梯度和特征图 gradients target_layer._modules[1].conv2.weight.grad # layer4[1].conv2的梯度 pooled_gradients torch.mean(gradients, dim[0, 2, 3]) # [512] # 加权特征图 for i in range(features.shape[1]): features[:, i, :, :] * pooled_gradients[i] heatmap torch.mean(features, dim1).squeeze() # [256,256] # ReLU 归一化 heatmap F.relu(heatmap) heatmap heatmap.cpu().numpy() heatmap np.maximum(heatmap, 0) heatmap / np.max(heatmap) return heatmap # 使用示例可视化一张雾天图被模型认为是“雾天”的依据 img_path dataset/test/fog/747b40ee-dc2acc5c.jpg img_pil Image.open(img_path).convert(RGB) img_tensor test_transform(img_pil) # 用test_transform不增强 heatmap grad_cam(model, img_tensor, target_class1) # fog类ID16.2 热力图分析模板建立天气误判的归因表格对100张误判样本如雾天被判晴天做Grad-CAM统计热力图聚焦区域形成归因表误判类型热力图高频区域物理原因改进方案雾天→晴天车道线、路牌等高对比区域模型依赖清晰边缘忽略整体低对比度在Loss中加入全局对比度约束项小雨→晴天雨滴反光点车窗/路面模型将反光误认为晴天高光数据增强中添加transforms.RandomGrayscale(p0.3)降低色差干扰大雨→中雨路面水洼边缘水洼大小被当作雨量指标添加语义分割辅助任务强制模型理解水体面积提示热力图本身不解决误判但告诉你模型在“看什么”。比如发现雾天误判样本中87%的热力图集中在天空区域说明模型过度依赖天空亮度此时应裁剪掉天空区域再训练或加天空掩码损失。6.3 自动化误判分析流水线一键生成诊断报告我写了一个analyze_misclass.py脚本输入模型和测试集输出HTML报告python analyze_misclass.py \ --model_path best_model.pth \ --data_root dataset/test \ --output_dir report/ \ --top_k 50 # 分析前50个最难样本报告包含每类误判数量饼图误判样本的Grad-CAM热力图网格3×3各类别的亮度/对比度统计箱线图“雾天误判TOP10”原始图热力图标注框用OpenCV画出模型关注区域。从那以后我每次交付模型前都强制走一遍这个诊断流程——不是为了炫技而是避免把“模型在训练集上很准”当成“模型在真实路上可靠”。真实路况不会给你重来的机会而这份数据集的价值正在于它逼你直面那些训练时看不到的退化角落。希望帮到你。本文还有配套的精品资源点击获取