均值、中值、圆周期:三种 InSAR 干涉相位滤波的实测对比与参数取舍

均值、中值、圆周期:三种 InSAR 干涉相位滤波的实测对比与参数取舍

前几天翻出一份 InSAR 干涉相位滤波的 MATLAB 代码。三个函数文件加一个主脚本,有效代码不到 50 行,分别实现均值滤波、中值滤波和圆周期滤波。

我把一副 699×1000 的真实干涉相位图丢进去各跑一遍,又补了一套可控仿真。

三个滤波器原理上的差别,其实只有一句话,怎么对一批角度求平均。但落到指标上,差别是实打实的。更意外的是,代码里一行 median(1i*mat_sin(:)) 藏着 MATLAB 的一个语义陷阱,两种理解方式在这份数据上差出 2.6 倍的残差点。

这篇把背景、公式、代码、参数和实测数据一次讲清楚。

图 1 待处理的原始干涉相位图。699×1000,值域 −π, π,色标单位 rad。右侧与下部的颗粒状区域是低相干区,条纹密集区集中在图像中下部。


一、干涉相位为什么这么"脏"

InSAR 测形变的原理并不复杂。同一块地方成像两次,两次回波相位之差里带着地形和形变信息。

麻烦在于相位是缠绕的。干涉相位只落在 −π, π 这一个区间里,真实相位差一旦超过一个整周,就绕回来了。噪声再一叠加,图上就出现大量无规律的相位跳变。图 1 右部那种雪花状的颗粒,就是跳变最密集的地方。

这些跳变有个专门的量化名字,残差点(residue)。按 Ghiglia 和 Pritt 的残差定理,绕任意一个 2×2 像素环一周,四个相位梯度之和如果不是 0,这个环就含一个残差点。残差点不能通过简单的相位加减消掉,它们会让后续的相位解缠无法得到全局一致的解。

我在这幅图上数了一遍,695,606 个有效像素里,残差点有 40,944 个,密度 5.89%。这个数字相当高,属于典型的低相干干涉图。

所以滤波的目标很明确,在尽量不破坏条纹(也就是相位梯度)的前提下,把随机跳变压下去。

难点也在这里。相位是角度,不能直接做算术平均。

二、三种滤波器到底在做什么

−π 和 π 指的是同一个方向。如果直接对角度取平均,这两个值会互相抵消,得到 0,那是完全错误的结果。

三种方法都要先在"圆"上做文章,区别只在于怎么定义"平均"。

方法 对窗口内相位的"平均"定义 关键性质
均值滤波 mean_fiter 映射到单位圆求和后取辐角(圆均值) 对高斯型噪声接近最优,平滑能力最强
中值滤波 mid_fiter 实部、虚部分别取中值再合成 对离群像素稳健,但不是旋转不变
圆周期滤波 ciciod_fiter 先定主方向,再对残差取算术均值 两步估计,不依赖分实部虚部

均值滤波,代码里的做法是先把相位映射成单位圆上的复数,滑动窗口求和,再取合矢量的辐角。

复制代码
z_k = exp(j·φ_k)
φ̂(i,j) = arg( Σ_{k∈W(i,j)} z_k )

合矢量的长度天然携带信息。窗口内相位一致时,合矢量长;相位完全随机时,合矢量趋近于 0,这时输出的方向就没有意义。这个模值可以直接当作一个免费的置信度指标来用,原代码把它丢掉了,有点可惜。

中值滤波,取窗口内 cos 分量的中值和 sin 分量的中值,再合成一个复数求角度。

复制代码
φ̂(i,j) = arg( med_k(cos φ_k) + j · med_k(sin φ_k) )

注意 cos 和 sin 是分别取中值的,取到的两个值很可能来自窗口内两个不同的像素。这个构造不是旋转不变的,后面仿真会看到它的代价。

圆周期滤波是三步。先求出窗口的主方向,再把所有相位旋转到该方向附近,对旋转后的残差取算术平均,最后转回去。

