引言
语义分割是遥感里最主流的密集预测任务,目标是为每个像元赋予一个地物类别标签,输出一张与原图同尺寸的分类图。它服务于土地利用制图、建筑物提取、水体与湿地范围、道路网提取、冰川与农作物分布等场景。与场景分类相比,分割给出的是空间上连续、可用于面积统计的结果;与目标检测相比,它不区分同类实例,但对不规则地物的边界刻画更自然。
工程难点有四个。第一是输入,遥感影像常有多于三个波段,还可能有 SAR、DEM、时序影像等异构模态,如何融合决定了信息上限。第二是类别不平衡,水体、建筑等目标常只占影像的百分之几,交叉熵会被背景主导,模型倾向于全判为背景。第三是边界,地物交界处的像元是混合光谱,模型在这些像元上的置信度天然偏低,而边界往往是业务最关心的部分。第四是大图,一景 Sentinel-2 影像上亿像元,无法一次进网络,必须切块推理再无缝拼接。
本文按「结构到训练再到推理」展开。先定义任务与评价口径,再讲网络结构、多模态融合、损失设计、边界处理与 Transformer 选型,最后落到大图推理、后处理与精度归因。分类任务的对照见 遥感影像分类 ,多模态里的点云输入见 LiDAR 点云处理 。
目录
- 分割任务的定义与评价口径
- 编码器解码器与多尺度结构
- 多光谱与多模态输入融合
- 类别不平衡与损失函数设计
- 边界与细小地物的处理
- Transformer 与通用分割器选型
- 大图滑窗推理与拼接
- 后处理与拓扑一致性
- 精度评估与误差归因
1. 分割任务的定义与评价口径
语义分割与实例分割、全景分割容易混淆。语义分割只关心类别,同一类的相邻实例连成一片;实例分割要区分每个个体;全景分割是二者的并集。遥感里绝大多数制图需求用语义分割就够,只有需要计数(多少栋楼、多少条船)时才升级到实例分割。
| 任务 | 输出 | 遥感典型用途 | 主指标 |
|---|---|---|---|
| 语义分割 | 每像元类别 | 土地利用、水体提取 | mIoU、F1 |
| 实例分割 | 每像元类别加实例 | 建筑计数、船舶计数 | mask mAP |
| 全景分割 | 语义加实例 | 城市要素普查 | PQ |
| 二值分割 | 每像元前景背景 | 单类地物提取 | IoU、F1 |
评价口径要提前冻结。mIoU 是主流,它对每类一视同仁,能暴露少数类问题;总体精度在不平衡数据上会虚高,不能单独使用。另一个常被忽视的是评价时的忽略区(ignore region),地物边界像元、云掩膜区、无数据区常被排除在评价外,忽略区怎么划直接影响数字,跨论文比较时必须确认口径。
二值分割任务(如只提取建筑)应报告 IoU 而不是准确率。一个建筑占 5% 的影像,全判背景就有 95% 准确率,而 IoU 会是 0。
2. 编码器解码器与多尺度结构
分割网络的主流范式是编码器解码器。编码器逐级下采样提取语义,解码器逐级上采样恢复分辨率,中间的跳跃连接把高分辨率细节接到解码器,兼顾语义与边界。U-Net 是这个范式的原型,遥感里几乎所有分割骨架都是它的变体。
结构上的几个关键选择:
- 下采样倍率。经典 U-Net 下采样 4 到 5 次,最小特征图是输入的 1/16 或 1/32。遥感小目标多,下采样 5 次后小地物会消失,实践中常压到 4 次并配合空洞卷积扩大感受野。
- 跳跃连接的形式。直接拼接(U-Net)简单有效,相加(FPN 式)参数少。拼接后通道数翻倍,解码器参数量随之上升。
- 多尺度聚合。ASPP 用不同膨胀率的空洞卷积并行提取多尺度上下文,PSPNet 用不同池化核,二者都能显著提升大范围地物的内部一致性。
import torch
import torch.nn as nn
import torch.nn.functional as F
class ASPP(nn.Module):
def __init__(self, cin, cout=256, rates=(6, 12, 18)):
super().__init__()
self.branches = nn.ModuleList([
nn.Sequential(nn.Conv2d(cin, cout, 3, padding=r, dilation=r), nn.BatchNorm2d(cout), nn.ReLU())
for r in rates
])
self.pool = nn.Sequential(
nn.AdaptiveAvgPool2d(1), nn.Conv2d(cin, cout, 1), nn.ReLU()
)
self.fuse = nn.Conv2d(cout * (len(rates) + 1), cout, 1)
def forward(self, x):
feats = [b(x) for b in self.branches]
p = F.interpolate(self.pool(x), size=x.shape[-2:], mode="bilinear", align_corners=False)
return self.fuse(torch.cat(feats + [p], dim=1))
编码器的预训练权重很关键。用 ImageNet 权重初始化比从头训快得多,但自然图像与遥感的分布差异会削弱收益。更好的选择是用遥感预训练权重(如 SSL4EO、SatMAE 这类自监督模型产出的编码器),它们在海量无标注遥感影像上训练,学到的特征与遥感分布匹配。
3. 多光谱与多模态输入融合
遥感分割相对自然图像分割的最大优势是输入不限于 RGB。多光谱的近红外与短波红外对植被与水体高度敏感,红边波段对作物类型区分有效;再加 DEM 的坡度坡向、SAR 的后向散射、时序影像的物候特征,信息量远超三通道。
融合策略按融合发生的位置分三类:
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 早期融合 | 波段直接堆叠成多通道输入 | 简单、端到端 | 异构模态难对齐 |
| 中期融合 | 各模态独立编码后特征拼接 | 保留模态特性 | 参数量与显存翻倍 |
| 晚期融合 | 各模态独立预测后加权 | 容错、可单模态降级 | 跨模态交互弱 |
早期融合最常用,把波段按固定顺序堆叠即可,第一层卷积的输入通道数等于波段总数。关键纪律是波段顺序必须固化并写进元数据,否则训练与推理顺序不一致会导致推理结果全错且难以排查。
import numpy as np
import torch
BAND_ORDER = ["B02", "B03", "B04", "B08", "B11", "B12", "DEM"] # 固化顺序
def build_input(bands: dict, stats: dict) -> torch.Tensor:
chans = []
for name in BAND_ORDER:
a = bands[name].astype("float32")
a = (a - stats[name]["mean"]) / (stats[name]["std"] + 1e-6) # 按波段标准化
chans.append(a)
return torch.from_numpy(np.stack(chans)) # (C, H, W)
异构模态(光学加 SAR 加 DEM)建议用中期融合,因为它们的物理量纲与统计分布差异大,直接堆叠会让标准化互相干扰。中期融合给每个模态一个轻量编码分支,在中层拼接,让网络自己学跨模态交互。融合层的设计比分支本身更重要,简单的通道拼接往往不如注意力加权融合。
时序输入是另一个维度。把多时相影像沿通道堆叠(如四个季度的 NDVI)能让模型学到物候特征,显著提升作物分类精度。代价是通道数翻倍,显存吃紧,可用时间维卷积或注意力先压缩时序再融合。
4. 类别不平衡与损失函数设计
交叉熵在类别不平衡时会被多数类主导。设背景占 90%,模型只要全判背景就能把损失降到很低,梯度信号被背景垄断,少数类几乎学不动。这是遥感分割最常见的失效模式。
主流损失与其特性:
| 损失 | 机制 | 对不平衡 | 对边界 |
|---|---|---|---|
| 交叉熵 | 逐像元对数损失 | 差 | 一般 |
| 加权交叉熵 | 按类频次倒数加权 | 中 | 一般 |
| Focal Loss | 降低易分样本权重 | 好 | 一般 |
| Dice Loss | 区域重叠度 | 好 | 好 |
| Tversky Loss | Dice 的精确召回可调版 | 好 | 好 |
| 边界损失 | 对边界像元加权 | 一般 | 很好 |
import torch
import torch.nn as nn
import torch.nn.functional as F
def focal_loss(logits, target, gamma=2.0, alpha=0.25):
ce = F.cross_entropy(logits, target, reduction="none")
pt = torch.exp(-ce)
return (alpha * (1 - pt) ** gamma * ce).mean()
def dice_loss(logits, target, num_classes, eps=1e-6):
prob = logits.softmax(1)
tgt = F.one_hot(target, num_classes).permute(0, 3, 1, 2).float()
dims = (0, 2, 3)
inter = (prob * tgt).sum(dims)
card = prob.sum(dims) + tgt.sum(dims)
return (1 - (2 * inter + eps) / (card + eps)).mean()
def combo_loss(logits, target, num_classes, w_dice=0.5):
return w_dice * dice_loss(logits, target, num_classes) + (1 - w_dice) * focal_loss(logits, target)
工程经验是组合优于单一。交叉熵提供稳定的逐像元梯度,Dice 或 Tversky 提供区域级约束,二者加权相加通常比任一单独使用都好。权重不必精调,0.5 比 0.5 在多数任务上已接近最优。
类频次统计要在训练集上做,且要排除忽略区。加权时别用原始倒数,极端不平衡下(如某类只占 0.1%)倒数权重会大到让该类的少量噪声样本主导梯度,建议对权重做截断或平方根压缩。
5. 边界与细小地物的处理
边界是最难也最重要的部分。地物交界处的像元是混合光谱,一个 10 米分辨率的像元可能一半是建筑一半是道路,无论判成哪类都不完全正确。模型在这些像元上的损失天然高,但常规损失对边界与内部一视同仁,导致边界被平均掉。
三类处理手段:
第一是损失加权。对边界像元加大权重,或用专门的边界损失,把预测边界的距离场作为回归目标。边界像元的提取可以用形态学梯度:对标注做膨胀减腐蚀。
import numpy as np
from scipy import ndimage
def boundary_weight(mask, width=3, boost=5.0):
# 对类别边界像元加权,内部权重为 1
edge = np.zeros(mask.shape, dtype=bool)
for c in np.unique(mask):
m = mask == c
edge |= ndimage.binary_dilation(m, iterations=width) & ~ndimage.binary_erosion(m, iterations=width)
w = np.ones(mask.shape, dtype="float32")
w[edge] = boost
return w
第二是高分辨率精修。用一个独立的分支或轻量网络在高分辨率特征上专门预测边界,再把边界结果与主分割图融合。这种两阶段思路在建筑提取里很常见,能把边界 IoU 提升几个百分点。
第三是后处理。分割图出来后用条件随机场(CRF)或形态学操作平滑边界,用最小面积过滤去掉孤立小斑。CRF 对边缘对齐效果好但慢,实践中更常用引导滤波或简单的开闭运算。
细小地物的另一面是「被下采样吃掉」。道路、田埂、沟渠这类线状地物宽度可能只有一两个像元,下采样后完全消失。对策是提高输入分辨率、减少下采样次数、或引入专门的多尺度路径保留高频细节。
6. Transformer 与通用分割器选型
Transformer 分割器把影像切成 patch,用自注意力建模任意两个 patch 的关系,天然具备全局上下文,对大范围地物的内部一致性有帮助。遥感里常用三类。
SegFormer 用分层 Transformer 编码器加轻量 MLP 解码器,结构简单、推理快、对输入分辨率不敏感,是性价比很高的选择。它的分层设计保留多尺度特征,比原始 ViT 更适合密集预测。
Mask2Former 把分割统一为掩膜分类,用一组可学习的查询生成掩膜,配合掩膜注意力在局部窗口内计算,精度高且能统一语义与实例分割。代价是训练慢、显存高。
import torch
import torch.nn as nn
class LightSegHead(nn.Module):
# 轻量解码器:融合多层 Transformer 特征
def __init__(self, dims=(64, 128, 320, 512), n_cls=10, embed=256):
super().__init__()
self.proj = nn.ModuleList([nn.Conv2d(d, embed, 1) for d in dims])
self.fuse = nn.Sequential(nn.Conv2d(embed * len(dims), embed, 3, padding=1), nn.BatchNorm2d(embed), nn.ReLU())
self.cls = nn.Conv2d(embed, n_cls, 1)
def forward(self, feats):
size = feats[0].shape[-2:]
up = [nn.functional.interpolate(p(f), size=size, mode="bilinear", align_corners=False)
for p, f in zip(self.proj, feats)]
return self.cls(self.fuse(torch.cat(up, dim=1)))
选型的判据是数据量与算力。样本少、算力有限时,U-Net 或 SegFormer 加遥感预训练权重是稳妥起点;样本充足、追求极致精度时再上 Mask2Former。别一开始就上大模型,遥感标注成本高,样本量通常撑不起大模型。
卷积与 Transformer 并非互斥。混合结构用卷积提取局部细节、用注意力建模全局关系,在遥感分割里往往比纯 Transformer 更省数据。红边与短波红外这类低分辨率波段,用卷积处理比直接进注意力更稳。
7. 大图滑窗推理与拼接
大幅影像无法一次进网络,必须滑窗推理。窗口大小受显存限制,同时要兼顾上下文:窗口太小则大范围地物的上下文不足,太大则显存爆掉。
标准做法是带重叠切块,推理时每块输出类别概率,重叠区按概率累加再平均,最后取 argmax。重叠区平均能有效抑制拼接缝。
import numpy as np
import torch
@torch.no_grad()
def sliding_inference(model, image, n_classes, win=512, stride=384, batch=4):
c, h, w = image.shape
prob = np.zeros((n_classes, h, w), dtype="float32")
cnt = np.zeros((h, w), dtype="float32")
ys = list(range(0, max(h - win, 0) + 1, stride))
xs = list(range(0, max(w - win, 0) + 1, stride))
if ys[-1] != h - win: ys.append(h - win) # 补齐右边界
if xs[-1] != w - win: xs.append(w - win) # 补齐下边界
tiles, coords = [], []
for y in ys:
for x in xs:
tiles.append(image[:, y:y + win, x:x + win])
coords.append((y, x))
for i in range(0, len(tiles), batch):
tb = torch.stack(tiles[i:i + batch])
p = model(tb).softmax(1).cpu().numpy()
for k, (y, x) in enumerate(coords[i:i + batch]):
prob[:, y:y + win, x:x + win] += p[k]
cnt[y:y + win, x:x + win] += 1
return (prob / np.maximum(cnt, 1)[None]).argmax(0)
三个细节决定拼接质量。第一,务必补齐右边界与下边界,range 的写法容易漏掉最后一条带,导致影像右下角系统性缺失。第二,stride 取窗口的 0.5 到 0.75,重叠太小则拼接缝明显,重叠太大则推理耗时成倍增加。第三,如果用了 BatchNorm,推理时的统计必须来自训练集而非当前块,否则块间统计差异会在接缝处造成色阶跳变。
推理还可以用测试时增强:把输入做水平翻转、垂直翻转、旋转 90 度,各推理一次再把概率平均。这能把 mIoU 提升一到两个百分点,代价是耗时翻四倍,适合离线制图。
7.1 显存与窗口尺寸的估算
窗口尺寸受显存约束,估算方式是先算单块激活值再乘批大小。以 512 乘 512 窗口、U-Net 类网络、混合精度为例,激活值约占 2 到 4 GB,加上参数与优化器状态,单卡 16 GB 通常只能跑批大小 4 到 8。
窗口尺寸与显存(粗略,16 GB 单卡,混合精度)
窗口 批大小 激活显存 适用
256 16 2~3 GB 小目标、样本多
512 8 4~6 GB 通用
1024 2 8~12 GB 大范围地物
2048 1 14 GB+ 需分块或梯度检查点
显存不够时有三条路:降窗口、开梯度检查点(用时间换显存)、或用分块卷积把大块拆成小块在通道维拼接。推理阶段还能进一步用 FP16 与算子融合压低占用,但要先验证精度无损。
8. 后处理与拓扑一致性
原始分割图几乎总有噪声:孤立的小斑、地物内部的孔洞、边界锯齿。后处理的目标是让结果既干净又保真。
常用操作按顺序:
- 最小面积过滤:去掉面积小于阈值的连通域,阈值按业务定,建筑提取常取 20 到 50 像元。
- 填洞:把被某类完全包围的小孔洞填成该类,用形态学闭运算或连通域分析。
- 平滑边界:用中值滤波或引导滤波减少锯齿,注意别把细小地物一并抹掉。
- 类别规则约束:用先验知识做硬约束,如「水体不能出现在坡度大于 30 度的区域」,用 DEM 交叉校验。
import numpy as np
from scipy import ndimage
def cleanup(mask, min_area=32, fill_holes=True):
out = mask.copy()
for c in np.unique(mask):
m = mask == c
lab, n = ndimage.label(m)
sizes = ndimage.sum(np.ones_like(lab), lab, range(1, n + 1))
for i, s in enumerate(sizes, start=1):
if s < min_area:
out[lab == i] = 0 # 过小连通域回退为背景
if fill_holes:
for c in np.unique(out):
m = out == c
filled = ndimage.binary_fill_holes(m)
out[filled & (out != c)] = c
return out
拓扑一致性是更高阶的要求。道路网、河网这类结果不仅要像元级正确,还要拓扑正确:道路必须连通、河流不能断头、建筑物不能有自交边界。像元级指标(mIoU)对拓扑错误完全不敏感,一条断开一处的道路 mIoU 可能只掉 0.1%,但业务上完全不可用。需要拓扑正确时,要么在损失里加入连通性约束,要么后处理做骨架化与连接修复。
9. 精度评估与误差归因
mIoU 是主指标,但必须配合每类指标与混淆矩阵才能定位问题。
import numpy as np
def segmentation_metrics(pred, truth, n_classes, ignore=255):
valid = truth != ignore
pred, truth = pred[valid], truth[valid]
cm = np.zeros((n_classes, n_classes), dtype=np.int64)
for t, p in zip(truth, pred):
cm[t, p] += 1
iou = np.diag(cm) / (cm.sum(1) + cm.sum(0) - np.diag(cm) + 1e-9)
f1 = 2 * np.diag(cm) / (cm.sum(1) + cm.sum(0) + 1e-9)
return {"mIoU": np.nanmean(iou), "mF1": np.nanmean(f1), "per_class_iou": iou, "cm": cm}
误差归因按三类拆:
| 误差类型 | 现象 | 归因 | 对策 |
|---|---|---|---|
| 少数类全丢 | 某类 IoU 接近 0 | 类别不平衡 | 加权重、Dice、Focal |
| 边界模糊 | 内部对、边界错 | 混合像元 | 边界损失、高分辨率精修 |
| 小地物消失 | 线状地物断裂 | 下采样过度 | 提高分辨率、减少下采样 |
| 类间混淆 | 两类互相误判 | 光谱可分性差 | 加波段、合并类别 |
| 拼接缝 | 块边界处跳变 | 重叠不足或统计不一致 | 增大 stride 重叠、固定 BN 统计 |
| 跨景失效 | 换一景精度骤降 | 域间隙 | 域适应、多区域训练 |
评估还要做空间分层。把验证区按地物复杂度、云量、季节分层,分别报指标。一个总体 0.78 mIoU 的模型,在城市密集区可能 0.85,在山区林地可能只有 0.6,如果业务覆盖山区,这个数字才是决定性的。
验证样本必须空间独立,随机划分会让相邻像元同时进入训练与验证,精度虚高十几个百分点。做法是按地理块或按影像划分,确保训练与验证之间没有空间重叠。
权衡取舍
- 卷积 vs Transformer:卷积省数据、推理快、对细节友好;Transformer 上下文强但需更多样本与算力,样本少时卷积更稳。
- 早期融合 vs 中期融合:早期融合简单但异构模态难对齐;中期融合保留模态特性但参数量翻倍,异构输入时值得。
- 交叉熵 vs Dice 组合:单一交叉熵在不平衡下失效,Dice 组合更稳但训练早期梯度不稳定,通常先用交叉熵热身再切组合损失。
- 边界精修 vs 端到端:独立精修分支能提边界 IoU 但增加流水线复杂度,端到端更简单,按业务对边界的要求取舍。
- 后处理力度:过滤与平滑能去噪但会误伤细小地物,参数要按最小目标尺寸反推。
- 测试时增强:能稳定提升一到两个点但耗时翻数倍,只适合离线制图,实时服务不用。
- 类别粒度:类别越细越贴合业务,但类间可分性不足会拉低 mIoU,先做可分性分析再定体系。
常见坑清单
- 波段顺序不固定:现象是推理结果全错且难排查,原因是训练与推理的通道顺序不一致,规避方法是把波段顺序写进配置并校验。
- 只用交叉熵:现象是少数类几乎不被预测,原因是类别不平衡,规避方法是加入 Dice 或 Focal 组合损失。
- 归一化用错统计:现象是精度低于预期,原因是沿用了 ImageNet 统计而非本数据集统计,规避方法是按波段统计均值方差。
- 切块漏掉边界带:现象是影像右下角缺失预测,原因是 range 步进未覆盖边界,规避方法是显式补齐最后一行一列。
- stride 等于窗口:现象是结果出现网格状接缝,原因是重叠为零,规避方法是 stride 取窗口的 0.5 到 0.75。
- 推理时 BN 统计漂移:现象是接缝处色阶跳变,原因是块间统计不一致,规避方法是固定推理模式并校验统计量。
- 忽略区口径不一:现象是精度与其他工作不可比,原因是忽略区定义不同,规避方法是明确并记录忽略规则。
- 随机划分验证集:现象是精度虚高,原因是空间自相关导致样本泄漏,规避方法是按地理块划分。
- 后处理过度平滑:现象是道路与田埂断裂,原因是滤波核过大,规避方法是按最小目标尺寸设置核大小。
- 用 mIoU 判断拓扑:现象是道路网断开却指标很高,原因是像元级指标对拓扑不敏感,规避方法是补充拓扑指标或骨架检查。
小结
遥感语义分割的成败,一半在输入与损失,一半在推理与后处理,网络结构的贡献反而没那么大。多光谱与多模态输入提供了自然图像没有的信息红利,类别不平衡与边界精度是必须专门设计损失去解决的问题,大图拼接与后处理则决定了结果能否真正可用。
落地路线建议这样排:先用 U-Net 或 SegFormer 加遥感预训练权重,配交叉熵加 Dice 的组合损失,把切块推理与拼接做扎实,跑通 mIoU 与每类 IoU 的评估;再针对最弱的一类做定向优化,通常是补波段、改损失权重或加边界精修;最后把后处理规则固化并做空间分层的精度报告。别指望换一个更大的模型能解决所有问题,样本质量与评估口径的收益往往更大。
下一步可以对照 遥感影像分类 理解分类与分割在样本、损失与评价上的差异,把云掩膜作为忽略区接入评价流程见 云检测与云掩膜工程实践 ,需要三维信息时结合 LiDAR 点云处理 的 DSM 与点云特征补充高度维度。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。