均值、中值、圆周期:三种 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 技术路线。从相位读入、复指数映射、滑动窗口算子,到值域回卷、质量评价与下游解缠,三种滤波方法共享同一套框架,只在中间的算子处分叉。
三、算法步骤
整个流程是"一次映射、一次窗口遍历、一次回卷"。
- 读入
data.dat,得到 699×1000 的双精度相位矩阵,确认值域已落在 −π, π,即已完成缠绕 - 预分配
result = zeros(699, 1000),取半窗宽mid = floor(winsize/2) - 只遍历内部像素,
i = mid+1 : 699−mid,j = mid+1 : 1000−mid - 取 w×w 邻域,按选定算子计算中心像素的相位估计
- 圆周期滤波额外执行一次值域回卷
- 输出结果,用
imagesc配jet色表伪彩色显示,附 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,对这幅密集条纹数据是合理的选择。
另外两处可以改进的地方。三行循环里每个像素都在调用 median 和 mean2,函数调用开销占比很高。更重要的是,整幅图用的是同一个窗口,而图 1 里既有相干性极好、条纹平滑的区域,也有雪花状的失相干区,一刀切的窗口在两边都不讨好。用相干性图或合矢量模值做加权,是最划算的一步升级。
八、运行环境
原始代码是纯 MATLAB 实现,脚本加三个函数文件,结构清晰。
- 语言与工具。MATLAB,
main.m主脚本配mean_fiter.m、mid_fiter.m、ciciod_fiter.m三个算子文件。圆周期滤波用到了mean2,属于 Image Processing Toolbox 的函数,如果目标机器没装这个工具箱,把它换成mean(x(:))即可,性能还更好 - 数据。
data.dat是 ASCII 文本,699 行 × 1000 列双精度浮点,约 17.5 MB - 可视化。
imagesc配jet色表,用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 #遥感