☰
ESKF误差状态卡尔曼滤波详解:从四元数运动学到工程实践
2026/10/5 6:11:54 网站建设 项目流程

做机器人定位和SLAM的同行,迟早都会撞上ESKF这堵墙。IMU能给出高频的角速度和加速度,但积分就飘;摄像头和激光雷达能给出低频但绝对值可靠的观测,又好死不死需要和IMU做紧耦合。把这两类数据揉到一起最成熟、最标准的思路之一,就是ESKF,也就是Error-State Kalman Filter。网上关于ESKF的代码不少,但真正愿意把“四元数运动学怎么推、误差状态方程每一项怎么来”讲透的教程真心不多,大部分人抄完公式遇到yaw慢漂、bias不收敛就直接卡住。这篇文章我想拉着你从头过一遍ESKF的核心推导:四元数运动学、误差状态传播、观测更新和重置,每一步都写清楚“为什么是这个形式”,再附上工程里的实操经验和踩坑记录。适合正在做VIO、LIO、组合导航,或者刚入门想搞懂IMU定位原理的朋友,读过之后至少能看懂主流开源框架在干什么,而不是只会调参。

1. 为什么是误差状态,而不是直接滤全量状态

1.1 IMU测量模型和坐标约定

先建立共同的底子。IMU输出的所谓“加速度”,其实不是刚体在世界系下的加速度,而是比力,是加速度计感受到的、扣除重力部分之后的本体系力。工程上最常用的测量模型写出来是:

a_m = R^T (a_w - g) + b_a + n_a

陀螺仪则更直接:

ω_m = ω + b_g + n_g

其中R是把body系转到world系的旋转矩阵,a_w是world系下的真实加速度,g是重力向量,b_a和b_g是加速度计和陀螺仪的零偏,n_a和n_g是测量白噪声。这里坐标系约定很重要,我默认使用ENU世界系,那么g就是[0, 0, -9.8]^T;如果你用NED就得反过来。很多推导对不上号、程序里跑出来的轨迹反着飘,八成是重力方向这个符号弄反了。

注意一个容易混淆的点:IMU不能直接测姿态,也不能直接测位移。你所谓“水平放置时加速度计读数约等于重力反方向”,本质上是比力模型里R^T(-g)这一项在起作用。理解了这个模型,后面所有ESKF公式才能看顺。

1.2 全量状态的非线性问题,和误差状态的“降维打击”

如果直接把IMU原始状态拿去写卡尔曼滤波,会遇到几个很麻烦的问题。第一个是旋转状态是四元数,四元数有单位约束,直接当成向量加噪声、求均值,结果会破坏单位性,还得反复归一化。第二个是系统强烈非线性,EKF的线性化点离真实状态稍微远一点,一阶泰勒展开就失效,扰动一大,协方差估计就失真。

ESKF的思路是“曲线救国”:状态分成两部分,一个叫标称状态(nominal state),一个叫误差状态(error state)。标称状态用IMU原始数据做高频积分,相当于一个全量、无约束、带bias补偿的kinematic积分器;误差状态才是滤波器真正估计和更新的对象。因为误差通常很小,旋转部分可以放心用三维旋转向量表示,没有万向锁,没有单位约束,线性化精度极高。最后把估计出的误差合并回标称状态,误差清空,进入下一轮。这种区分本质上是把一个长时间、大范围的非线性问题,拆成了高频的非线性标称积分和低频的、几乎线性可加的误差估计。

工程上,ESKF这个方案能稳定工作极度依赖一个前提:bias估计和姿态估计不能差太远。误差状态线性化只在真值落在标称状态附近时才靠谱。所以第一次初始化时用静止数据做重力对齐、估bias初值,不是“锦上添花”,而是“必须做的第一步”。

2. 四元数运动学:旋转变化到底怎么随角速度走

2.1 四元数基本运算法则

我们要推误差状态方程,逃不掉四元数。这里统一用Hamilton约定,标量在前,形如q = [w, x, y, z],纯向量写成实部为0的四元数[0, v]。两个四元数的乘法定义是:

q1 ⊗ q2 = [s1 s2 - v1·v2, s1 v2 + s2 v1 + v1 × v2]

单位四元数的共轭等于逆,q* = [s, -v],旋转向量v用四元数旋转表示为q ⊗ [0, v] ⊗ q*。从旋转向量θ到四元数的指数映射写为:

Exp(θ) = [cos(||θ||/2), sin(||θ||/2) θ/||θ||]

小角度下就有关键近似:

Exp(δθ) ≈ [1, 0.5 δθ]^T

