简介一份聚焦遥感影像海陆分割技术研究的学术文档面向从事遥感图像分析、深度学习语义分割的研究者与工程师。该研究针对传统阈值分割、区域生长等方法在复杂海岸背景下鲁棒性差、海陆边界分割不准确的痛点提出基于Res2Net的多尺度海陆分割网络MSRNet。文档详细阐述了网络架构利用Res2Net提取多尺度特征通过压缩与注意力模块强化弱边界信息并借助深度监督策略提升边界定位精度同时给出两组不同海岸类型数据集的实验对比涵盖多种自然与人工岸线验证了所提方法能获得更完整清晰的海陆边界。资源共1个docx文档压缩包仅345KB包含摘要、引言、网络结构、实验结果与结论等完整章节可系统学习从问题建模、算法设计到实验验证的整个研究流程为海岸线提取、海岛礁识别、近岸目标检测等应用提供可靠技术参考。目前已有128人学习。1. 海陆分割用上 Res2Net一个二分类为什么要折腾多尺度遥感影像语义分割里海陆分割看起来是门槛最低的二分类任务像素不是海就是陆。可真在海岸带数据上跑过一遍的人都知道翻车点全在“看起来简单”的地方——山体阴影被成片识别成水近岸高浊度浅水被漏检一两米宽的细小水沟在结果里直接蒸发。问题的根源是尺度矛盾同一景影像里海岸线和百米宽的大江大河是同一类目标而它们表现的像素尺度差了上百倍。常规分割网络靠堆深度来扩大感受野代价是牺牲细节靠ASPP这类并行空洞卷积做多尺度又受限于卷积核尺寸和显存。Res2Net给了另一个思路不靠额外的结构分支而是在残差块内部把通道分组用层级连接的方式在单个模块里构造出多尺度感受野。这个设计对海陆分割尤其合适因为海岸线场景里的多尺度是持续的、跨像素级的而不是某个固定尺度上的离散变化。本文会从数据准备、模型搭建、训练策略一路讲到边界验证把这条路从头到尾趟一遍。2. 数据准备与标签处理多尺度模型先喂对尺度的数据2.1 海陆分割数据的特殊性与选星策略海陆分割和通用语义分割的数据准备有个关键差异水体和陆地的光谱特征受传感器、大气条件、太阳高度角影响极大不同影像之间的分布偏移非常明显。我在实际项目中分别用过高分二号2m分辨率和 Landsat 系列30m分辨率做训练结论是二者不能混着直接喂给模型否则模型学到的是“某一类传感器的光谱风格”而不是“水”和“陆”的本质区别。常见做法是按传感器分组各自训练或者至少对波段做归一化时按影像统计量分别处理而不是用全局均值和方差。选星策略上需要先明确最终应用的分辨率再决定训练数据。只做大范围海岸线制图用30m级数据就够训练patch可以取256如果任务是监测滩涂养殖池或者精细河口湿地就得上2m级或者更高分辨率的数据patch建议取512到640因为细碎地物需要在更大上下文里才能判得准。一个容易被忽略的坑是投影和重采样带来的边缘误差——影像在做几何校正时重采样会让水陆边界外扩或内缩一两个像素。如果是深度学习训练这个误差会被当作标签噪声吸收掉一部分但后处理做边界统计时可能差出几十米。我一般建议对标签做一次腐蚀或膨胀校验看预测结果是否系统性偏向某一侧。2.2 标签制作与噪声控制海陆分割的标签看起来只是二值图但做过的都知道真正的功夫在边界。水体边界并不是一条干净的线近岸有潮间带、滩涂、浅水区这些区域在影像上的光谱是水和泥沙的混合不同标注员会给出完全不同的边界。控制标签噪声我的做法有三个第一标注时只标确定区域不确定的潮间带单独建一个“ignore”类别训练时不计入损失第二对标签做边缘平滑用半径为3到5像素的高斯滤波作用于标签边缘区域让模型在边界附近输出更柔和的概率而不是强行拟合一个像素级的硬边界第三抽样检查边界质量如果同一区域不同标注员的一致性低于95%需要回到标注规范上重新定义边界判据。另一个数据层面的问题是类别不平衡。海域面积通常远大于陆地或者相反直接拿原始分布训练损失会被多数类主导。我常用的做法是数据采样时按类别占比做重采样让每个训练patch里水体像素占比落在40%到60%之间而不是让模型看全原图。实现上很简单在滑动切patch时如果当前窗口内水体占比太低就丢弃或者偏移窗口位置重切。这个操作比在损失函数里调权重更直观效果也更稳定。2.3 尺度增强与边界样本扩充尺度抖动是对付多尺度问题最直接的数据增强手段。对遥感影像做随机缩放系数取0.75到1.5之间再将缩放后的影像随机裁剪成固定patch这样相当于让模型同时见过“大江大河被缩小”和“小水沟被放大”两种极端尺度配合Res2Net的通道内多尺度结构能把这两种极端情形统一到一个表征空间里。此外边界难例的扩充比单纯增加数据量更有效。具体做法是在训练时按一定的概率我一般取30%去采样包含海岸线的patch而不是全部随机切。实现方式是先对整景影像计算一个水陆边界带掩膜然后随机选边界带内的一个像素作为patch中心点做裁剪。这样模型每个epoch都能多次见到海岸线附近的特征组合能明显减少边界区域的锯齿和孤立误检。提示尺度抖动会在导入模型训练时引入一个看不见的问题——缩放后水体的纹理频率变了训练过程中loss会产生周期性震荡。遇到这个情况优先减小缩放范围而不是增加学习率衰减。3. Res2Net 模块与分割模型搭建通道分组的多尺度从哪来3.1 Res2Net 的通道分组残差结构Res2Net的核心设计是在一个残差块的3x3卷积之前把输入通道沿通道维分成若干组然后让后一组的卷积输入叠加前一组经过卷积后的输出形成层级式的感受野组合。与直接使用不同膨胀率的空洞卷积相比这种方式的好处是多尺度信息是在同一层内通过通道交互自然产生的不需要额外的并行结构也基本不增加参数量。我用PyTorch实现了一个标准的Res2Net模块代码如下import torch import torch.nn as nn class Res2NetBottleneck(nn.Module): def __init__(self, in_channels, out_channels, stride1, scales4, base_width26): super().__init__() self.scales scales mid_channels out_channels // scales # 第一个 1x1 卷积做通道压缩 self.conv1 nn.Conv2d(in_channels, mid_channels * scales, kernel_size1, biasFalse) self.bn1 nn.BatchNorm2d(mid_channels * scales) self.relu nn.ReLU(inplaceTrue) # 按 scales 分组每组的 3x3 卷积独立处理 self.convs nn.ModuleList() for i in range(scales): self.convs.append(nn.Conv2d(mid_channels, mid_channels, kernel_size3, stridestride, padding1, biasFalse)) self.bns nn.ModuleList([nn.BatchNorm2d(mid_channels) for _ in range(scales)]) # 输出卷积恢复通道数 self.conv3 nn.Conv2d(mid_channels * scales, out_channels, kernel_size1, biasFalse) self.bn3 nn.BatchNorm2d(out_channels) # 残差连接的 1x1 卷积处理通道数不匹配 self.shortcut nn.Sequential() if stride ! 1 or in_channels ! out_channels: self.shortcut nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size1, stridestride, biasFalse), nn.BatchNorm2d(out_channels) ) def forward(self, x): identity x x self.relu(self.bn1(self.conv1(x))) # 通道拆成 scales 份前向逐组传递 xs torch.chunk(x, self.scales, dim1) ys [] for i, conv in enumerate(self.convs): if i 0: ys.append(self.relu(self.bns[i](conv(xs[i])))) else: # 第 i 组输入为 xs[i] 与上一组输出 ys[i-1] 相加 ys.append(self.relu(self.bns[i](conv(xs[i] ys[-1])))) out torch.cat(ys, dim1) out self.bn3(self.conv3(out)) out self.shortcut(identity) out self.relu(out) return out逻辑说明第0组只处理自己的通道切片从第1组开始每个3x3卷积的输入是“本组通道切片”加上“前一组卷积输出”。这个残差式的通道级联让第一组的感受野最小越靠后的组感受野越大但所有组共享同一组权重以外的独立参数。输出时把各组concat起来再用1x1卷积融合。参数说明scales控制分组数论文和大多数复现用4base_width表示每组的通道基数控制模块宽度。这里mid_channels是组内通道数等价于base_width乘以扩张系数。需要注意stride2时所有组的卷积都会做空间下采样但层级传递关系不变。3.2 把 Res2Net 接到 U 型分割头上单有backbone还不够分割任务需要一个解码器把多尺度特征恢复到像素分辨率。我常用的是一个轻量U型结构编码器各层输出通过跳跃连接送入解码器解码器逐级上采样并concat对应层特征再做两个3x3卷积融合。Res2Net负责把“多尺度”做进特征提取阶段解码器只需要做简单的特征恢复和边界锐化不需要额外堆ASPP或者FPN——这也是Res2Net方案在显存和速度上有优势的地方。下面给出一段可运行的模型组装代码backbone直接使用预训练的Res2Net-50并裁剪掉最后的全局池化和全连接层import torch import torch.nn as nn import torchvision.transforms.functional as F class Res2NetUNet(nn.Module): def __init__(self, backbone, num_classes2): super().__init__() # 取 Res2Net 主干保留到 layer4 self.layer0 backbone.conv1 self.layer1 backbone.bn1 self.layer2 backbone.relu self.layer3 backbone.maxpool self.layer4 backbone.layer1 # 1/4 分辨率 self.layer5 backbone.layer2 # 1/8 分辨率 self.layer6 backbone.layer3 # 1/16 分辨率 self.layer7 backbone.layer4 # 1/32 分辨率 # 解码器逐级上采样 跳跃连接 self.up1 nn.Sequential(nn.ConvTranspose2d(2048, 1024, 2, 2), nn.BatchNorm2d(1024), nn.ReLU(inplaceTrue)) self.dec1 nn.Sequential(nn.Conv2d(2048, 1024, 3, 1, 1), nn.BatchNorm2d(1024), nn.ReLU(inplaceTrue)) self.up2 nn.Sequential(nn.ConvTranspose2d(1024, 512, 2, 2), nn.BatchNorm2d(512), nn.ReLU(inplaceTrue)) self.dec2 nn.Sequential(nn.Conv2d(1024, 512, 3, 1, 1), nn.BatchNorm2d(512), nn.ReLU(inplaceTrue)) self.up3 nn.Sequential(nn.ConvTranspose2d(512, 256, 2, 2), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue)) self.dec3 nn.Sequential(nn.Conv2d(512, 256, 3, 1, 1), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue)) self.up4 nn.Sequential(nn.ConvTranspose2d(256, 128, 2, 2), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue)) self.dec4 nn.Sequential(nn.Conv2d(256, 128, 3, 1, 1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue)) self.head nn.Conv2d(128, num_classes, 1) def forward(self, x): # 编码 x self.layer0(x) x self.layer1(x) x self.layer2(x) x self.layer3(x) e1 self.layer4(x) # 1/4 e2 self.layer5(e1) # 1/8 e3 self.layer6(e2) # 1/16 e4 self.layer7(e3) # 1/32 # 解码 d self.up1(e4) d torch.cat([d, e3], dim1) d self.dec1(d) d self.up2(d) d torch.cat([d, e2], dim1) d self.dec2(d) d self.up3(d) d torch.cat([d, e1], dim1) d self.dec3(d) d self.up4(d) d torch.cat([d, e1.shape[2] and F.resize(e1, d.shape[2:]) or e1], dim1) # 尺寸对齐 d self.dec4(d) return self.head(d)逻辑说明Res2Net-50的标准结构包含conv1到layer4五个stage。上面代码把layer1到layer4当作编码器的四个分辨率层级对应的下采样倍率分别是4、8、16、32。解码器每次上采样2倍并和同层级的编码器特征做通道拼接用3x3卷积融合后进入下一级。最后一层用1x1卷积输出类别logits。参数说明解码器各层通道数从2048开始逐级减半这是为了配合Res2Net-50的输出通道数。如果换成Res2Net-101需要把2048改成3072其余结构不变。拼接前的尺寸对齐我在代码里用了F.resize做动态缩放实际使用中也可以用torch.nn.functional.interpolate效果等价。3.3 backbone 替换的注意事项Res2Net在ImageNet上有预训练权重但直接拿来跑遥感影像有两个注意事项。第一是输入通道数如果只用RGB三波段不需要改但如果要加入近红外波段或者NDWI指数就需要把backbone第一层卷积的权重做均值复制或截断初始化。我一般会把近红外作为第四个通道拼进去第一层卷积输入改成4通道新的权重用原有三通道权重的均值初始化跑出来的效果通常比单纯用RGB好1到2个点的IoU。第二是BatchNorm统计量遥感影像的分布和自然影像差异大预训练的BN均值和方差在头几个epoch会被快速修正所以训练初期学习率不宜过大建议前5个epoch用线性预热到目标学习率避免BN统计量和网络权重“互相打架”。提示如果显存紧张可以把解码器的卷积通道整体除以2即解码器通道数按1024、512、256、64递减。代价是边界精度略降但对于大范围海陆制图够用。4. 训练策略与损失设计类别不平衡在水陆边界上怎么解4.1 损失函数组合BCE、Dice 与 Focal 的取舍海陆分割虽然是二分类但直接使用BCE损失会在边界上给出模糊预测。我实测下来的结论是纯BCE训练出来的模型在海岸线附近的输出概率只有0.6到0.8做阈值分割后边界呈锯齿状加上Dice损失之后边界明显收紧但小目标容易丢失再加上Focal损失细小支流和破碎水体的召回明显提升。推荐的组合是import torch.nn.functional as F def hybrid_loss(pred, target): # pred: [B, 2, H, W] logits # target: [B, H, W] long, {0, 1} bce F.cross_entropy(pred, target) # Dice loss prob F.softmax(pred, dim1)[:, 1, :, :] # 取水体类别概率 smooth 1.0 intersection (prob * target.float()).sum(dim(1, 2)) union prob.sum(dim(1, 2)) target.float().sum(dim(1, 2)) dice 1 - ((2 * intersection smooth) / (union smooth)).mean() # Focal loss集中处理边界难例 log_prob F.log_softmax(pred, dim1) pt torch.exp(log_prob.gather(1, target.unsqueeze(1)).squeeze(1)) focal -(1 - pt).pow(2) * log_prob.gather(1, target.unsqueeze(1)).squeeze(1) focal focal.mean() return 0.4 * bce 0.4 * dice 0.2 * focal逻辑说明BCE提供稳定的梯度信号Dice loss直接优化IoU指标Focal loss的(1-pt)^2调制因子让模型把注意力放在置信度低的像素上——在海岸线附近和水陆光谱接近的区域这些“低置信度像素”正是难例聚集的地方。参数说明三个损失的权重0.4/0.4/0.2是我在多个数据集上试过的均衡值。如果发现模型倾向于漏检召回低把Focal的权重提高到0.3如果发现模型乱切边界精度低把Dice权重提高到0.5。smooth1.0是Dice loss的标准平滑项防止分母为0。4.2 训练超参与学习率策略分割模型训练超参有一个比较稳的起点参数建议值说明优化器AdamW权重衰减设为1e-4比Adam收敛更稳初始学习率1e-4如果用了第3.3节的BN预热可以放宽到3e-4训练轮数60海陆分割收敛比多分类分割快40轮后进入平台期batch size8取决于patch大小和显存patch512时8是相对稳的损失权重BCE 0.4 / Dice 0.4 / Focal 0.2按第4.1节尺度抖动0.75 ~ 1.5见第2.3节学习率衰减我建议使用poly策略即lr lr_init * (1 - epoch / max_epoch) ** 0.9而不是固定间隔的阶梯衰减。遥感数据每个epoch的方差比自然图像大poly的慢衰减让模型在后期有更多时间稳定住海岸线附近的细节预测。另外EMA指数移动平均对分割模型的BN层有稳定作用我一般会设置ema_decay0.999在验证时用EMA权重而不是临时权重。4.3 推理的后处理概率图与形态学操作模型输出的是每个像素的概率值0.5阈值切分只是第一步。为了产出干净的二值图我会在阈值之后做两步后处理。第一步是形态学开闭运算先用3x3的核做开运算先腐蚀后膨胀去掉水体内部和陆地内部的孤立噪点再做闭运算先膨胀后腐蚀填补水体内部的细小空洞。这两个操作的顺序不能反反了会把细小的水沟直接抹掉。第二步是连通域过滤用scipy.ndimage.label标记连通区域然后删除面积小于阈值的独立区域。海陆分割场景里面积阈值一般取影像总像素数的十万分之一到百万分之一之间。比如512x512的一个patch去掉小于10到20个像素的连通域是合理的。这一步对去除“单一建筑物阴影”“小水洼误检”非常有效。5. 常见坑与排查水陆边界翻车的 4 个现场5.1 山体阴影成片被识别成水体现象模型在山地丘陵的地形阴影区域输出了大范围水体概率超过0.7而这些区域在真值标签里明明是陆地。原因阴影区域在可见光波段的反射率低、纹理平滑和水体在RGB空间的分布高度重叠。如果训练数据里水体样本多而阴影样本少模型就会把“暗且平滑”归纳为水。解决最有效的手段是给训练数据增加“阴影样本”。我常用的做法是把NDWI指数作为额外输入通道——水体在近红外波段吸收强而阴影在近红外波段反射率虽然低但仍高于水体加入NDWI通道后模型能更容易从光谱上区分二者。效果上在山区海岸带测试集上加入NDWI通道可以让误检面积下降30%以上。如果不想改模型输入也可以在数据增强时对暗区域做亮度抖动让模型见过更多“暗但属于陆地”的样本。5.2 近岸高浊度浅水被漏检现象近岸几米深的浅水区、入海口泥沙混合区模型预测为陆地。单独看召回率并不低但恰恰是海岸带管理最关心的区域错得最厉害。原因浅水区的光谱是“水泥沙”的混合反射率偏高在水体训练样本里这类边缘情形占比低。模型学到的水体特征偏向“深蓝清澈”的水。解决第一在数据策略上强制采样河口和滩涂区域具体做法是围出这些区域的ROI在ROI内额外多切patch。第二训练时对水体样本做随机颜色增强包括饱和度抖动和对比度拉伸模拟不同浊度水体的光谱范围。第三在损失里把边界区域权重抬高比如在Dice loss里对边界带内像素额外加0.5的权重强制模型把不确定区域往水体侧偏移。5.3 大江大河断线、细小支流完全消失现象验证集整体IoU不错但可视化时发现干流河道在中段断开了或者支流只剩上游一小截下游完全没有检测到。原因深层特征的感受野大能识别出“这里是水体”的全局信息但解码器在上采样恢复细节时细小水道只占几个像素宽经过池化后容易被边缘特征淹没。解决有两个有效手段。第一是在训练时使用辅助监督——把编码器第二层和第三层的输出各接一个1x1卷积和损失让低层特征直接学习水体细节而不是把所有压力给到最后融合层。第二是训练时保持分辨率输入patch不要太小512是底线我实际测试中patch从384提升到640后细小支流的IoU能提升3到5个点。5.4 验证集指标虚高可视化完全不像一回事现象mIoU达到0.92看起来成绩很好但把预测结果和原始影像叠加后海岸线位置偏移了十几个像素管理部门拿这个结果去做面积统计会差出几条街。原因OIoU是个区域重叠指标水体面积大海岸线只占整体很少的像素。也就是说即使海岸线周围宽带上所有像素全错对整体IoU的影响也不超过1%。常规语义分割指标根本衡量不了这种“宽边界误差”。解决不要只看mIoU和OA增加两个辅助指标沿真实海岸线向外扩10个像素的边界带单独统计这个带的IoU可以叫Boundary IoU另外统计预测海岸线与真实海岸线的平均距离用距离变换图求均值量化边界偏移的像素量级。这两个指标在模型选择上比mIoU更有参考价值。6. 验证方法与提升技巧关注海岸线而不仅是区域准确率6.1 用边界带指标和距离变换评估海岸线质量海陆分割的最终应用总是落到海岸线位置上所以验证环节我建议把两类指标分开看。区域精度看mIoU和F1-Score边界精度单独看两个指标第一个是Boundary IoU实现上先对真实标签提取边界带——用二值图减掉腐蚀后的二值图得到宽度数像素的边界像素集合再对预测图做同样的操作最后计算这两个边界带在宽度方向扩展一定像素后的IoU。我通常扩展5到10个像素并让这个指标随扩展宽度变化形成一个曲线观察曲线斜率能直观看出预测边界和真实边界是平行还是交叉偏差方向一目了然。第二个是平均边界距离Average Boundary Distance做法是对真实标签和预测标签分别做距离变换然后在真实边界所在位置采样预测边界距离值取平均。这个指标的解读很直接平均距离是3个像素意味着预测海岸线平均偏离真实海岸线3个像素对应到30米分辨率影像上就是约90米。6.2 低成本提升边界精度的三个技巧如果模型已经训练完不打算重新训又想让边界更干净一点有三个低成本的技巧按性价比排序第一是测试时增强TTA。推理时对输入做水平翻转、垂直翻转和180度旋转得到四张概率图取平均后再做阈值分割。这个操作基本白捡0.5到1个点的Boundary IoU代价是推理时间变成4倍。第二是密集CRF后处理。用pydensecrf对模型输出的概率图做全局优化常说的参数是dxyz10, dbi5, compat8这套参数在大多数遥感影像上能起到平滑同类区域、锐化边界的作用。但需要注意CRF对GPU推理流水线是额外的CPU开销在批量生产环境中要把这一步骤放到并行线程里否则会成为吞吐瓶颈。第三是滑动窗口预测时的重叠策略。推理时不要用相邻窗口无缝拼贴而是让窗口之间保留20%到25%的重叠对重叠区域的预测概率做加权平均。这样能消除窗口边界处的接缝效应效果和TTA类似代价是推理时间增加约25%。我自己做遥感分割项目养成了一个习惯每轮验证先看可视化切片再看定量指标指标提升但可视化没有明显变好大概率是过拟合到了数据集噪声上。这个习惯帮我避掉很多无效调参的坑。模型做出来是要交付给人用的最终效果永远是用眼睛和现场测量来验收而不是看一个孤立涨了0.5个点的mIoU。希望这篇文章能帮你在海陆分割这条路上少踩几个已经存在的坑。本文还有配套的精品资源点击获取