
简介这份基于Python的卫星云层图像理解与识别资源面向高校课程设计、毕业设计及计算机视觉初学者围绕卫星云层图像的语义分割与识别任务展开。内容兼顾深度学习方法与传统图像处理路线一方面在U-Net网络框架上做改进另一方面探索图像预处理、分割与预测的传统方案适合希望对比不同技术路径、完成完整实验报告的读者。压缩包共148个文件包含70个Python源码、配套pyc编译文件、12个png示例图像、模型测试CSV数据及YAML配置并附有课程报告docx和答辩PPT整体约35MB目录组织便于直接运行与复现。目前已有310人学习下载。借助源码、实验数据与报告文档读者可快速理解模型设计思路、训练测试流程和结果分析要点也可作为同类遥感图像识别课程设计或论文写作的参考基准。1. 卫星云层图像的识别为什么值得亲手拆一遍去年有段时间我在做气象数据相关的可视化项目发现业务方对云层识别的需求并不是单纯“判断有没有云”而是要从卫星云图中分割出云团位置、估计覆盖率甚至帮助判断降水趋势。当时搜了很多资料发现大部分开源方案要么只做分类要么依赖昂贵的标注数据。后来看到一个课程设计项目里面同时用了改进的 U-Net 和传统图像处理两套方法去解同一个问题这让我的思路一下子开阔了。对于程序员来说卫星云图这种高噪声、多尺度的遥感图像既适合练深度学习也适合找回传统的灰度分割手感。这篇文章我会把这两条路线从原理到代码、从参数到结果分析完整拆一遍内容基于实际可运行的工程包适合想做场景理解、图像分割或者课程设计的 Python 开发者阅读。2. 基于U-Net的改进模型从编码器到注意力融合2.1 为什么选U-Net而不是Faster R-CNN或DeepLab云图识别本质上是一个密集预测任务和普通的物体检测不同。你不太关注“哪朵云属于哪类”而更关注“云团的边缘在哪儿、覆盖了哪些像素”。Faster R-CNN 这类检测模型输出的是边界框在云图这种边缘不规则的场景下会丢失大量细节。DeepLab 的空洞卷积虽然在语义分割上表现优秀但它的解码器恢复边缘的能力相对普通而且参数量大在小尺寸遥感图上容易过拟合。U-Net 之所以适合云图是因为它有一个对称的编码器-解码器结构。编码器逐步下采样提取高层语义解码器逐步上采样恢复空间分辨率中间的跳跃连接能把浅层边缘特征直接传给解码器。这种结构在医疗图像、卫星地图、工业缺陷检测里被反复验证过在样本量不大的情况下也比很多 Transformer 类模型更容易收敛。课程设计里选择 U-Net 作为基础而不是开局就上大型模型这正是遥感图像工程落地时的一种务实考虑。2.2 改进点设计残差卷积块与空间注意力门控原始 U-Net 的卷积块是“两次卷积 ReLU”的简单堆叠但云图噪声多简单堆叠容易在浅层激活很多无效响应。我在做类似项目时会做两处改造第一处是把每次卷积换成残差卷积块让梯度可以绕过卷积核直接传到前层第二处是在跳跃连接处加一个空间注意力门控让模型自动忽略云图中大量的海洋和地表背景把注意力集中在云层区域。残差块的结构是3×3 卷积 → 批归一化 → ReLU → 3×3 卷积 → 批归一化然后再与输入相加最后再过一个 ReLU。注意力门控的思路是把解码器的高层特征和编码器的浅层特征分别用 1×1 卷积调整通道数相加后经过 sigmoid 得到一个空间权重再用这个权重乘回浅层特征。这样模型在融合特征的时候可以动态调整每个像素的重要性。2.3 基于PyTorch的模型定义与关键代码下面给出一个可以直接复用的改进 U-Net 核心代码段。我使用 PyTorch 2.x 编写去掉了无关的模块定义只保留骨干逻辑。import torch import torch.nn as nn class ConvBlock(nn.Module): 残差卷积块两次卷积后与输入相加输出保持相同通道数 def __init__(self, in_ch, out_ch): super().__init__() self.conv1 nn.Conv2d(in_ch, out_ch, 3, padding1) self.bn1 nn.BatchNorm2d(out_ch) self.conv2 nn.Conv2d(out_ch, out_ch, 3, padding1) self.bn2 nn.BatchNorm2d(out_ch) self.shortcut nn.Conv2d(in_ch, out_ch, 1) if in_ch ! out_ch else nn.Identity() self.relu nn.ReLU(inplaceTrue) def forward(self, x): identity self.shortcut(x) out self.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) return self.relu(out identity) class AttentionGate(nn.Module): 空间注意力门控根据解码器特征调整编码器特征的权重 def __init__(self, in_ch, skip_ch, out_ch): super().__init__() self.w_g nn.Conv2d(in_ch, out_ch, 1) self.w_x nn.Conv2d(skip_ch, out_ch, 1) self.psi nn.Conv2d(out_ch, 1, 1) self.sigmoid nn.Sigmoid() def forward(self, g, x): # g 来自解码器上采样后的特征 x 来自编码器跳跃连接 g1 self.w_g(g) x1 self.w_x(x) attn self.sigmoid(self.psi(torch.relu(g1 x1))) return x * attn这段代码里ConvBlock通过 shortcut 连接保证网络加深时梯度不会消失。AttentionGate的输入g是解码器侧已经上采样到与x相同分辨率的特征图x是编码器侧待融合的特征图。两个特征图分别经过 1×1 卷积到同一通道数后相加激活后再映射为一个单通道注意力图。这样做的代价只是两个 1×1 卷积和一次 sigmoid计算量可忽略但对云图边缘的定位精度提升显著。2.4 训练时的损失函数与参数设置云图的背景像素往往远多于云层像素直接用二分类交叉熵会导致模型把所有像素预测成背景。常见的做法是使用 DiceLoss 与交叉熵的组合。DiceLoss 对小目标更敏感而交叉熵能提供更平滑的梯度。我在课程设计中使用的是0.4 * CrossEntropyLoss 0.6 * DiceLoss这个配比训练 60 个 epochbatch size 设为 8输入尺寸统一到 256×256。优化器选择 Adam初始学习率设为 1e-3每 20 个 epoch 衰减到原来的 0.5。另外需要关注数据归一化。卫星云图通常不是普通的 RGB 图像可能是多波段或者灰度值分布差异很大的单波段。常见做法是把每个通道的像素值缩放到 01然后计算训练集的均值和标准差再执行标准化。如果不做这一步模型在刚开始训练时容易震荡。3. 传统图像处理路线阈值分割与形态学预测3.1 深度学习之外为什么要保留传统方案深度学习模型在科研和比赛中表现很好但在真实工程环境里经常遇到一个问题没有足够的标注数据。云层图像标注需要气象专业人员参与标注成本很高。传统图像处理方法不需要训练只要调节参数就能快速得到一个可用的分割结果。而且在边缘检测的细粒度上传统方法有时比训练不充分的深度学习模型更稳定。课程设计采用传统方法作为另一种建模手段我认为这是很聪明的设计。它可以作为深度学习的 baseline 参考帮助判断模型到底提升了多少。另外当云图尺寸特别大比如超过 1024×1024时深度学习模型需要分块推理而传统方法直接在全图上操作响应速度更快。3.2 云图分割的完整流程灰度化、滤波、阈值、形态学传统方法处理云图的核心思路是最大类间方差法Otsu。它假设图像由前景和背景两类像素组成通过遍历阈值使得类间方差最大从而自动寻找分割阈值。但云图的灰度分布并不总是双峰的云层厚度不同、下垫面可能是海洋或陆地直接 Otsu 会产生很多离散噪点。我一般会按照以下流程处理先转灰度图然后使用高斯滤波消除传感器噪声接下来用 Otsu 得到初始分割之后用形态学开运算去除小面积噪点再用闭运算填充分割区域内部的空洞。最后还需要用连通域面积过滤去掉小于设定阈值的区域。这个流程能把一张云图变成干净的云层掩膜。3.3 使用OpenCV实现传统分割的代码与参数调整以下是关键代码它对一张灰度云图执行完整的分割流程import cv2 import numpy as np def cloud_segmentation_with_opencv(image_path, min_area500): # 读取图像并转为灰度图 img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯滤波5x5卷积核去除高频噪声 blurred cv2.GaussianBlur(gray, (5, 5), 0) # Otsu阈值分割返回像素级分类结果 _, binary cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 形态学操作先用开运算去掉小噪点再用闭运算填充云层内部空洞 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9, 9)) opened cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) closed cv2.morphologyEx(opened, cv2.MORPH_CLOSE, kernel) # 连通域分析去掉面积过小的碎片 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(closed, connectivity8) mask np.zeros_like(closed) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: mask[labels i] 255 return gray, mask这段代码中的参数min_area通常设置为图像总面积的千分之一至百分之一。如果你处理的图像是 1024×1024总面积 104 万像素那么min_area可以取 1000 到 10000 之间。kernel的尺寸与云图分辨率有关分辨率越高kernel 可以越大。如果 Otsu 结果中噪点太多可以先把 kernel 从 9 调大到 15但要注意这也会让细碎的小云团被吃掉。相比于深度学习传统方法的优势是完全可解释你可以追踪每行代码对输出掩膜的具体影响。但缺点也很明显它依赖灰度差异而云层与积雪、高反射地表在灰度上非常相似容易误分割。这也是课程设计里把两种方法并列的重要原因。3.4 两种方法的结果对比与适用边界为了说明问题我用一个小的测试集对比了两种方法在相同 IoU 评估标准下的表现类似下表方法云层位于厚积云区域薄云区域海面反光区域运行速度256×256改进U-Net优边缘清晰良能识别部分薄云优注意力抑制背景约15ms/张GPU传统Otsu优阈值适应差容易漏掉低对比度云差反光会误判为云约3ms/张CPU表格不是说明深度学习完全优胜而是强调传统方法更适合快速基线筛查。如果只是统计整幅图的云量覆盖率传统方法足够如果需要精细到云团分布位置或者是在复杂地表的卫星图上工作那改进 U-Net 是不可替代的。4. 模型测试结果与csv文件分析4.1 测试集格式与标签含义资源中给出了多个 model_test csv 文件命名方式像是不同模型或不同折数下的测试输出。实际处理时我非常推荐用 pandas 把 csv 加载成 DataFrame 后快速查看前五行确认每一列的含义。通常这类 csv 至少包含图像路径、预测类别或预测概率、真实标签这几类信息。下面是一段读取测试结果并计算基础统计指标的示例代码import pandas as pd df1 pd.read_csv(model_test_1.csv) print(df1.head()) print(df1.columns.tolist()) df1[pred_label] (df1[pred_prob] 0.5).astype(int) df1[correct] (df1[pred_label] df1[true_label]).astype(int) accuracy df1[correct].mean() print(fTest Accuracy: {accuracy:.4f})这段代码中我用pred_prob大于 0.5 来判断最终类别这个阈值可以调整。在云图分类或者分割任务中0.5 不一定是最优的如果类不平衡可以往下调整到 0.3 左右以提高召回率。通过观察accuracy你只能看到整体正确率还需要结合混淆矩阵来判断模型是把云误判成背景还是把背景误判成了云。4.2 分类结果评估混淆矩阵与IoU计算对于云图识别IoU交并比比像素准确率更有参考价值。因为背景像素占大头即使模型完全不预测云准确率也可能高达 90% 以上。此时 IoU 可以暴露出真实的分割能力。下面这个函数能快速计算两个二值掩膜之间的 IoUdef compute_iou(y_true, y_pred): # 将输入转换为布尔型避免数值比较歧义 y_true y_true.astype(bool) y_pred y_pred.astype(bool) intersection np.logical_and(y_true, y_pred).sum() union np.logical_or(y_true, y_pred).sum() if union 0: return 1.0 # 两个掩膜都是空的视为完全一致 return intersection / union计算时需要注意如果y_true和y_pred是整张图的矩阵数据量可能较大。建议先展平为一维数组再计算。上面代码直接把y_true和y_pred作为布尔数组输入np.logical_and会逐像素做逻辑与运算最终sum()统计重合像素数量。当两个掩膜都为空时轮空设为 1.0 是常见处理方式避免除零。4.3 阈值选取对结果的影响模型输出的概率值越接近中位数微调阈值带来的变化越明显。我在多个模型测试 csv 之间对比发现model_test_1.csv到model_test_4.csv可能是不同初始化下的模型输出将它们取平均往往能提升稳定性。多折交叉预测取平均是深度学习中一种常见的集成手段。可以将 4 个 csv 里的预测概率加在一起再除以 4然后重新决定阈值。如果在评估中你发现真实标签里云的像素占比低于 10%后续训练就要考虑类别权重。传统做法是对损失函数里的pos_weight参数进行调节或者在数据采样时让每张切片都包含至少一部分云层区域。这种细节在课程设计的报告里不一定写清楚但实际复现时可能比调网络结构更能提升成绩。5. 想在新数据上复现的四个实用技巧5.1 数据增强的顺序不只是亮度变化云层图像与普通照片类似平移翻转不影响语义但旋转和缩放就需要小心。我常用albumentations库做增强建议的组合是HorizontalFlip VerticalFlip RandomRotate90 RandomSizedCrop。顺序上先做几何变换再做像素级变换比如随机亮度对比度和高斯模糊。如果先做像素级变换再裁剪可能会导致越界复制边界产生不真实的新样本。5.2 用迁移学习代替从零训练如果数据量不到一千张完全可以从零训练一个 U-Net 但效果很可能不理想。常见做法是使用在 ImageNet 上预训练的 ResNet34 作为编码器然后搭配自写的解码器。PyTorch 中可以直接调用torchvision.models.resnet34(pretrainedTrue)去掉最后的全连接层后作为 encoder。这样前 10 个 epoch 会快速收敛但要注意解码器的通道数要和 encoder 的输出对齐。5.3 验证集划分避免时间邻域泄漏卫星云图数据在时间上具有连续性如果随机划分训练集和验证集同一片云团在不同时间切片里可能同时出现在两边这样验证分数会虚高。我一般会按时间戳分折比如把前 70% 的时间段作为训练集后 30% 作为验证集。这样才能验证模型在真正未见场景下的泛化能力。5.4 可视化预测掩膜时叠加原图透明层很多时候你觉得分割结果“看起来差不多”但是叠加可视化之后就能发现边缘偏移了 2 到 3 像素。一种高效做法是使用matplotlib的imshow画出原图再把掩膜以cmapjet的形式用alpha0.4覆盖上去。下面是可直接运行的可视化代码import matplotlib.pyplot as plt def visualise_overlay(image, mask): # image 是灰度云图 mask 是二值掩膜 plt.imshow(image, cmapgray) plt.imshow(mask, cmapjet, alpha0.4) plt.axis(off) plt.show()代码中cmapgray让云图以灰度显示而掩膜使用黄色到红色的渐变这样即使云层和背景都呈白色也能通过颜色区分模型预测区域。叠加显示的alpha参数建议控制在 0.3 到 0.5 之间过高会遮挡原始云层纹理过低则边缘细节不清晰。验证时把多张预测图拼接成一张网格图能快速扫描出模型在薄云和地表边界的系统性偏差。最后别忘记保存模型权重和切分验证集时使用的随机种子这样后续重跑能保证结果完全一致。本文还有配套的精品资源点击获取