
简介基于Matlab卡尔曼滤波的IMU 9轴姿态解算源码包专为从事惯性导航、机器人姿态估计及组合导航研究的学习者与工程师设计。资源内含完整可运行的MATLAB程序主函数main.m直接驱动整个解算流程配合EKF.m等核心滤波算法文件可帮助读者理解卡尔曼滤波在姿态解算中的具体实现并借助运行结果图直观对比解算效果。压缩包共6个文件包含2个m源码文件、3张jpg结果示意图和1个txt数据文件整体仅118KB轻量易用适合快速复现实验。代码基于Matlab 2019b编写可直接运行得到解算结果并已通过实际测试。目前已有376人学习该资源适合需要掌握IMU数据融合、四元数姿态更新或卡尔曼滤波工程应用的读者。通过阅读源码与运行效果可快速上手姿态解算的基本框架并为进一步扩展误差补偿、动态调参等研究打下基础。1. 姿态解算的入口9轴IMU为什么绕不开卡尔曼滤波9轴IMU姿态解算的麻烦在于三类传感器各有各的不可信陀螺仪短时精准但积分必漂加速度计静态可靠却怕振动和线性加速度磁力计能锁定航向但在铁架和电机旁说偏就偏。卡尔曼滤波做的是把三个半可信来源按统计权重拧成一个连续且不发散的姿态。很多人是从MPU6050姿态解算的互补滤波切过来的。互补滤波轻量但强振动、快速机动和磁干扰混在一起时参数拉不动。扩展到卡尔曼滤波严格说是扩展卡尔曼滤波EKF之后能显式估计陀螺零偏还能在加速段自动降低加速度计的置信度这正是它在惯性导航领域长期被选中的原因。内容按数据预处理、滤波建模、9轴融合、源码调试展开落点在四元数姿态解算、Q/R矩阵整定、磁力计倾斜补偿和yaw慢漂排查。最后的调试技巧可以直接拿去对照手里的Matlab源码工程。2. 9轴IMU数据基础坐标系约定、四元数与预处理2.1 三类传感器的分工与坐标系约定9轴IMU的原始输出是三组量纲完全不同的向量陀螺仪给角速度rad/s加速度计给比力m/s²或g磁力计给地磁强度µT或Gauss。姿态解算的第一步不是滤波而是把三组数据统一到同一个姿态描述上。陀螺仪负责预测短时间内的角速度积分是准的加速度计和磁力计负责修正分别给出重力方向和地磁方向这两个绝对参考。三类传感器的噪声频段互不重叠这正是卡尔曼滤波能把它们融合的前提。坐标系约定是源码里最容易栽跟头的地方。常见约定分两套导航系用NED北东地或ENU东北天机体系用右前上。同一份数据在NED和ENU下算出的欧拉角符号完全不同尤其是yaw。拿到工程先确认初始化注释里写的是哪套约定我一般用ENU加右前上机体系后面代码也按这个约定写。如果你的数据是NED采集的把加速度计和磁力计的z轴取反或者把欧拉角换算里的pitch取负两者等价但别两处同时改。2.2 四元数姿态解算基础与欧拉角换算姿态表示有欧拉角、旋转矩阵和四元数三家。欧拉角直观但pitch到±90°会遭遇万向锁连续转动时角度从359°跳回0°卡尔曼滤波的残差计算会直接失效。旋转矩阵没有奇异问题9个元素冗余度高。四元数用4个元素表达三维旋转没有万向锁迭代中只要归一化就能保持正交性所以四元数姿态解算才是主流。MATLAB虽然有自带quat2eul但很多演示工程会自己写便于替换和理解。自己写转换时有两个细节atan2的参数顺序是(y,x)asin的输入要钳位到[-1,1]因为四元数数值积分后会略微偏离单位范数不钳位会得到NaN。function [roll, pitch, yaw] quat2eulerZYX(q) % 输入四元数q[q0 q1 q2 q3]标量在前ZYX内旋顺序 q q / norm(q); q0q(1); q1q(2); q2q(3); q3q(4); sinp 2*(q0*q2 - q1*q3); % sin(pitch)符号取决于旋转顺序 sinp max(-1, min(1, sinp)); % 钳位防NaN roll atan2(2*(q0*q1 q2*q3), 1 - 2*(q1^2 q2^2)); pitch asin(sinp); yaw atan2(2*(q0*q3 q1*q2), 1 - 2*(q2^2 q3^2)); end这套公式对应ZYX内旋顺序即先yaw再pitch最后roll。常见的错误是把XYZ顺序的公式套在ZYX数据上结果pitch稍大时roll和yaw同时出现非线性抖动这不是滤波的问题是坐标变换写错了。roll绕x轴、pitch绕y轴、yaw绕z轴正方向按右手定则。2.3 单位换算、零偏剔除与归一化原始数据能不能直接用取决于采集时的配置。以MPU9250为例工程里一般把陀螺仪量程设±2000°/s、加速度计±16g、磁力计±4800µT换算关系如下表。把°/s当成rad/s用是姿态飞掉的第一个常见原因。传感器原始量程换算到标准单位陀螺仪±2000 °/s2000/32768 × pi/180加速度计±16 g16/32768 × 9.8 m/s²磁力计±4800 µT4800/32768 µT换算之后还有三步陀螺零偏剔除、加速度计归一化、磁力计归一化。零偏的估计方法是让IMU静止取前100~200个样本求均值后续样本逐个相减。注意零偏与温度强相关静止采集温度和实际跑动温度不一致时会有残差这部分残差是yaw慢漂的来源之一。gyro raw_gyro * (2000/32768*pi/180); % 原始°/s转rad/s acc raw_acc * (16/32768*9.8); % 原始量转m/s^2 mag raw_mag * (4800/32768); % 原始量转uT gyro_bias mean(gyro(1:100,:), 1); % 前100个静止样本均值 gyro gyro - gyro_bias; % 剔除零偏 acc acc ./ vecnorm(acc, 2, 2); % 归一化只用方向信息 mag mag ./ vecnorm(mag, 2, 2);加速度计和磁力计在观测模型里只提供方向参考进滤波器之前就归一化避免量纲差异把卡尔曼增益带偏。vecnorm是R2017b之后才有的函数老版本写sqrt(sum(acc.^2,2))。磁力计归一化只能消除模长影响硬磁和软磁误差需要椭球拟合标定第5章调yaw时再展开。3. 卡尔曼滤波建模状态方程、观测方程与Matlab实现3.1 状态向量选四元数加陀螺零偏7维扩展卡尔曼滤波卡尔曼滤波用两个模型描述问题状态演化模型和测量更新模型。陀螺仪角速度是状态演化的输入但陀螺零偏不是常量会随温度和时长缓慢变化所以姿态估计里最稳妥的状态向量是7维四元数4个分量加陀螺零偏3个分量。省略零偏的话滤波器静止时roll和pitch会缓慢爬升精度被残留零偏限制。反过来把加速度计零偏也加进状态的做法不推荐加速度计零偏和重力方向在观测上不可区分可观测性不足硬加进去动态时姿态会变形。四元数运动学是非线性的因此这里用的是扩展卡尔曼滤波EKF。一阶离散化在采样率100~500Hz、每个dt内转动远小于1rad时精度足够。状态转移矩阵F需要把四元数乘法和零偏耦合项写出来下面代码直接给出。3.2 Q与R矩阵整定先调对角项再看输出响应EKF里需要人工指定的就是过程噪声协方差Q和测量噪声协方差R。两者不必按物理单位死抠重要的是相对大小。Q决定对陀螺仪预测的信任程度R决定对加速度计和磁力计测量的信任程度。整定顺序是先固定R调Q再固定Q微调R每次只改一个数量级跑同一段回放数据对比输出曲线相当于在MATLAB里看一次阶跃响应和静止方差。矩阵调大的效果调小的效果Q过程噪声更信测量响应快静止时偏抖更信陀螺预测曲线平滑R测量噪声更信陀螺预测滞后、有拖尾更信测量噪声放大信任方向新手经常搞反。记住R调大等于告诉滤波器测量不可靠输出主要由陀螺积分决定R调小则滤波器跟着加速度计和磁力计的高频噪声走静止方差变大。调参时先保证R的对角项三个轴一致非对角项保持0等姿态不偏了再考虑耦合。提示Q和R同时调是调参的大忌两个矩阵相互掩盖收敛曲线抖动得根本看不出谁出了问题。3.3 卡尔曼滤波单步更新的Matlab代码下面给一个可以直接运行的EKF单步函数输入当前状态x、协方差P、一组九轴数据和dt输出更新后的x与P。观测对四元数的雅可比用数值差分计算离线验证完全够用部署到实时系统再换成解析式。function [x, P] attitudeEKF_step(x, P, gyro, acc, mag, dt, Q, R) % 状态 x[q0 q1 q2 q3 bgx bgy bgz]四元数标量在前 % 观测 z[acc(3); mag(3)]均已归一化R为6x6 q x(1:4); w gyro(:) - x(5:7); % ---- 预测四元数运动学一阶离散化 ---- Omega [0 -w(1) -w(2) -w(3); w(1) 0 w(3) -w(2); w(2) -w(3) 0 w(1); w(3) w(2) -w(1) 0]; q_next q 0.5*dt*Omega*q; % 4x1 q_next q_next/norm(q_next); x_pred [q_next; x(5:7)]; F eye(7); F(1:4,1:4) eye(4) 0.5*dt*Omega; J [0 0 0; 1 0 0; 0 1 0; 0 0 1]; % [0; w] 对 bg 的系数 F(1:4,5:7) -0.5*dt*quatLeft(q_next)*J; % 零偏耦合项 P F*P*F Q; % ---- 观测重力与地磁在机体系的投影 ---- Rnb quat2Rot(q_next); % 导航系到机体系 g_nav [0;0;1]; % ENU下的重力参考方向 % mag_ref 在进入循环前定义静止水平第一次测量归一化 h [Rnb*g_nav; Rnb*mag_ref]; z [acc(:); mag(:)]; % ---- 数值差分雅可比只对四元数求偏导 ---- H zeros(6,7); for i 1:4 dq q_next; dq(i) dq(i) 1e-6; dq dq/norm(dq); hp [quat2Rot(dq)*g_nav; quat2Rot(dq)*mag_ref]; H(:,i) (hp - h)/1e-6; end % ---- 更新 ---- S H*P*H R; K P*H/S; x x_pred K*(z - h); P (eye(7) - K*H)*P; x(1:4) x(1:4)/norm(x(1:4)); % 四元数保持单位范数 end代码里有三个点单独说明。第一mag_ref是导航系下的地磁参考向量工程上取静止水平位置第一次测量的归一化值即可它不准确只影响yaw的零点不影响收敛性。第二数值差分步长1e-6对归一化四元数足够可靠步长太大雅可比噪声大太小被浮点精度吞掉。第三acc和mag要时间戳对齐才能放进同一个观测向量两者采样频率不同时先interp1统一采样率否则输出会周期性抖动。两个辅助函数如下quatLeft实现四元数左乘quat2Rot把四元数转成旋转矩阵均为标量在前约定。function L quatLeft(q) % 四元数左乘矩阵q⊗p L(q)*p q0q(1); q1q(2); q2q(3); q3q(4); L [q0 -q1 -q2 -q3; q1 q0 -q3 q2; q2 q3 q0 -q1; q3 -q2 q1 q0]; end function Rnb quat2Rot(q) % 四元数转旋转矩阵导航系到机体系 q0q(1); q1q(2); q2q(3); q3q(4); Rnb [1-2*(q2^2q3^2), 2*(q1*q2-q0*q3), 2*(q1*q3q0*q2); 2*(q1*q2q0*q3), 1-2*(q1^2q3^2), 2*(q2*q3-q0*q1); 2*(q1*q3-q0*q2), 2*(q2*q3q0*q1), 1-2*(q1^2q2^2)]; end4. 9轴融合策略加速度计修水平、磁力计修航向4.1 互补滤波与卡尔曼滤波的取舍9轴姿态解算题目下互补滤波Mahony与卡尔曼滤波的边界经常被混淆。互补滤波把陀螺积分的高频段与矢量观测的低频段用固定截止频率拼起来只有一个参数调参直觉强、落地快所以MPU6050姿态解算教程里到处都是它。短板是没法显式估计陀螺零偏机体剧烈运动时加速度计读到的线性加速度会直接污染姿态。卡尔曼的代价是协方差矩阵传播的计算量但在MATLAB离线处理里可忽略。常见取舍如下表。对比项互补滤波卡尔曼滤波EKF需要整定的参数1个截止频率Q与R矩阵陀螺零偏估计不支持状态里显式建模动态加速度处理需外加检测逻辑自适应R即可计算量极小7x7协方差传播适合场景单片机低算力移植MATLAB离线验证我的建议是在MATLAB里做数据分析和源码验证直接用EKF参数可解释性强真要移植到STM32且算力吃紧再退回互补滤波。两者的输入输出接口可以设计成同一套切换时只改融合函数。4.2 磁力计倾斜补偿把地磁投影回水平面再算yawyaw方向的校正是9轴融合里最容易被低估的一步。磁力计三轴读数本身是地磁场在机体轴上的投影pitch和roll不为零时直接用atan2(my,mx)算航向会明显偏斜。正确做法是先利用当前roll和pitch把磁力计向量转回水平面再用水平分量算yaw。% 倾斜补偿先撤roll再撤pitch把磁力计投回水平面 function yaw mag_tilt_compensated(mx, my, mz, roll, pitch) sp sin(pitch); cp cos(pitch); sr sin(roll); cr cos(roll); Bx mx*cp my*sp*sr mz*sp*cr; % 水平面x分量 By my*cr - mz*sr; % 水平面y分量 yaw atan2(By, Bx); % 符号与轴定义有关 end这版公式是应用笔记里流传最广的写法与ZYX欧拉角顺序对应。三个坑必须知道。一是atan2参数顺序依然是(y,x)。二是磁力计轴指向不同会让yaw偏转90°或反向不要硬凑公式先做一次水平转90°对照偏90°就交换Bx/By角色反向就给By加负号。三是硬磁干扰会让补偿无解水平旋转一圈yaw出现双正弦波时去标定不要调公式系数。更省事的替代方案是把姿态解算输出的四元数当作已知用R(q)把磁力计直接转到水平面再算yaw等价但少一套符号推理。4.3 重力对齐与动态加速度剔除别让线性加速度骗了滤波器加速度计在观测模型里被当作重力方向测量这个假设只在载体没有线性加速度时成立。电机启动、急停、转弯都会在读数里叠加远大于重力的分量不处理的话roll和pitch瞬间被拉偏运动结束再花几百毫秒收敛回来这就是imu重力对齐在动态场景下的表现。解决思路是检测加降权用加速度计模长偏离9.8m/s²的程度判断当前是否处于加速段偏离越大对测量的信任越低。% 自适应R加速度计模长偏离重力越多测量噪声越大 ga vecnorm(acc,2,2); % acc为单样本1x3 if abs(ga - 9.8) 0.6 % 经验阈值四旋翼放宽到1.5 R(1,1) R_base(1,1)*30; % R前3行3列为加速度计 R(2,2) R_base(2,2)*30; R(3,3) R_base(3,3)*30; else R(1:3,1:3) R_base(1:3,1:3); end阈值0.6对车载和手持设备够用四旋翼这类持续振动的平台要放到1.5以上否则加速判定过于频繁水平姿态跟着振动走。放大倍数30同样不是定死的太小加速段姿态仍被污染太大加速结束后重新收敛变慢。磁力计可以套用同一套逻辑检测模长偏离当地总场强度超过门限时放大R(4:6,4:6)。另一种做法是残差超门限直接跳过更新但自适应R的平滑度更好残差跳步会在轨迹上留阶梯。5. 源码回放与yaw慢漂排查让卡尔曼参数现场收敛拿到标了含Matlab源码的工程包第一件事不是读滤波代码而是确认数据入口。常见做法是包里有录好的IMU log或一个从CSV读数的脚本三列时间戳加九列原始数据。先按时间戳排序检查丢帧和重复时间戳这两类问题在排查基于IMU的位姿解算yaw仍会慢漂时最难定位。5.1 用离线回放把整个滤波循环跑一遍x [1 0 0 0 0 0 0]; % 初始四元数零偏 P eye(7)*0.01; eul zeros(N,3); for k 2:N dt t(k) - t(k-1); [x, P] attitudeEKF_step(x, P, gyro(k,:), acc(k,:), mag(k,:), dt, Q, R); eul(k,:) quat2eulerZYX(x(1:4)); end plot(t, eul*180/pi);验证标准三条静止段roll/pitch方差小于0.2°、yaw保持不动绕z轴匀速一圈yaw平滑回到原点快速起停时水平姿态可短暂偏离但0.5秒内收敛。静止段yaw持续漂移先查磁力计标定静止时roll/pitch漂移先查陀螺零偏残差动态段发散则看Q和R是否差距超过两个数量级。5.2 yaw慢漂的排查顺序与三个收敛技巧yaw慢漂的常见来源只有三个。第一磁力计没有做硬磁校准模长随姿态变化水平分量被污染。简单做法是水平转一周记录各轴max/min按(m-(maxmin)/2)做零偏修正椭圆形的软磁误差需要椭球拟合。第二dt抖动。真实采样每个间隔都有微小波动逐点用t(k)-t(k-1)会让噪声特性逐周期变化输出叠加低频抖改用中位数dt或重采样到固定频率即可。第三yaw的可观测性完全依赖磁力计桥洞、高压线下磁干扰严重时yaw慢漂物理上无法消除这时把磁力计的R调大、让yaw跟陀螺慢漂而不是盲调Q。最后一个技巧检查acc和mag时间戳是否对齐。MPU9250这类芯片加速度计和磁力计采样时刻天然不同步差的几十毫秒在静止时看不出来快速转动时会在残差里叠加与角速度成正比的假误差表现为yaw对转动方向偏敏感。离线处理时先用interp1把磁力计插到加速度计时间轴上再做参数收敛这一步往往比反复调R更直接。本文还有配套的精品资源点击获取