ARTICLE DETAIL

资讯详情

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

高分遥感语义分割实战:用PyTorch实现地物分类全流程

高分遥感语义分割实战:用PyTorch实现地物分类全流程 简介面向遥感地物分类场景这份基于PyTorch的高分遥感语义分割项目提供了从模型训练到预测输出的完整算法工程实践适用于深度学习入门者及从事遥感图像分析的研究人员。代码实现覆盖膨胀预测、后处理流程与半监督伪标签策略并接入TensorBoardX可视化输出可直接迁移到地块、建筑、水体等地物类别提取任务中。资源包共858个文件其中819张PNG格式的影像样本、标签图及预测结果可视化图占据主体同时包括35个Python源码脚本、CSV标注表与README说明文档压缩包整体约548MB目录按数据、脚本和输出划分便于按需检索和分模块研习。目前已有3041人学习下载较受同类开发者认可数据清洗、样本组织、模型训练、预测结果融合与效果对比等完整环节均能在压缩包内找到对应实现适合作为课程设计、毕业课题或算法复现的参考模板。1. 高分遥感语义分割为什么说它是地物分类里最难“落地”的一类同样是语义分割自然图像里把猫和狗分开和遥感影像里把房子、道路、耕地分开完全是两码事。高分遥感影像幅面大、地物尺度差异悬殊、标注成本高再加上光照、季节、传感器差异带来的“同物异谱”让通用分割模型直接跑遥感数据时经常出现小目标丢失、边缘锯齿、类别混淆这类问题。这篇笔记要讲的就是用 PyTorch 从数据准备、模型选型、训练调参到推理后处理完整走通一个高分遥感地物分类的语义分割项目。适合正在做遥感落地、或准备把语义分割算法迁移到遥感方向的工程师也适合拿公开遥感数据集做课程项目的同学照步骤复现。2. 高分遥感与自然图像的三个本质差异先搞懂数据再谈模型2.1 分辨率、幅宽与“同物异谱”为什么通用分割模型直接跑会翻车遥感影像和自然图像的第一个差异在尺度。自然图像里一个目标通常占画面主体而高分遥感影像里一栋房子可能只有几十个像素一条乡村道路可能细到 35 个像素宽。常用的分割模型为了增大感受野会连续下采样输入 512×512到输出头可能只剩 1/16 或 1/32 分辨率细小的道路和独立房屋在这一过程中直接被“抹平”。这就是为什么把 DeepLabV3 原封不动搬到遥感上第一轮跑出来的预测图往往只见大片农田和森林道路断成虚线。第二个差异是“同物异谱”。同样的混凝土屋顶在不同卫星、不同季节、不同太阳高度角下RGB 值可能相差很大反过来水泥地、裸土、某些屋顶在光谱上又很接近。这意味着模型不能只依赖颜色做决策还得靠纹理、形状和上下文。第三个差异是幅宽。一景高分影像动辄上万像素见方没法整图塞进 GPU你必须做切片tiling切片策略直接决定训练样本的质量和推理时的拼接效果。这三个差异不是调参能绕过的它们决定了数据怎么标、模型怎么改、损失函数怎么设。提示如果只是拿 Cityscapes 或 ADE20K 的预训练权重直接 fine-tune 遥感数据前几个 epoch 的 loss 通常降得很快但 val 的 mIoU 很快就会停滞。这不是模型坏了而是数据分布差异太大光靠微调不够需要在输入侧和损失侧同时做适配。2.2 标签体系怎么定从二分类到多类的取舍地物分类的标签体系没有统一标准但有一个基本原则标签类别一定要和你的应用目标对齐而不是照搬别人论文里的类别表。常见做法是先做二分类建筑物分割、水体提取、道路提取再做多分类城市、农业、牧场、森林、水域、裸地这类土地覆盖分类。二分类适合任务目标单一的项目比如“查违建”只需要建筑物掩膜多分类适合做国土普查、生态评估这类需要完整地表覆盖信息的场景。类别的粒度也要想清楚。把“不透水面”拆成屋顶、道路、停车场会让标注成本翻倍而且这三个类在 RGB 影像上边界模糊标注工人和模型都会懵。我的经验是第一版尽量控制在 68 类先把大类的骨架打出来后面再按需求细分。另外类别定义必须写进标注规范里比如“道路”是否包含人行道、“农田”是否包含休耕地否则不同标注员画出来的标签会有系统性偏差这个偏差最后都会变成模型在特定区域的固定错误而且很难通过调参弥补。2.3 数据集制作标注工具、切片策略与文件组织标注工具方面遥感语义分割的标注基本都在 GIS 软件里先画矢量再转栅格而不是直接在像素图上刷。QGIS 和 ArcGIS 是主流选择画好的 Shapefile 通过栅格化转成和影像同分辨率的单波段标签图。如果数据量不大、类别简单也可以用 LabelMe 这类通用工具直接在切片后的图像上画但对高分影像来说先矢量后栅格更符合地物分类的工作流也方便后期修改和复用。切片策略是遥感语义分割最容易踩坑的一环。整景影像不能直接训练要先切成 512×512 或 1024×1024 的小块。切片时要注意三点一是重叠率我一般用 10%20% 的重叠避免地物恰好被切在边缘导致标签不完整二是过滤策略全背景比如全是水体的切片要丢掉或降采样否则模型会被背景类带偏三是训练集和验证集必须在切片之前按“图幅”划分而不是按切片划分。否则同一栋建筑的不同切片会同时出现在训练集和验证集里验证指标虚高换到新地区立刻打回原形。文件组织建议按影像名建目录影像和同名标签放一起用 JSON 或 CSV 记录每个切片的原始图幅坐标。这个坐标信息在推理拼接阶段是必须的否则预测完拼不回去。标准目录结构长这样data/ ├── images/ │ ├── scene_001_512_000_000.tif │ └── scene_001_512_000_512.tif ├── labels/ │ ├── scene_001_512_000_000.tif │ └── scene_001_512_000_512.tif ├── train.txt ├── val.txt └── class_dict.jsontrain.txt和val.txt里每行写一对影像和标签的相对路径class_dict.json记录类别名和对应的像素值。这套组织方式和 torchvision 的Dataset接口天然契合写数据加载器时不需要额外处理复杂的目录结构。注意标签图必须是单波段、像素值和类别一一对应不要用 RGB 彩色标签图那会给损失函数计算带来不必要的转换麻烦。3. 模型选型与 PyTorch 实现从 FCN 到 DeepLabV3 再到 U-Net 系3.1 编码器-解码器结构为什么适合遥感地物语义分割模型经历了 FCN、U-Net、DeepLab 系列、SegFormer 等多次迭代但遥感地物分类场景里编码器-解码器结构至今仍是性价比最高的选择。原因有三一是遥感地物有明确的“尺度层级”大尺度的农田、水体需要全局上下文小尺度的道路、独立房屋需要局部细节编码器逐步下采样提取多尺度特征解码器逐级恢复分辨率正好匹配这种需求二是这类结构对显存相对友好训练和推理的开销可控三是预训练权重丰富ResNet 系列和 EfficientNet 系列的 ImageNet 权重容易拿到迁移到遥感数据上能显著加速收敛。具体选哪个取决于你的数据量和硬件条件。数据量在几千张切片以内DeepLabV3 配 ResNet50 是稳妥起点ASPP 模块的多空洞率卷积对尺度差异大的遥感地物很有效。数据量更大、显存充足可以上 U-Net 配 EfficientNet 编码器或者直接换 SegFormer 这类 Transformer 结构。如果专注道路、河流这类线状地物U-Net 的跳跃连接比 DeepLab 系更有优势因为线状目标的细节更容易在浅层特征里保留。注意不要一上来就追求大模型。遥感影像的通道数、幅宽和自然图像差异很大模型再大数据没准备好也是白搭。先把 DeepLabV3 跑通再根据瓶颈决定要不要换更强的 backbone。3.2 用 PyTorch 搭一个 DeepLabV3 训练骨架这里给一个可以照着抄的 PyTorch 训练骨架。使用torchvision自带的 DeepLabV3 实现把 backbone 换成 ResNet50输出头改成自己的类别数。先写模型定义import torch import torch.nn as nn from torchvision.models.segmentation import deeplabv3_resnet50 from torchvision.models.segmentation import DeepLabV3_ResNet50_Weights class RemoteSensingSegmenter(nn.Module): def __init__(self, num_classes: int, pretrained: bool True): super().__init__() if pretrained: weights DeepLabV3_ResNet50_Weights.DEFAULT self.model deeplabv3_resnet50(weightsweights) else: self.model deeplabv3_resnet50(weightsNone) # 替换分类头原输出是21类改成自己的地物类别数 in_channels self.model.classifier[-1].in_channels self.model.classifier[-1] nn.Conv2d( in_channels, num_classes, kernel_size1 ) def forward(self, x: torch.Tensor) - torch.Tensor: # torchvision的DeepLabV3返回的是 OrderedDict这里只取out return self.model(x)[out]这个封装把预训练权重加载和分类头替换都处理好了。逻辑上deeplabv3_resnet50最后的分类头是 1×1 卷积输入通道数是 DeepLab 最终特征图的通道数替换时要用in_channels动态获取不要写死。注意DeepLabV3_ResNet50_Weights.DEFAULT在 torchvision 0.13 以上版本可用老版本用pretrainedTrue参数。然后是数据加载器。遥感影像通常是 TIF 格式用rasterio读取标签是单波段 TIF用同样的坐标读出来。训练时要做随机裁剪、翻转和色彩增强import random import numpy as np import rasterio from torch.utils.data import Dataset from torchvision import transforms class RSegDataset(Dataset): def __init__(self, file_list, crop_size512, augmentTrue): self.pairs [line.strip().split(,) for line in open(file_list)] self.crop_size crop_size self.augment augment def __len__(self): return len(self.pairs) def __getitem__(self, idx): img_path, lbl_path self.pairs[idx] with rasterio.open(img_path) as src: image src.read() # 形状 (C, H, W), 值域 0-65535 with rasterio.open(lbl_path) as src: label src.read(1) # 单波段标签 # 归一化到 [0,1]高分影像常见16bit image image.astype(np.float32) / 65535.0 # 随机裁剪 h, w label.shape y random.randint(0, h - self.crop_size) x random.randint(0, w - self.crop_size) image image[:, y:yself.crop_size, x:xself.crop_size] label label[y:yself.crop_size, x:xself.crop_size] if self.augment: if random.random() 0.5: image image[:, :, ::-1].copy() label label[:, ::-1].copy() if random.random() 0.5: image image[:, ::-1, :].copy() label label[:: -1, :].copy() return torch.from_numpy(image), torch.from_numpy(label).long()这里有一个关键参数16bit 影像除以 65535 归一化。如果影像已经是 8bit0-255要改成除以 255否则输入分布会偏预训练权重的前几层统计特征就失效了。随机裁剪的crop_size在 512 和 1024 之间选择显存允许就尽量用大的更大的裁剪窗口意味着模型能看到更多的上下文对农田、林地这类大尺度地物有直接帮助。3.3 损失函数交叉熵之外遥感还得加什么遥感地物分类的类别分布极度不平衡。一张典型乡村影像里农田和森林可能占 70% 以上道路、独立房屋加起来不到 5%。普通交叉熵在这个分布下会被大类别主导小类别地物的梯度信号被淹没。常见做法是给交叉熵加类别权重权重按“样本量倒数”或“中位数频率”来算# 统计训练集各类别像素占比计算权重 def compute_class_weight(label_list, num_classes): counts np.zeros(num_classes, dtypenp.float64) for lbl_path in label_list: with rasterio.open(lbl_path) as src: label src.read(1) for c in range(num_classes): counts[c] np.sum(label c) total counts.sum() # 用中位数频率平衡过小类别给大权重 median_freq np.median(counts / total) weights median_freq / (counts / total 1e-8) return torch.from_numpy(weights).float()这个权重数组传给nn.CrossEntropyLoss(weightweights)就行。注意权重不是越大越好权重过大会让模型把大量像素误分成小类别mIoU 反而下降。我一般会把权重裁剪到 [0.1, 10] 区间内防止极端类别把训练带偏。如果小目标地物依然丢得厉害就在损失函数里叠加 Dice Loss。Dice Loss 对类别不平衡不敏感特别适合道路、房屋这类稀疏目标。常见的做法是交叉熵和 Dice 按 7:3 或 6:4 加权前者保证收敛稳定性后者拉小类别的召回率。实现上可以直接用 PyTorch 的torch.nn.functional手写Dice Loss 的输入是 logits需要先过 softmaxdef dice_loss(logits, targets, smooth1.0): probs torch.softmax(logits, dim1) # 转成 one-hotshape: (N, C, H, W) targets_onehot torch.zeros_like(probs) targets_onehot.scatter_(1, targets.unsqueeze(1), 1) intersection (probs * targets_onehot).sum(dim(2, 3)) union probs.sum(dim(2, 3)) targets_onehot.sum(dim(2, 3)) dice (2.0 * intersection smooth) / (union smooth) return 1.0 - dice.mean()smooth参数防止除零一般在 1.0 附近。训练时把这个值和交叉熵加起来做总损失。需要提醒的是Dice Loss 的梯度行为比交叉熵“猛”学习率要适当调低否则前几十个 iteration 的 loss 会剧烈震荡。4. 训练策略与必调参数让模型在遥感数据上真正收敛4.1 预训练权重与 backbone 选择的常见做法遥感影像的通道和自然图像不同但预训练权重仍然值得用。ResNet50 在 ImageNet 上学到的纹理和边缘特征对遥感地物同样有效。如果你的影像只有 RGB 三通道直接用 torchvision 的预训练权重如果有多光谱通道比如红边、近红外常见做法是复制 RGB 预训练权重的第一层卷积对多出的通道做平均初始化或者把多光谱通道降维到 RGB 再喂给模型。第一种做法实现上更干净很多遥感项目实际用的就是 RGB 三通道近红外等额外通道对深度学习模型的增益并没有传统遥感算法里那么明显。backbone 的选择上ResNet50 是均衡点ResNet101 在显存允许时能带来 23 个点的 mIoU 提升但训练时间几乎翻倍。EfficientNet 系在遥感上的表现也不错参数量更少但 torchvision 没有现成的分割封装需要自己拼解码器工程成本高一些。我的建议是第一版用 ResNet50 把整条链路跑通记录 baseline之后再根据瓶颈决定是否升级 backbone。4.2 学习率、batch size 与多尺度训练的配合遥感语义分割的训练参数和自然图像相比有一些明显的调整方向。学习率方面语义分割常用多项式衰减Poly策略初始学习率 0.01 到 0.02配合 SGD 时或 0.0001 到 0.0003配合 AdamW 时。预训练 backbone 部分的学习率要打折扣通常乘 0.1防止破坏 ImageNet 上学到的底层特征。batch size 受显存限制一般只能开到 48此时 BatchNorm 的统计量会不稳定要么用更大的 batch多卡或梯度累积要么换成 GroupNorm。多尺度训练是遥感任务里性价比很高的技巧。因为地物尺度差异太大固定 512×512 输入会让模型对尺度变化敏感。常见的做法是训练时从 [0.75, 1.0, 1.25] 等尺度中随机采样把影像缩放到对应尺寸再裁剪。PyTorch 里可以用torchvision.transforms.Resize在__getitem__里做代价是每个 epoch 多花一点时间在图像缩放上但换来的尺度鲁棒性很值。推理时再用多尺度融合也叫多尺度 TTA在每个尺度上跑一次把 softmax 输出平均。优化器方面SGD 配 momentum 0.9 和 weight decay 1e-4 是分割任务里经过大量验证的组合AdamW 收敛更快但最终精度往往略低于调好的 SGD。如果你希望快速看到结果、快速迭代先用 AdamW如果要刷最终精度换成 SGD 多跑几个 epoch。4.3 验证指标mIoU 和 Kappa 哪个更该盯着看语义分割论文里最常报的指标是 mIoU它计算每个类别的预测和真值交集除以并集再对所有类别取平均。mIoU 的优点是每个类别同等权重大类别不容易掩盖小类别的表现缺点是它只关心区域重叠不关心错分的结构化特征。遥感地物分类里我一般同时看三个指标mIoU、类别平均准确率mPA、以及逐类的 IoU。逐类 IoU 才是定位问题的关键——看道路 IoU 和房屋 IoU 是否被农田甩开就能判断小目标丢失的严重程度。Kappa 系数是另一个遥感领域常用的指标它衡量的是“分类结果和随机分类相比有多大的提升”。Kappa 对类别不平衡更敏感在土地覆盖分类的文献里很常见但它和 mIoU 高度相关实际使用中看一个就够了。我的建议是调参阶段盯 mIoU 和逐类 IoU项目验收时再补上 Kappa 和混淆矩阵。混淆矩阵能告诉你“道路被错分成了什么”这个信息对后续针对性优化最有价值。验证时还有一个容易忽略的坑评估必须在原始分辨率下进行。如果训练时做了多尺度、随机裁剪验证时也要用同样的预处理流程在原始影像的完整切片上推理而不是把标签 resize 到某个固定尺寸再比。标签插值尤其是最近邻以外的插值会把边界拉花mIoU 出现虚高或虚低都会误导判断。5. 遥感语义分割常见问题排查从训练损失到推理结果的踩坑记录5.1 现象损失曲线下降但 mIoU 停滞预测图几乎全是背景类原因类别严重不平衡交叉熵被农田、水体等大类主导。模型发现“全预测成大类”的损失也很低于是陷入这种次优解小类别地物完全没被学到。 解决先做类别权重。用前面提到的中位数频率法给交叉熵加权重同时叠加 Dice Loss。如果加了权重后 loss 曲线开始剧烈震荡说明权重给高了把权重裁剪区间从 [0.1, 10] 收窄到 [0.3, 5]或者降低 Dice Loss 的权重。5.2 现象道路、独立房屋等小目标在预测图里断裂或消失原因连续的池化和下采样把细小目标的空间信息抹掉了特别是 ResNet 最后一个 stage 输出分辨率只有输入的 1/32几个像素宽的道路根本留不下来。 解决优先检查训练时用的crop_size是否太小512 的裁剪窗口对道路这种长条形目标来说上下文不够。可以试试 768 或 1024。其次检查损失函数是否给了小类别足够的梯度Dice Loss 在这里比类别权重更直接。最后如果模型是 DeepLabV3考虑把 ASPP 的 rate 调低一些或者切到 U-Net 这类带跳跃连接的模型。5.3 现象预测图边缘出现规则的棋盘格纹路或者物体边缘对不齐原因几乎都是标签和影像在预处理时发生了错位。最常见的是随机裁剪时对影像和标签分别做了不同的变换或者某些增强操作如旋转、缩放只对影像做了、没对标签做。 解决打开增强代码确认所有对影像的操作都对标签做了完全相同的变换且变换参数一致。对于随机翻转需要注意copy()的使用如前面代码里image image[:, :, ::-1].copy()因为 NumPy 切片是视图不 copy 会导致后续操作互相污染。另外验证一下读取的影像和标签的仿射变换geotransform是否对齐如果两者来自不同来源需要用rasterio的投影信息做一次配准。5.4 现象训练到中途显存溢出OOM只能把 batch size 调到 2 以下原因遥感影像即使切片后也是三通道 512×512DeepLabV3 的中间特征图占用很大加上预训练 backbone 的 BatchNorm 对大 batch 有依赖batch size 太小会让训练不稳定。 解决优先级从高到低先用混合精度训练AMPPyTorch 里用torch.cuda.amp.autocast包住前向和损失计算显存能省下约 40%其次用梯度累积每 4 个 step 累积一次梯度等效 batch size 不变但显存压力大减再然后检查是否关闭了不需要的梯度比如冻结 backbone 前几层最后才考虑换轻量 backbone比如 ResNet34 或 MobileNetV3。不要一开始就牺牲 batch size遥感任务的 batch size 小于 4 时BatchNorm 的统计量会很不稳定。6. 推理与后处理技巧滑窗、TTA 与地物分类结果的精度验证6.1 滑窗推理与重叠拼接整景影像推理时把影像切成和训练时相同尺寸的窗口带重叠率滑动推理再把预测图按位置贴回去。重叠区域的处理有两种直接取最大值或者按到中心的距离加权平均。后者边缘过渡更自然。贴回时要用训练时记录的坐标信息否则位置对不齐。6.2 测试时增强TTA在遥感上的取舍TTA 的常见做法是水平翻转、垂直翻转、对角线翻转各跑一次把 4 张 softmax 输出取平均。遥感地物本身没有“上下”概念翻转增强和模型训练时的增强是对称的一般能带来 12 个点的 mIoU 提升。代价是推理时间翻 4 倍如果项目对时效性有要求只在验证集上开 TTA线上推理关掉。6.3 用混淆矩阵和逐类 IoU 做最终验收最终验收不要只看一个 mIoU 数字。我习惯的做法是跑完验证集导出混淆矩阵逐行看每个类别的 recall——道路被错分成了哪几类、工业厂房和居民区是否混淆、阴影和水体是否分不开。这些信息直接决定下一步优化方向阴影问题说明需要加 NDVI 或多光谱通道道路断裂说明需要换模型结构或加后处理细化比如条件随机场 CRF 做边缘修正。我自己的教训是第一次做完遥感分割项目我用一个平均 mIoU 很漂亮的模型上了现场测试结果在城市里还行到了郊区农田区域临时建筑和裸土被成片错分最后花了大量时间在数据补充和类别重定义上。后来我养成了习惯——每次训练结束除了看指标一定随机抽 20 张验证集影像把预测结果和真值叠在一起肉眼过一遍。这个习惯帮我发现了很多指标看不出来的系统性问题比如特殊光照下的整体色偏导致大范围误判。希望这个习惯对你也管用。本文还有配套的精品资源点击获取
返回列表