NISAR 数据开放下载之后,我第一个拿来跑 GMTSAR 的场景就是 Erta Ale 火山。理由很直接:这座火山的熔岩湖太适合当 InSAR 算法的“标定物”了——熔岩湖液面升高或下降时,整个火山口周边的地表会产生一套非常规则的环形形变场,信号强、形态简单。拿它来检验一条新数据处理链路是否可靠,比随便找一块构造形变区要省心得多。这篇文章就记录我用 GMTSAR 处理一组 NISAR L 波段 SLC 数据、提取 Erta Ale 火山形变的全过程,包括完整代码、关键参数,以及几次让我记忆深刻的踩坑经历。
如果你正准备用 GMTSAR 处理 NISAR 数据,或者想拿 L 波段新数据做火山和构造形变研究,这篇应该能帮你省下不少时间。先说说结论:NISAR 的数据在 GMTSAR 里并不能像 Sentinel-1 那样“一把梭”直接跑通,需要先做格式转换和轨道提取;但一旦搞定预处理,后面的干涉链路其实相当顺手。整个流程我大概用了两三个晚上跑通,主要时间都花在轨道单位换算和子区裁剪上。下面的代码和参数都是我在本地验证过的,你直接抄过去改路径基本就能用。
1. NISAR 数据时代,GMTSAR 的新用场与 Erta Ale 这个试验场
1.1 NISAR 数据到底特殊在哪
提到 NISAR,多数人对“12 天重访、L 波段、大幅宽”这几个词很熟悉,但真正下到数据之后,你才会感受到它和过去常用的 C 波段数据之间的差距。NISAR 的 L 波段波长在 24cm 左右,相比之下 Sentinel-1 的 C 波段只有 5.6cm。对植被覆盖厚的地方,长波意味着更强的穿透能力;对 Erta Ale 这种地面以裸露玄武岩熔岩流为主的火山场,长波带来的好处其实是“时间失相干”变得非常低——两次观测之间即使地表有一些轻微扰动,相位依然能保持稳定。
换句话说,用 NISAR 做火山区的干涉测量,你拿到的高相干像元数量通常会比 Sentinel-1 多不少。我同一区域用 12 天间隔的数据做了对比,干涉图里相干性高于 0.4 的像元占比能提升一大截,这对追踪熔岩湖周边的微小形变非常关键。做火山监测的人应该清楚,失相干一直是 InSAR 在活动火山区最大的敌人,尤其是熔岩流频繁覆盖的区域,C 波段数据经常一大片全都磨掉了相位。而 L 波段对粗糙地表的容忍度高得多,Erta Ale 周边大部分熔岩地表都能保持稳定回波。
还有一点值得注意:NISAR 的 SLC 产品采用 HDF5/NetCDF 容器格式,数据组织跟旧式 CEOS 格式、或者 Sentinel-1 的文件夹结构都不一样。这导致很多旧版本 GMTSAR 的默认读取入口直接失效。我一开始天真地以为把文件后缀改成.SLC就能跑,结果 GMTSAR 读出的振幅图全是空白,后来才意识到是格式和字节序的问题。这块放在后面预处理部分细说。
1.2 为什么选 Erta Ale
Erta Ale 位于东非大裂谷北段的阿法尔三角区,是一座以持续活跃熔岩湖闻名的盾状火山。它厉害的地方在于,湖面不是安静的,而是随岩浆房压力变化周期性抬升和回落,往往一次活动在雷达视线方向上能产生数厘米甚至更大的位移量。对 InSAR 来说,信号幅度大、空间形态规则,几乎就是天然实验场。在干涉图上,熔岩湖周围会呈现一套同心圆状条纹,条纹间隔和相位梯度直接反映浅部岩浆囊的膨胀或收缩状态。
Erta Ale 周边干旱少雨,地表以熔岩为主,没有茂密植被,大气水汽变化也相对弱。这些条件叠加在一起,让它在数据处理阶段几乎不会遇到严重的失相干干扰。早期我拿它做测试时,一个很直观的感受是:许多在其他地方需要反复调整参数才能勉强解缠的区域,在这里用默认参数就能得到干净的相位结果。
选择它还有一个现实原因:这里属于长期活动的火山,历史形变模式已有不少公开研究,目标地区的“答案”相对明确。对于一个以验证 NISAR 数据处理流程为目的的项目来说,答案越明确,你越容易分辨出哪些是真实形变、哪些是处理误差。换句话说,拿 Erta Ale 作为 GMTSAR 处理 NISAR 数据的第一个样本,是性价比很高的选择。
2. 处理前的准备:数据获取、文件结构和 GMTSAR 环境
2.1 数据获取与产品选择
NISAR 的科学数据目前通过公开的数据门户开放下载,你需要先注册账号,再按轨道号、时间范围和覆盖区域搜索。我当时的做法是在界面上画一个包围 Erta Ale 火山口的矩形范围,然后筛选出同一轨道方向、同一路径号的 SLC 产品。
选择两个时间点的 SLC 作为干涉对时,我建议优先考虑时间间隔为一个或两个卫星重访周期的组合。这样形变积累量适中,空间基线也相对稳定。我用的一组数据是某月 3 日的影像作为主影像,同月 15 日的影像作为副影像,中间没有长间隙断档。下载时还要确认轨道方向(升轨或降轨),后面解释条纹方向时要回查这个信息,千万别搞混。
文件到手之后,先用工具看一下内部结构。NISAR 的 SLC 产品内部包含影像数据、轨道状态向量、元数据、校准参数等多个分组。我习惯用 Python 的 h5py 快速浏览一遍键名,确认 SLC 复数数组的维度、轨道向量的字段名,再决定写转换脚本。这一步看起来多余,实际上非常省时间——不同产品版本的内部路径偶尔会有差异,直接硬编码路径最容易翻车。
另外有一点容易被忽略:SLC 产品的幅宽比一般 Sentinel-1 单条带要宽。Erta Ale 只是其中一小块,如果整景读进来再做裁剪,内存消耗会很大。我建议在转换阶段就把研究区外的数据扔掉,只保留火山口周围几十公里的范围。
2.2 GMTSAR 版本和依赖准备
GMTSAR 旧版本对 NISAR 的支持并不友好,建议直接用较新的发布版。编译前确保系统里已经装好 GMT 6.x、gcc/gfortran、netCDF、HDF5 开发库。HDF5 这个点尤其关键:如果编译时没带 HDF5 支持,后面读取 NISAR 产品内部数据就只能靠外部工具先转换,流程会多绕一大圈。
安装完成后,务必检查一下 GMTSAR 自带脚本列表,尤其看看有没有针对 NISAR 的示例脚本或模板。不同分支的命名可能不一样,但处理思路类似。我建议任何新装的 GMTSAR 都先跑一遍自带的测试数据集,通常是 ALOS 或 Sentinel-1 的小样本,确认链路没问题再切到 NISAR 数据。跳过这一步的代价,可能是在一个环境错误上耗掉一个晚上。我之前就碰到过 GMT 版本不匹配导致脚本里的绘图接口起不来的问题,排查很久才发现是环境变量没指到正确的 GMT 6 路径。
2.3 主副影像选择与 DEM 准备
干涉对选择上,主影像尽量选时间居中、垂直基线较小的“锚定”影像。如果下载的日期序列里某一天正好处于形变事件的中间段,拿它当主影像反而能得到比较均衡的条纹,处理起来更稳。实际操作中,可以用 GMTSAR 的基线估计工具,也可以先快速做一次粗配准看两景之间的空间基线是否异常,把明显超标的组合提前排除掉。
DEM 方面,GMTSAR 地理编码阶段需要把雷达坐标下的结果投影回经纬度高程格网,我用的是 SRTM 30m 分辨率 DEM,对 Erta Ale 这种地形起伏不算极端的地方完全够用。下载 DEM 后裁剪到研究区,范围要覆盖火山口周围至少 40km,再统一转换成 WGS84 经纬度格网格式。这里有个细节要提醒:如果目标区域近年来发生过新的熔岩流覆盖,DEM 里的高程和雷达观测时的实际地面会有偏差,这种偏差会在干涉图里以地形残余条纹的形式出现。火山活动频繁的区域,最好留意一下数据获取日期,能选到尽量新的 DEM 就用新的。
3. 从 SLC 到形变图:GMTSAR 处理 NISAR 的完整流程与代码
3.1 把 NISAR 的 SLC 转换到 GMTSAR 通用格式
先说清楚原理:GMTSAR 读取的 SLC 文件通常是一个二进制浮点文件,按 float32 单精度存储,实部虚部交错排列。与其指望 GMTSAR 去读懂 NISAR 的 HDF5 内部布局,不如直接用 Python 把目标区域裁剪出来,再写成 GMTSAR 熟悉的格式。这样后面所有步骤基本可以沿用处理 ALOS 的流程,少踩很多兼容性坑。
下面这段代码负责把 NISAR SLC 产品中某个窗口的复数数据导出成 GMTSAR 能直接读取的二进制文件。不同产品内部路径可能有差异,跑之前先用 h5py 把所有键名打印一遍:
import h5py import numpy as np def nisar_slc_to_gmtsar(h5_path, out_path, rng_slice, az_slice): # 不同产品内部路径有差异,先打印所有键确认后再跑 with h5py.File(h5_path, 'r') as f: dset = f['science/LSAR/SLC/swath0'] # 示意路径,以实际为准 slc = dset[az_slice, rng_slice] slc = np.asarray(slc, dtype=np.complex64) # 实部/虚部交错,输出为 float32 n = slc.size out = np.empty((n, 2), dtype=np.float32) out[:, 0] = slc.real.ravel() out[:, 1] = slc.imag.ravel() out.tofile(out_path) print(f"wrote {out_path}, shape={slc.shape}")调用时给rng_slice和az_slice传一个窗口切片,比如slice(5000, 20000), slice(2000, 15000)。窗口大小的确定方法我放在后面踩坑部分讲,这里只提醒一句:宁可稍微多留一点范围,也不要裁得太紧导致形变场被切掉。
轨道状态向量同样需要导出。GMTSAR 的轨道文件格式是每行一组状态向量:时间(秒)、位置 x y z(米)、速度 vx vy vz(米/秒)。下面是转换模板:
import h5py import numpy as np def nisar_orbit_to_gmtsar(h5_path, out_orb_path, t0): with h5py.File(h5_path, 'r') as f: orbit = f['science/LSAR/orbit'] # 示意路径,以实际为准 t = orbit['epoch'][...] # 可能是时间字符串或秒数 pos = orbit['position'][...] # 注意单位:km 还是 m vel = orbit['velocity'][...] # 注意单位:km/s 还是 m/s with open(out_orb_path, 'w') as fo: for k in range(len(t)): # 统一转成自 t0 起的秒数 sec = (np.datetime64(t[k]) - np.datetime64(t0)) / np.timedelta64(1, 's') x, y, z = pos[k] * 1000.0 # 如果原单位是 km,转成 m vx, vy, vz = vel[k] * 1000.0 fo.write(f"{sec:.6f} {x:.6f} {y:.6f} {z:.6f} {vx:.6f} {vy:.6f} {vz:.6f}\n")这段代码最关键的其实是单位换算。NISAR 产品里位置和速度字段的单位并不统一,我第一版脚本漏了乘 1000,出来的干涉图整体都是大尺度弯曲条纹,看起来像一大片假形变,后面专门花了半天才定位到这个环节。
3.2 配准、干涉生成与关键参数
SLC 和轨道文件都准备好之后,就进入 GMTSAR 的常规链路。我用的 GMTSAR 版本里,ALOS 的 L 波段 stripmap 流程脚本处理 NISAR 转出来的 SLC 完全够用。如果你下载的新版本自带 NISAR 入口脚本,直接调用会更省事;如果没有,就用 ALOS 流程作为起点,把波长改成 0.238 米。
我使用的config.in最小模板如下:
sat=ALOS2 interf=1 n_az=10 n_rg=2 filter=1 wavelength=0.238 dem=./dem/dem.grd几个参数的含义简单说一下:
sat=ALOS2:告诉脚本按照 L 波段 stripmap 的默认参数去处理,NISAR 的宽幅模式在波位细节上有差异,但初步测试时这样设置是可行的;n_az和n_rg:多视数。10:2 是分辨率和信噪比之间比较平衡的组合,条纹太密就降低距离向视数;filter=1:开启 Goldstein 滤波,后面还可以单独调整滤波因子;wavelength=0.238:相位转形变时用的雷达波长,单位米。
运行流程脚本:
p2p_ALOS.csh config.in >& p2p.log这一步跑起来后,GMTSAR 会逐阶段生成配准结果、干涉图、滤波后的相位图。建议第一次跑的时候不要让它一口气跑到底,而是先跑到干涉图输出就停下来,用 GMT 打开干涉图看一眼条纹质量。如果条纹方向和形态明显不对劲,趁早回头查轨道文件或配准参数,比等所有阶段跑完再排查要快得多。
3.3 滤波、解缠与相位转形变
干涉图生成之后,解缠前先看相干性图。Erta Ale 火山口附近的熔岩湖区域相干性会骤降,因为熔岩流表面持续移动、温度高,相位基本是白噪声。这一块通常是不可解缠的,我用相干性阈值 0.3 生成掩膜,把低相干像元排除掉。
Goldstein 滤波因子我一般取 0.4,兼顾条纹保真和噪声抑制。如果条纹非常密集导致解缠跑不通,可以把滤波因子降到 0.2;但不要低于 0.1,否则滤波几乎不起作用,噪声会直接传导到解缠阶段。
解缠我用的还是 snaphu,配置关键项如下:
defomax = 0 costmode = 0 mask = coh_mask.grd其中defomax = 0表示允许的最大形变梯度不设额外限制,适合 Erta Ale 这种形变集中在火山口周边的场景;costmode = 0是默认的统计成本模式,稳定可靠。如果解缠时出现大面积跳变,可以改成costmode = 1试试,虽然慢一些,但边缘处表现往往更好。
解缠完成后,地理编码阶段会把雷达坐标结果投影到经纬度网格。GMTSAR 输出的解缠相位单位是弧度,转成视线向形变时用下面这条grdmath命令:
grdmath phase_unw.grd -1 mul 0.238 4 PI mul div mul 1000 mul = los_disp_mm.grd这条命令的数学逻辑是:形变量 = -相位 × 波长 / (4π),再乘 1000 变成毫米。负号是为了保证相位增加对应视线远离卫星的方向,方便后面跟地壳形变的正负号判读统一起来。
3.4 完整处理脚本骨架
为了让上面这些步骤更好复用,我把它们整理成一个脚本骨架:
#!/bin/bash # GMTSAR + NISAR 处理流程骨架 # 假设已有 master.slc / slave.slc 以及对应 .orb 文件 set -e DEM_DIR=./dem DEM=$DEM_DIR/dem.grd # 1. 生成干涉图(使用 GMTSAR 自带流程脚本) p2p_ALOS.csh config.in >& p2p.log # 2. 相干性阈值掩膜,排除熔岩湖等低相干区 grdmask coh.grd -NAN/0.3/NaN -Gmask.grd # 3. 解缠 snaphu -f snaphu.conf # 4. 地理编码并转换 LOS 形变 # 注:geocode 命令在不同版本里名称略有差别,按你版本里的实际命令执行 geocode.csh config.in grdmath phase_unw.grd -1 mul 0.238 4 PI mul div mul 1000 mul = los_disp_mm.grd如果你不熟悉 GMTSAR 的批处理机制,建议一开始按步骤手动执行每个阶段,看看中间产物再继续,不要一上来就跑完整脚本。等流程完全稳定了,再把这套骨架一键化,效率会高很多。
4. 火山形变结果解读与质量校验
4.1 从干涉条纹看熔岩湖的“呼吸”
处理完 Erta Ale 这组数据后,典型的干涉纹形态在火山口周围出现一组近似圆环的条纹,半径大约在 2 到 4 公里,相位从中心往外平滑过渡,中心区域有小范围失相干——那正是熔岩湖本身。结合相位正负方向和轨道方向,可以判断这次观测期间浅部岩浆囊处于压力调整状态,整体表现为以小湖盆为中心的复胀或复缩。
这类同心圆条纹在火山 InSAR 里非常经典,但你很容易被它误导,因为视觉上“圆”并不等于源就是正圆形的点源。真正要确认形变源参数,还需要做点源模型拟合,或者至少沿不同方位角方向切出几条相位剖面做对比。我只是做了剖面检查,确认条纹梯度在东西和南北方向上基本对称,才敢把它判读为近似点源的压力变化信号。
顺带提醒一个基本概念:InSAR 测到的是雷达视线方向(LOS)的一维投影。熔岩湖附近地表如果是纯垂直运动,在升轨和降轨数据里 LOS 形变幅度会有差异,因为两种轨道观测几何不同。如果只用一个轨道方向的数据,你只能给出相对定性的“抬升还是沉降”;想要约束真实三维形变,至少要把升降轨两个方向的数据都处理一遍,在重叠区做联合解算。
4.2 相干性图是第一个质检员
处理完成不等于结果可信。我每次都会打开相干性图,先看低相干区域是否集中在预期位置。Erta Ale 的熔岩湖中心、部分坡度陡峭的火山口壁,甚至一些年代较新的熔岩流边界,相干性低都属于正常;真正需要警惕的是大面积连片的低相干区出现,那往往意味着配准失效或者轨道状态向量有问题。
相位解缠质量同样需要检查。一个简单有效的办法是看解缠相位图中是否出现孤立的条纹“断层”:如果同一圈条纹里出现明显的相位台阶,多半是解缠跳变。这时返回到滤波参数和掩膜阈值去调整,比硬着头皮继续做地理编码要好。
大气误差是另一个躲不掉的话题。Erta Ale 所在区域水汽梯度不算极端,但火山雷达数据的电离层和对流层延迟依然会叠加在形变信号上。我的处理习惯是在解缠前用一次平面趋势拟合把大尺度的轨道残差先去掉,保留短波长的形变信号;如果同一条轨道方向有多期数据,还可以用多景平均的方法压制大气噪声。这个做法不能说完全消除误差,但对熔岩湖这种空间尺度只有几公里的信号来说,效果已经足够明显。
4.3 和常规现象对照,防止把误差当信号
Erta Ale 长期活动的核心特征是熔岩湖液面周期性涨落。根据公开的观测记录,当湖面活动加强时,火山口附近往往会出现新的熔岩流,同时伴随震群或者热异常。这些信息虽然不能直接转换成相位值,但能帮你在解释干涉图时建立一个合理预期:如果解译出的形变形态和这些独立观测严重冲突,那就要回头检查处理链路是不是出了问题。
我拿到这组数据时,时间窗口内恰好有熔岩湖活动的报道,干涉图中火山口中心区域的相位累积方向和形态,与这次活动的预期位移方向基本吻合。这种交叉验证不需要复杂计算,但能显著提升结论的说服力。对于只想快速测形变、暂时不打算做完整建模的人来说,做到这一步已经能支撑一篇不错的分析报告了。
5. 踩坑实录:NISAR 数据与 GMTSAR 配置的细节
5.1 轨道状态向量的单位与时间基准
这个坑让我浪费的时间最多。GMTSAR 的轨道文件需要时间(秒)、位置(米)、速度(米/秒),而 NISAR 产品里位置字段可能以公里为单位,速度字段可能以公里/秒为单位,时间也可能是相对某历元的秒数或者 UTC 字符串。我第一版转换脚本忘了把位置乘以 1000,结果干涉图里布满大尺度弯曲条纹,看起来像一整片很假的形变。
教训是:转换脚本里一定要显式检查数值量级。地心地固坐标下的位置分量正常应该在 6.4e6 米量级,速度应该在 7e3 米/秒量级。你可以在 Python 里加一段断言,数值不在合理范围就直接报错。时间部分更需要仔细核对参考历元,统一换算成自影像开始时刻起的秒数,否则配准时会在方位向出现整体错切,整个干涉图会像被切线“推歪”了一样。
5.2 整景处理的内存压力与裁剪策略
NISAR 一幅影像的幅宽非常大,整景直接跑后续的干涉、滤波、解缠,内存占用会非常夸张。处理 Erta Ale 时其实只需要火山口周围几十公里的数据,所以第一步就在 SLC 导出阶段做了窗口裁剪,把整景数据瘦身成一个小方块。
裁剪窗口怎么定?最简单的方式是先用幅度图打开整景,手工圈出火山口的位置,再根据 DEM 范围和雷达几何换个粗略的像素窗口。宁可稍微多留一点边界,也不要太抠,否则形变场的边缘可能被切掉,影响后面的解缠和建模。我最后把处理子区控制在约 40km×40km,内存峰值从接近 32GB 降到了 8GB 以内,单台工作站就能顺利跑完全流程。对以后要做时间序列的人来说,裁剪策略尤其重要,因为它直接影响批量处理时的吞吐量。
5.3 熔岩湖本身的失相干和相位卷绕
Erta Ale 最大的特色也带来了最棘手的现象:熔岩湖表面在观测间隔内变化太快,湖面区域在干涉图上往往是完全失相干的白噪声。一开始我想在解缠阶段把这块硬解出来,结果解缠错误向周围扩散,环形条纹里出现大片跳变,整个火山口的相位图都变得没法用。
后来我果断放弃,解缠前直接用相干性阈值把熔岩湖和火山口北东侧的低相干区掩膜掉,只在周边相干好的区域做解缠。因为浅部岩浆源引起的形变场本来就是空间连续函数,掩膜不会影响对外围弹性响应的还原。这一招之后几乎用在了我所有的处理流程里,效果好得出奇——前提是你要清楚自己丢掉的是“湖面本身”,而不是想测的周边形变。如果确实需要测量湖面变化,建议改用更短时间基线、减少多视窗口,或者直接用官方解缠级别产品做参考对比。
最后分享一个实用的小习惯。每次跑 GMTSAR 处理 NISAR 数据,我都会把当天使用的软件版本、GMT 版本、config.in参数、裁剪范围记在一个proc.log文件里,下次拿到新数据直接在脚本里改日期和路径重跑。形变结果出来后,再在 GMT 里把升降轨数据的形变网格叠在一起交叉验证。这套流程处理 Erta Ale 花了两三天,后来换到其他火山区域时,基本只需要调整裁剪窗口和 DEM 范围,半天就能出第一版形变图。希望上面这些代码和踩坑记录,能让你省下几小时甚至几天的弯路。