ARTICLE DETAIL

资讯详情

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

FlowS-Unet卫星图变化检测模型:结构解析与工程实践

FlowS-Unet卫星图变化检测模型:结构解析与工程实践 简介针对国土监察中建筑物建、拆、改、扩的监管需求遥感图像变化检测已成为重要手段。这份基于FlowS-Unet的遥感图像建筑物变化检测文档面向深度学习与遥感图像处理领域的研究人员、工程师及高校学生系统阐述了如何改进U-Net以精准检测不同时期卫星影像中建筑物的变化区域。文档先从人工巡查与视频监控的局限引入梳理传统图像处理方法在配准精度、数据适配上的痛点继而介绍FCN、U-Net、FlowNet等深度学习模型的发展脉络重点讲解FlowS-Unet的网络结构、细化特征融合思想及其在QuickBird影像上的实验对比与竞赛成绩能帮助读者快速把握从问题建模到模型设计的完整思路。资源为单个docx文件大小约322KB阅读轻便目前已有314人学习适合作为遥感变化检测、语义分割方向的学习笔记或论文写作参考。1. FlowS-Unet 是在解决卫星图变化检测的哪个痛点国土监察业务中真正难啃的不是“哪里有房子”而是“房子的建、拆、改、扩怎么自动发现”。人工巡查在大城市根本不现实高清摄像头加 GPS 的方案只适合小范围试点建设成本和维护周期都太长。卫星影像覆盖广、成本低但拿两期影像直接做差会得到满屏噪声不同拍摄角度导致建筑倾斜、拼接区域色调不一致、云和雾覆盖任何一项都能把传统分割模型打回原形。FlowS-Unet 是 2017 年阿里云天池“广东政务数据创新大赛-智能算法赛”的参赛方案最终拿了综合第 2 名。它把两期影像合并成 8 通道输入端到端输出变化建筑物的二值图不需要精确配准也不需要先把所有建筑物分割出来再相减。后处理前 F1 分数 0.933比同条件下 U-Net 的 0.898 高出近 4 个百分点。如果你在做遥感图像变化检测、建筑物提取或者想给 U-Net 类模型加“时相变化”能力这套思路可以直接复用。2. FlowS-Unet 结构拆解编码器-解码器骨架、超列融合与分层独立预测2.1 为什么 U-Net 和 FCN 在变化检测上都不够用FCN 的核心问题是低分辨率输出牺牲了定位精度它对每个像素独立分类没有把像素与像素之间的空间关系和值关系纳入考虑分割结果缺乏空间一致性。U-Net 通过编码器-解码器结构缓解了一部分问题浅层特征保留位置信息深层特征做语义分类但它的最后一层只是把前几层特征上采样后简单融合这种特征表达更适合“把所有建筑物分割出来”而不是“判断哪些像素在两期影像之间发生了变化”。遥感图像本身的特殊性也放大了这个缺陷分辨率低、建筑物差异性小、轮廓简单再加上不同卫星拍摄角度和色调不同模型很容易过拟合到“分割建筑物”而不是“检测变化”。传统图像处理路线比如均质区域识别、分水岭分割、形态学房屋指数、先验形状约束水平集模型都依赖同一地点不同时期图像的高精度配准。实际业务中这个前提很难满足漏检率和错检率居高不下。FlowS-Unet 的出发点就是把配准问题交给网络自己学用 8 通道输入让模型同时看到两期影像自行建立对应像素之间的联系。2.2 超列与 FlowNet 细化结构带来的启发Hariharan 等人提出的 Hypercolumn超列解决的是 CNN 最后一层特征缺少位置细节的问题做法是把对应像素在各层的激活值上采样后串联起来让最后一层特征在空间上也足够精细。FlowNet 的放大部分则是反复做反卷积和上采样每步把分辨率提升一倍同时把各层的光流预测连接起来。这两个思路本质上是同一件事不同层特征的空间分辨率不同浅层负责边界定位深层负责语义分类把它们融合起来才能做细粒度预测。FlowS-Unet 把这个融合逻辑放进了 U-Net 的扩张路径在每次融合后都做独立预测。2.3 编码器4 组卷积、池化与批量归一化FlowS-Unet 共 17 个卷积层3 次下采样、3 次上采样没有全连接层。编码器部分是典型的卷积特征提取结构4 组卷积操作同一组内连续做 2 次 3×3 卷积卷积核数目从浅到深依次为 32、64、128、256每两组之间做一次池化降维过滤高频噪声。每步卷积后使用 ReLU 激活编码器一侧加入 BNBatchNorm层让每个 batch 的特征分布相对稳定缓解网络加深带来的梯度弥散问题。用 PyTorch 复现编码器骨架时我一般这样写import torch import torch.nn as nn class FlowSUnetEncoder(nn.Module): def __init__(self): super().__init__() # 两期影像各4通道合并为8通道输入 self.conv1 self._block(8, 32) # 输出 (B, 32, H, W) self.pool1 nn.MaxPool2d(2) # 输出 (B, 32, H/2, W/2) self.conv2 self._block(32, 64) # 输出 (B, 64, H/2, W/2) self.pool2 nn.MaxPool2d(2) # 输出 (B, 64, H/4, W/4) self.conv3 self._block(64, 128) # 输出 (B, 128, H/4, W/4) self.pool3 nn.MaxPool2d(2) # 输出 (B, 128, H/8, W/8) self.conv4 self._block(128, 256) # 输出 (B, 256, H/8, W/8) def _block(self, in_ch, out_ch): # 每组连续2次3x3卷积卷积后接BN和ReLU return nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), )注意输入通道数必须是两期影像通道数之和。原始数据是 4 通道 tiff两期合并后为 8 通道如果换成 RGB 三通道影像这里改成 6 即可。每次 3×3 卷积 padding 为 1 是为了保持特征图尺寸不变池化负责降维。编码器各阶段输出尺寸如下阶段操作通道数空间尺寸输入两期影像合并8H×Wconv12×3×3 卷积 BN32H×Wpool12×2 最大池化32H/2×W/2conv22×3×3 卷积 BN64H/2×W/2pool22×2 最大池化64H/4×W/4conv32×3×3 卷积 BN128H/4×W/4pool32×2 最大池化128H/8×W/8conv42×3×3 卷积 BN256H/8×W/82.4 解码器三输入融合、Dropout 与 1×1 卷积独立预测解码器是 FlowS-Unet 和普通 U-Net 差异最大的地方。每个融合节点的输入分三部分上一层反卷积得到的深层抽象特征、编码器对应层生成的浅层局域特征以及当前步骤自身反卷积的结果。这三部分通过通道连接concatenate融合逐步恢复目标的细节和空间维度。融合后加 Dropout 层随机让 50% 的隐藏节点停止工作提高泛化能力防止在小数据集上过拟合。之后用两次 3×3 卷积消除上采样带来的混淆效应。关键改动是每层融合后都用 2 个 1×1 卷积核把特征向量映射到期望的类别数上。也就是说每一层都能独立输出一个预测图而不是只有最后一层出结果。这样做的好处是浅层特征里含有丰富的位置信息对小建筑物的变化检测特别重要。解码器部分我这样实现class FlowSUnetDecoder(nn.Module): def __init__(self): super().__init__() # 上采样反卷积输出尺寸翻倍 self.up1 nn.ConvTranspose2d(256, 128, 2, stride2) self.fuse1 self._fuse_block(128 128, 128) # 与编码器conv3特征拼接 self.up2 nn.ConvTranspose2d(128, 64, 2, stride2) self.fuse2 self._fuse_block(64 64, 64) # 与编码器conv2特征拼接 self.up3 nn.ConvTranspose2d(64, 32, 2, stride2) self.fuse3 self._fuse_block(32 32, 32) # 与编码器conv1特征拼接 # 每层用2个1x1卷积核独立预测2类变化/不变 self.pred1 nn.Conv2d(128, 2, 1) self.pred2 nn.Conv2d(64, 2, 1) self.pred3 nn.Conv2d(32, 2, 1) def _fuse_block(self, in_ch, out_ch): return nn.Sequential( nn.Dropout(0.5), nn.Conv2d(in_ch, out_ch, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.ReLU(inplaceTrue), )pred1、pred2、pred3就是三个独立预测头分别对应不同上采样阶段的特征融合结果。训练时三个预测头都会参与损失计算推理时可以只取最后一层的输出也可以把多层概率做加权平均。2.5 8 通道输入的设计含义把两期影像直接拼成 8 通道输入是 FlowS-Unet 能绕过配准的关键。网络在卷积过程中会自动学习两幅影像之间的对应关系不需要预先做像素级对齐。实验里用的 QuickBird 卫星影像不同年份拍摄角度和色调都有差异但模型依然能保持稳定的变化检测能力说明这种设计对不可控的成像条件有天然容忍度。3. 遥感数据预处理与训练集构造拼接缝、云雾、多尺度切片与 z-score3.1 原始数据的坑16 位深、拼接痕迹、云雾覆盖原始卫星图是 15106×15106 像素、4 通道、每像元 16 位数据实际范围在 0 到 2774 之间其中 0450 和 15002774 两个区间占比非常少。影像由多景数据拼接而成拼接边缘有明显痕迹部分区域有云和雾。更麻烦的是原始数据只包含 5% 地块的国土审批记录图和一份人工精准标注的小数据集完整标注需要自己做。16 位数据不能直接当作 8 位图输入网络。直接线性映射到 0255 会让低值区间占比过大的问题被放大直方图累积 2% 到 98% 之间的线性拉伸更实用。具体做法是取累积直方图 2% 处的像元值作为下限 MinValue98% 处作为上限 MaxValue像元值在两者之间则线性拉伸到 0255小于 MinValue 置 0大于 MaxValue 置 255。import numpy as np def linear_stretch(img, low2, high98): # 计算累积直方图指定百分位对应的像元值 p_low, p_high np.percentile(img, [low, high]) out (img.astype(np.float32) - p_low) * 255.0 / (p_high - p_low) # 低于下限的置0高于上限的置255 out np.clip(out, 0, 255) return out.astype(np.uint8)np.percentile计算的是整幅图的百分位点参数[low, high]控制截断范围。低 2% 和高 2% 的离群值被裁掉避免少数亮像素把整个拉伸范围撑开。这个函数对每个通道分别调用不能在通道间混算否则会破坏波段间的相关性结构。3.2 Canny 拼接检测与 DehazeNet 微调去云雾拼接痕迹用 Canny 边缘检测确认后把原图分成四块每次只取一块作为感兴趣区域其他区域用掩膜屏蔽。这样做是为了避免在拼接缝附近做拉伸时两边色差被同时拉进有效范围。对每一块掩膜图像单独做 2% 线性拉伸后拼接区域的颜色差异会明显降低。云雾处理参考了 DehazeNet 的思路。原论文里没有展开细节我复现时一般这样处理用带云雾的影像块和无云雾影像块做微调训练让网络学习云雾特征并生成透射率图再按大气散射模型反演出去雾后的图像。参数微调时学习率要比原模型小一个数量级通常取 0.0001否则容易把去雾模型原本学到的自然图像分布破坏掉。3.3 人工标注、one-hot 标签与数据集划分标注环节是遥感图像标注流程里最耗时也最决定模型上限的一步。原方案用自制标注工具绘制多边形覆盖变化的建筑物标签做 one-hot 处理成二通道数据变化建筑物记为 1其他记为 0。注意这里和普通分割的差异——标注对象不是“所有建筑物”而是“变化了的建筑物”两期影像对比着标工作量集中在判断“变没变”上。数据集划分和裁剪参数如下数据集区域尺寸像素是否做数据增强用途训练集15106×11106是旋转/翻转/缩放模型训练验证集15106×1000否收敛判断与调参测试集15106×3000否最终效果评估训练集采用多尺度图像块与滑动窗口结合的方式切割图像块尺寸为 224、256、288、320 像素区域块重叠覆盖到整幅图像。之后再通过旋转、翻转和缩放做数据增强。四个尺度的设计不是随意定的224 适合小建筑320 能覆盖更多上下文信息256 和 288 作为中间档让网络适应不同建筑物尺度。3.4 z-score 归一化为什么要用全局统计量不同区域的遥感图像平均亮度和像素值分布差异很大直接训练会让网络对亮度敏感。z-score 标准化把输入数据调整到均值为 0、方差为 1 的标准正态分布计算式为 x (x - μ) / δ其中 μ 是全体样本数据的均值δ 是全体样本数据的方差。注意这里用的是全体样本统计量不是单张图逐图归一化。逐图归一化会抹掉图像间的真实亮度差异模型在预测时反而会因为测试图像亮度分布不同而失效。归一化要在数据增强完成之后、输入网络之前执行。代码实现上就是先统计所有训练图像块的均值和标准差保存成 npy 文件训练和推理时统一加载这两个值避免训练和测试数据预处理不一致。4. 多尺度交叉训练与多重损失FlowS-Unet 的尺度鲁棒性来源4.1 单一尺寸切片的边缘问题全卷积网络能接受任意尺寸输入但数据裁剪方式决定了模型能看到什么。用单一尺寸训练时处于裁剪边缘的建筑物变化很难被正确预测尤其是同一个建筑物被切到两张图上时模型可能只在一张图上检测到变化另一张图上什么都学不到。多尺度交叉训练的思路是把 224、256、288、320 四种尺寸的图块分别组成 batch轮流输入网络训练。这样既不会让某一种尺度主导特征分布又能在显存有限的情况下让模型见到完整的建筑物结构。不同训练尺度和预测尺度的 F1 分数对比可以很清楚地看到多尺度训练带来的收益训练尺度像素预测尺度像素F1后处理前/后2242240.903 / 0.9232562560.909 / 0.9282882880.913 / 0.9313203200.911 / 0.932多尺度交叉2240.933 / 0.939多尺度交叉2560.938 / 0.943多尺度交叉2880.939 / 0.945多尺度交叉3200.939 / 0.944多尺度交叉四种尺度概率平均0.942 / 0.946单尺度训练时F1 分数最高只有 0.913288 尺度换成多尺度交叉训练后即使只用 224 窗口预测也能到 0.933说明模型真正学到了尺度无关的变化特征。4.2 多重损失每一层都要监督FlowS-Unet 在解码器每次融合后都做独立预测得到 pred0、pred1……predn 多个预测结果总损失是各层损失的加权和。除最后一层外其他层的输出特征图都比输入图像小计算损失前需要把标签图像缩小到对应层的输出尺寸。权重 W_i 在实验中设为 1即各层损失等权相加。这样做的好处是梯度能直接传递到浅层融合节点浅层特征里的位置信息不会被深层语义特征淹没。import torch.nn.functional as F def multi_scale_loss(preds, label, weightsNone): # preds: 各层独立预测输出列表元素形状为 (B, 2, H_i, W_i) # label: 原始分辨率标签形状为 (B, 1, H, W)值为0或1 if weights is None: weights [1.0] * len(preds) total_loss 0.0 for pred, w in zip(preds, weights): # 把标签缩放到当前预测层的尺寸用nearest保持离散类别 target F.interpolate(label.float(), sizepred.shape[-2:], modenearest) loss F.cross_entropy(pred, target.squeeze(1).long()) total_loss w * loss return total_loss缩放标签时必须用nearest模式不能用bilinear。标签是离散类别双线性插值会生成 0 到 1 之间的中间值反向传播时给模型传递错误信号。单层损失使用交叉熵代价函数表达式为 C -(1/N) Σ [y ln a (1-y) ln(1-a)]其中 y 是期望输出a 是神经元实际输出。交叉熵在误差大的时候权重更新快误差小的时候更新慢比平方误差更适合分类任务。4.3 优化器、学习率与收敛判断优化器用 Adam初始学习率 0.001。Adam 能自适应调整每个参数的学习率省去手动调学习率衰减策略的麻烦。在 GTX 980Ti 6GB 显存环境下多尺度 batch 要错开处理——先喂 224 的 batch再喂 256 的 batch依次轮转而不是把四种尺度拼成一个超大 batch。训练到 160000 次迭代后验证集准确率不再上升损失值趋于平稳模型收敛。如果训练数据分布差异特别大可以把各层损失的权重改成按层递减例如深层权重 1.0、浅层 0.8让网络优先保证语义分类的准确性。5. 基于 FlowS-Unet 的后处理与推理调优膨胀腐蚀、孔洞填充与 F1 对比5.1 先膨胀后腐蚀参数依据是训练集标签网络预测结果存在孤立点、空洞和边缘断裂。膨胀操作把与物体接触的背景点合并进来让边界向外扩张可以消除建筑边缘预测很弱的现象平滑边界同时不明显改变建筑物面积。操作内核用简单的 3×3 矩阵连续膨胀 5 次。腐蚀则相反消除边界点让边界向内收缩用于去掉小且无意义的孤立区域。腐蚀的阈值面积定为 200这个数字来自训练集标签统计——最小建筑物标记面积就是 200小于这个面积的连通域被认为不是真实建筑。import cv2 import numpy as np def post_process(pred, min_area200, iterations5): kernel np.ones((3, 3), dtypenp.uint8) # 先膨胀5次平滑弱边缘 dilated cv2.dilate(pred, kernel, iterationsiterations) # 连通域分析过滤面积小于min_area的区域 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(dilated, 8) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: dilated[labels i] 0 # 腐蚀回边界消除膨胀带来的面积扩张 eroded cv2.erode(dilated, kernel, iterationsiterations) return erodedconnectedComponentsWithStats返回每个连通域的包围盒和面积遍历时从 1 开始0 是背景。面积过滤放在膨胀和腐蚀之间这样膨胀出的细小噪声连通域会在腐蚀前被清除最终结果相比原始预测更干净。5.2 孔洞填充漫水填充的完整步骤预测结果中极少大建筑物内部会出现封闭孔洞。孔洞填充用漫水填充算法实现具体步骤是先把原图向外扩展两个像素填充值为背景色黑色然后对待处理图像用 floodFill 函数从种子点开始填充连通域种子点取 (0, 0)染成白色此时背景全部变白原图中的封闭孔洞因为被白色背景包围保留为黑色最后剪裁回原图大小取反后与原图相加得到填充后的结果。def fill_holes(pred): h, w pred.shape # 原图外扩两个像素避免背景连通到图像边缘 canvas np.zeros((h 4, w 4), dtypenp.uint8) canvas[2:2 h, 2:2 w] pred # floodFill的mask需要比原图大2个像素 mask np.zeros((h 8, w 8), dtypenp.uint8) cv2.floodFill(canvas, mask, (0, 0), 255) # 裁剪回原图大小后取反与原图相加得到填充结果 filled canvas[2:2 h, 2:2 w] return cv2.bitwise_or(pred, cv2.bitwise_not(filled))floodFill第一个参数是输入单通道图像第二个参数是填充算法的起始种子点第三个参数是像素点被染色的新值。mask 的宽高必须比输入图像大 2否则 OpenCV 会报错。5.3 F1 指标与模型对比F1 分数是精确率和召回率的调和平均数被称为平衡 F 分数。它把标签中非 0 像素点作为正样本0 像素点作为负样本每个像素的分类结果都会影响最终得分。公式为 F1 2 × precision × recall / (precision recall)。三种模型在相同数据集上的对比结果如下序号方法F1后处理前/后推理时间秒1FlowS-Unet0.933 / 0.943622FCN0.858 / 0.873503U-Net0.898 / 0.913594人工标注1.00018000FlowS-Unet 后处理前已经比 U-Net 后处理后高 2 个百分点后处理完成后 F1 达到 0.943。推理时间 62 秒和 U-Net 的 59 秒基本持平说明多层独立预测并没有带来显著计算开销。预测时测试数据用滑动窗口处理窗口大小为 256×256 像素步长 150步长小于窗口尺寸以保证建筑物边缘被充分覆盖。6. 进阶技巧多尺度平均预测、空洞填充双刃剑与可迁移落地6.1 推理阶段的多尺度平均免费再涨 0.4 个百分点多尺度交叉训练后推理时用不同窗口大小预测并平均概率比固定单一窗口更稳。具体做法是分别用 224、256、288、320 四种窗口对预测区域做滑动窗口推理每个像素取四个窗口预测概率的平均值再根据平均概率判断是变化还是不变。这个方法不需要重训模型只在推理时多跑几次前向传播。从前面的对比表可以看到多尺度平均的后处理前 F1 是 0.942比单用 256 窗口的 0.938 高 0.4 个百分点。显存充足时这是性价比最高的提分手段。推理时的窗口重叠率也要注意。步长 150、窗口 256意味着相邻窗口有 106 像素的重叠为什么要这么设计因为建筑物和其周边上下文往往比窗口尺寸大得多非重叠滑窗会把一栋建筑切碎导致模型在窗口边缘反复预测不一致。重叠步长保证每个像素至少在多个窗口的中间位置出现一次但代价是推理时间成倍增加落地时可以根据时效要求调整步长。6.2 空洞填充的双刃剑效应真实结构不能乱填测试结果里出现过这样的案例一块区域真实情况是新造地基中间带有一个真实孔洞人工标签把它标成变化区域的一部分。空洞填充算法把这个洞填平了反而让后处理结果偏离了真实标签。本质上空洞填充处理的是“预测噪声导致的假洞”但无法区分“真实存在的洞”。变化检测业务的目标是发现建、拆、改、扩建筑内部有没有洞并不影响判断所以这个误差对最终结果影响不大但如果要做面积统计或生成工程量清单这个会被“填错”的洞就会造成偏差。实际操作中我一般会统计训练集标签里真实孔洞的面积分布如果孔洞面积占比超过连通域面积的 15%就保留该连通域不填充。6.3 可迁移方向与落地建议FlowS-Unet 的结构不局限于遥感影像任何输入是多时相或成对图像的像素级任务都能直接套用只需调整输入通道数和输出类别数。医疗时序影像里病灶的消退和新增判断、纸币等印刷品的缺陷检测本质都是“两幅图——一个变化图”的建模方式。落地时我会把验证流程固定为四步先量化输出和人工标注的 F1再做连通域级别的面积误差统计分析然后检查真实孔洞保留率最后用不同区域数据做泛化抽检。这四个指标都达标模型才能进入业务闭环。专门讲一下面积统计这块通常要对每个变化连通域记录面积、位置和置信度再把置信度低的区域标出来让审核人员抽查这部分对国土监察场景尤其重要。本文还有配套的精品资源点击获取
返回列表