复制代码
S = Σ_{k∈W} z_k,   ψ = arg(S)
φ̂(i,j) = ψ + (1/N) · Σ_{k∈W} arg( z_k · e^{−jψ} )
        = ψ + (1/N) · Σ_{k∈W} wrap( φ_k − ψ )

最后一步是值域回卷,把结果压回主值区间。

复制代码
φ ← mod(φ + π, 2π) − π

原代码写作 rem(result+11*pi, 2*pi)-pi,这个 11 一开始很让人费解。拆开看就明白了,11π = π + 5×2π,多出来的 5 个整周是为了让 rem 的自变量保持非负。MATLAB 的 rem 对负数取余会返回负值,而 result 最小能到 −π,所以需要补一个足够大的正数偏移。它和上面那行 mod 完全等价。

图 2 技术路线。从相位读入、复指数映射、滑动窗口算子,到值域回卷、质量评价与下游解缠,三种滤波方法共享同一套框架,只在中间的算子处分叉。

三、算法步骤

整个流程是"一次映射、一次窗口遍历、一次回卷"。

  1. 读入 data.dat,得到 699×1000 的双精度相位矩阵,确认值域已落在 −π, π,即已完成缠绕
  2. 预分配 result = zeros(699, 1000),取半窗宽 mid = floor(winsize/2)
  3. 只遍历内部像素,i = mid+1 : 699−midj = mid+1 : 1000−mid
  4. 取 w×w 邻域,按选定算子计算中心像素的相位估计
  5. 圆周期滤波额外执行一次值域回卷
  6. 输出结果,用 imagescjet 色表伪彩色显示,附 colorbar

计算量不算小。每个滤波器要处理 695,606 个窗口,w=3 时每窗口 9 个像素,总计约 626 万次基本运算。MATLAB 双重循环能跑,向量化实现更快,但数据量再上一个量级就该换积分图或 FFT 方案了。

四、实现细节里的三个坑

第一个坑是边界。 只遍历内部区域,意味着最外一圈像素始终是 0。结果图上是一条 1 像素宽的黑边,可视化时几乎看不出来(图 5 到图 7 里确实看不出),但下游解缠会把它当成真实相位。稳妥的做法是镜像填充后再滤波,最后裁掉。

第二个坑在数据格式。 相位数据以 ASCII 文本存储,17.5 MB,69.9 万个双精度数。load 需要逐字符解析,I/O 开销远大于滤波本身的计算量。换成 .mat 二进制,读取速度能差一个数量级。

第三个坑最隐蔽,值得单独说。

mid_fiter.m 里这一行:

matlab 复制代码
result(i, j) = angle( median(mat_cos(:)) + median(1i*mat_sin(:)) );

按公式本意,它想写的是 cos 的中值和 sin 的中值。但 MATLAB 对复数数组排序的规则是先比模值、模值相同再比相位角 (这一点在 MathWorks 官方 sort 文档 里写得很清楚),而 median 在向量上的实现就是排序取中间。所以 median(1i*mat_sin(:)) 实际取到的是"模值居中的那个 sin 分量",与 1i*median(mat_sin(:)) 并不是一回事。

我把两种语义都实现了一遍,在同一幅数据上跑:

中值滤波的写法 残差点数 相对原图的下降
写法 A,按公式本意 median(cos) + j·median(sin) 12,769 68.8%
写法 B,按 MATLAB 复数排序语义 median(cos) + median(j·sin) 32,748 20.0%

同样的意图,同样的窗口,残差点差了 2.6 倍。 写成 median(mat_cos(:)) + 1i*median(mat_sin(:)) 就没有歧义了。

图 3 中值滤波两种写法在同一幅干涉图上的结果。左为按公式本意的写法,残差 12,769 个;右为按 MATLAB 复数排序语义的写法,残差 32,748 个,颗粒噪声明显更多。

五、实测结果