这个近似是整个误差状态四元数处理的地基,后面所有δθ的出现都从这里来。建议想透彻理解ESKF的人,先把四元数乘法和这个指数映射练成条件反射,再往下看。

2.2 从旋转向量极限到运动学方程 q̇ = 0.5 q ⊗ ω

四元数运动学方程可以直接从旋转的定义出发推。设t时刻姿态为q(t),在很小的Δt内,body系里有一个角速度ω,相当于在原有的旋转基础上再局部旋转一个增量。旋转向量形式下,这个增量是ωΔt,对应四元数增量Exp(ωΔt),并且这个增量是从body本身系下叠加上去的,所以在四元数层面应该右乘:

q(t+Δt) ≈ q(t) ⊗ Exp(ωΔt) ≈ q(t) ⊗ [1, 0.5 ωΔt]^T

展开并减去q(t),除以Δt取极限:

q̇ = 0.5 q ⊗ [0, ω]^T

写成四元数乘法矩阵形式就是:

q̇ = 0.5 Ω(ω) q

其中Ω(ω)是左乘角速度四元数对应的4×4矩阵。这里要特别注意“右乘”这个操作的含义,它对应的是body系本体系下的旋转增量。如果搞成左乘,那是世界系下施加增量,推导结果会差一个符号,后续误差方程也会全乱。

工程中做四元数积分时,我非常建议直接用离散旋转四元数Exp(ωΔt)去乘,而不是用连续微分方程再去数值积分。这样做能天然保持四元数模长为1,漂移小,代码也简单。真的非要数值积分,每一步之后记得归一化,不然后面误差会越积越大,定位结果直接不能看。

3. 误差状态运动学:从误差四元数出发一步步推导

3.1 误差状态的维度与定义

ESKF的状态一般取五块,15维:

标称状态:x = [p, v, q, b_a, b_g] 误差状态:δx = [δp, δv, δθ, δb_a, δb_g]

其中姿态误差用局部扰动定义:

q = q̂ ⊗ δq,δq ≈ [1, 0.5 δθ]^T

之所以选局部扰动而不是全局扰动,是因为IMU积分过程里角速度本身就是定义在body系的,局部扰动和运动学方程天然匹配,而且误差姿态在标称姿态附近的协方差解释也最干净。位置和速度的误差就是简单相加:p = p̂ + δp,v = v̂ + δv;bias误差也是相加:b_a = b̂_a + δb_a,b_g = b̂_g + δb_g。

3.2 姿态误差方程的核心推导:别看最后公式很短,中间全是对消

这是全篇含金量最高的一个推导,值得盯紧公式算一遍。真实姿态和标称姿态都满足四元数运动学:

q̇ = 0.5 q ⊗ ω,q̂̇ = 0.5 q̂ ⊗ ω̂

因为q = q̂ ⊗ δq,求导展开:

q̇ = q̂̇ ⊗ δq + q̂ ⊗ δq̇

代入两个运动学方程:

0.5 q̂ ⊗ δq ⊗ ω = 0.5 q̂ ⊗ ω̂ ⊗ δq + q̂ ⊗ δq̇

左乘q̂的逆,再把δq̇单独提出来:

δq̇ = 0.5 (δq ⊗ ω - ω̂ ⊗ δq)

现在把δq ≈ [1, 0.5 δθ]^T代进去,利用四元数乘法公式展开。这里你会看到,标量部分全是二阶小量,可以丢掉;向量部分经过两个叉乘项合并,最后得到:

δθ̇ = -[ω̂]× δθ + δω

其中δω = ω - ω̂。这个结果抵消得很漂亮:如果误差姿态为零,即使角速度误差存在,它也会直接以积分形式注入δθ;而标称角速度ω̂对误差姿态的作用是一个叉乘矩阵,相当于把误差姿态带着转。正是因为叉乘矩阵项存在,误差姿态方程才是旋转耦合的,忽略它会让你估计出来的姿态误差协方差完全失真。

实际中角速度误差的来源要写全。按本章开头的测量模型,真实角速度ω = ω_m - b_g - n_g,而标称角速度ω̂ = ω_m - b̂_g,于是:

δω = -δb_g - n_g

代入后就得到工程实现里最终用的姿态误差方程:

δθ̇ = -[ω̂]× δθ - δb_g - n_g

3.3 速度、位置和bias的误差方程

速度误差推导同样是“真值减标称”的思路。真实速度满足:

v̇ = R f + g

其中f是本体系比力真值,g是世界系重力。把R = R̂(I + [δθ]×)和f = f̂ - δb_a - n_a代入,展开后只保留一阶项:

