首页 / 资讯中心 / 文章详情

9轴IMU姿态解算:卡尔曼滤波算法设计与Matlab实现

9轴IMU姿态解算:卡尔曼滤波算法设计与Matlab实现 ★ FEATURED ARTICLE
1. 为什么9轴IMU需要卡尔曼滤波从传感器噪声到姿态解算搞过姿态解算的人都知道9轴IMU加速度计陀螺仪磁力计看起来什么都能测但单独拎出来每一个都不靠谱。加速度计静态时能靠重力分量算出俯仰和横滚但一振动就满屏噪声陀螺仪积分出来的角度短期平滑可零偏随时间漂移几秒钟就能跑偏好几度磁力计能提供绝对航向参考但周围一点铁磁性物质就能让它指东打西。这三个传感器各自的短板恰好能被另外两个的长处补上而卡尔曼滤波就是干这个取长补短活的。我最初接触这个课题的时候以为卡尔曼滤波就是个加权平均把三个传感器的数据按置信度混一混就完事了。实际跑起来才发现远没那么简单。卡尔曼滤波的核心在于它维护了一个状态估计和协方差矩阵前者告诉你我现在认为姿态是什么后者告诉你我对这个估计有多不确定。每次有新测量进来滤波器会根据测量噪声和当前不确定度动态调整信任谁多一点。这个过程是递推的不需要存历史数据非常适合嵌入式实时运行。这篇文章适合谁看如果你正在做飞控、平衡车、云台稳定、VR/AR头显、行人导航这类需要姿态估计的项目或者你正在学卡尔曼滤波但不知道怎么用到实际传感器上那这篇内容应该能帮你少走不少弯路。我会从算法设计思路讲起把状态方程、观测方程、噪声参数怎么定这些关键细节拆开说然后给出完整的Matlab实现流程最后把我踩过的坑和排查经验整理出来。代码可以直接抄作业参数需要根据你的传感器实际情况微调。2. 算法整体设计与方案选型为什么选误差状态卡尔曼滤波2.1 四元数 vs 欧拉角状态量的选择姿态解算第一步要决定用什么表示姿态。欧拉角直观俯仰、横滚、航向三个角一看就懂但它有两个致命问题万向节死锁和三角函数非线性。当俯仰角接近±90度时航向和横滚会耦合在一起方程出现奇异点。四元数用四个参数表示旋转没有奇异点计算量也小主要是乘法和加法更适合递推运算。我选的是四元数作为状态量具体是误差四元数的形式。什么意思呢就是我不直接估计四元数本身而是估计当前估计值和真实值之间的误差四元数。这样做的好处是误差量通常很小可以近似为线性系统卡尔曼滤波的线性假设就成立了。如果直接对四元数做卡尔曼滤波观测方程是非线性的就得用扩展卡尔曼滤波EKF计算量和复杂度都上去了。误差状态卡尔曼滤波ESKF的状态向量是15维的位置误差3、速度误差3、姿态误差3、加速度计零偏3、陀螺仪零偏3。如果只做姿态估计不做位置估计可以简化到6维或9维。我这份代码做的是完整的15维ESKF因为实际项目中位置和姿态往往要一起估计。2.2 预测与更新卡尔曼滤波的两个核心步骤卡尔曼滤波分两步走预测和更新。预测阶段用陀螺仪的角速度积分来推姿态同时协方差矩阵按系统模型传播更新阶段用加速度计和磁力计的测量值来修正预测的姿态同时协方差矩阵按观测模型收缩。预测阶段的系统模型是q_{k1} q_k ⊗ exp(0.5 * ω * dt)其中ω是陀螺仪测得的角速度减去零偏。这个公式的物理含义是姿态的变化量由角速度在时间上的积分决定。协方差传播用雅可比矩阵FF I A * dtA是系统矩阵对于姿态部分A -[ω×]即角速度的反对称矩阵。这个推导过程涉及四元数微分方程我在代码注释里写得很清楚这里不展开。更新阶段的观测模型分两部分加速度计观测重力方向磁力计观测地磁方向。加速度计的观测方程是a_measured R(q)^T * g b_a n_a其中R(q)是旋转矩阵g是重力向量[0, 0, -9.81]b_a是加速度计零偏n_a是噪声。磁力计的观测方程类似只是参考向量换成地磁向量。这两个观测方程都是非线性的需要对状态量求雅可比矩阵H。2.3 噪声参数卡尔曼滤波的灵魂卡尔曼滤波的效果好不好八成取决于噪声参数设得对不对。Q矩阵是过程噪声协方差代表你对系统模型的信任程度R矩阵是观测噪声协方差代表你对传感器测量的信任程度。Q设大了滤波器更信任测量响应快但噪声大Q设小了滤波器更信任模型平滑但可能滞后。我的经验是陀螺仪的噪声密度可以从数据手册查到比如某款IMU的陀螺仪噪声密度是0.01 °/s/√Hz换算成离散时间的方差就是噪声密度乘以采样频率的平方根。加速度计的噪声密度类似。但数据手册的值往往偏乐观实际使用中我会把Q和R都放大2到5倍让滤波器更谦虚一点。还有一个关键参数是零偏随机游走。陀螺仪的零偏不是恒定的它会随时间缓慢变化这个变化率就是随机游走噪声。如果这个参数设得太小滤波器会认为零偏不变导致姿态慢慢漂移设得太大零偏估计会抖动。我一般先用Allan方差分析工具标定出零偏随机游走系数然后在此基础上微调。3. 核心细节解析与实操要点3.1 四元数运算的Matlab实现细节Matlab里没有内置的四元数类老版本没有新版本有但功能有限所以需要自己写四元数乘法、共轭、归一化这些基本运算。我写了一个quat_multiply函数输入两个四元数输出乘积。这里有个坑四元数的乘法顺序很重要q1⊗q2和q2⊗q1结果不一样。在姿态更新中如果角速度是在机体坐标系下测量的那么更新公式是q_new q_old ⊗ Δq如果角速度是在世界坐标系下则是q_new Δq ⊗ q_old。搞反了姿态会往反方向转。另一个坑是四元数归一化。理论上四元数的模应该恒为1但数值积分误差会让模慢慢偏离1。如果不归一化旋转矩阵会逐渐变形姿态解算就废了。我一般在每次预测之后都做一次归一化代价很小但能避免大问题。function q quat_normalize(q) n sqrt(q(1)^2 q(2)^2 q(3)^2 q(4)^2); q q / n; end function q quat_multiply(q1, q2) w1 q1(1); x1 q1(2); y1 q1(3); z1 q1(4); w2 q2(1); x2 q2(2); y2 q2(3); z2 q2(4); q [w1*w2 - x1*x2 - y1*y2 - z1*z2; w1*x2 x1*w2 y1*z2 - z1*y2; w1*y2 - x1*z2 y1*w2 z1*x2; w1*z2 x1*y2 - y1*x2 z1*w2]; end3.2 加速度计和磁力计的预处理原始传感器数据不能直接扔进滤波器需要做预处理。加速度计的数据要先做低通滤波截止频率设在5到10Hz左右。为什么因为振动噪声主要集中在高频而重力方向的变化是低频的。我试过用巴特沃斯二阶低通效果比简单的一阶RC滤波好相位滞后也小。磁力计的数据更麻烦。首先要做椭球拟合校准因为磁力计有硬铁和软铁干扰。硬铁干扰是固定的偏置软铁干扰是各向异性的缩放。校准的方法是把传感器在空间中转几圈采集数据用最小二乘拟合一个椭球然后求出变换矩阵。这个步骤不做的话航向角能差几十度。还有一个细节磁力计和加速度计的数据要对齐。如果采样率不同需要做时间同步。我一般用线性插值把磁力计数据插到加速度计的时间戳上。如果传感器有硬件同步引脚那最好不过但大多数低成本IMU没有只能软件对齐。3.3 观测方程的雅可比矩阵推导扩展卡尔曼滤波需要对观测方程求雅可比矩阵。加速度计观测方程对姿态误差的雅可比是H_a [0_{3x3}, 0_{3x3}, -R(q)^T * [g×], 0_{3x3}, I_{3x3}]其中[g×]是重力向量的反对称矩阵。这个推导的关键在于姿态误差δθ对旋转矩阵的影响是R(q) * exp([δθ×])对δθ求导就得到反对称矩阵。磁力计的雅可比类似只是把重力向量换成地磁向量。这里有个容易搞错的地方雅可比矩阵的符号。如果姿态误差的定义是q_true q_est ⊗ δq那么雅可比是负的如果是q_true δq ⊗ q_est雅可比是正的。我一开始符号搞反了结果滤波器发散调了两天才发现。建议在代码里加一个断言检查协方差矩阵是否正定如果出现负特征值就说明符号错了。4. 完整实操流程与Matlab代码实现4.1 数据采集与格式整理我用的数据集是自己采集的用一款常见的9轴IMU模块通过串口以100Hz的速率输出原始数据。数据格式是时间戳、加速度计三轴、陀螺仪三轴、磁力计三轴共10列。采集的时候要注意传感器要静止放置几秒钟让滤波器初始化零偏然后缓慢转动覆盖各个姿态最后快速晃动测试动态性能。Matlab读取数据用readmatrix或者importdata都行我习惯用readmatrix然后手动指定列。时间戳要转换成秒并且减去起始时间。如果时间戳有重复或者跳变需要先做清洗。data readmatrix(imu_data.csv); t data(:,1) / 1000; % 假设时间戳是毫秒 t t - t(1); acc data(:,2:4); gyr data(:,5:7); mag data(:,8:10);4.2 滤波器初始化初始化包括初始姿态、初始零偏、初始协方差矩阵。初始姿态可以用加速度计和磁力计算出来。加速度计测的是重力方向可以算出俯仰和横滚磁力计算航向。具体做法是先把加速度计归一化然后计算俯仰角θ asin(-a_x)横滚角φ atan2(a_y, a_z)。航向角需要把磁力计数据旋转到水平面再计算。初始零偏一般设为零因为静止时陀螺仪的输出就是零偏。初始协方差矩阵P设为单位矩阵乘以一个较小的值比如0.01表示对初始姿态比较有信心。Q和R矩阵根据传感器噪声参数设定我一般先设一个经验值然后根据滤波效果调整。% 初始姿态 q0 euler_to_quat(roll0, pitch0, yaw0); % 初始协方差 P eye(15) * 0.01; % 过程噪声 Q diag([0.001*ones(1,3), 0.001*ones(1,3), 0.01*ones(1,3), ... 0.0001*ones(1,3), 0.0001*ones(1,3)]); % 观测噪声 R_acc eye(3) * 0.1; R_mag eye(3) * 0.5;4.3 主循环预测与更新主循环按时间步进每个时间步做一次预测和一次更新。预测用陀螺仪数据更新用加速度计和磁力计数据。如果某个传感器数据缺失可以跳过对应的更新步骤只做预测。这种松耦合的结构很灵活实际项目中经常用到。for k 2:length(t) dt t(k) - t(k-1); % 预测 [q_pred, P_pred] predict(q_est, P, gyr(k,:), b_g, Q, dt); % 更新加速度计 [q_upd, P_upd] update_acc(q_pred, P_pred, acc(k,:), b_a, R_acc); % 更新磁力计 [q_est, P] update_mag(q_upd, P_upd, mag(k,:), R_mag); % 保存结果 q_history(k,:) q_est; end预测函数的核心是四元数积分和协方差传播。四元数积分用一阶近似就够了因为dt很小0.01秒高阶项可以忽略。协方差传播用F矩阵F I AdtA是系统矩阵。更新函数的核心是计算卡尔曼增益K PH(HP*H R)^(-1)然后更新状态和协方差。4.4 结果可视化与评估跑完滤波之后要把结果画出来看看。我一般画三张图欧拉角随时间变化、陀螺仪零偏估计、协方差对角线。欧拉角图能直观看出姿态是否平滑、有没有漂移零偏图能看出滤波器是否收敛协方差图能看出滤波器对估计的信心。评估指标方面如果有真值比如用高精度转台可以算均方根误差。没有真值的话可以看静态时的角度波动和动态时的跟随延迟。我实测下来静态时俯仰和横滚的波动在0.1度以内航向在0.5度以内动态时延迟在10毫秒左右对于大多数应用足够了。% 四元数转欧拉角 euler zeros(length(t), 3); for k 1:length(t) euler(k,:) quat_to_euler(q_history(k,:)); end % 画图 figure; subplot(3,1,1); plot(t, euler(:,1)); ylabel(Roll (deg)); subplot(3,1,2); plot(t, euler(:,2)); ylabel(Pitch (deg)); subplot(3,1,3); plot(t, euler(:,3)); ylabel(Yaw (deg));5. 常见问题与排查技巧实录5.1 滤波器发散协方差矩阵爆炸滤波器发散是最常见的问题表现是姿态角突然跳变或者慢慢漂移。原因通常是Q设得太大或者R设得太小导致卡尔曼增益过大滤波器过度信任测量。排查方法是打印协方差矩阵的对角线如果数值指数增长那就是发散了。解决方法先把Q调小一个数量级R调大一个数量级看是否收敛。如果还不行检查雅可比矩阵的符号是否正确。我遇到过一次符号搞反了协方差矩阵出现负特征值滤波器直接崩溃。后来加了一个检查如果P的特征值有负的就重置P为单位矩阵。5.2 航向角漂移磁力计干扰航向角漂移通常是因为磁力计受到干扰。室内环境钢筋结构多磁力计读数会偏。解决方法是要么做更精细的椭球校准要么在磁力计干扰大的时候降低它的权重。我一般会计算磁力计的模长如果偏离当地地磁强度太多就认为有干扰把R_mag调大。还有一个技巧磁力计只用来修正航向不用来修正俯仰和横滚。因为俯仰和横滚已经被加速度计修正得很好了磁力计再插一脚反而引入噪声。具体做法是在观测方程里只取磁力计在水平面的分量忽略垂直分量。5.3 动态响应滞后Q和R的权衡动态响应滞后表现为快速转动时滤波后的角度跟不上真实角度。原因是滤波器太信任模型不相信测量。解决方法是增大Q或者减小R。但这样静态噪声会变大。这是一个权衡没有两全其美的方案。我的经验是根据应用场景调整。如果是静态或者慢速应用比如云台可以偏重平滑Q小R大如果是动态应用比如飞控可以偏重响应Q大R小。我一般会准备两套参数根据运动状态自动切换。判断运动状态的方法很简单看陀螺仪的模长如果超过阈值就认为是动态。5.4 常见问题速查表问题现象可能原因排查方法解决方案姿态角跳变协方差发散打印P对角线调小Q调大R航向缓慢漂移磁力计干扰检查磁力计模长椭球校准调大R_mag动态跟随滞后Q太小或R太大对比原始陀螺仪积分调大Q调小R静态噪声大Q太大或R太小看静态方差调小Q调大R零偏估计不收敛随机游走参数太小看零偏曲线调大零偏对应的Q四元数模偏离1未归一化检查四元数模每步归一化6. 参数调优与性能提升的进阶技巧6.1 自适应卡尔曼滤波让噪声参数自己调整固定噪声参数的卡尔曼滤波在复杂环境下表现有限。自适应卡尔曼滤波可以根据残差测量值与预测值之差动态调整R矩阵。具体做法是计算残差的协方差如果残差比预期大说明测量不可信增大R反之减小R。这个方法在磁力计干扰大的时候特别有用。实现上我一般用滑动窗口计算残差协方差窗口长度设为20到50个采样点。然后根据残差协方差和理论协方差的比值调整R的缩放因子。这个缩放因子限制在0.1到10之间避免过度调整。6.2 零偏在线估计让陀螺仪越来越准陀螺仪零偏是姿态漂移的主要来源。卡尔曼滤波可以把零偏作为状态量在线估计。关键是零偏的随机游走参数要设对。设太小零偏估计不动设太大零偏估计抖动。我一般先用Allan方差标定出零偏随机游走系数然后在此基础上乘以2到3倍作为Q中零偏对应的值。还有一个技巧静止检测。当检测到传感器静止时加速度计模长接近g陀螺仪模长接近0可以强制零偏估计收敛。具体做法是在静止时把零偏对应的Q调小让滤波器更信任当前的零偏估计。这个方法能显著加快零偏收敛速度。6.3 多传感器融合从9轴到更多9轴IMU加上其他传感器能进一步提升性能。比如加上气压计可以估计高度加上GPS可以估计位置加上视觉可以估计更精确的姿态。卡尔曼滤波的框架很容易扩展只需要增加状态量和观测方程。我做过一个项目把9轴IMU和光流传感器融合用于室内无人机定位。光流提供水平速度IMU提供姿态和加速度融合后位置估计的漂移从米级降到了分米级。关键是把光流的观测噪声设对光流在纹理丰富的地方准在纹理少的地方差需要根据图像质量动态调整R。6.4 计算效率优化从Matlab到嵌入式Matlab代码跑通了下一步往往是移植到嵌入式平台。嵌入式的计算资源有限需要优化。我一般做这几件事把矩阵运算展开成标量运算避免动态内存分配用定点数代替浮点数如果精度允许把三角函数查表化。还有一个技巧降低滤波频率。姿态估计不需要和传感器采样率一样高100Hz采样可以降到50Hz滤波计算量减半精度损失很小。我实测过50Hz和100Hz滤波后的姿态角差异在0.05度以内完全可接受。7. 我在实际项目中的几点体会这个9轴IMU卡尔曼滤波的代码我前后改了十几版踩过的坑比写过的代码还多。最大的体会是噪声参数比算法本身更重要。同样的EKF参数调好了精度能到0.1度调不好能漂到10度。所以别急着优化算法先把Q和R调明白。另一个体会是仿真和实测差距很大。Matlab里跑得再好移植到嵌入式上可能完全不是那么回事。传感器的实际噪声特性、安装误差、温度漂移这些在仿真里都体现不出来。我的建议是先在Matlab里把算法验证通然后尽快上真实硬件测试用真实数据调参数。最后分享一个小技巧保存原始数据和滤波结果。每次调参之后把结果存下来对比不同参数的效果。我建了一个表格记录每次调参的Q、R值和对应的均方根误差这样能快速找到最优参数。这个习惯帮我省了很多重复劳动。代码方面我整理了一份完整的Matlab实现包括数据读取、滤波器初始化、主循环、结果可视化还有几个测试数据集。代码结构清晰注释详细可以直接拿来用。参数需要根据你的传感器调整但框架是通用的。如果你在做类似的项目希望这份内容能帮你少走点弯路。
阅读完成 · 觉得有帮助?
咨询建站