下面所有结果都取 3×3 窗口。为了做定量评价,我把三个算子用 NumPy / SciPy 向量化重写了一遍。均值与圆周期两个算子可以与 MATLAB 循环逐像素对齐,中值算子按公式本意实现(第四节那个语义问题单独讨论),这样就能在秒级完成多组参数扫描。

评价用了三个指标。残差点数看噪声压制程度,相位导数方差(PDV)看相位场的局部不规则程度,梯度幅值均值则反映条纹锐度保留了多少。前两个越低越好,第三个越高说明条纹保留得越完整。

方法 残差点数 残差密度 PDV 梯度幅值
原图 40,944 5.89% 1.1895 1.4151
均值滤波 11,974 1.72% 0.4881 0.7147
中值滤波 12,769 1.84% 0.6102 0.7408
圆周期滤波 12,347 1.77% 0.6248 0.8819

图 4 3×3 窗口下三种滤波器的三个定量指标对比。

图 5 均值滤波结果。平滑最彻底,残差降到 11,974 个,代价是条纹锐度也一起被磨掉了。

图 6 中值滤波结果(按公式本意复现)。

图 7 圆周期滤波结果。肉眼能看出比前两者保留了更多细节颗粒。

数据读下来,三者的差别比想象中明确。

均值滤波把残差压到最低,PDV 也最低,但梯度幅值只剩原来的 50.5%。 它是最彻底的平滑器,也最不挑食,条纹和噪声一起磨。

中值滤波居中,残差 12,769 个,梯度保留 52.3%。

圆周期滤波保留了最多的梯度,62.3%,明显高于前两者。 但它的 PDV 也最高(0.6248,只比原图降了 47.5%),说明多出来的这部分梯度里,既有条纹,也有残余噪声。

图 8 密集条纹区(第 438--678 行,第 54--294 列)局部放大。这一带是原图残差最密集的区域,窗口内残差从 4,227 个降到 1,700 上下,但三者的空间纹理差别很小。

图 9 残差点的空间分布。左边原图,右边圆周期滤波后。红色为正残差,蓝色为负残差。降幅很大,但残余的残差点明显聚集在条纹陡变处,这正是滤波最难处理的区域。

有一件事需要说清楚。这幅数据没有真值,所以"谁更好"没有绝对答案。 均值滤波更激进,圆周期滤波更保守,实测指标只能在"压制程度"和"保留程度"之间给出取舍依据,真正该由谁来裁决的,是下游相位解缠的成功率。

六、可控仿真:谁在什么工况下赢

实测数据给不出真值,我补了一套可控仿真。真实相位取线性条纹,梯度 g 从缓变到密集,叠加高斯型相位噪声,再分别用三种滤波器估计,计算与原真值的 RMSE(缠绕差分后的均方根误差)。400×400 网格,每组重复 12 次取均值。

条纹梯度 g (rad/px) 噪声 σ 未滤波 均值 中值 圆周期
0.30(缓变) 0.5 0.4999 0.1679 0.2160 0.2397
0.30(缓变) 1.0 0.9974 0.3756 0.4668 0.5035
0.30(缓变) 1.6 1.4782 0.9937 1.0659 1.1046
0.80(中等) 0.5 0.5000 0.1765 0.2888 0.2384
0.80(中等) 1.0 0.9985 0.4620 0.5802 0.5483
0.80(中等) 1.6 1.4775 1.1307 1.1908 1.1938
1.40(密集) 0.5 0.5005 0.2875 0.5509 0.3088
1.40(密集) 1.0 0.9969 0.8880 0.9926 0.8890
1.40(密集) 1.6 1.4780 1.4305 1.4632 1.4296

三点值得记住。

第一,均值滤波在绝大多数工况下都是最优的,这是理论上的预期,噪声在圆上呈对称分布(例如 von Mises 分布)时,复数求和取辐角就是最大似然估计。它的"藏拙"之处在于无差别平滑。