δv̇ = -R̂ [f̂]× δθ - R̂ δb_a - R̂ n_a

这里有一个容易看晕的符号:-R̂ [f̂]× δθ这一项是因为旋转误差改变了比力在世界系下的投影方向。想象一下,姿态偏了哪怕一度,加速度在世界系下投影出来的方向就偏了,长期积分就是厘米级甚至米级的位置误差。后面那项-R̂ δb_a则是加速度计零偏误差直接通过旋转矩阵投影到位移空间,这也是为什么bias估计不准几乎立刻反映为轨迹漂移。

位置误差最简单:

δṗ = δv

bias本身工程上常用随机游走建模,即:

δḃ_a = n_ba,δḃ_g = n_bg

也就是说,在预测阶段,bias误差的均值保持不变,但它的不确定性随时间线性增长。这句话翻译成代码行为就是:滤波跑的越久、没有观测更新时,bias的方差越大,观测一来,它对bias的修正幅度就越猛。

3.4 连续时间误差状态方程汇总

把上面几个式子拼成完整矩阵形式,就是ESKF误差状态运动学的连续时间模型:

δẋ = F_c δx + G_c w

其中w = [n_g, n_a, n_ba, n_bg]^T是噪声向量,F_c的具体分块是:

F_c =

[ -[ω̂]× 0 0 -I 0 ] [ -R̂[f̂]× 0 0 -R̂ 0 ] [ 0 I 0 0 0 ] [ 0 0 0 0 0 ] [ 0 0 0 0 0 ]

噪声耦合矩阵G_c把四组噪声分别映射到对应方程,由于协方差传播中负号会被平方消去,G_c具体写成单位阵还是负单位阵不影响最终协方差,但公式里要保持符号一致。

看到这个15维矩阵不用怕,后面离散化只是一阶近似,代码里实际用到的也就是每一小块3×3矩阵的加减乘。

4. 离散化:从连续微分方程到能写代码的预测步

4.1 标称状态的离散积分

预测步对标称状态直接用IMU测量做数值积分。最简单又足够用的是一阶欧拉,实际工程中建议用中值积分或者Runge-Kutta,尤其角速度变化很快的场景,欧拉积分会让姿态误差显著增大。一阶欧拉形式如下:

p̂_{k+1} = p̂_k + v̂_k Δt + 0.5 (R̂_k f̂_k + g) Δt^2 v̂_{k+1} = v̂_k + (R̂_k f̂_k + g) Δt R̂_{k+1} = R̂_k Exp(ω̂_k Δt) b̂_{a,k+1} = b̂_{a,k} b̂_{g,k+1} = b̂_{g,k}

这里f̂_k和ω̂_k是经过bias估计补偿后的比力和角速度。旋转更新一定要用四元数指数映射乘法,然后归一化,不要直接q += 0.5 q ⊗ ω Δt,后者每步都会引入模长漂移,长时间跑下来姿态基准会越来越歪。

另外一个实际经验:如果IMU频率是200Hz到1000Hz,预测步每次只推一个Δt就够了;但如果你的状态估计频率要和相机帧率对齐,可能需要对多个IMU样本做积分下采样,这时候别在ESKF外部把IMU平均成低频,而是把中间每次IMU测量都走一遍标称传播,只在相机观测时刻做滤波更新。平均IMU看起来省计算,实际上会丢信息,bias可观测性会变差。

4.2 误差状态协方差传播矩阵

离散误差状态转移矩阵取一阶近似:

F_x = I + F_c Δt

其中F_c就是上一节那个15×15矩阵。把它展开就是:

F_x =

[ I - [ω̂]×Δt 0 0 -IΔt 0 ] [ -R̂[f̂]×Δt I 0 -R̂Δt 0 ] [ 0 IΔt I 0 0 ] [ 0 0 0 I 0 ] [ 0 0 0 0 I ]

这个矩阵的意义是:误差状态经过Δt后在各个维度上如何相互耦合、自身如何演变。注意第1行第4列-IΔt,表示陀螺仪bias误差在Δt内直接累计成姿态误差;第2行第4列-R̂Δt,表示加速度计bias误差在Δt内直接累计成速度误差。这两个耦合项就是为什么ESKF能在紧耦合中主动估计bias的根本原因——观测更新里位置/速度残差会顺着这两个通道去修正bias。

噪声协方差离散化也需要近似。连续噪声谱密度Q_c在不同IMU里差异很大,没有一键统一答案。工程上先按传感器手册给的白噪声密度和随机游走密度填初值,再用Allan方差或标定工具校准。离散化一步近似为:

