遥感语义分割

本文深入讲解遥感语义分割的工程实现,覆盖编码器解码器与多尺度结构、多光谱与多模态输入融合、类别不平衡下的损失函数设计、边界与细小地物的精细处理、Transformer 与通用分割器的选型,以及大图滑窗推理拼接、后处理拓扑一致性和 mIoU 误差归因分析,并给出可复用的损失与滑窗推理代码。

引言

语义分割是遥感里最主流的密集预测任务,目标是为每个像元赋予一个地物类别标签,输出一张与原图同尺寸的分类图。它服务于土地利用制图、建筑物提取、水体与湿地范围、道路网提取、冰川与农作物分布等场景。与场景分类相比,分割给出的是空间上连续、可用于面积统计的结果;与目标检测相比,它不区分同类实例,但对不规则地物的边界刻画更自然。

工程难点有四个。第一是输入,遥感影像常有多于三个波段,还可能有 SAR、DEM、时序影像等异构模态,如何融合决定了信息上限。第二是类别不平衡,水体、建筑等目标常只占影像的百分之几,交叉熵会被背景主导,模型倾向于全判为背景。第三是边界,地物交界处的像元是混合光谱,模型在这些像元上的置信度天然偏低,而边界往往是业务最关心的部分。第四是大图,一景 Sentinel-2 影像上亿像元,无法一次进网络,必须切块推理再无缝拼接。

本文按「结构到训练再到推理」展开。先定义任务与评价口径,再讲网络结构、多模态融合、损失设计、边界处理与 Transformer 选型,最后落到大图推理、后处理与精度归因。分类任务的对照见 遥感影像分类 ,多模态里的点云输入见 LiDAR 点云处理 。

目录

  1. 分割任务的定义与评价口径
  2. 编码器解码器与多尺度结构
  3. 多光谱与多模态输入融合
  4. 类别不平衡与损失函数设计
  5. 边界与细小地物的处理
  6. Transformer 与通用分割器选型
  7. 大图滑窗推理与拼接
  8. 后处理与拓扑一致性
  9. 精度评估与误差归因

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 LossDice 的精确召回可调版好好
边界损失对边界像元加权一般很好
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. 后处理与拓扑一致性

原始分割图几乎总有噪声:孤立的小斑、地物内部的孔洞、边界锯齿。后处理的目标是让结果既干净又保真。

常用操作按顺序:

  1. 最小面积过滤:去掉面积小于阈值的连通域,阈值按业务定,建筑提取常取 20 到 50 像元。
  2. 填洞:把被某类完全包围的小孔洞填成该类,用形态学闭运算或连通域分析。
  3. 平滑边界:用中值滤波或引导滤波减少锯齿,注意别把细小地物一并抹掉。
  4. 类别规则约束:用先验知识做硬约束,如「水体不能出现在坡度大于 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 与点云特征补充高度维度。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「遥感与空间数据」更多文章

  1. 云原生遥感处理
  2. 卫星平台与任务规划
  3. 高光谱遥感处理