
简介这是一份面向图像分类任务的恶劣天气数据集覆盖雾、暴雨、沙尘暴、暴雪四类天气场景共约 1000 张已标注图片适合气象监测、智能驾驶、安防监控等场景下的天气识别模型训练与算法验证。压缩包内含 1030 个文件以 1028 张 jpg 图片为主体另附 1 个 Python 可视化脚本和 1 个 json 类别配置文件整体大小 132.04MB。文件按四个类别分别存放并已划分训练集与测试集目录结构清晰可直接投入深度学习项目使用json 文件记录具体类别标签便于加载和检查配套的 show 脚本则能快速预览数据分布。对于入门级研究者和开发者而言该数据集规模适中、标注完整既可用于快速搭建图像分类基线也可作为数据增强、迁移学习和模型改进的实验基础。目前已有 668 人学习下载是天气识别相关项目起步时一份实用的已标注小规模数据集。1. 恶劣天气图像分类数据集一千张已标注图能做什么做自动驾驶感知的工程师最怕的就是这一摞“恶劣天气图像分类数据集”——雾、暴雨、沙尘暴、暴雪每一类都在逼模型做判断题这条路现在能不能看清。手里这约1000张已标注的图像量不大但它是验证分类模型鲁棒性、做小样本图像分类 1-shot/5-shot 实验、以及给下游检测模型做预筛的最轻量素材。它解决的核心问题不是“训出一个量产模型”而是“用最少的数据快速判断我的分类架构在恶劣天气下有没有区分度”。适合两类人一是刚接触图像分类的学生拿它跑通从数据拆到训练评估的全流程二是在做自动驾驶或户外监控的工程师需要一个小而干净的基准来评估模型的退化边界。2. 先看清这张牌目录结构、标签体系与数量分布拿到数据集的第一件事不是训练而是把所有图像看一遍。约1000张已标注的数据你首先要确认四个变量每类多少张、标签是目录名还是CSV、图像尺寸是否一致、有没有混入损坏或误标的文件。这四个变量直接决定后面拆分脚本和训练参数怎么写。2.1 目录结构与数量统计用命令行摸清底细先看目录怎么组织再统计数量最省时间的是几条命令直接跑完# 打印两级目录确认标签是按目录名还是CSV管理 find . -maxdepth 2 -type d | sort # 统计每个子目录下的图片数量确认四类分布是否均衡 for d in */; do echo -n $d: find $d -type f \( -name *.jpg -o -name *.png -o -name *.jpeg \) | wc -l done # 统计图片总体数量与总体积 find . -type f \( -name *.jpg -o -name *.png -o -name *.jpeg \) | wc -l du -sh .第一段命令看标签组织方式如果一级目录就叫 fog/rain/dust/snow说明标签在目录名里后面直接用 ImageFolder 加载即可如果是一堆扁平图片加一个 labels.csv就要回到 CSV 里做路径到标签的映射。第二段是统计每类图片数量这里能直接看出类别不均衡的严重程度。第三段看总体数量与体积约1000张、每张几百KB到几MB完全可以整批放进内存做实验不必考虑分批加载。从命令输出里你可以做两个判断。一是看最小类的数量如果最小类只有五十张后面要按 5-shot 甚至 1-shot 的粒度来对待它而不是当普通四分类任务来硬训。二是看目录里有没有 normal 或 clear 这类负类目录有负类时分类任务从“四选一”变成“五选一”类间边界完全不同。这里还要把任务的定位说清楚标题写的是“图像分类”不是“目标检测”——如果你之后要做车辆检测这1000张也只能拿来做检测模型的天气预筛因为检测需要物体级别的标注框而图像分类只有场景级的天气标签。2.2 标签体系边界四类天气还是“有雾有雨”的多标签图像分类数据集的标签体系主要分两种一种是把天气做成互斥的单标签一个场景只能属于雾、雨、尘、雪之一另一种是允许一个场景同时有雾和雨的多标签。这类公开数据集大多是单标签组织因为简单、评估快但实际图像里雨和雾常常同时出现低视距下模糊感和雨丝是叠加的。我会做一个验证动作随机抽查每类若干张图看有没有一张图同时具备两种天气的视觉特征。如果有训练时归到单一标签就会制造误标噪声模型在雨、雾上的召回率都会上不去。下表是三种标签方案在这个数据集上的取舍标签方案适合的任务这1000张上的代价单标签四分类雾/雨/尘/雪快速跑通小样本分类流程类间边界模糊雾天雨天易互相误判加一个“正常天气”负类更贴近真实巡检与驾驶场景需要额外找正常天气图1000张分布更紧多标签分类每类一个二分类器处理“雾雨同时存在”需要改损失函数和评估指标开发量最大我的经验是数据量只有1000张时不要急着做多标签。先把单标签四分类跑通保留混淆矩阵如果雾和雨的互相误判率超过 25%说明视觉特征本身纠缠此时再考虑把这两类合并或者改成多标签。这个判断可以用数字说话不需要凭感觉赌。标签是目录名还是 CSV 也要在这个阶段定下来目录名组织直接省事CSV 组织则要写一个加载器但好处是可以同时保存图片路径、拍摄时间、来源等额外字段后面做去重和事件切分时用得上。2.3 图像尺寸与颜色模式的差异用脚本批量检查天气数据集经常混入分辨率不一的图我来写个脚本统一检查一下尺寸、颜色模式和坏图from PIL import Image from pathlib import Path from collections import Counter root Path(dataset) size_counter Counter() mode_counter Counter() invalid_files [] for suffix in (*.jpg, *.jpeg, *.png): for img_path in root.rglob(suffix): try: with Image.open(img_path) as img: size_counter[img.size] 1 mode_counter[img.mode] 1 except Exception: invalid_files.append(str(img_path)) print(尺寸分布:, size_counter.most_common(10)) print(颜色模式分布:, mode_counter) print(打不开的文件数:, len(invalid_files)) print(invalid_files[:5])尺寸分布要特别注意很多公开来源的图像有从 320x240 监控截图到 1920x1080 手机照片的跨度训练时统一 Resize 就行但隐藏的问题是比例差异。如果绝大多数是横屏监控图个别竖屏手机图混进来模型会把“横竖”当成天气特征的一部分。颜色模式分布则是检查是否有灰度图混入某些夜间暴雨图像在压缩后可能变成单通道而预训练模型的三通道输入会把它复制成三个相同通道等于引入一个假特征。这类检查的结论会直接影响后续处理方式如果尺寸分布集中在 800x600 到 1920x1080 之间Resize 到 256 再裁剪是安全的如果出现大量 320x240 的低分辨率图就要先做一次锐化或对比度拉伸否则模型学到的可能只是马赛克感不是你想要的天气特征。打不开的文件直接按清单剔除不用犹豫。3. 把1000张图拆成可训练的数据集按类拆分、增强与不均衡处理数据清理完之后接下来就是拆分和预处理。约1000张数据在规模上属于标准的小样本图像分类任务拆分策略和增强参数直接决定了这个任务到底像 1-shot 还是像普通的少样本分类。下面是我的做法。3.1 按类拆分脚本用分层切分避免训练集漏类很多人一把梭地把所有图片 shuffle 后按 75/15/15 切这是最典型的一个翻车点。逐类切分才能真正让每个天气都进训练集、验证集和测试集。脚本如下import random import shutil from pathlib import Path from collections import defaultdict random.seed(42) source_dir Path(dataset) train_dir Path(split/train) val_dir Path(split/val) test_dir Path(split/test) # 按类收集所有图片路径标签取目录路径的倒数第二级 class_to_files defaultdict(list) for suffix in (*.jpg, *.jpeg, *.png): for img_path in source_dir.rglob(suffix): label img_path.parts[-2] # 上级目录名作为标签 class_to_files[label].append(img_path) # 逐类按比例拆分保证每个类都出现在三个集合里 for label, files in class_to_files.items(): random.shuffle(files) n_val int(len(files) * 0.15) n_test int(len(files) * 0.15) n_train len(files) - n_val - n_test for i, img_path in enumerate(files): if i n_val: dest val_dir / label elif i n_val n_test: dest test_dir / label else: dest train_dir / label dest.mkdir(parentsTrue, exist_okTrue) shutil.copy2(img_path, dest / img_path.name) print(f{label}: train{n_train}, val{n_val}, test{n_test})这里核心是“逐类切分”而不是全局切分。如果拿1000张图全体 shuffle 按 75/15/15 切很可能出现暴雨图占了大部分、沙尘暴一张都没进验证集的情况那验证集的准确率就完全被多数类绑架了。逐类切分保证最小类哪怕只有50张也会预留7张做验证、7张做测试——虽然很少但那才是它真实的模型表现。参数说明这里的 0.15 表示 15% 图片进验证集、15% 进测试集。数据量达到1000张时验证集约有150张比例合理如果最小类只有50张15% 只有7张此时可以把比例改为 0.1守住最少测试样本这条底线。random.seed(42)必须固定不然每次跑脚本划分不同后面几次实验就不好做横向对比了。复制而不是移动原图也是个小习惯如果训练过程中发现某张图是坏图还能回原目录核查相当于留了一手后悔药。3.2 图像增强参数怎么设别让增强把“天气特征”洗没了小样本图像分类的常见误区是增强越花越好但在天气数据集上雨丝、雾的散射、沙尘的黄调都是重要的判别特征。增强的选择原则是“保全局结构抖局部细节”。我用的组合如下import torchvision.transforms as T # ImageNet 预训练模型的三通道均值与方差 IMAGENET_MEAN [0.485, 0.456, 0.406] IMAGENET_STD [0.229, 0.224, 0.225] # 训练集轻度几何扰动 适度的颜色抖动 train_transform T.Compose([ T.Resize((256, 256)), T.RandomCrop(224), T.RandomHorizontalFlip(p0.5), T.RandomRotation(degrees10), T.ColorJitter(brightness0.2, contrast0.2, saturation0.1, hue0.02), T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD), ]) # 验证集与测试集只做 Resize 和标准化不做随机增强 eval_transform T.Compose([ T.Resize((224, 224)), T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD), ])这几个参数是我试过之后比较稳的组合。Resize 到 256 再 RandomCrop 到 224是 ResNet 系的标准尺寸同时多出来的 32 像素能让随机裁剪产生一定的平移增广RandomRotation 的 10 度要克制因为监控图像本身有地平线的强先验转太多会让模型把“歪”当天气特征。ColorJitter 里亮度 0.2、对比度 0.2 是安全的但饱和度只敢给 0.1——沙尘暴靠黄褐色调区分饱和度抖动太大会把这个特征洗掉。hue 给 0.02 几乎不动原因一样。特别注意不要给雾天、沙尘暴图片加高斯模糊增强因为图像本身就带大气散射的模糊再加模糊会让模型更分不清“雾”和“雨天”。这条路我走过一次增强后的训练准确率一直上不去去掉模糊后马上恢复属于这个数据集特有的坑。做完增强后用 torchvision 的 ImageFolder 加载即可from torchvision.datasets import ImageFolder train_dataset ImageFolder(split/train, transformtrain_transform) val_dataset ImageFolder(split/val, transformeval_transform) test_dataset ImageFolder(split/test, transformeval_transform)ImageFolder 会按照子目录名自动生成 class_to_idx 映射默认按字母排序。加载后记得打印train_dataset.classes确认 fog、rain、dust、snow 的索引顺序避免后面看混淆矩阵时对不上号。3.3 类别不均衡与1-shot场景加权采样与Focal Loss在这种数据集里暴雨和雾天往往占了大部分沙尘暴、暴雪可能只有几十张后者就逼近 1-shot/5-shot 的实验设置。我的处理分两条路第一条是加权采样from torch.utils.data import DataLoader, WeightedRandomSampler class_names sorted(class_to_files.keys()) # 按每类样本数算权重样本少的类权重更高 class_weights {c: 1.0 / len(class_to_files[c]) for c in class_names} # 把每个样本的权重展开成和样本数一样长的列表 sample_weights [] for c in class_names: sample_weights.extend([class_weights[c]] * len(class_to_files[c])) sampler WeightedRandomSampler( sample_weights, num_sampleslen(sample_weights), replacementTrue, ) train_loader DataLoader( train_dataset, batch_size16, samplersampler, )WeightedRandomSampler 会让小类别被更频繁地采样到缓解少数类欠拟合。代价是同一张沙尘暴图可能在一轮里反复出现模型更容易过拟合到该类的个别样本上因此小类要配合早停或更大的 weight_decay。逻辑说明一下class_weights用样本数的倒数样本量 500 的暴雨类权重是 0.002样本量 80 的沙尘暴权重是 0.0125后者被采样概率是前者的六倍多但代价是整体 epoch 里多数类见到的独立样本变少了。第二条路是改损失函数。用 CrossEntropyLoss 的weight参数也能达到类似效果但不改变采样分布多数类每一轮还是能看到全部样本只是损失被压低。对小样本场景我一般优先用加权采样因为它的作用更直接。如果最小类只有十几张就直接把问题声明为 1-shot/5-shot 小样本实验不硬训几十轮而是用 ResNet50 的池化层特征配最近邻分类先看骨架效果再做微调。4. 用ResNet50做小样本微调分类头调整、训练循环与评估指标现在进入真正动手的环节。用 ResNet50 做小样本图像分类的整个过程可以拆成三步加载预训练模型并决定冻结范围、配好优化器和训练循环、用混淆矩阵做评估。每一步都有明确的参数选择逻辑不靠玄学。4.1 预训练模型与分类头调整冻结哪些层输出类别怎么改ResNet50 在 ImageNet 上预训练学到的是通用视觉特征包括边缘、纹理、形状这对自然场景的天气图像依然有效。小样本场景下直接全量微调训练集只有几百张图很容易过拟合。常见做法是冻结大部分层只解冻高层语义层和分类头。import torchvision.models as models # 使用 ImageNet 预训练权重 model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) # 替换最后一层全连接输出四类天气 num_classes 4 model.fc torch.nn.Linear(model.fc.in_features, num_classes) # 先冻结全部参数再按需解冻 for param in model.parameters(): param.requires_grad False # 解冻 layer4 和 fc让高层语义特征适配天气场景 for param in model.layer4.parameters(): param.requires_grad True for param in model.fc.parameters(): param.requires_grad True为什么只解冻到 layer4因为 layer1 到 layer3 学到的是边缘、色彩、纹理这些底层视觉特征在自然图像和天气图像之间是通用的重学它们只会浪费本就紧张的样本layer4 更贴近语义“这是什么场景”的判断逻辑需要按数据重新调整所以要解冻。分类头是随机初始化的必须要求梯度。如果训练集不到500张还想再保守一点可以把解冻范围缩小到只解冻model.layer4[2]即最后一个残差块。这里分类头的调整方式对所有预训练模型通用ResNet 是替换fc层ViT 则是把 CLS token 后的 Linear 输出维度改成类别数核心思想都是保留预训练特征空间只重训输出层。4.2 训练循环与关键参数lr、weight_decay、batch_size怎么配训练循环本身不复杂复杂的是参数怎么选。先看最小可跑通的版本import torch from torch.optim import AdamW device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 只把 requires_gradTrue 的参数交给优化器 optimizer AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr1e-4, weight_decay0.01, ) criterion torch.nn.CrossEntropyLoss() num_epochs 30 for epoch in range(num_epochs): model.train() total_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() avg_loss total_loss / len(train_loader) print(fepoch {epoch 1}/{num_epochs}, loss{avg_loss:.4f})参数说明lr1e-4是冻结了大部分层后比较稳的起点1e-3太大在预训练特征上横冲直撞验证损失很容易反弹如果训练损失降得很慢可以在1e-4到3e-4之间微调。weight_decay0.01是 AdamW 的常见设置对随机初始化的分类头有约束作用。batch_size16在 ResNet50 上显存占用约 2-4GB可以放大到 32但低于 8 时 BN 层的归一化统计量会不稳定梯度噪声变大在小样本下尤其明显宁可减小输入尺寸也不要让 batch_size 跌破 8。num_epochs30是1000张规模的典型值观察训练损失是否平滑下跌如果前 5 个 epoch 就降到接近 0说明过拟合来了回头检查增强强度或增加 weight_decay。一个实用习惯每跑完一个 epoch 就在验证集上算一次准确率把结果打印出来。不要在训练全结束后才第一次看验证集——一旦发现验证损失在第 10 轮开始反弹就能立刻停掉省下后面 20 轮的时间。4.3 评估不止看准确率混淆矩阵与逐类recall小样本分类很容易被整体准确率欺骗。模型如果全猜“暴雨”在暴雨占 400 张的数据集上也有 40% 的准确率看起来不错实际雾天一张都没分出来。所以评估必须看混淆矩阵和逐类指标from sklearn.metrics import confusion_matrix, classification_report model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in test_loader: images images.to(device) logits model(images) preds logits.argmax(dim1) all_preds.extend(preds.cpu().tolist()) all_labels.extend(labels.tolist()) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵:) print(cm) report classification_report( all_labels, all_preds, target_names[fog, rain, dust, snow], ) print(report)这个脚本输出的混淆矩阵是逐类的“实际为 A 类、预测为 B 类”的交叉表对角线越高越好。classification_report会给出每类的 precision、recall 和 f1-score。对最小类recall 尤其重要沙尘暴只有几十张测试图时recall0.6 意味着有 40% 的沙尘暴被漏掉直接说明模型没抓住这个类的核心特征。更严格一点的做法用三个不同的随机种子各跑一遍完整流程记录三次逐类 recall 的波动范围。1000 张数据划分一次的结果可能有多达 5% 的方差如果某类 recall 从 0.7 掉到 0.4说明这个类上模型的分类能力不稳定后面对这个类要做专门处理比如补数据或合并类别。5. 避坑排查这一千张数据的五个坑从误标到假高分1000 张的规模做图像分类最大的风险不是模型不够强而是数据本身的坑让结果不可信。下面五条每一条都是实打实踩过的。5.1 雾天与暴雨的边界模糊合并类别做消融现象训练后看混淆矩阵雾天和暴雨互相误判率极高尤其是带水汽的场景模型几乎在“猜”。原因很多图像采集自公开网络“雾天”图像其实是在雨后拍的空气中的水汽让视觉表现接近雾天标签边界本身就不客观。解决先跑一个“雾雨 vs 沙尘暴暴雪”的二分类消融看模型能否分大类再回到四分类任务对比两种设置下的准确率差距决定要不要把雾和雨合并成一个“低能见度降水”大类。用消融结果来决策不要凭感觉分。5.2 多数类绑架准确率加权采样与macro F1现象训练后打印整体准确率接近 90%但逐类 recall 里沙尘暴和暴雪只有 0.3。原因数据里暴雨、雾天占了接近 70%模型倾向预测多数类整体准确率被多数类抬高。解决评估时用 macro F1对每类的 F1 取平均而不是 accuracy训练时用 WeightedRandomSampler 或给损失函数加权把多数类的采样率降下来。这两步配合比单独做任何一步都有效因为我实测只加权不换指标时看结果仍然被整体准确率迷惑换指标才发现小类已经崩了。5.3 增强过头把天气特征洗没了弱增强先做baseline现象训练损失一直在降验证集准确率反而波动大而且不同 seed 的结果差异明显。原因增强太激进比如用了 RandomRotation(45) 或 ColorJitter 饱和度 0.5导致本就不充裕的类间特征被随机抖动掩盖。解决先关掉所有随机增强只保留 Resize Normalize 跑一个 baseline确认数据本身可分再逐步打开增强观察验证集变化。我自己的习惯是先把 baseline 的验证准确率记录在案之后每加一个增强算子都对比一次只有能提升验证集的增强才保留。5.4 误标与平台水印人工抽查每个类别的代表图现象某个类 recall 特别低查训练图发现这个类下混入了无关图像或带着社交平台二次压缩的水印。原因这类天气图像数据多从公开图库抓取存在个别图像实际不是对应天气、或者带 Logo 的情况。解决人工抽查1000 张图在屏幕上能在一个小时内看完别把时间全省在自动化上。我不会做复杂的可视化工具直接写个脚本把每类前 10 张图拼成网格用 matplotlib 或 OpenCV 显示肉眼过一遍重点看三类问题水印、重复图、完全不对题的图。发现误标就移动到对应类别或直接剔除不要硬留着训练。5.5 同源图导致验证假高分图像hash去重现象训练集和验证集准确率都高得离谱但拿到新的恶劣天气视频上测试时大幅退化。原因数据集中可能存在同一事件连续帧或同源图片同时出现在训练集和验证集里验证学到的是“重复图像”而不是“泛化特征”。解决用感知哈希对全部图片去重两张图片的汉明距离小于阈值时只保留一张。具体做法是计算每张图的 dHash汉明距离小于等于 10 的视为同一来源手动保留其中一张并按 3.1 的脚本重新拆分。如果数据本身带拍摄时间或来源字段更合理的做法是按事件分组切分保证同一事件的图像不会同时出现在训练和验证里。6. 让1000张数据用出更大价值基线对比与场景迁移6.1 与最新图像分类模型做对比ViT在小样本下的取舍ResNet50 跑通后我会再找一两个最新图像分类模型做对照基线比如 ViT 或 Swin。小样本下 ViT 的强注意力机制在数据量少时容易过拟合常见做法是保留预训练特征、只替换最后的分类头并用更低的学习率比如 5e-5做微调。分类头的调整方式和 ResNet 本质相同ViT 的 CLS token 出来接一个 Linear把输出维度换成四类。如果 ViT 在这个千张数据集上的效果低于 ResNet50那不是模型不行而是小样本下卷积归纳偏置更占优这个结论本身就能写进方案文档比盲目追新模型更有说服力。6.2 从混淆矩阵里读场景关系雾天和暴雨该不该合并混淆矩阵不只是评估工具也是设计标签体系的依据。如果雾和雨的误判率双向都高就把它们合并成“低可见度”类再在更粗的粒度上做任务。这个判断可以直接指导自动驾驶场景里的“能见度分类”落地——下游系统真正关心的往往是“当前可见度属于哪一档”而不是“这到底是雾还是雨”。用混淆矩阵里的数字决定标签粒度比死守四个标签更重要。6.3 从“分类”到“检测”的迁移路径1000 张天气分类数据的最终价值不只在分类任务本身。一种务实用法是把预训练后的分类模型当作特征抽取器用它的倒数第二层特征去初始化一个小型检测器另一种是把分类结果当作天气先验筛选器在带天气标签的自动驾驶数据集比如 BDD100K 这类常用公共数据集上统计天气分布选出雾天、雨天子集用于下游检测或分割模型的评估。这样千张级别的分类数据集就能为自动驾驶感知提供一块“天气分布地图”而不只是孤立地跑一个分类模型。如果你想走 YOLO 这类检测器路线也可以用本数据集的分类模型先做候选帧筛选再喂给检测器能省掉大量无效标注。我自己犯过的错误是只盯着整体准确率把雾天和暴雨互相混淆的模型交付给下游做能见度判定结果在桥上遇到雨雾混合天气时系统误判为“正常”那次之后我养成了习惯先看混淆矩阵再决定要不要交付。希望帮到你。本文还有配套的精品资源点击获取