我第一次跑通TERCOM的时候,用的还是MATLAB R2018b。说实话,当时对着屏幕上那张被MSD平面填满的图,心里只有一个念头:这么"笨"的匹配方法,居然真的能在一张大地图里把我模拟的飞行器位置给揪出来。后来反反复复做了两年多的地形匹配相关课题,踩过不少坑,也积累了一些实打实的优化经验。今天这篇就把"基于MATLAB的TERCOM算法实现与优化"这件事从头到尾拆开讲,把原理、代码、优化思路和那些容易翻车的地方一次性讲清楚。无论你是在做水下无人平台的导航修正,还是研究低空飞行器的辅助定位,或者只是对地形匹配算法本身感兴趣,这篇文章应该都能给你一些直接能抄作业的东西。
1. TERCOM到底在解决什么问题:从"找自己"说起
1.1 一个反直觉的导航思路
先说一个很朴素的问题:一个飞行器在天上飞,它怎么知道自己在哪?
最常见的办法是GPS,其次是用惯性导航——从出发点开始,测量加速度和角速度,不断积分算出位置。惯性导航的问题在于积分误差会随时间累积,飞得越久,位置漂移越大,而且它完全依赖起点,一旦初始对准有偏差,后面所有推算都会跟着偏。
于是有人想到了另一种思路:不看起点,不看过程,只看脚下。飞行器下方装一个高度计,飞行过程中记录一个地形剖面高度序列,然后拿这个序列去和预存的数字高程模型(DEM)做对比,找到地图上"看起来最像"的那条线,当前位置自然就确定了。这个思路就叫TERCOM,TERrain COntour Matching的首字母组合,中文一般翻译成地形轮廓匹配。
为什么说反直觉?因为常规导航是"往前推",而TERCOM是"停下来认一认"。就像你去一个陌生城市迷了路,不看路牌不查手机,但是你看了一眼周围的建筑轮廓和街道布局,再和你脑中的城市地图对比一下,大概就能知道自己在哪。TERCOM做的事情本质上就是这个——脚下的地形轮廓就是"街景",DEM就是"脑内地图"。
1.2 地形匹配导航用在哪些场景
可能有人会问:现在GPS这么普及,GNSS接收机又小又便宜,为什么还要折腾一个地形匹配?
因为有很多场景是GPS靠不住的:
- 水下航行器。无线电信号在水里衰减得非常厉害,GPS天线基本收不到星。水下平台做长航时航行时,惯性导航的漂移误差会随着时间累积到不可接受的程度,这时候就需要通过测量海底地形来做位置修正。
- 在山区、峡谷、城市高楼区飞行的低空无人机。这些环境下GPS信号会因为多路径效应、遮挡等原因出现跳变或丢失,单独依赖卫星定位容易出问题。
- 远程飞航平台在没有外部无线电辅助的时段,需要一种完全被动、不依赖任何外部信号源的定位手段,地形匹配正好满足这个要求。
这类算法的共同特点是"被动、无源、自主",不向外发射任何信号,也不依赖地面站。这在很多场合下是实打实的优势,毕竟你不能指望所有环境里都有稳定的卫星信号或无线电信号覆盖。
1.3 和惯性导航、卫星导航的分工关系
要特别强调一点:TERCOM通常不是拿来替代惯导或卫星导航的,而是作为"修正源"存在的。
主导航系统一般还是惯性导航(INS),它能给出高频、短期高精度的位置、速度和姿态。但INS有累积误差,这时候就需要一个绝对参考来定期"校一下"。GPS是一种绝对参考,地形匹配也是。TERCOM输出的是一个位置修正量(或者干脆输出一个匹配结果),和惯导预测值做融合——工程上常用卡尔曼滤波来做这个融合,让TERCOM低频的校正修正INS高频的漂移。
我在仿真中最常用的做法是:惯导提供预测位置,以预测位置为中心划出一个搜索范围,TERCOM在这个范围内找匹配峰值,找到后把峰值位置作为观测更新送进滤波器。这个设计非常关键,它直接决定了搜索范围应该多大、计算量能压到多少,后面第四部分会专门展开。
2. 算法核心原理:相关匹配背后的数学直觉
2.1 地形高度剖面为什么能当"指纹"
要理解TERCOM,先要回答一个问题:为什么一组地形高度数据可以唯一确定一个位置?
答案在于真实地形的"随机性"与"空间相关性"的结合。真实的地形高度分布不是完全杂乱无章的——相邻高度之间存在正相关,所以地形看起来是连绵起伏的,而不是雪花噪声式的一颗颗独立像素;但如果看整体格局,每个区域的高度起伏组合又是独特的,几乎没有两处地方的地形剖面完全一样。
这是一个很有趣的统计特性:地形场既"相关"又"独特"。用专业一点的话说,地形可以看作一个随机场,其自相关函数决定了不同位置之间的统计关联,而关联长度越长,该地形场的"指纹"尺度越大。对TERCOM而言,地形相关长度是选择匹配窗长度的核心依据——如果相关长度是500米,高度剖面在500米范围内才有明显的起伏特征,匹配序列至少要覆盖两三个相关长度,否则两个相隔很远的区域可能在统计上长得差不多,导致误匹配。
我在构造仿真地形时最常用的一招是:生成一个高斯白噪声场,然后用高斯核做平滑滤波,这样得到的数字地形既保留了随机性,又具有空间相关性,非常接近真实地形特征。MATLAB里几行就能搞定:
rng(20250214); sigma = 5; % 地形相关长度,单位:网格 dem = imgaussfilt(randn(512, 512), sigma);这里的sigma直接控制地形起伏的相关长度,sigma越大,地形越平缓,反过来则越"碎"。做算法验证的时候,用这种合成地形比真实的DEM数据方便得多,因为你可以精确控制各种地形属性。
2.2 常用相关度量的四种形式
TERCOM的核心操作,是把实时测量的高度序列和地图上每个候选位置的"地图高度序列"做一个相似性度量,然后找出度量最优的那个位置。常用的度量有以下四种。
记测量序列为(h_m(i), i=1\ldots N),地图上某个候选位置对应的高度序列为(h_g(i), i=1\ldots N)。
相关系数COR(Cross Correlation): [ C = \frac{1}{N}\sum_{i=1}^{N} h_m(i) \cdot h_g(i) ] COR越大,说明两个序列越"同相"。它对信号幅度的整体缩放不敏感,但可能对局部高幅值地带过于敏感,峰值不够尖锐。
平均绝对差MAD(Mean Absolute Difference): [ M = \frac{1}{N}\sum_{i=1}^{N} |h_m(i) - h_g(i)| ] MAD越小越匹配。它对野值(异常尖峰)的承受能力较强,因为绝对值运算不像平方那样会放大离群点。计算也简单,适合初期快速测试。
均方差MSD(Mean Squared Difference): [ S = \frac{1}{N}\sum_{i=1}^{N} \left(h_m(i) - h_g(i)\right)^2 ] MSD越小越匹配。平方运算会让"接近匹配"和"明显不匹配"的情况差异拉大,因此MSD的匹配峰通常比MAD更尖锐。这是我实际项目中使用最多的度量,后面所有代码也以MSD为例。
归一化相关NCC(Normalized Cross Correlation): [ N = \frac{\sum (h_m-\bar{h_m})(h_g-\bar{h_g})}{\sqrt{\sum(h_m-\bar{h_m})^2 \cdot \sum(h_g-\bar{h_g})^2}} ] NCC会先对两个序列做去均值处理,天然消除高度计常数偏置的影响,抗直流漂移能力最强。代价是计算量略大,且每次滑动窗口都需要计算窗口内均值与方差,不容易用卷积直接提速。
四种度量,各有各的脾气。从工程实践的角度,我总结了一张对比表:
| 度量 | 表达式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| COR | Σ hm·hg | 计算简单 | 峰不够尖锐,受幅值缩放影响 | 快速初筛 |
| MAD | Σ |hm-hg| | 抗野值,稳健 | 峰不够锐利 | 噪声较强时 |
| MSD | Σ (hm-hg)² | 峰尖锐,区分度最高 | 对野值敏感 | 常规首选 |
| NCC | Σ (hm-μₘ)(hg-μg)/σₘσ_g | 抗偏置与增益变化 | 计算量大,不易卷积加速 | 高度计有偏置漂移时 |
顺带提一句:很多论文里还会对MSD、MAD这些"差越小越匹配"的指标取倒数或取负,把问题转成"越大越好"的寻峰问题,方便和COR统一处理。这个纯粹是个人习惯,不影响结果。
2.3 MSD为什么更适合做匹配峰检测
每种度量我都在合成DEM上做过对比实验,结论很稳定:在中等噪声环境下,MSD的定位精度最好。原因在于平方项对"接近于匹配但存在细微差异"的情况做了强化——距离真值位置差一个网格和差三个网格时,MSD的差异会被拉开,而MAD的差异相对没那么明显。
打个比方:MAD像是你看模糊的照片,只能大概觉得"这几个地方都差不多像";MSD像是把照片放大逐像素对比,最像的那个点会因为平方惩罚而被显著突出。代价是,如果高度测量数据里混入了几个比较离谱的野值(比如飞过一个尖锐塔楼的瞬间),平方项会把错误位置的地图序列也拉得很远,反而可能把原本正确的位置给"误伤"。所以如果实测数据噪声比较重,我会先用一个3点中值滤波把明显野值压一压,然后放心用MSD。
至于具体实现里如何把MSD的计算量压到极限,这就引出了MATLAB实现的核心技巧。
3. MATLAB实现:从DEM数据到匹配定位的完整链路
我见过不少初学者写TERCOM的MATLAB程序,一上来就是三重循环:外层遍历地图的行,中层遍历地图的列,内层遍历测量序列的每个点,逐点计算高度差累加。这个写法逻辑上没错,但在地图是1000×1000、测量序列长几十个点的时候,跑一趟要十几秒甚至更久,完全不具备实用性。
这一节我给出一个"干净版"的实现链路,重点在于用MATLAB的矩阵思维替代显式循环。
3.1 仿真实测数据生成:DEM、真值航迹、测量序列
先准备测试环境。假设DEM是一块512×512的网格,每个网格代表10米,高度是上述高斯平滑随机地形。
% 生成仿真DEM res = 10; % 每个网格10米 cols = 512; rows = 512; dem = imgaussfilt(randn(rows, cols), 5); % 相关长度约5个网格 % 设定真实航迹:从(200,100)水平向东飞80个网格 trueStart = [200, 100]; lenSeq = 80; rowTrue = trueStart(1) * ones(1, lenSeq); colTrue = trueStart(2) : trueStart(2) + lenSeq - 1; elevTrue = dem(sub2ind([rows, cols], rowTrue, colTrue)); % 加高度计测量噪声(标准差0.5m) rng(0); meas = elevTrue + 0.5 * randn(1, lenSeq);这段代码里我故意把真值航迹设成了一条水平直线。现实中航迹当然可以是任意曲线,但匹配算法本身只关心你拿到的测量序列与地图上某一段序列的相似程度,直线只是让第一版代码更容易对照验证。
3.2 滑动窗相关计算的"卷积加速"写法
现在进入关键部分。我们要在地图上的每一个可能起点位置,裁出长度为lenSeq的地图高度序列,和测量序列计算MSD。
直接写循环当然可以,但MATLAB强在矩阵运算上。MSD的展开式给了我们一个绝佳的加速机会:
[ MSD(j) = \frac{1}{N}\sum_{i=1}^{N} h_m(i)^2 + \frac{1}{N}\sum_{i=1}^{N} h_g(i+j)^2 - \frac{2}{N}\sum_{i=1}^{N} h_m(i)\cdot h_g(i+j) ]
三项中,第一项是常数,不用重复算。第二项是对地图高度做平方后在一个长度为N的滑动窗内求"窗口和"——这本质上就是一个一维卷积。第三项是测量序列与地图序列的滑动内积,同样可以看成"翻转后的测量序列与地图的卷积"。
所以整个MSD矩阵可以通过两次conv2来实现:
L = numel(meas); % 项1:测量序列平方和(常数) meas2Sum = sum(meas.^2); % 项2:地图高度平方的滑动窗和(沿行方向) sumMap2 = conv2(dem.^2, ones(1, L), 'valid'); % 项3:地图与测量序列的滑动内积(翻转测量序列做卷积) crossTerm = conv2(dem, fliplr(meas(:)'), 'valid'); % 合成MSD矩阵 msdMap = (sumMap2 - 2*crossTerm + meas2Sum) / L;conv2的'valid'选项会返回窗口完全覆盖地图时的有效结果,尺寸正好是(rows, cols-L+1),每个元素就对应一个候选起始位置的MSD值。这个写法避免了三个循环,MATLAB底层C语言实现的卷积计算非常快。
注意我的演示中测量序列沿地图行方向滑动,也就是假设航迹平行于地图x轴。如果实际航迹是任意航向角,通常需要把地图旋转到航迹坐标系,或者沿多个航向角分别做匹配,这在工程实现时会体现为一个"航向搜索"的外层循环。
3.3 从相关面提取位置和亚网格插值
拿到msdMap后,找最小值位置:
[minVal, linIdx] = min(msdMap(:)); [posR, posC] = ind2sub(size(msdMap), linIdx);这样就得到了按网格计的匹配位置。但TERCOM的定位精度其实可以做到亚网格级别。方法很简单:在最小值周围的3×3邻域内,用一个二次曲面拟合MSD曲面,然后求曲面的极小值点。
% 取最小值周围3×3区域 rR = max(posR-1,1):min(posR+1,size(msdMap,1)); cC = max(posC-1,1):min(posC+1,size(msdMap,2)); patch = msdMap(rR, cC); % 二次曲面拟合(简化版:分别对行、列方向做抛物线插值) denomR = patch(1,2) - 2*patch(2,2) + patch(3,2); denomC = patch(2,1) - 2*patch(2,2) + patch(2,3); deltaR = (patch(1,2) - patch(3,2)) / (2*denomR); deltaC = (patch(2,1) - patch(2,3)) / (2*denomC); subR = posR + deltaR; subC = posC + deltaC;这个抛物线插值虽然简单,但把位置精度从±1个网格提高到了约±0.1个网格(在噪声水平较低时)。注意deltaR和deltaC的分母为0或接近0时(对应MSD平面太"平"),说明那个地方匹配峰不尖锐,插值结果不可靠——这个现象本身也是可用的质检信号,后面会再提到。
3.4 可视化验证
调试阶段一定要把MSD曲面画出来,因为很多问题一眼就能从图上发现:
figure; imagesc(msdMap); hold on; plot(posC, posR, 'rx', 'MarkerSize', 10, 'LineWidth', 2); colorbar; colormap(flipud(hot)); % 深色表示MSD小,即更匹配 title(sprintf('MSD Map, min=%.3f', minVal));正常情况下,MSD曲面上应该有一个明显区别于周围区域的"深色坑",且坑的位置正好落在真值附近。如果这张图上一片模糊、看不出明显的坑,基本可以断定某个环节出了问题,最常见的三个原因:地形太平、序列太短、测量噪声太大。这三类问题我在第五部分再细说。
4. 性能优化:搜索策略与计算加速两手抓
4.1 粗-精金字塔搜索:先看个大概,再看细节
MATLAB代码就算用卷积加速,在二维全图上做0.5米分辨率的滑动匹配,仍然存在性能瓶颈。更聪明的做法是不在一开始就用全分辨率搜索。
金字塔搜索的思路是:把DEM逐级降采样(每隔几个网格取平均或取最大值),生成一个低分辨率的"粗地图",先在这个粗地图上做一遍匹配,锁定一个大概的候选区域;然后再回到原始分辨率,只在这个候选区域附近做精细匹配。
为什么可行?地形是空间自相关的,低分辨率地图保留了大尺度起伏特征,粗匹配足以排除掉绝大多数不相关区域。而精细匹配只需要在一个小得多的范围内进行,计算量呈数量级式下降。
% 降采样函数示例:2×2块平均 demCoarse = imresize(dem, 0.5, 'box'); % 或用 blockproc 做更可控的降采样 % 粗匹配... % 锁定粗位置后放大回原图坐标,再在原图上以粗位置为中心, % 裁剪一块半径为searchRadius的搜索区域,做细匹配MATLAB自带的imresize在配合'box'方法时可以直接做块平均,非常方便。如果对降采样方式有特殊要求,比如需要保留地形中的尖峰特征,也可以用blockproc配合自定义函数。
4.2 搜索窗自适应:惯性导航先指个路
一个很多新手容易忽略的优化点是:TERCOM完全不必搜全图。飞行器或水下航行器有惯性导航,惯导虽然漂移,但至少能给出一个大致位置。以惯导预测位置为中心,按惯导估计误差扩散的半径划定搜索窗,匹配只在这个窗内做,计算量可以从全图级的几百万点降到几万点。
这里的关键是搜索半径的确定。惯导误差常用位置误差的方差来刻画,假设惯导的水平定位误差标准差为(\sigma_{ins}),那么一个合理的搜索半径大约取(3\sigma_{ins})到(4\sigma_{ins}),确保真实位置落在窗内的概率足够高。比如惯导漂移后水平误差标准差为300米,DEM分辨率为10米,那搜索窗半径至少需要(3\times300/10=90)个网格。
放在代码里就是:
driftRadius = 3 * sigmaIns; % 单位:米 margin = ceil(driftRadius / res); searchMap = dem(posRIns-margin:posRIns+margin, posCIns-margin:posCIns+margin);配合第四部分讲的conv2技巧,在这个小窗里做滑动匹配,速度飞快。
4.3 向量化、并行化、编译加速的组合拳
即使搜索窗缩小了,有些人可能还是嫌慢。进一步提速还有三招。
第一招,尽可能把多个匹配任务向量化。比如我们要同时评估多条候选航迹(例如飞行器转弯前的多条可能路径),可以一次性在三维数组上做运算,避免循环。MATLAB的bsxfun虽然老,但配合现代MATLAB的广播机制,写起来非常自然:
% X,Y为测量序列矩阵,每行一条序列,同时和多条地图剖面对比 % 用隐式扩展构造三维差矩阵后做sum diff3D = mapPatchWindow - permute(measMatrix, [2,3,1]); msd3D = squeeze(mean(diff3D.^2, 1));第二招,用parfor做多条航迹的并行匹配。如果你的机器有6个物理核,这一步可以近似线性加速。之前我在一个四核机器上同时处理32条备选航迹,开parfor后耗时从单核的0.25秒降到0.07秒。
第三招,把核心函数编译成MEX。MATLAB对纯循环不友好,但对向量化后的代码其实优化得不错了。如果还想更快,可以用codegen把匹配函数转成C代码并编译为MEX文件:
codegen tercom_msd -args {coder.typeof(0, [inf inf]), coder.typeof(0, [inf 1]), 0}编译后在MATLAB里直接调用tercom_msd_mex,速度通常会再快3到10倍。代价是要稍微注意一下函数里哪些MATLAB函数支持代码生成(imgaussfilt这类就未必支持,但纯数值运算的匹配函数没问题)。
这三招是层层递进的,我建议按"卷积累加+搜索窗自适应 → 金字塔 → 并行 → MEX"的顺序来优化,每一步都先跑基准测试,别一上来就搞最复杂的MEX。
5. 实测优化效果与踩坑记录
5.1 优化效果对比
我拿一块1000×1000的合成DEM、测量序列长度80做了一组测试,环境是MATLAB R2022b + i7-10700台式机。结果如下:
| 实现方式 | 耗时 | 说明 |
|---|---|---|
| 三重循环朴素版 | 约13.8秒 | 最直观的写法,不可工程化 |
| 卷积向量化 | 约0.06秒 | 几乎实时 |
| 卷积向量化+搜索窗(100×100) | 约0.008秒 | 搜索窗缩小40倍 |
| 搜索窗+金字塔两层 | 约0.004秒 | 再快约一倍 |
| 搜索窗+MEX编译 | 约0.001秒 | 百微秒级,真·实时 |
这个表的数据比很多论文里贴的数字都好看,不是因为我的机器强,而是因为"把循环换成卷积"和"别全图搜索"这两件事太重要了。如果你的项目对速度还有更极端的诉求,可以继续往下做GPU加速——MATLAB的gpuArray配合conv2其实改动不大,但一般嵌入式导航设备用的是FPGA或DSP,MATLAB更适合做算法验证和离线分析。
5.2 平坦区域误匹配:MSD平面没有"坑"
第一个坑,也是所有做TERCOM的人都会遇到的坑:飞过一段特别平坦的地形时,MSD平面会出现一大片接近相等的低值区域,找不到明显的极小值。
这不是算法写错,而是目标区域本身缺乏可区分的地形特征。就像你在一个全是沙漠的地方看地图,任何位置看起来都差不多,当然无法匹配。
此时绝对不能直接取全局最小值——那个位置大概率是噪声随机翻出来的。正确做法是加一个"匹配质量门限",只有匹配峰足够尖锐才认为定位有效,否则就拒绝这次TERCOM修正。我的经验做法是:
[MSDsorted, sortIdx] = sort(msdMap(:), 'ascend'); ratio = MSDsorted(2) / max(MSDsorted(1), eps); if ratio < 1.3 % 主峰和次峰差距太小,匹配不可靠 % 放弃本次匹配结果,等待下一轮测量 end阈值1.3可以根据地形情况调,地形起伏越大,这个比值会自然越大,气动环境越好。宁可不修正,也不要修正到错误位置上——在组合导航里,一次错误的TERCOM修正对滤波器的损害远大于一次"没有修正"。
5.3 测量序列长度:既不能太短,也不能太长
第二个常见坑是测量序列长度选得不合适。
太短的序列(比如只有10个点)包含的地形信息太少,容易匹配到多个相似位置。太长也不好——如果序列跨越了不止一个大的地形单元,平坦区会把特征区的贡献稀释掉;另外序列越长,需要的计算窗越大,虽然用卷积后多出来的计算量没有想象中那么大,但边界效应仍在。
我的经验规则是:测量序列长度至少覆盖3到5个地形相关长度。所谓地形相关长度,可以用DEM的自相关函数来估计:在地形中取一条线,计算它的自相关,降到1/e时对应的距离就是相关长度。如果DEM分辨率是10米,相关长度约为30米,那么序列长度选300到500米比较合适。
% 粗略估计地形相关长度 profileLine = dem(256, :); acf = xcorr(profileLine - mean(profileLine), 'coeff'); halfLen = floor(numel(acf)/2); acf = acf(halfLen+1:end); corrLenIdx = find(acf < 1/exp(1), 1, 'first'); corrLen = corrLenIdx * res;这个公式很有用,它给了你一个选择匹配窗长度时的基线值,而不是靠拍脑袋。
5.4 高度计噪声和DEM误差的容忍度
第三个坑是关于噪声的。TERCOM对测量噪声有一定容忍度,但容忍度是有限的。我做过一个扫描实验:在MSD匹配下,噪声标准差从0.1米加到1.5米时,匹配成功率从接近100%下降到了约70%。这里的成功率定义为"匹配位置与真值相差不超过2个网格"。
因此,如果飞行器高度计的噪声比较大,最好在进TERCOM之前做一次预处理。常用操作有两个:
一是对测量序列做中值滤波,长度取3到5个点,专门对付野值。
二是做"去趋势"。飞行器在长距离飞行时,测量序列会叠加一个缓慢变化的趋势项(比如跨过一片从海拔200米升到400米的区域),这个趋势项如果不去掉,匹配结果可能会偏向某个方向。处理方法很简单:对测量序列和地图分别做高通滤波,或者直接在匹配前都用各自的"减均值后序列"。NCC度量天然具备这个效果,如果你发现MAD/MSD都容易在这种场景下跑偏,直接改用NCC往往就能解决。
% 去趋势后的MSD匹配(等价于对两个序列都减均值后再算MSD) measDetrend = meas - mean(meas); % 地图侧的处理需要滑窗减均值,通常用卷积先算滑动平均再做差 mapMovAvg = conv2(dem, ones(1,L)/L, 'valid'); mapDetrend = mapPatchWindow - mapMovAvg; % 注意尺寸对齐把测量侧和地图侧都变成"零均值的起伏量",再拿这套预处理后的数据去做匹配,可以有效规避地形梯度引起的系统性偏差。
5.5 航向误差问题
还有一个容易被忽略的点:真实航迹的航向不是完全准确的,惯导给出的航向可能偏了几个度。如果你的匹配算法假设航迹严格平行于地图坐标系的某条轴,航向误差会直接反映在匹配位置上。
处理办法是在匹配外层增加一个航向搜索循环。例如在预测航向角±3°的范围内,每隔0.5°生成一条旋转后的参考航迹,逐条与地图做匹配,最后取MSD最小的那一条作为匹配结果——它的航向和位置同时被估计出来。因为有了第四部分的优化手段,这个航向搜索循环带来的计算量完全在可接受范围内。
我在一次仿真中把2°的固定航向误差加进去,如果不做航向搜索,匹配位置偏移了约5个网格;做了航向搜索后,偏移降到了1个网格以内。这个经验说明:TERCOM不只是估计位置,在航迹足够长的时候,它甚至能辅助估计航向,关键就看你的实现里有没有把航向这个自由度纳入搜索空间。
6. 一些MATLAB工具箱和版本上的贴心提示
做这套算法用到的MATLAB功能,主要分布在基础模块、Image Processing Toolbox和Signal Processing Toolbox。imgaussfilt在IP Toolbox里,xcorr在Signal Processing Toolbox里,conv2和ind2sub是基础函数。如果你手头的MATLAB没有装图像处理工具箱,也可以手动用conv2加高斯核做平滑来替代imgaussfilt,实现起来并不复杂:
gaussKernel = fspecial('gaussian', [2*ceil(3*sigma)+1, 1], sigma); demSmooth = conv2(randn(rows, cols), gaussKernel, 'same');还有一点:新版本MATLAB中imresize的插值方法名称每次大版本都可能微调,如果你用的版本比较老(比如2018b),'box'这个写法可能不支持,改用'bilinear'或者直接用blockproc更稳妥。这类琐碎的兼容问题,在做算法工程化时反而最常见,我建议在代码最开始做一个版本判断或工具箱存在性检查,免得在别的机器上跑的时候突然报错。
assert(license('test', 'image_toolbox'), '需要Image Processing Toolbox');如果只依赖基础函数会更好,但有些便利功能确实没法完全绕开,做个检查总比半夜跑程序报错强。
7. 从仿真到实际工程化,还差哪些事
在MATLAB里跑通TERCOM只是第一步。如果你要把它应用到实际的无人平台上,还有几件额外的事需要做。
第一件事是DEM数据的高程基准统一。实测高度计给的是相对某个基准面的高度,DEM储存的是海拔或深度,两者必须统一到同一个高程基准和坐标投影下。这个在仿真中不存在,但实际数据中经常因为基准不一致导致系统性偏差。匹配前最好对测量序列和地图序列之间做一次线性回归,把偏置和尺度系数估计出来再校正,或者直接用NCC这种抗偏置的度量。
第二件事是匹配结果的时间同步。飞行器是运动的,一次匹配得到的当前位置对应的其实是"测量序列中间时刻"的位置,而不是匹配计算的时刻。飞机飞得快的时候,这个时间差可能导致几十米到几百米的位置误差。工程上要对匹配位置做运动补偿,最简单的做法是依据惯导输出的速度,把匹配时刻的位置外推到当前时刻。
第三件事是把TERCOM输出接入卡尔曼滤波器时要处理好"相关观测"问题。连续两次TERCOM匹配之间如果测量序列有重叠,那么两次匹配结果的误差是相关的,直接当成独立观测送进滤波器会低估误差方差,导致滤波器过度信任匹配结果。解决办法是让相邻两次的测量序列不重叠,或者在滤波器里对TERCOM的观测噪声协方差进行保守地放大。
这三件事是我在实际项目中踩过坑后总结出来的,MATLAB编程本身反而最简单。
我所接触的地形匹配项目,最后基本都走上了"惯导为主、TERCOM为辅、匹配结果滤波融合"的路线。TERCOM这块老技术,在图像匹配算法日益强大的今天确实显得朴素,但它的可靠性、被动性和无源特性是很多高大上传感器代替不了的。如果你正在MATLAB里实现这个算法,希望这篇文章能帮你少走一些弯路——尤其是那个卷积加速和搜索窗自适应的组合,真的能让你的仿真速度产生质变。
若后续有精力,我建议你把TERCOM和粒子滤波做一个结合:以惯导预测为中心撒一批粒子,每个粒子计算MSD作为权重,用粒子滤波逐步收敛到真实位置。那套方案在地形特征不明显的区域表现比纯TERCOM好不少,本系列如果继续写,我会重点拆这一块。