ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

卫星云图识别实战:PyTorch实现分类、回归与分割

卫星云图识别实战:PyTorch实现分类、回归与分割 简介这是一份面向计算机视觉课程的完整大作业项目以卫星云层图像的理解与识别为核心任务包含可直接运行的Python源码、说明文档、答辩PPT与实验报告。项目代码经测试可运行适合计算机、人工智能、电子信息等专业学生用于课程设计、毕设或初期项目演示也适合初学者对照学习图像分类与深度学习的完整流程。压缩包共140个文件大小62.33MB其中以70个Python脚本为主干另有44个编译生成的pyc文件、6个shell脚本、5个CSV数据文件、3个YAML配置及实验报告PDF与PPT等涵盖数据预处理、模型训练、测试评估和结果可视化等环节。已有239人浏览学习这是一份典型的课程作业与毕设参考资源。配套内容不仅包含模型测试CSV与结果图片还提供README说明和实验报告方便快速复现完整目录结构清晰下载后按文档指引即可运行和修改能够帮助理解卫星云图识别的数据划分、模型设计与结果分析全过程。1. 卫星云图“理解与识别”一门大作业的信息量藏在后半句拿到“卫星云层图像的理解与识别”这个题目别急着把它当成一个普通的图像分类任务。云图不是猫狗照片它没有清晰的物体边界层云、积云、卷云之间是渐变过渡同一片区域在不同时刻、不同波段下长得完全不一样。所以这个标题后半句的“理解与识别”才是真正的难点既要判断云的类型又要估计云量还要能逐像素标出云层范围。对计算机视觉课程大作业来说它最大的优点是数据公开、基线清晰、可以一路从图片分类做到语义分割最后还能用热力图把模型行为讲清楚正好把一份大作业做成能写进简历的项目经历。适合动手能力一般但想完整走一遍深度学习图像识别流程的同学。2. 任务和指标先立住云层图像识别不是只会贴标签2.1 把“理解”拆成三个子问题分类、云量回归与分割拿到这种题目第一件事不是下模型而是把“理解与识别”翻译成计算机视觉能做的具体任务。最常见的拆法是三个子问题一起上既能撑起大作业的工作量又不会因为目标太分散而失控。第一个是云图分类。给定一张云图判断它是晴空、卷云、积云还是层云本质是一个图像识别问题。第二个是云量回归。模型输出一个 0 到 1 之间的数值表示该区域被云覆盖的比例这是气象里非常实用的指标。第三个是云层分割。逐像素判断每个位置是云还是晴空对云层边缘不规则、分布零散的情况特别有效。这三个子问题不是并列独立的关系。我的做法是把它们做成一个共享主干、多输出的结构前面共用一个卷积网络提特征后面分叉出分类头、回归头和分割头。训练时三个损失一起回传好处是共享部分能学到云层的通用纹理和空间结构单个任务不容易过拟合。如果你只想用最少的代码跑通那就先做分类加云量回归二者共用同一个特征提取器改动量最小分割作为加分项放到后面补。2.2 公开数据怎么找、自建目录怎么组织卫星云图数据集不像 ImageNet 那么好找但也不是没有。常见的公开来源包括 Himawari-8/9 卫星的 L1B 数据、MODIS 云掩膜产品以及一些带标注的天气影像数据集。这些数据通常需要自己去官网申请或按波段拼接所以大作业里比较务实的做法是选一个已有的公开云图子集把它整理成自己的数据目录而不是真的去拉一整年的卫星原始数据。我一般这样组织数据目录data/ ├── train/ │ ├── clear/ # 晴空 │ ├── cirrus/ # 卷云 │ ├── cumulus/ # 积云 │ └── stratus/ # 层云 ├── val/ │ └── (same class folders) └── test/ └── (same class folders)分类任务可以直接用torchvision.datasets.ImageFolder读取省去写自定义 Dataset 的麻烦。分割任务则需要另外放一份像素级标注我习惯把标注做成 PNG 掩膜文件名与原始图像一一对应data/masks/train/ ├── 000001.png ├── 000002.png └── 000003.png掩膜里云层像素标为 255背景标为 0。这样训练时读取原图和掩膜计算逐像素损失就行。统计类别分布这一步千万别省。卫星云图里晴空和层云往往占大头积云、卷云样本偏少如果不做统计后面训练时类别不平衡会非常明显。2.3 先定评估指标再碰模型准确率不是唯一答案大作业答辩时被问得最多的问题就是“你这个模型到底好不好”如果你只回答准确率 90%老师追问一句“测试集里云量大的样本表现如何”就可能卡壳。所以我在动手之前就固定三个子任务各自的评估指标。分类任务我用top-1 accuracy加macro-F1。卫星云图类别不平衡准确率容易虚高macro-F1 对少数类更公平两个指标一起报更有说服力。云量回归任务用 MAE 和 R²MAE 衡量平均误差R² 衡量拟合程度。分割任务用 mIoU均交并比和 pixel accuracy其中 mIoU 是必须报的pixel accuracy 在云层占比很小的时候会虚高单独看容易误判。一个更关键的技巧是把所有指标写进实验记录表的同一行。每个模型配置对应一行包含准确率、macro-F1、MAE、mIoU 四个数字。这个习惯会在大作业后期写实验报告时救你一命因为你会发现很多模型“感觉差不多”只有把指标并排摆开才看得出哪个真正值得保留。3. 用PyTorch跑通分类与分割模型选型、数据加载到训练管线3.1 ResNet做分类、U-Net做分割为什么这两个是默认答案卫星云图大作业不是打比赛目标是稳定跑通、结果可解释、答辩讲得清楚。所以我不会选 ViT、Swin Transformer 这类大模型而是选验证成本最低的两个基线ResNet 和 U-Net。分类用 ResNet-18 或 ResNet-34。理由有三个预训练权重成熟训练收敛快网络结构教科书级写进报告好讲参数量适中普通笔记本的 GPU 就能跑。ResNet-18 的参数量约 1100 万在 224×224 输入下单卡训练很快。在云图这种纹理占主导、物体边界不清晰的数据上太深的网络收益很小还容易过拟合所以我一般从 ResNet-18 起步验证集不涨再换 ResNet-34。分割用 U-Net。它结构简单自带跳跃连接对小样本的遥感影像分割非常友好。DeepLab 系虽然精度更高但空洞卷积和 ASPP 模块调参空间大在大作业的时间预算里没必要。U-Net 的跳跃连接能保留云层边缘的细节这对云层分割特别重要因为云的边界往往是渐变的下采样太狠就找不回来了。回归任务不需要单独设计网络直接在 ResNet 的 pool 层后面接一个全连接层输出 1 个神经元sigmoid 激活压到 0 到 1 之间就是云量预测。这样三个任务共享一个编码器代码维护最省事。3.2 云图数据加载与预处理通道、尺寸与归一化云图的原始数据是多波段的通常包含可见光、短波红外、水汽等多个通道。大作业里最稳妥的做法是选三个通道合成一张伪彩色图按三通道 RGB 处理。常见组合是把可见光作为红通道、近红外作为绿通道、红外作为蓝通道让不同云型在颜色上有区分度。用三通道的好处是能直接用 ImageNet 预训练权重不用改模型第一层。预处理代码我这样写from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(p0.5), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])这段代码里有两个值得注意的地方。第一训练集用 RandomResizedCrop 而不是单纯 Resize这样能把云的局部纹理放大增加样本多样性。第二归一化直接沿用 ImageNet 的均值方差。虽然云图的像素分布和自然图像不一样但用统一的 ImageNet 统计值能让预训练权重更好迁移我只是在后续训练中让 BN 层自适应调整。尺寸上 224×224 是分类任务的常用值分割任务我会用 256×256 作为输入因为 U-Net 要求尺寸能被 2 的整数次幂整除256 是 2 的 8 次方下采样过程不会出现尺寸对齐错误。3.3 训练循环与关键参数损失函数、优化器与学习率策略这一节是整套代码的核心也是最容易出问题的地方。我先给出一个精简但完整的训练循环骨架再逐个解释参数。import torch import torch.nn as nn from torch.optim import AdamW from torch.optim.lr_scheduler import CosineAnnealingLR model init_model(num_classes4) # 分类回归共享主干 optimizer AdamW(model.parameters(), lr1e-4, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max50, eta_min1e-6) ce_loss nn.CrossEntropyLoss() mse_loss nn.MSELoss() for epoch in range(50): model.train() running_loss 0.0 for images, labels, cloud_fraction in train_loader: images images.to(device) labels labels.to(device) cloud_fraction cloud_fraction.to(device) logits, cloud_pred model(images) loss_cls ce_loss(logits, labels) loss_reg mse_loss(cloud_pred.squeeze(), cloud_fraction) loss loss_cls 0.5 * loss_reg optimizer.zero_grad() loss.backward() optimizer.step() running_loss loss.item() scheduler.step() val_acc, val_f1 evaluate(model, val_loader) print(fepoch {epoch} | loss {running_loss:.4f} | val_acc {val_acc:.4f} | val_f1 {val_f1:.4f})训练目的只在分类和回归时我用 0.5 作为回归损失的权重避免两个任务量纲差异导致回归任务喧宾夺主。分开训练分割模型时使用CrossEntropyLoss加DiceLoss的混合损失。单独 CE 在云层像素占比小的时候容易全判为背景叠加 Dice 损失等于把区域重叠度直接纳入优化目标能明显改善边缘细节。优化器默认 AdamW学习率 1e-4。预训练模型用过大学习率容易把已有的特征破坏掉1e-4 是我在云图这种非自然图像上反复试出来的安全值。调度器用余弦退火T_max设为总 epoch 数让学习率从 1e-4 平滑降到 1e-6后期收敛更稳。batch size 建议 16 起步。显存不够就降到 8但不要为了提速硬开 64。更大的 batch 在云图这种类别不平衡的数据上会让少数类的梯度被稀释。3.4 保存结果与实验日志让每次训练都能回放训练代码跑通只是第一步真正拉开差距的是实验管理习惯。每跑一次实验我至少保存三样东西模型权重、命令行参数、指标结果。权重文件命名带时间戳比如resnet18_cls_reg_epoch50_acc0.93.pth避免把不同配置的模型覆盖掉。指标记录我推荐用最朴素的 CSV 追加写入不引入额外工具import csv with open(exp_log.csv, a, newline) as f: writer csv.writer(f) writer.writerow([timestamp, model_name, epoch, lr, val_acc, val_f1, mae, mIoU])这个小文件就是你的实验日志。后期写报告时把 CSV 里每一行对应一段文字描述实验章节基本就成型了。比起截图一堆训练曲线这个表在答辩现场更实用老师问“你这个正则化加了有没有用”时你直接指着一行数据说“加了之后 mIoU 从 0.71 涨到 0.74”比任何口头解释都有说服力。4. 交付一套能直接跑的源码四件套目录、命令与文档4.1 源码目录把数据、模型、工具分开标题里写明了交付物是“python源码文档说明报告ppt实验报告”所以源码结构必须让人一眼看懂。我采用一个极少在课程作业里见到但工作里非常常见的组织方式cloud_understanding/ ├── README.md ├── requirements.txt ├── configs/ │ └── train.yaml ├── data/ │ ├── train/ │ └── masks/ ├── models/ │ ├── __init__.py │ ├── backbone.py # ResNet 分类回归头 │ └── unet.py # 分割网络 ├── utils/ │ ├── metrics.py # mIoU、macro-F1 │ ├── losses.py # DiceLoss │ └── logger.py # CSV 实验日志 ├── train.py ├── infer.py └── visualize.py每个文件职责单一看名字就知道是干什么的。train.py只管训练infer.py只管加载模型做推理visualize.py负责把预测结果画出来。初学者最容易犯的错误是把所有逻辑写在一个几百行的 python 文件里跑通了却没法解释。如果答辩时老师指着一个函数问它是干什么的你不需要翻到几百行以外去找答案这种组织方式本身就是文档。requirements.txt只需要列核心依赖torch、torchvision、opencv-python、numpy、pyyaml。版本号不锁死写最低版本就行方便在不同机器上复现。4.2 训练与推理的最小命令从源码到结果一个有说服力的大作业源码应该允许别人用一行命令从头跑完整个流程。我会把超参数放在 YAML 配置文件里命令行入口保持精简python train.py --config configs/train.yaml --task cls_reg python train.py --config configs/train.yaml --task seg训练脚本内部用yaml.safe_load读取配置把--task参数作为区分分类回归任务和分割任务的开关。这样一个入口同时支持两个子任务源码的可读性比复制两份训练脚本好很多。推理命令更加简单python infer.py --ckpt checkpoints/resnet18_cls_reg_epoch50_acc0.93.pth \ --image data/test/cloud_001.png \ --output output/infer.py做的事情是加载权重、预处理单张图片、前向传播、把分类结果和云量数值写到屏幕和 JSON 文件里。这一步的作用是让源码的最终产物可视化让使用者在没有训练条件的情况下也能验证模型是活的。4.3 文档说明、报告PPT、实验报告各写什么边跑边记文档说明对应 README是给陌生人看的使用指南。我写 README 只需要四块内容项目是什么、数据怎么放、训练和推理命令、目录结构说明。命令必须是能直接复制的不能是“大概是这样”的描述。一份合格的 README 应该让拿到源码的人在十分钟内把模型跑起来。报告 PPT 参照计算机视觉课程经典汇报结构走问题定义、数据展示、方法架构、实验对比、结论。每页最好只放一个核心信息实验对比那页放三到四组基线模型的数据表格不要贴满损失曲线。实验报告是把 README、PPT 和 CSV 实验日志串成完整叙事的地方。我的经验是实验报告必须回答三个问题为什么选这个模型、参数怎么调的、效果比基线好多少。这三个问题的答案全部应该来自你实际跑过的实验记录绝不能事后编。所以最优做法是边跑边记每次改一个参数就在 CSV 里加一行写报告时直接引用。5. 卫星云图识别避坑实录五个我踩过的坑与修正5.1 云量回归指标好看分割结果却是一团糊现象回归任务的 MAE 降到 0.03看着非常优秀但把模型的注意力可视化后发现它只是记住了整张图的平均亮度完全没捕捉到云的纹理和位置。分割模型的 mIoU 也只有 0.4 左右。原因云量回归是一个全局平均意义上的任务模型只要输出一个整体比例就能把损失降到很低不需要理解云的局部结构。单独训练回归头时网络很容易走捷径学到“亮度高则云量多”这种粗糙映射。解决把回归和分割联合训练或者在回归 head 之前从分割解码器接入特征。如果只想做回归就额外加一个辅助分类任务强制网络提取类别层面的特征回归分支只在共享特征上做预测。5.2 训练验证准确率90%换一张云图就翻车现象训练集和验证集准确率都超过 90%自我感觉良好。结果从测试集里随机抽一张没见过的云图模型直接判错而且错得很离谱。原因数据划分的时候我只用了 random split同一颗卫星、同一天拍摄的相邻云图被同时分到了训练集和验证集。云图相邻帧之间纹理高度相似验证集成了“记忆测试”而不是“泛化测试”。解决划分数据要按时间切片按天或者按小时块划分。例如前 80% 的时间切片进训练集后 20% 进验证集保证任何一张验证图在时间上都不与训练图相邻。这个操作对遥感类数据极其重要大作业里加了时间划分类别后验证准确率会掉到 70% 左右但这才是模型真实能力的反映。5.3 模型把整张图判成晴空类别不平衡的连锁反应现象训练 Loss 下降很顺利但分割结果几乎全黑也就是所有像素都被判为背景。mIoU 里云层那一类的 IoU 接近 0。原因云层在大多数云图里只占 20% 到 30% 的像素。模型发现把所有像素都判为晴空损失依然很低因为 70% 以上的像素本来就是背景。优化器当然选择最简单的路。解决先用 DiceLoss 替换单纯的 CE Loss。然后统计训练集里云层像素的占比给损失函数加权重。经验做法是把背景权重设为云层权重的 0.3 到 0.5 倍让模型在粗粒度上先学会哪儿有云再通过微调修正边缘细节。这个操作做一次下来云层的 IoU 通常能从 0.1 级别跳到 0.6 级别。5.4 ImageNet预训练权重在云图上不灵迁移学习也要分领域现象加载了 ImageNet 预训练权重训练 50 个 epoch分类准确率卡在 60% 上下怎么调学习率都上不去。原因ImageNet 学到的特征是物体边缘、轮廓、颜色渐变自然图像占据画面中心的物体有明确边界。云图是完全不规则的纹理场没有“主体物体”的概念全局纹理统计才是关键。预训练权重的前几层卷积核和云图分布差异太大。解决一个有效的方法是加载预训练权重后把前几层卷积的参数也设置为可训练不要冻结。我试过冻结前两层的效果明显更差。另外训练时对云图使用更强的基础增广比如随机旋转 90 度、多尺度缩放让模型摆脱自然图像的“上方”先验。这类增广在自然图像上会损坏语义在云图上却非常合适因为云没有“头朝上”的概念。5.5 实验报告里的曲线和提交代码对不上版本管理的血泪经验现象写实验报告时打开训练曲线截图发现模型结构和最后提交的代码对不上某个层的参数名不同损失曲线复现不出来。原因训练中途改了模型结构没有重新跑完整实验或者保存的权重文件被覆盖原配置丢失。大作业时间线拉长以后这个坑几乎是必然踩的。解决现在我的习惯是在训练脚本里把模型的state_dict结构和代码仓库的 commit 信息写在一起。每次训练启动时自动把model.__class__.__name__、所有超参数和当前代码的 git commit hash 记录到实验日志里。这样任何时候翻出实验结果都能找到对应的代码版本。就算做不了 git至少也要在 CSV 里加一列写清楚config文件名。6. 用CAM热力图给答辩报告做增色收盘小技巧模型跑通、指标合格大作业还剩最后一件事证明模型不是黑匣子。这一步我用 Grad-CAM 做类激活热力图把“模型为什么这么判断”直观地画出来放进实验报告和 PPT 里会非常加分。import cv2 import torch from torchvision import transforms def grad_cam(model, image_tensor, target_class): model.eval() features {} def hook_fn(module, input, output): features[activation] output target_layer model.layer4[-1] hook target_layer.register_forward_hook(hook_fn) logits model(image_tensor) model.zero_grad() score logits[0, target_class] score.backward() grads target_layer.weight.grad # 简化示意实际需取 activation 梯度 hook.remove() return features[activation], grads实际使用时用torch.autograd.grad取最后一个卷积层输出的梯度按通道求平均得到权重再和激活图加权求和最后过 ReLU 和归一化生成热力图。把热力图叠加到原始云图上半透明渲染就能看到模型判断这张图是积云时主要依据的是画面右下角的高亮纹理区域而不是整张图的平均亮度。这个操作在大作业里有三个直接收益。第一它在答辩现场比任何指标都直观老师看到热力图聚焦在云的边缘和纹理区会立刻认可模型学到了语义特征。第二它能反向检验模型是否在走捷径如果热力图集中在图像角落的水印上你就要警惕数据集有问题。第三它让实验报告从“我跑了一个模型”升级为“我理解了这个模型的行为”。我现在的习惯是拿到任何一张云图先跑一次可视化看一眼热力图再决定要不要继续调参。这个习惯帮我在这类数据上避开了很多反复训练却毫无进展的弯路。希望帮到你。本文还有配套的精品资源点击获取
返回列表