3、数学基础回顾(二):非线性优化基础

好,咱们接着聊。上一节我们把状态估计的数学框架搭起来了,这一节要动真格的了——怎么把那些看起来很美的公式,变成能在嵌入式芯片上跑起来的代码。

说实话,我刚入行那会儿,觉得非线性优化就是个黑盒子。调个参数,跑个迭代,能收敛就行。直到有一次在无人机上做VIO,飞着飞着位置就飘了,排查了一整天,最后发现是LM算法的阻尼因子没处理好。从那以后,我再也不敢轻视这些“基础”了。

3.1 从最小二乘说起

先问个问题:为什么VIO里到处都是最小二乘?

你想想看,我们观测到的像素坐标有噪声,IMU的加速度和角速度也有噪声。每个测量值都不完美,但又都有用。怎么办?折中呗。最小二乘就是干这个的——让所有误差的平方和最小。

数学上长这样:

minimize  ∑ ||e_i||²

其中 e_i 是第 i 个观测的残差。比如重投影误差,就是特征点投影到图像上的位置,跟实际观测到的位置之间的差。

但这里有个坑:如果某个传感器特别不准,或者某个观测明显是野值,你还把它跟其他误差平等对待,那结果就崩了。所以就有了加权最小二乘:

minimize  ∑ e_i^T · W_i · e_i

这个 W_i 就是权重矩阵。权重越大,这个观测在优化中的话语权就越大。

3.2 马氏距离:给误差加个“尺子”

说到权重,就不得不提马氏距离。我见过不少新手,上来就用欧氏距离算残差,结果优化出来的轨迹歪歪扭扭的。

为什么?因为欧氏距离假设所有维度是独立同分布的。但现实中,比如IMU的加速度计,三个轴的噪声方差可能不一样,而且轴之间还有相关性。这时候用欧氏距离,相当于无视了这些统计特性。

马氏距离的定义是:

d_M = sqrt( (x - μ)^T · Σ^{-1} · (x - μ) )

其中 Σ 是协方差矩阵。说白了,就是用数据的分布特性来“归一化”距离。如果某个方向上的噪声大,那在这个方向上的偏差就会被容忍一些。

核心理解: 马氏距离 = 在数据空间里,用协方差矩阵做了一次“白化”处理,然后再算欧氏距离。

在VIO里,我们通常把马氏距离直接嵌入到最小二乘的代价函数中:

代价 = (z - h(x))^T · Σ^{-1} · (z - h(x))

这里的 Σ^{-1} 就是信息矩阵,它告诉优化器:哪些测量值更可信。

我的习惯: 在写代码时,我会把协方差矩阵的逆(信息矩阵)提前算好存起来,避免每次迭代都求逆。尤其是当传感器数量多的时候,这个优化能省不少CPU。

3.3 高斯牛顿法:最朴素的迭代思路

好了,代价函数有了,怎么求最小值?

最直接的想法:对代价函数求导,令导数为零。但代价函数通常是非线性的(比如重投影误差里有个投影矩阵),没法直接解。

那就迭代呗。高斯牛顿法的思路很简单:

  1. 在当前估计值 x_k 处,把代价函数做一阶泰勒展开(线性化)
  2. 解这个线性化后的最小二乘问题,得到增量 Δx
  3. 更新 x_{k+1} = x_k + Δx
  4. 重复直到收敛

数学推导我就不啰嗦了,直接给核心公式:

J^T · J · Δx = -J^T · r

其中 J 是残差对状态量的雅可比矩阵,r 是残差向量。

这个方程叫正规方程。解它,你就得到了增量方向。

我曾经踩过的坑: 高斯牛顿法要求 J^T·J 是可逆的。如果状态量之间有冗余(比如VIO中的尺度模糊),这个矩阵就是奇异的,解出来的增量会乱跳。所以实际中,我一般不会裸用高斯牛顿,至少加个阻尼。

3.4 LM算法:给增量加个“刹车”

LM算法(Levenberg-Marquardt)就是在高斯牛顿的基础上,加了一个阻尼项:

(J^T · J + λ · I) · Δx = -J^T · r

这个 λ 就是阻尼因子。当 λ 很大时,LM退化为梯度下降法(步长小,但稳定);当 λ 很小时,LM退化为高斯牛顿法(收敛快,但可能发散)。

LM的精髓在于动态调整 λ:

  • 如果这一步让代价下降了,就减小 λ,让算法更“激进”
  • 如果这一步让代价上升了,就增大 λ,让算法更“保守”

判断依据是增益比(gain ratio):

ρ = (实际下降量) / (预测下降量)

如果 ρ 接近1,说明线性化很准,可以减小 λ;如果 ρ 很小甚至为负,说明线性化不准,要增大 λ。

我建议: 在嵌入式平台上,λ 的初始值可以设成 J^T·J 的迹乘以一个小系数(比如1e-6)。这样能自动适应不同量级的雅可比矩阵,省得你手动调参。

3.5 状态估计问题的完整流程

把上面这些串起来,一个典型的VIO状态估计步骤是这样的:

  1. 预测:用IMU数据做一次状态传播(运动模型)
  2. 观测:拿到新的图像特征点(观测模型)
  3. 构建代价函数:把预测残差和观测残差加起来,每个残差都乘上对应的信息矩阵
  4. 线性化:在当前状态附近,对每个残差求雅可比
  5. 求解增量:用LM算法解正规方程
  6. 更新状态:把增量加到当前状态上
  7. 判断收敛:如果增量足够小,或者迭代次数到了,就停止;否则回到第4步

下面这张图可以帮你理清整个逻辑:

VIO状态估计优化流程 ① IMU预测(运动模型) ② 图像特征观测 ③ 构建代价函数(含马氏距离) ④ 求雅可比矩阵 J ⑤ LM算法求解增量 Δx ⑥ 收敛? 是 → 输出状态 否,继续迭代

3.6 工程中的几个关键细节

理论说完了,聊点实际的。我在嵌入式平台上做VIO优化时,有几个地方特别容易出问题:

问题 表现 我的做法
雅可比计算错误 优化发散,或者收敛到错误值 用数值微分做一次校验,确保解析雅可比正确
信息矩阵未归一化 不同传感器量纲差异大,优化被某个传感器主导 检查信息矩阵的迹,确保各传感器贡献大致相当
LM阻尼因子初始化不当 前期迭代太慢,或者直接发散 用 J^T·J 的迹乘以1e-6作为初始λ
浮点精度不足 在MCU上跑时,正规方程求解误差大 必要时用double,或者用QR分解代替直接求逆

特别提醒: 在嵌入式平台上,矩阵求逆是个大坑。我见过有人直接用 cv::Mat::inv(),结果在ARM Cortex-M上跑一次要几十毫秒。建议用Cholesky分解或者QR分解,速度快一个数量级。

3.7 小结

这一节我们聊了:

  • 最小二乘的本质:在噪声中找最优折中
  • 马氏距离:用协方差矩阵给误差加权
  • 高斯牛顿法:线性化+迭代求解
  • LM算法:加个阻尼,稳如老狗
  • 完整的VIO状态估计流程

这些数学工具,说白了就是帮你把传感器数据“拧”成一条平滑的轨迹。下一节我们会把这些东西串起来,看看一个完整的VIO系统到底是怎么工作的。


专注资料整理