
简介面向遥感图像语义分割任务这份zip资源提供了结合自注意力机制、膨胀卷积与高分辨率网络HRNet的完整实现适合研究生、算法工程师及遥感应用开发者用于地物分类、变化检测等精细解译场景。包内共100个文件以61个Python源码为主包含网络定义、训练与推理脚本另有26个编译后的pyc文件以及C/CUDA扩展源码、XML/IML工程配置、LICENSE等便于直接运行或二次开发压缩包仅145KB轻量易部署。已有191人学习下载。资源内可找到网络核心结构、训练入口与底层加速实现等关键模块理解“注意力膨胀卷积HRNet”的融合方式并能加载代码库对自有遥感影像执行语义分割同时通过阅读底层扩展源码可深入优化加速细节为后续调整模型结构、膨胀率或训练流程提供完整参考。1. 高分辨率还能不能打把注意力机制和膨胀卷积装进 HRNet遥感图像语义分割和普通自然图像分割最大的差异在于目标尺度跨度极大且背景极度复杂。一颗树的阴影可能只有几个像素而一片农田的边界却要依赖上下文纹理来闭合。把 512×512 的输入直接扔进分类网络后接上采样边缘细节基本就没救了。所以遥感赛道里捍卫高分辨率特征不是风格问题是命门。HRNet 从第一层开始就把高分辨率分支保留到底天然适合遥感这种“小目标密度高、边界语义弱”的数据。但 HRNet 也吃亏——感受野偏小多尺度上下文不够用纯靠卷积抓不住大范围依赖。把通道注意力、空间注意力和膨胀卷积按正确的姿势组合进去等于在高分辨率骨架上同时补了“看哪里”和“看多大”两块短板。这篇文章直接把一条能落地的方案讲透结构怎么改、损失怎么配、膨胀率怎么设、推理时影像太大怎么切块。不整花活只讲遥感语义分割里真正会遇到的取舍。2. HRNet 能成为遥感语义分割骨架靠的不是“高分辨率”三个字2.1 HRNet 和 ResNet 系骨架在高分辨率保持上的本质差异先看 ResNet 系的做法输入图像逐步降采样到 1/32 分辨率提语义再靠上采样把空间信息找回来。这一去一回边缘和细碎目标的响应已经被平均掉了。遥感图像里的房屋边缘、裸土和植被过渡带恰恰是这类高频信息。HRNet 把这条路反过来了。它不是先降再升而是从一开始就用一个高分辨率分支后续每下一层分辨率就新开一个分支四个分支彼此并行。关键操作是重复的多分辨率融合每个 stage 结束不同分辨率的特征互相交换信息。高分辨率分支接收低分辨率分支带来的语义上下文低分辨率分支则拿到高分辨率的空间细节。因为高分辨率分支从第一层到最后一层始终存在所以空间位置信息从来没被整体丢掉。# 以 torch 伪代码表示 HRNet 分支融合的核心理念 def fuse_branches(branches): # branches 是不同分辨率的特征列表 target_shape branches[0].shape[-2:] # 以最高分辨率分支为准 fused branches[0] for feat in branches[1:]: if feat.shape[-2:] ! target_shape: feat F.interpolate(feat, sizetarget_shape, modebilinear, align_cornersFalse) fused fused feat return fused这段代码代表的是最朴素的融合方式直接上采样后相加。真实 HRNet 里用的是可学习的 1×1 卷积完成分辨率对齐但思想一致——把低分辨率分支的语义信息逐步“喂”回高分辨率分支。参数上HRNetV2-W18 大约 21M 参数HRNetV2-W48 约 66M比同量级 ResNet 略重但换来的是特征图分辨率整体抬高。2.2 HRNet 输出特征应该怎么取V1 和 V2 的差异语义分割任务上到底取哪个分支的输出很多人一开始会搞错。HRNetV1 只保留高分辨率分支输出用来做关键点检测没问题HRNetV2 把四个分支全部上采样到最高分辨率再 concat再接分割头。遥感语义分割要用 V2 的输出原因很简单低分辨率分支带的是全局语义高分辨率分支带的是局部细节concat 之后分割头能在同一组特征里同时看到两者。实际使用时建议这样组织解码器输入# 取 HRNetV2 的四个层输出 feat1, feat2, feat3, feat4 hrnet(x) # 分辨率分别为 1/4, 1/8, 1/16, 1/32 for i, feat in enumerate([feat2, feat3, feat4]): size feat1.shape[-2:] feat F.interpolate(feat, sizesize, modebilinear, align_cornersFalse) features.append(feat) # 输出通道数 sum(各分支通道数)再接分割头这里有个参数细节值得注意align_cornersFalse是分割任务的标准选择原因是它把像素中心对齐而不是角点对齐对边缘的偏移影响更小。2.3 遥感场景下 HRNet 的已知短板感受野不足与计算量问题HRNet 高分辨率分支的卷积核基本是 3×3堆叠再多层感受野也是线性增长。遥感图像里一个 1024×1024 的影像可能同时包含几米宽的屋顶和几百米宽的湖泊。要让网络“看到”湖泊的整体边界感受野必须覆盖足够大的区域——这是 HRNet 原版架构最别扭的地方。另一个问题是计算量。高分辨率分支始终保持在 1/4 分辨率以 512×512 输入为例这个分支的特征图是 128×128×C通道数又不可能太少整体 FLOPs 明显高于 ResNet-50 类骨架。所以遥感场景里直接替换原版 HRNet 会发现训练速度和显存消耗都上了一个台阶。这不是 HRNet 的缺陷而是高分辨率策略本身的代价解决方向不是砍掉高分辨率分支而是用注意力机制和膨胀卷积把有限的计算用到刀刃上。3. 注意力机制不是“加个模块”那么简单从通道到空间的遥感适配3.1 SE、CBAM、CA 和自注意力到底各自适合解决什么问题很多人在 HRNet 的每个 block 后面无脑接一个 CBAM训完发现涨点有限。原因很现实——注意力机制的作用范围不同解决的问题也不同。SE 注意力机制只做通道维度的重标定全局平均池化得到通道描述向量经过两个全连接层学习通道间的依赖关系。对遥感图像而言SE 能帮网络区分“植被绿”和“水体蓝”这些通道响应模式但它完全不做空间上的选择。CBAM 在 SE 的基础上加了空间注意力分支用通道均值池化和最大值池化拼出空间注意力图能告诉网络“哪个位置更重要”——但它的空间注意力本质上是局部操作对长距离依赖没有建模能力。CA 注意力机制则是把空间信息分别沿水平和垂直方向编码对遥感图像特别有意义。因为遥感目标往往具有方向性比如道路沿某一方向延展CA 能把这种位置感知编码进通道权重。自注意力机制包括多头自注意力原理能建模任意像素之间的长程依赖但计算复杂度是 O(HW)²遥感影像动辄上百万像素直接用在特征图上会直接爆显存。# CA 注意力的核心对空间维度做两个方向的池化 def coordinate_attention(x): b, c, h, w x.shape # 沿高度方向全局平均池化输出形状 [b, c, h, 1] x_h x.mean(dim3, keepdimTrue) # 沿宽度方向全局平均池化输出形状 [b, c, 1, w] x_w x.mean(dim2, keepdimTrue) # 后续拼接、卷积、分离再与原始特征相乘CA 对道路、河流这类定向目标非常有效而 CBAM 更适合目标尺度均匀的场景。这意味着选择注意力模块前要先看你的标注数据里哪种目标占主导而不是照搬论文里的结构。3.2 在 HRNet 的哪个位置插入注意力最划算这是整个结构设计里最关键的问题。在 HRNet 的每个 basic block 后加 CBAM计算开销增加 10% 到 20%但涨点可能只有 1 个 mIoU。我通常会分三档来插第一档是在 1/8 分辨率及其以下分支的 block 后加轻量 SE 或 CA。理由是这些分支负责语义信息提取通道重标定能强化类别相关的响应。第二档是在融合阶段之后、进入下一 stage 之前加一个 CBAM 或坐标注意力这时特征图包含了多分辨率信息注意力能直接做跨尺度的特征选择。第三档是分割头前——这是收益最稳定的一处因为解码器的输入来自四个分支的 concat通道数多、信息冗余大在分割头前加一个通道注意力机制来压缩冗余效果会在 mIoU 上稳定体现。需要注意的是别在一个分支里反复堆叠多种注意力模块。通道注意力机制和自注意力机制各有建模侧重叠多了只会让梯度传播路径变长遥感数据量本来就有限很容易过拟合。3.3 一个可复现的 CBAM 实现与参数设置CBAM 的实现不复杂但两个全连接层的压缩比设置直接影响效果import torch import torch.nn as nn class ChannelAttention(nn.Module): def __init__(self, in_channels, reduction16): super().__init__() self.mlp nn.Sequential( nn.Conv2d(in_channels, in_channels // reduction, 1, biasFalse), nn.ReLU(inplaceTrue), nn.Conv2d(in_channels // reduction, in_channels, 1, biasFalse), ) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out self.mlp(x.mean(dim(2, 3), keepdimTrue)) max_out self.mlp(x.amax(dim(2, 3), keepdimTrue)) return self.sigmoid(avg_out max_out) class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super().__init__() self.conv nn.Conv2d(2, 1, kernel_size, paddingkernel_size // 2, biasFalse) self.sigmoid nn.Sigmoid() def forward(self, x): avg_out x.mean(dim1, keepdimTrue) max_out x.amax(dim1, keepdimTrue) return self.sigmoid(self.conv(torch.cat([avg_out, max_out], dim1)))reduction16是经验值遥感图像类别多且语义相近时比如区分草地和耕地建议把 reduction 降到 8保留更多通道描述能力。空间注意力卷积核大小设为 7覆盖范围更大对边缘过渡带更敏感但如果目标是道路这类细长结构kernel_size3 反而更好因为大卷积核会把相邻的干扰信息也融合进来。4. 膨胀卷积怎么接才不破坏 HRNet 的高分辨率优势4.1 膨胀卷积的语义感受野扩大而不降低分辨率普通卷积在 stride1 的情况下每层只让感受野线性增长。膨胀卷积通过在卷积核内填充空洞来跳着采样输入膨胀率为 d 的 3×3 卷积实际感受野相当于 (2d1)×(2d1)而参数量和计算量不变。对 HRNet 而言膨胀卷积是补感受野短板最自然的手段——因为它的特征图分辨率本来就高不需要通过降采样来扩大感知范围。问题在于怎么选膨胀率。我用一个很直观的准则来处理每个 level 的分支配一组膨胀率1/4 分辨率分支用小膨胀率因为它的特征图大盲目用大膨胀率会引入太多无效背景信息1/16 和 1/32 分支用大膨胀率它们是语义信息的主载体。# 建议的分支膨胀率配置 dilation_config { low_level: [1, 2], # 1/4 分辨率分支 middle_level: [2, 4], # 1/8 分辨率分支 high_level: [4, 8], # 1/16 和 1/32 分支 }4.2 用 ASPP 还是手动替换卷积一个结构上的取舍直接把 HRNet 里所有 3×3 卷积换成膨胀卷积会带来一个严重问题——网格伪影。当多个膨胀卷积串联且膨胀率互为倍数时特征图会出现棋盘格状的信息丢失。这是遥感图像分割里最容易被忽略的结构性坑。解决方式有两种一种是在低分辨率分支上叠加 ASPP 模块用并行多膨胀率的卷积组合最后融合另一种是手动把特定位置的卷积替换为膨胀卷积但必须控制膨胀率的组合方式。实操中 ASPP 更稳因为它把不同膨胀率的特征融合起来网格伪影可以被其他分支的信息填充。# 简易 ASPP 模块用于替换 HRNet 最小分辨率分支后的解码器输入 class ASPP(nn.Module): def __init__(self, in_channels, out_channels, rates[1, 6, 12, 18]): super().__init__() self.branches nn.ModuleList() for rate in rates: if rate 1: self.branches.append(nn.Conv2d(in_channels, out_channels, 1, biasFalse)) else: self.branches.append( nn.Conv2d(in_channels, out_channels, 3, paddingrate, dilationrate, biasFalse)) self.project nn.Sequential( nn.Conv2d(out_channels * len(rates), out_channels, 1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue)) def forward(self, x): feats [branch(x) for branch in self.branches] return self.project(torch.cat(feats, dim1))ASPP 的四个膨胀率固定为 1、6、12、18这是 DeepLab 系列沉淀下来的配置。它在 512×512 输入下能覆盖大约 60 到 100 像素范围的感受野跨度对遥感图像里的农田地块、道路交叉口这类中尺度目标足够用。如果目标更大比如要分割整片水域更建议把 input 尺寸调大而不是无限加大膨胀率。4.3 膨胀卷积和注意力的协同方式先筛选再扩大结构顺序上我建议在 HRNet 融合后的特征上先做注意力加权再接膨胀卷积提取多尺度上下文。逻辑是注意力先告诉网络哪些位置值得看膨胀卷积再把这些位置周围的信息放大收集进来。反过来先膨胀后注意力也行但效果往往不如前者——膨胀卷积会把噪声也放大注意力来不及做筛选。5. 遥感数据集的标注与预训练策略决定了模型上限5.1 遥感图像标注的现实约束与最小样本方案遥感语义分割数据集标注成本比自然图像高得多。一般流程是先在目标区域用简易工具勾出类别边界再用 GEEGoogle Earth Engine导出历史影像做预标注最后人工修正。但精度要求高的任务比如建筑物提取还是得手动标注。样本量不足时常见做法是在 ImageNet 预训练的 HRNet 基础上微调。然而遥感图像的光谱分布和自然图像相差很大尤其多光谱影像通道数都对不上直接复用 ImageNet 权重在首层是无效的。一个有效的最小样本方案是如果数据量少于 5000 张优先冻结 HRNet 前两个 stage只训练后面的 stage 和解码器让网络先学遥感特有的纹理结构再解冻全部层做整体微调。这里有个参数经验供参考初始学习率用 0.001 的十分之一即 0.0001。# 冻结前两个 stage 的参数 for name, param in hrnet.named_parameters(): if name.startswith(stage1) or name.startswith(stage2): param.requires_grad False5.2 训练过程的必备设置Loss、学习率与类别不均衡遥感语义分割最常见的训练问题是类别不均衡。建筑物、道路这类目标在整幅影像里占比往往不到 10%而植被、裸土可能占 60% 以上。光用交叉熵损失模型会偏向多数类。实践上我常用 Lovasz-Softmax 或 Dice Loss 与交叉熵按 1:1 加权组合。# 组合损失交叉熵 Dice Loss def combined_loss(logits, target, epsilon1.0): ce nn.functional.cross_entropy(logits, target) pred torch.softmax(logits, dim1) target_onehot F.one_hot(target, num_classespred.shape[1]).permute(0, 3, 1, 2).float() dice 1 - (2.0 * (pred * target_onehot).sum() 1.0) / (pred.sum() target_onehot.sum() 1.0) return ce epsilon * diceepsilon控制 Dice Loss 的比重建议从 1.0 开始如果验证集 mIoU 提升缓慢可以加大到 2.0 甚至 3.0。学习率的调整策略推荐用 poly 衰减lr * (1 - iter/total_iter)^0.9比 StepLR 在遥感数据上更平滑。优化器选 AdamWweight decay 设为 0.01 或 0.05比 Adam 默认的 0 更稳。6. 推理阶段的几个硬经验切块策略、评价指标与模型导出遥感影像通常是几千乘几千的大图HRNet 加上注意力机制和膨胀卷积整图推理显存会直接爆掉。标准做法是滑窗切块将大图切成 512×512 或 1024×1024 的重叠块重叠率一般设为 10% 到 20%推理完再拼回去。拼回去时重叠区域取平均值可以减轻边缘伪影。切块大小直接决定 mIoU 指标的表现。一个小目标是道路块越小越容易保留局部连续性大目标是水域块太小会让模型丢失上下文导致分割结果支离破碎。我的经验是如果目标尺度特征差异极大先用 1024×1024 跑一遍把预测置信度低的地方再切小块重跑——这个策略比单纯调模型结构涨点更明显。验证阶段不要只看 mIoU遥感语义分割还要额外关注边界 F1-score 和类别 IoU 的方差。边界 F1-score 能反映注意力机制对边缘的改善程度类别 IoU 方差则能暴露模型是整体均衡地变强还是只提升了那几个占比大的类。# 推理结束后统计各类别的 IoU用 torchmetrics 输出报告 python eval.py --checkpoint best_model.pth --input_dir ./val_imgs --output_dir ./pred_masks --patch_size 1024 --overlap 0.2最后提醒一个经常翻车的地方分块推理时 BatchNorm 的统计量会变化。一旦模型在单张图上做切块推理BatchNorm 的 running_mean 和 running_var 会受单 batch 统计影响结果和训练时不一致。解决方案是推理前把 BatchNorm 切换成 eval 模式model.eval()并确保用预训练时累积的全局统计量而不是直接沿用训练模式下的 batch 统计。如果显存允许用梯度累积等价增大 batch size比调低分辨率更有利于稳定 BatchNorm 行为。本文还有配套的精品资源点击获取