第二,中值滤波在密集条纹区明显失效。 g=1.40、σ=0.5 那一行,中值的 RMSE 是 0.5509,均值的 1.9 倍。原因就在前面说的,cos 和 sin 分别取中值会取到不同像素,窗口内相位变化越大,这个错位越严重,直接破坏了相位的圆周期性。

第三,圆周期滤波只在极端工况下追平均值。 表中最后一行,密集条纹叠加强噪声,两者几乎完全一致(1.4296 与 1.4305)。它的价值不在于"更准",而在于它不依赖分实部虚部的中值,所以在条纹极密的区域不会像中值那样崩掉。

仿真还顺手验证了一件事,三种方法的系统偏差都小于 0.003 rad,基本上可以忽略。它们之间比的是方差,不是偏差。

再看噪声类型不同的情况。这次在真实相位上额外撒入随机离群像素,模拟低相干区那种"整点坏掉"的相位跳变。

离群像素比例 均值 中值 圆周期
0% 0.1680 0.2160 0.2397
2% 0.1760 0.2208 0.2612
5% 0.1890 0.2286 0.2918
10% 0.2138 0.2449 0.3413
误差劣化幅度 +27.3% +13.4% +42.4%

离群比例从 0 加到 10%,中值滤波的误差只涨了 13.4%,是三者里最抗离群像素的。均值涨 27.3%,圆周期涨 42.4%。中值虽然基线偏差最大,但它在坏点面前的稳定性确实是实打实的。

七、参数设定:窗口越大越好吗

原代码把窗口尺寸写成参数 winsize,调用时传 3。这是唯一一个需要调的量。

我扫了一遍 w 从 3 到 11 的效果。

窗口尺寸 w 均值·残差 中值·残差 圆周期·残差 圆周期·梯度幅值
3×3 11,974 12,769 12,347 0.882
5×5 5,726 6,187 5,809 0.603
7×7 3,576 4,022 3,609 0.476
9×9 2,731 2,936 2,762 0.402
11×11 2,243 2,439 2,269 0.353

图 10 窗口尺寸对残差点数与梯度幅值的影响。残差随窗口单调下降,梯度幅值同步流失,两条曲线之间的空隙就是这台"天平"的代价。

从 3×3 加宽到 11×11,圆周期滤波的残差从 12,347 降到 2,269,降幅 94.5%。听起来很诱人。

但同一行数据里,梯度幅值从 0.882 掉到 0.353,只剩原图的 24.9%。条纹被磨平了。

这就是参数设定的全部内容,窗口尺寸就是在"抑制噪声"和"保住条纹"之间推一根滑块。没有普适的最优值,只有针对数据的取舍。经验上,条纹稀疏、相干性好的区域可以放到 7 到 9,条纹密集的形变区最好守在 3 到 5,宁可多迭代几次也不要一次开大窗。原代码取 3,对这幅密集条纹数据是合理的选择。

另外两处可以改进的地方。三行循环里每个像素都在调用 medianmean2,函数调用开销占比很高。更重要的是,整幅图用的是同一个窗口,而图 1 里既有相干性极好、条纹平滑的区域,也有雪花状的失相干区,一刀切的窗口在两边都不讨好。用相干性图或合矢量模值做加权,是最划算的一步升级。

八、运行环境

原始代码是纯 MATLAB 实现,脚本加三个函数文件,结构清晰。

  • 语言与工具。MATLAB,main.m 主脚本配 mean_fiter.mmid_fiter.mciciod_fiter.m 三个算子文件。圆周期滤波用到了 mean2,属于 Image Processing Toolbox 的函数,如果目标机器没装这个工具箱,把它换成 mean(x(:)) 即可,性能还更好
  • 数据。data.dat 是 ASCII 文本,699 行 × 1000 列双精度浮点,约 17.5 MB
  • 可视化。imagescjet 色表,用 jet(64) 的第 13 至 56 行,色标范围固定为 −π, π,方便横向比对
  • 定量评价。本次评价用 Python 3 加 NumPy 2.2.6、SciPy 1.15.3 做向量化复现,绘图用 Matplotlib 3.10.8(中文字体 Noto Sans SC)