Q_d ≈ G_c Q_c G_c^T Δt

很多初学ESKF的人都会卡在这个“离散化”上:我到底要不要精确算矩阵指数?我的建议是,对Q_d和F_x都先用一阶近似,跑通整个系统后再精细。一阶近似引入的误差,远小于你IMU噪声参数没标准引入的误差。先用简单的,把滤波逻辑调到自洽,再去抠精度。

4.3 初始化与重力对齐:ESKF能不能收敛,开局就定了

ESKF需要有一个大致准确的初始姿态和bias初值,否则误差状态线性化前提不成立。最常用的办法是静止初始化:把IMU平放或任意静止放置几秒,取加速度计平均值方向来推初始roll和pitch,因为静止时加速度计读到的就是重力反方向。具体做法是:

设平均加速度为ā,取重力参考为g = [0, 0, -9.8]^T(ENU系)。用ā和g做叉乘可以求旋转轴,用点乘求夹角,就能构建初始四元数,把body系的“重力方向”旋转到world系的“负z方向”。这一步做完,roll和pitch就对齐了。yaw没有绝对参考,设成0即可,后续靠视觉、激光或磁力计去校正。

加速度计bias初值通常直接取静止时加速度计读数模长减去9.8后的一部分残余,更稳的做法是把陀螺仪bias取静止时角速度平均值,加速度计bias先设0,由滤波器后台慢慢估计。这里有个很常见的问题:如果初始姿态算错了2度,ESKF的确也能勉强跑,但yaw会漂得更快,而且bias会往错误方向收敛去补偿姿态误差。你最后看到“状态不发散但轨迹歪了”,很多时候不是滤波器的锅,是初始重力对齐那一步就没做对。

5. 观测更新与重置:闭环修正和误差归零

5.1 观测模型与H矩阵怎么搭

ESKF的观测来源可以是GPS位置、轮速、视觉重投影残差、激光点云配准残差等。关键不是观测形式,而是把观测残差线性映射到误差状态。以最简单的3D位置观测为例:

z = p + v_pos,残差r = z - p̂

由于p = p̂ + δp,残差在误差状态下的期望就是δp,因此量测雅可比直接是:

H = [0, 0, I, 0, 0]

这简直不要太清爽,全量状态EKF里H至少要和一堆四元数块纠缠。但对于姿态观测就要小心。假设我们观测到姿态q_m,用残差r = q_m ⊗ q̂*取对数后的向量部分:

r ≈ 2 * vec(q_m ⊗ q̂*)

这个残差在误差状态的雅可比不是简单的一个单位阵,而是和δq的定义方向相关。工程上最稳妥的做法是在代码里定义好“真实四元数 = 标称四元数 ⊗ 误差四元数”这一约定后,统一写成:

q_m = q̂ ⊗ Exp(δθ) ⊗ q_noise => r = log(q_m ⊗ q̂*) ≈ δθ

对,前面那个2倍关系取决于你采取的旋转向量定义,很多框架直接用2 * vec(...)来减小线性化误差。这里不展开太多矩阵,给出结论:如果你观测的是绝对姿态,H对应δθ那三列取单位阵即可,但前提是残差计算方向和误差四元数定义方向一致。方向差一个符号,你最直观的表现就是更新之后姿态不是收敛而是震荡。

5.2 更新、注入与重置

更新方程就是标准卡尔曼滤波形式:

S = H P H^T + R K = P H^T S^{-1} δx = K r

得到误差状态后,把它注入标称状态:

p̂ ← p̂ + δp v̂ ← v̂ + δv q̂ ← q̂ ⊗ Exp(δθ) b̂_a ← b̂_a + δb_a b̂_g ← b̂_g + δb_g

注入后必须做四元数归一化。然后最重要的“重置”来了:误差状态清空为0,但协方差不能直接保留原样,而是要经过一个线性变换:

δx_new = 0,P ← G P G^T

因为真实状态没变,你把误差融进了标称状态,相当于坐标原点挪了位置,误差状态的协方差自然要跟着变换。G矩阵在误差很小的时候姿态子块近似为:

G_rot ≈ I - [0.5 δθ]×,其余子块取单位阵

实际代码里,如果每次更新的δθ非常小,很多人直接把G当单位阵用,在大多数场景下确实不影响大局。但在bias快速变化或者观测频率很低、δθ偏大的时候,忽略G会让P的估计偏高,下一轮增益就不那么自信。写代码时别偷懒,这个矩阵只有6×6的内容,算起来很快。

