1. 三种状态到底在解决什么问题
第一次翻开MSCKF相关材料的时候,我盯着"真实状态、标称状态、误差状态"这九个字看了很久,心里想的是:不就是个状态量吗,为什么要拆成三份?后来踩过几次坑才明白,这套拆分不是数学上的花架子,而是整个滤波框架能不能收敛、能不能长期稳定运行的根基。如果你也在学MSCKF,或者正在做视觉惯性里程计、组合导航相关的工程,这三个概念绕不过去,而且必须从直觉上真正理解,而不是背公式。
先把结论摆出来:真实状态是物理世界客观存在、但我们永远无法精确获知的那个值;标称状态是我们用非线性模型递推出来的"大估计",它承载了主要的运动信息;误差状态是真实状态与标称状态之间的偏差,它是一个小量,我们用线性模型去估计它。三者之间的关系可以写成一句话:真实状态等于标称状态"叠加"误差状态。这个"叠加"在不同参数上形式不同,有的用加法,有的用四元数乘法,这正是新手最容易翻车的地方。
为什么非要这么折腾?核心原因有两个。第一,旋转量(姿态)在三维空间里不是向量,它活在流形上,直接对四元数做线性卡尔曼滤波会破坏单位约束,长期跑下来四元数范数漂移,姿态就废了。第二,非线性系统直接做EKF,线性化点离真实值太远时误差巨大,而误差状态本身是小量,围绕它做线性化,雅可比矩阵的近似精度高得多。这两点决定了误差状态卡尔曼滤波(Error-State Kalman Filter)在惯性导航领域几乎是标配。
这篇文章我打算按我自己学习的顺序来写:先讲清楚三种状态各自的定义和物理意义,再讲它们之间怎么转换、为什么这么转换,然后落到MSCKF里具体怎么用,最后把我踩过的坑和排查经验整理出来。适合刚接触MSCKF、被状态定义绕晕的朋友,也适合已经能跑通代码但说不清楚原理的工程师。看完你至少能做到:拿到任何一段MSCKF代码,能准确指出哪个变量是标称状态、哪个是误差状态、它们在哪个步骤被更新。
2. 三种状态的定义与物理意义拆解
2.1 真实状态:存在但不可知
真实状态(true state)记作 $x_t$,它代表系统在某一时刻客观的物理量,比如IMU的真实位置、真实速度、真实姿态、真实的陀螺仪和加速度计零偏。注意,连零偏都有"真实值"——传感器内部确实存在一个固定的或缓慢变化的偏差,只是我们测不到。
这里有个哲学味道很浓但工程上极其重要的点:真实状态永远不可观测。我们能拿到的只有带噪声的测量值,比如加速度计读数、相机观测到的特征点像素坐标。滤波器输出的从来不是真实状态,而是真实状态的一个估计。理解这一点,你才不会对"为什么滤波器收敛了还有误差"感到困惑——收敛只是说估计的协方差变小了,不代表误差为零。
在推导里,真实状态的演化由真实的非线性动力学方程描述:
$$x_{t}(t+\Delta t) = f(x_t(t), u_t, w_t)$$
其中 $u_t$ 是真实输入(比如真实的角速度和比力),$w_t$ 是真实的过程噪声。这个方程是"上帝视角"的,写推导时用它,但实际计算中我们拿不到 $u_t$ 和 $w_t$ 的真值,只能拿到带噪声的测量。
2.2 标称状态:能算出来的那个大估计
标称状态(nominal state)记作 $x$,有时候也叫名义状态。它是我们用测量值(注意不是真值)代入动力学方程递推出来的状态:
$$x(t+\Delta t) = f(x(t), u_m, 0)$$
这里 $u_m$ 是实际测量到的输入,最后那个 0 表示我们在递推标称状态时不考虑噪声。这是关键:标称状态的传播是确定性的,噪声的影响全部被赶到误差状态里去处理。
标称状态承载了系统的主要运动信息。位置、速度、姿态的大部分数值都在这里。你可以把它理解成"我目前对系统状态的最佳猜测,但还没考虑不确定性"。在MSCKF里,标称状态还包括滑窗里所有相机克隆的姿态和位置,这部分是MSCKF区别于普通EKF的标志。
为什么标称状态传播时要把噪声设为零?因为如果我们在标称传播里也加噪声,那噪声就重复计入了——误差状态那边还要再算一遍噪声传播。工程上必须保证噪声只在一个地方进入,否则协方差会虚高,滤波器变得过度保守,更新时几乎不动,表现出来就是"滤波器反应迟钝"。
2.3 误差状态:真正被卡尔曼滤波估计的东西
误差状态(error state)记作 $\delta x$,定义为真实状态与标称状态的差。但正如前面说的,这个"差"对不同分量形式不同:
- 对位置、速度、零偏这类向量量:$\delta p = p_t - p$,直接相减
- 对姿态四元数:$q_t = \delta q \otimes q$,用四元数乘法,其中 $\delta q$ 是一个小旋转
误差状态是一个小量,这是它最大的价值。小量意味着两件事:一是线性化精度高,二是可以用最小参数化(比如姿态误差只用三维的旋转向量表示,而不是四维四元数),避免了过参数化带来的协方差奇异问题。
卡尔曼滤波真正在估计的就是这个 $\delta x$。预测步骤传播它的协方差,更新步骤用观测修正它,修正完之后再把 $\delta x$ 注入到标称状态里,然后把 $\delta x$ 清零。这个"注入并清零"的操作叫误差注入(error injection)或者反馈,是ESKF的标准流程。
注意:误差注入之后必须把误差状态重置为零,协方差矩阵保持不变(姿态部分要做相应的旋转调整)。很多新手忘了清零,导致误差被重复注入,状态直接发散。
3. 三种状态的转换关系与数学原理
3.1 真实状态方程与标称状态方程
把真实状态和标称状态分开写,是理解整个框架的第一步。真实状态的连续时间演化:
$$\dot{x}_t = f(x_t, u_m - b_t - n, w_t)$$
这里 $u_m$ 是测量输入,$b_t$ 是真实零偏,$n$ 是测量噪声。标称状态的演化则用零偏的估计值 $\hat{b}$,且不含噪声:
$$\dot{x} = f(x, u_m - \hat{b}, 0)$$
两者相减,就得到误差状态的演化方程。这一步的推导是MSCKF理论的核心,涉及对非线性函数 $f$ 在标称状态处做一阶泰勒展开。展开后你会发现,误差状态的动力学是线性的:
$$\dot{\delta x} = F \delta x + G w$$
其中 $F$ 是误差状态转移矩阵,$G$ 是噪声驱动矩阵。这个线性方程就是卡尔曼滤波能用的前提。
3.2 姿态误差的两种参数化:局部与全局
姿态误差的定义方式有两种,这是MSCKF学习中的一个大坑,必须分清楚。
局部参数化(body frame):$\delta q = q_t \otimes q^{-1}$,误差定义在机体坐标系下。这种定义下,误差旋转向量 $\delta\theta$ 的物理意义是"从标称姿态到真实姿态需要绕机体轴转多少"。
全局参数化(global frame):$\delta q = q^{-1} \otimes q_t$,误差定义在世界坐标系下。
两种定义导出的误差动力学方程不同,雅可比矩阵也不同。MSCKF的经典论文里用的是全局定义,但很多开源实现用的是局部定义。如果你在对照代码和论文时发现雅可比对不上,八成是这里定义不一致。
我个人的经验是:选定一种定义后,从头到尾保持一致,包括状态传播、观测模型、误差注入。中途混用是灾难性的,滤波器会以一种很难调试的方式缓慢发散。
3.3 误差注入与重置的完整流程
误差注入不是简单地把 $\delta x$ 加到 $x$ 上就完事,姿态部分要特殊处理。完整流程如下:
- 卡尔曼更新得到误差状态的后验估计 $\delta \hat{x}$
- 位置、速度、零偏:$p \leftarrow p + \delta p$,$v \leftarrow v + \delta v$,$b \leftarrow b + \delta b$
- 姿态:$q \leftarrow \delta q \otimes q$(全局定义)或 $q \leftarrow q \otimes \delta q$(局部定义)
- 误差状态清零:$\delta \hat{x} \leftarrow 0$
- 协方差调整:姿态对应的协方差块要做旋转,其余块不变
第5步经常被忽略。因为姿态误差的参考系在注入后变了,协方差矩阵里姿态相关的块需要左乘右乘一个旋转矩阵。如果忽略这一步,短期看不出问题,长期跑协方差会不匹配,表现为滤波器过度自信或过度保守。
4. MSCKF中三种状态的具体落地
4.1 MSCKF的状态向量构成
MSCKF的全称是Multi-State Constraint Kalman Filter,它的状态向量比普通ESKF多了一块:滑窗内的相机克隆。完整状态包括:
- IMU状态:位置 $p$、速度 $v$、姿态 $q$、陀螺零偏 $b_g$、加速度零偏 $b_a$
- 相机克隆:滑窗内每个历史时刻的相机位姿
误差状态向量就是这些量对应的误差。IMU部分维度是 15(位置3+速度3+姿态3+陀螺零偏3+加计零偏3),每个相机克隆贡献 6 维(姿态3+位置3)。如果滑窗里有 N 个克隆,总维度就是 15 + 6N。
这个维度会随滑窗滑动而变化,所以MSCKF的协方差矩阵是动态大小的。工程实现上通常预分配一个最大维度的矩阵,滑窗满时做边缘化(marginalization)把最老的克隆移出去。
4.2 状态传播:标称状态与协方差同步推进
传播阶段做两件事:一是用IMU测量递推标称状态,二是用误差状态转移矩阵递推协方差。
标称状态递推用IMU的积分。位置、速度、姿态的更新公式这里不展开,但要注意姿态更新用的是四元数乘法,且角速度要先减去陀螺零偏估计:
$$\omega = \omega_m - \hat{b}_g$$
协方差递推是:
$$P_{k+1} = \Phi P_k \Phi^T + Q$$
其中 $\Phi$ 是离散化的误差状态转移矩阵,$Q$ 是离散过程噪声协方差。$\Phi$ 的构造是MSCKF实现里最容易出错的地方,尤其是姿态相关的块,符号错一个整个滤波器就废了。
实操心得:验证 $\Phi$ 是否正确,有个简单办法——让IMU静止,只传播不更新,看协方差是否按预期增长。如果姿态协方差增长方向反了或者量级离谱,基本就是 $\Phi$ 的符号或定义有问题。
4.3 观测更新:从特征点到误差状态的链路
MSCKF的观测更新是它最精妙的部分。它不把特征点坐标放进状态向量(那是SLAM的做法),而是利用同一个特征点在多个相机克隆里的观测构成几何约束,通过零空间投影消掉特征点,得到一个只与相机克隆位姿有关的残差。
这个残差的雅可比矩阵,就是残差对误差状态的偏导。推导链条是:残差对特征点求导,特征点对相机位姿求导,相机位姿对误差状态求导。最后一步又涉及姿态误差的参数化定义,所以前面强调的定义一致性在这里体现得淋漓尽致。
更新完之后,误差状态被注入到标称状态,滑窗里最老的克隆如果不再被任何特征点观测到,就被边缘化掉。
5. 实操中的常见问题与排查技巧
5.1 滤波器发散的典型症状与定位
MSCKF调试中最常见的就是发散。症状和可能原因我整理成一张表:
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
| 姿态缓慢漂移 | 姿态误差定义不一致 | 检查传播和更新的参数化是否统一 |
| 协方差爆炸 | 过程噪声设置过大 | 检查 $Q$ 矩阵量级 |
| 滤波器不更新 | 协方差过小,增益趋零 | 检查是否有重复注入噪声 |
| 位置跳变 | 误差注入符号错误 | 检查注入时是加还是减 |
| 特征点残差异常大 | 相机-IMU外参标定不准 | 重新标定外参 |
定位发散问题的核心思路是分段隔离:先只跑传播,看标称状态是否合理;再加观测,看残差是否在预期范围;最后看误差注入后的状态跳变是否合理。每一段单独验证,比整体调试快得多。
5.2 误差注入的符号陷阱
误差注入的符号是新手最容易搞错的地方。因为误差状态的定义是 $\delta x = x_t - x$,所以注入时应该是 $x \leftarrow x + \delta \hat{x}$。但如果你在推导时不小心把定义写成了 $x - x_t$,那注入符号就要反过来。
更隐蔽的是姿态部分。四元数乘法不满足交换律,$\delta q \otimes q$ 和 $q \otimes \delta q$ 结果不同。如果你的姿态误差定义是全局的,注入用 $\delta q \otimes q$;如果是局部的,用 $q \otimes \delta q$。搞反了姿态会朝错误方向修正,表现为"越修越偏"。
提示:验证符号有个土办法——人为给一个已知的小误差,看滤波器是否朝减小误差的方向修正。比如故意把初始姿态偏 1 度,如果滤波器把它修回 0 附近,说明符号对;如果偏到 2 度,符号反了。
5.3 协方差矩阵的正定性维护
卡尔曼滤波要求协方差矩阵对称正定。数值计算中,由于浮点误差,$P$ 可能逐渐失去对称性或正定性,导致滤波崩溃。工程上通常做两件事:一是每次更新后强制对称化 $P \leftarrow (P + P^T)/2$;二是用Joseph形式更新协方差,数值稳定性更好。
如果发现 $P$ 出现负特征值,说明数值问题已经比较严重,可能需要改用平方根滤波(square-root filter)或者UD分解。不过对于大多数MSCKF应用,强制对称化加上合理的噪声设置就够用了。
6. 我踩过的坑与经验总结
学MSCKF这段时间,最大的体会是:三种状态的拆分不是可选项,而是必选项。我一开始图省事,想直接在四元数上做EKF,结果跑了不到一分钟四元数范数就漂到 1.1,姿态完全乱套。后来老老实实按误差状态框架重写,同样的数据能稳定跑十几分钟。
第二个体会是定义一致性比什么都重要。误差状态的定义、姿态误差的参数化、雅可比的推导、误差注入的方式,这四者必须严格自洽。我建议在动手写代码前,先在纸上把整套定义写清楚,包括每个符号的含义和参考系,然后再写代码。这一步花的时间,会在调试阶段十倍地省回来。
第三个是不要迷信开源实现的正确性。我对照过几个不同的MSCKF实现,发现它们在姿态误差定义、协方差调整等细节上各有差异,有的甚至存在微妙的bug。理解原理之后,你才有能力判断哪份代码是对的,而不是盲目照抄。
最后分享一个我觉得很有用的学习路径:先用一维的简单例子(比如只有位置和速度)把ESKF的完整流程走一遍,理解误差注入和协方差调整;再扩展到三维姿态,重点搞懂四元数误差的参数化;最后加上相机克隆,理解MSCKF的零空间投影。每一步都自己推导一遍,比看十篇博客都管用。