顺带提一句主脚本里的两个小问题。文件底部把三个函数又注释粘贴了一遍,属于早期调试残留,删掉更干净。另外用 eval(['pho', num2str(i)]) 来循环取变量,虽然在这里没问题,但换成 cell 数组会更安全,也更好维护。

九、应用场景

这套滤波器的定位很清楚,它是相位解缠的前置环节。

  • InSAR 形变监测。 地面沉降、滑坡、矿区沉陷、冰川流动、火山形变。这些场景的干涉图往往处于低相干或大形变梯度状态,残差点密度高,预滤波的成败直接决定时序分析能不能做下去
  • DEM 生成与地形相位估计。 地形相位同样需要解缠,滤波质量影响高程精度
  • 时序 InSAR 预处理。 PS-InSAR、SBAS 这类方法要先筛干涉对,滤波后的残差密度是重要的选对依据
  • 更广义的缠绕相位场。 任何"角度型"的测量数据都能用,比如光学干涉测量、MRI 相位成像、自适应光学波前传感、合成孔径声呐

需要说明的是这套实现的局限。它只做单次局部滤波,没有相干性自适应加权,没有迭代重滤波,也没有做地形相位去除,边界像素直接置零。作为教学和算法验证的起点,它足够干净,作为生产工具还需要补不少东西。

十、把结论收一下

三个滤波器解决的是同一个问题,怎么在圆上求平均。选谁,取决于噪声是什么类型。

  • 均值滤波(圆均值) 综合表现最好,对高斯型噪声接近最优。适合条纹较缓、相干性尚可的数据
  • 中值滤波 抗离群像素最强,10% 坏点下误差只涨 13.4%。适合"整体质量还行、但散布孤立坏点"的干涉图,注意避开密集条纹区
  • 圆周期滤波 保边能力最好,在密集条纹叠加强噪声的极端工况下追平均值。适合形变梯度大、条纹极密的区域

三个数字或许最能概括这次测试。3×3 单次滤波把残差点从 40,944 降到 1.2 万,削掉七成,但密集条纹区仍残留大量残差点。窗口放大到 11×11 能削掉 94.5%,代价是条纹锐度只剩四分之一。

滤波器的能力边界,从来不在算法本身,而在你愿意为条纹付出多少噪声。 相位是角度这件事,让所有平均都得绕道圆上走一圈,也让每一个平滑决策都变成一次不可逆的取舍。


本文数据来自本地干涉相位滤波工程,定量评价通过向量化复现三种算子完成。文中公式与 MATLAB 排序语义依据 MathWorks 官方文档。

标签:#InSAR #干涉相位滤波 #相位解缠 #MATLAB #遥感

相关推荐
xy20261 小时前
多轮 Tool Agent 训不动?先把环境 token 从 loss 里拿掉
算法
wzdark1 小时前
多核架构下算法并行化的瓶颈与突破点4
算法·架构
阿明62 小时前
Leetcode: 283. 移动零
算法·leetcode·职场和发展
葫三生2 小时前
三生原理与《涌现:从简单规则到复杂世界》在“简单规则生成复杂系统”核心思路上存在理论呼应?
人工智能·科技·算法·机器学习·开源
6Hzlia2 小时前
【Classic 150 刷题计划】 LeetCode 274. H 指数 | C++ 二分答案法 (单调性降维打击)
c++·算法·leetcode
行者全栈架构师2 小时前
WorkBuddy 实战:50 份简历 30 分钟筛完,还能自动出评估报告
人工智能·算法·微信
ysu_03142 小时前
19-插入排序与希尔排序
数据结构·算法·排序算法
qq_508576093 小时前
人性机器人部分 算法
算法·机器人
Polevne3 小时前
C# GRPC 一元与双向流
linux·算法·c#