
简介这份资源是面向遥感图像处理研究者与深度学习工程师的Pytorch语义分割实战教程配套包聚焦高分辨率遥感影像的地物信息提取适合具备一定Pytorch基础、希望将语义分割技术落地到环境监测、资源勘探等场景的学习者。压缩包共1029个文件约577.48MB以819张png图像样本、35个py源码脚本为主辅以zbak备份、csv标签说明、jpg效果图与md说明文档覆盖数据预处理、网络搭建、模型训练到结果评估的完整链路。教程从遥感图像基本概念讲起逐步深入到分割网络结构选择与优化策略并演示标注工具制作像素级训练样本的方法帮助读者亲手跑通端到端流程。目前已有94人学习适合想系统掌握遥感语义分割、对照代码与数据快速复现实验的读者参考。1. 高分遥感语义分割落地从 GF2 影像到 PyTorch 端到端训练手里拿到一景 GF2 PMS2 的高分遥感影像想把它变成能自动识别道路、建筑、植被的语义分割模型这件事的门槛其实不在模型结构而在数据怎么组织、标签怎么对齐、训练怎么不翻车。这份资源给的正是一条完整链路GF2_PMS2 的 MSS2 影像切片、classes_sample.jpg 类别示意、README 说明以及 predict.png、blend.png、img_gt_pre.png 三张结果对照图配合 PyTorch 代码把预处理、训练、推理、评估串起来。它适合已经会写 PyTorch 训练循环、但对遥感数据特性不熟的人也适合想拿一套能跑通的语义分割 baseline 去改自己数据集的工程师。下面按「资源是什么 → 怎么用 → 坑在哪」推一遍。2. 遥感语义分割的数据组织GF2 切片、标签与类别映射2.1 为什么遥感分割不能直接套自然图像那套自然图像语义分割比如 VOC、Cityscapes的影像视角是地面平视目标尺度相对统一标注边界清晰。遥感影像不一样GF2 PMS2 是星下点成像一景覆盖几十公里同一张图里既有几米宽的小路也有成片的耕地尺度差异极大。更麻烦的是多波段——PMS2 包含全色和多光谱MSS2 产品里蓝、绿、红、近红外四个波段叠在一起直接当 RGB 三通道喂给网络会丢掉近红外信息而近红外恰恰是区分植被和人工地物的关键。所以这份资源里出现的GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2_380.png这类切片本质是把大景切成固定尺寸的小块每块配一张同名的标签图。切片尺寸常见做法是 256×256 或 512×512太小则上下文不足太大则显存吃紧。我一般会先看classes_sample.jpg它把每个类别用固定颜色标出来这张图就是你的类别映射表——颜色到类别 ID 的对应关系必须和标签生成脚本完全一致否则训练时 loss 会莫名其妙不降。2.2 从原始影像到训练对切片与标签对齐假设你已经把 GF2 大景和对应标签栅格准备好了切片脚本的核心逻辑是滑动窗口加随机裁剪。下面这段是我常用的切片代码输入是大图路径和标签路径输出成对的 pngimport os import numpy as np from PIL import Image def slide_crop(img_path, lbl_path, out_dir, patch512, stride256): img np.array(Image.open(img_path)) # H,W,C 或 H,W lbl np.array(Image.open(lbl_path)) # H,W值为类别 ID os.makedirs(os.path.join(out_dir, img), exist_okTrue) os.makedirs(os.path.join(out_dir, lbl), exist_okTrue) h, w lbl.shape idx 0 for y in range(0, h - patch 1, stride): for x in range(0, w - patch 1, stride): img_patch img[y:ypatch, x:xpatch] lbl_patch lbl[y:ypatch, x:xpatch] # 跳过全背景块减少无效样本 if len(np.unique(lbl_patch)) 1 and lbl_patch[0,0] 0: continue Image.fromarray(img_patch).save( os.path.join(out_dir, img, f{idx:06d}.png)) Image.fromarray(lbl_patch).save( os.path.join(out_dir, lbl, f{idx:06d}.png)) idx 1 print(ftotal patches: {idx}) slide_crop(GF2_PMS2_..._MSS2.tif, GF2_PMS2_..._label.tif, ./dataset)逻辑说明patch控制切片大小stride控制重叠程度stride 小于 patch 时相邻块有重叠能缓解边缘目标被切断的问题。np.unique那行是过滤全背景块遥感图里大量区域是裸地或背景不过滤的话正负样本极度失衡。参数上如果你显存只有 8Gpatch 建议 256stride 取 128显存 16G 以上可以上 512。注意标签图必须是单通道且像素值为整数类别 ID如果标签是 RGB 彩色图得先做颜色到 ID 的映射这一步和classes_sample.jpg的配色严格对应。2.3 类别不平衡与权重设置遥感分割里建筑、道路这类目标占比往往不到 5%植被和背景占大头。直接算交叉熵模型会倾向于全预测背景也能拿到很低的 loss。常见做法是在 loss 里加类别权重权重可以按类别像素频率的倒数来设import torch import numpy as np # 统计训练集各类别像素数 freq np.array([...]) # 长度等于类别数 weight 1.0 / (freq 1e-6) weight weight / weight.sum() * len(weight) weight torch.tensor(weight, dtypetorch.float32) criterion torch.nn.CrossEntropyLoss(weightweight)freq从你的标签统计来1e-6防止除零。归一化那步让权重均值回到 1 附近避免整体 loss 尺度变化太大影响学习率。如果某些类别样本极少光靠权重可能还是学不好那就得考虑重采样或者对稀有类别做数据增强。3. PyTorch 训练管线模型选型、训练循环与显存控制3.1 语义分割模型选型U-Net、DeepLabV3 还是 SegFormer这份资源的核心是 PyTorch 实现模型结构没有限定死但遥感分割场景下常见的选择有这么几类。U-Net 系列编码器-解码器加跳跃连接对小目标边界恢复好参数量适中适合 patch 256 起步的训练DeepLabV3 用空洞卷积扩大感受野对大面积地物分类稳但边界偏粗SegFormer 这类 Transformer 结构在遥感上表现也不错尤其多尺度特征融合强但显存占用高小数据集容易过拟合。我一般会先用 U-Net 跑一个 baseline确认数据管线和标签没问题再换 DeepLabV3 对比。如果你用的是 ResNet backbone注意把预训练权重加载上遥感数据量通常不够从头训。下面是一个最小可用的 U-Net 定义片段import torch.nn as nn import torch.nn.functional as F class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.net nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.net(x) class UNet(nn.Module): def __init__(self, in_ch4, n_cls6): super().__init__() self.d1 DoubleConv(in_ch, 64) self.d2 DoubleConv(64, 128) self.pool nn.MaxPool2d(2) self.up1 nn.ConvTranspose2d(128, 64, 2, stride2) self.u1 DoubleConv(128, 64) self.out nn.Conv2d(64, n_cls, 1) def forward(self, x): x1 self.d1(x) x2 self.d2(self.pool(x1)) x self.up1(x2) x torch.cat([x, x1], dim1) x self.u1(x) return self.out(x)in_ch4对应 MSS2 的四波段输入如果你只用 RGB 就改成 3。n_cls是你的类别数包含背景。这个结构是最简版实际用的时候编码器可以换成 ResNet 或 EfficientNet解码器加更多上采样层。3.2 训练循环与验证指标训练循环本身不复杂关键是验证阶段要算对指标。遥感分割不能只看像素准确率因为背景占比高全预测背景也能到 80% 以上。我一般同时看 mIoU 和各类别 IoUdef evaluate(model, loader, n_cls, device): model.eval() inter torch.zeros(n_cls) union torch.zeros(n_cls) with torch.no_grad(): for img, lbl in loader: img, lbl img.to(device), lbl.to(device) pred model(img).argmax(1) for c in range(n_cls): p (pred c) t (lbl c) inter[c] (p t).sum().item() union[c] (p | t).sum().item() iou inter / (union 1e-6) return iou, iou.mean().item()inter和union按类别累加最后逐类算 IoU 再取平均。这样你能看到到底是哪个类别拖后腿——通常是道路或者小建筑。如果某个类别 IoU 长期为 0先回去查标签里这个类别的像素数是不是太少或者颜色映射有没有搞错。3.3 显存不够时的几个实用手段遥感切片训练显存吃紧是常态。除了调小 patch还有几个手段混合精度训练用torch.cuda.amp能省 30% 到 40% 显存梯度累积模拟大 batch把optimizer.step()放到累积若干次之后如果模型固定可以用torch.utils.checkpoint做梯度检查点用计算换显存。我一般先上混合精度不够再梯度累积最后才考虑 checkpoint因为 checkpoint 会拖慢训练速度。4. 推理与结果可视化predict、blend 与 img_gt_pre 怎么读4.1 推理脚本与滑动窗口拼接训练完模型对大图做推理不能直接整图塞进去得用滑动窗口加重叠拼接。资源里的predict.png就是推理输出的类别图blend.png是原图和预测的半透明叠加img_gt_pre.png是原图、真值、预测三栏对比。自己写推理时窗口大小和训练时一致重叠区域取概率最大或者投票def infer_big_image(model, img, patch512, stride256, n_cls6, devicecuda): model.eval() h, w img.shape[:2] prob np.zeros((n_cls, h, w), dtypenp.float32) count np.zeros((h, w), dtypenp.float32) for y in range(0, h - patch 1, stride): for x in range(0, w - patch 1, stride): crop img[y:ypatch, x:xpatch] tensor torch.from_numpy(crop).permute(2,0,1).float().unsqueeze(0).to(device) with torch.no_grad(): out torch.softmax(model(tensor), dim1)[0].cpu().numpy() prob[:, y:ypatch, x:xpatch] out count[y:ypatch, x:xpatch] 1 prob / np.maximum(count, 1) return prob.argmax(0)prob累加每个窗口的 softmax 输出count记录覆盖次数最后取平均再 argmax。这样重叠区域的预测更平滑不会出现明显的拼接缝。blend.png那种叠加图可以用cv2.addWeighted做alpha 取 0.5 左右方便肉眼检查哪里分错了。4.2 从 img_gt_pre 对比图定位问题img_gt_pre.png这种三栏对比是排查问题最直接的工具。看的时候重点盯三类区域边界处预测是否比真值粗或细、小目标是否被漏掉、同类地物内部是否有碎斑。如果边界普遍偏粗可能是上采样方式太粗暴换成双线性插值或者加边界损失如果小目标漏检多检查训练时 patch 里小目标占比或者加一个针对小目标的加权 loss碎斑多说明模型对纹理敏感可以加后处理比如条件随机场或者形态学开闭运算。5. 避坑与排查遥感分割训练里最容易翻车的几件事5.1 标签颜色映射错位导致 loss 不降现象训练几个 epoch 后 loss 卡在某个值不动验证 mIoU 接近 0。原因标签图是 RGB 彩色但训练时直接当单通道读像素值变成 0-255 的某个通道值和类别 ID 完全对不上。解决写一个颜色到 ID 的映射函数用classes_sample.jpg里的配色做字典把 RGB 标签转成单通道 ID 图转换后抽查几个像素确认值在[0, n_cls-1]范围内。5.2 波段顺序搞反导致植被识别崩现象植被和建筑混淆严重近红外特征完全没起作用。原因MSS2 波段顺序是蓝、绿、红、近红外但读图时按 RGB 顺序取了前三个通道近红外被丢掉。解决读图后显式按波段索引取数据确保输入通道顺序和训练时一致最好在 README 里记下波段顺序避免换数据集时搞混。5.3 切片重叠太多导致验证集泄漏现象验证集 mIoU 高得离谱但换一片区域推理效果很差。原因切片时 stride 太小训练块和验证块来自同一区域且有重叠模型记住了局部纹理。解决按地理区域划分训练和验证确保两边的切片在空间上不重叠划分时以整景或者大块为单位不要随机打散切片。5.4 显存溢出但报错信息误导现象训练中途报 CUDA out of memory但 batch size 已经调到 1。原因可能是验证阶段没加torch.no_grad()或者中间特征图没释放。解决验证和推理一律包在with torch.no_grad():里训练循环里及时del中间变量用torch.cuda.empty_cache()清理缓存。如果还是爆检查是不是某张切片尺寸异常大。5.5 类别权重设太大导致训练震荡现象loss 剧烈震荡某些类别 IoU 忽高忽低。原因稀有类别权重设得过高梯度被这些样本主导。解决权重不要超过 10可以先从频率倒数开方再归一化或者用 focal loss 替代加权交叉熵让难样本主导而不是稀有样本主导。6. 进阶技巧用验证集反推数据质量与模型上限跑通 baseline 之后真正决定上限的往往不是换模型而是数据本身。我习惯在训练前先做一件事把验证集里每个类别的像素占比统计出来和训练集对比。如果某个类别在验证集里占比远高于训练集说明你的划分有偏模型在这个类别上的表现不可信。这个统计用几行 numpy 就能做import numpy as np from PIL import Image import glob def class_ratio(lbl_dir, n_cls): cnt np.zeros(n_cls) for p in glob.glob(f{lbl_dir}/*.png): lbl np.array(Image.open(p)) for c in range(n_cls): cnt[c] (lbl c).sum() return cnt / cnt.sum() train_ratio class_ratio(./dataset/train/lbl, 6) val_ratio class_ratio(./dataset/val/lbl, 6) print(train:, train_ratio) print(val: , val_ratio)对比两组数字如果差异超过一倍就得重新划分。另一个技巧是拿img_gt_pre.png里预测错的区域反查训练集看这些区域在训练集里有没有类似样本。如果没有说明模型不是学错了而是没见过这时候补数据比调模型有用得多。还有一个容易被忽略的点遥感影像的时相。GF2 这景是 2017 年 3 月的如果你拿它训练的模型去推理夏季影像植被茂密程度完全不同模型很可能把农田判成林地。我一般会在 README 里记下影像的时相和区域推理新数据前先确认时相差异差异大就补几景对应季节的样本做微调。从那以后我每次拿到新的遥感数据集都强制先跑一遍类别占比统计和时相核对再开始训练。希望帮到你。本文还有配套的精品资源点击获取