6. 工程实战:避坑、标定与调试经验

6.1 新手最容易掉的坑:yaw慢漂和bias不收敛

“基于IMU的位姿解算yaw仍会慢漂”是几乎每个人都会遇到的高频问题。这里要说清楚一个底层事实:在只有IMU、没有绝对航向观测的系统里,yaw是弱不可观测的,慢漂是正常的,不是bug。ESKF只能通过加速度计和运动加速度的关系间接估计部分姿态和bias,但绝对航向没有信息来源,只能靠视觉、激光、磁力计或轮速等外部传感器注入。

如果你的系统里明明有视觉或激光,yaw还漂,那先检查三件事。第一,外参标定对不对,尤其是IMU和相机/雷达之间的旋转外参;外参错了,视觉和IMU给出的航向增量互相矛盾,滤波器会顶着干,yaw自然漂。第二,IMU角速度bias初值估得准不准,静止初始化时一定要取足够长的时间平均,最好imu热身几秒再采集,因为很多MEMS陀螺仪bias在上电初期会明显漂移。第三,过程噪声里的陀螺仪随机游走给得太小,导致滤波器过于相信IMU积分,yaw的方差缩得太紧,更新很难把它拉回来。

排查思路我一般是这样:先砍掉所有观测,只跑标称积分,看yaw能维持多久不严重漂移,大致摸清IMU本身质量;再接上观测,看更新残差的符号和幅值是否合理;最后再调噪声参数。别上来就乱调Q和R,那是自欺欺人。

6.2 IMU标定和内外参标定:别省这一步

ESKF对IMU内参的敏感度非常高。加速度计和陀螺仪的尺度因子、轴间非正交角、bias,这堆内参如果不校准,误差状态模型里的“噪声”只是吸收了所有未建模项,而不是真正的随机噪声,滤波结果会次优甚至发散。常用做法是用转台做六位置法标定加速度计,用高精度转台或角速度参考标定陀螺仪;手头没有转台就用多位置静止法,至少把加速度计尺度因子和bias粗标一遍。

外参标定同样绕不开。lidar和IMU之间、相机和IMU之间的旋转和平移外参,本质上是传感器坐标系的刚体变换。以相机IMU联合标定为例,Kalibr这类工具的核心思想是:相机给出的连续帧运动轨迹和IMU积分出的运动轨迹,在正确外参下应该一致,于是把外参作为待估计量放进一个非线性优化问题里求最优旋转平移。实际标定时建议让IMU充分激励,六个自由度都动一动,旋转和平移分开来运动,不要只平移或只旋转。如果你恰好用车辆动力学软件做仿真,比如在CarSim里设置IMU传感器,也一样要让其在标定工况下充分运动,多设计大角度转向和加减速工况,否则外参标定结果会非常病态。

6.3 参数初始化和调试节奏的心得

ESKF噪声参数就那么几组:陀螺仪白噪声、加速度计白噪声、陀螺仪bias随机游走、加速度计bias随机游走、初始协方差P和观测噪声R。初次调参时,我建议按这个顺序:先设传感器的白噪声与加速度计/陀螺仪数据手册一致,bias随机游走先给一个稍大的值,让滤波器在前期比较“敢”去修bias;然后固定Q,调R,让位置/姿态残差大致和传感器精度匹配;最后回过头微调bias随机游走,观察bias估计是否稳定、不发抖。

还有一个特别好用的调试习惯:对滤波器做“闭环注入测试”。在仿真里给IMU数据加一个已知的bias阶跃,比如第10秒让陀螺仪bias突然跳变0.1 rad/s,然后观察ESKF能不能在几秒内把这个阶跃估计出来。如果估计得很慢、甚至震荡,说明bias随机游走Q给小了或观测更新频率太低;如果估计出来了但速度误差瞬间冲高,说明Q给大了,滤波器修bias太激进。这个测试能帮你把参数整定从玄学变成工程学。

刚开始写ESKF代码时,最容易忽略的是crazy sign convention:测量模型里加速度计bias的符号、误差四元数用左扰动还是右扰动、残差方向是z-h还是h-z,任一环节差个负号,算法表现会是天差地别。解决这类问题的唯一可靠办法,就是亲手从连续时间模型推到离散方程,哪怕只推一次,之后再碰到符号问题也能用逻辑判断,而不是靠试。我个人手推一遍之后,再去看MSCKF、VINS-Mono这些框架里的代码,很多原来觉得“莫名其妙”的负号和系数瞬间就通了。这个功夫值得花。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询