简介:这份资源面向导航系统开发、自动驾驶与无人机控制方向的学习者,聚焦GPS、惯性测量单元与地磁传感器融合的组合导航方案,重点解决单一传感器在城市峡谷、室内或信号受扰环境下精度下降的问题。压缩包内共1个文件,为MATLAB脚本(.m格式),整体约3KB,可直接用于算法复现与仿真验证。脚本内容涵盖传感器数据读取、噪声建模、联邦滤波器结构设置、状态估计更新以及结果误差评估等关键环节,帮助读者理解卡尔曼滤波与多传感器信息融合的权重分配逻辑。目前已有329人学习下载,适合希望掌握联邦滤波实现思路、提升组合导航鲁棒性分析能力的中高级工程技术人员参考。
1. 拆开这个 GPS/INS/地磁组合导航仿真包:它到底能跑出什么
如果你手头只有一段纯 GPS 轨迹,进了城市峡谷或者树荫遮挡就飘得没法看;只靠 IMU 积分,几十秒后位置就漂到姥姥家;只用地磁,航向能稳住但位置完全没谱。这个GPS_ins_mag_int_sim.rar里的GPS_ins_mag_int_sim.m,就是冲着这三者互补来的——用 GPS 压住 IMU 的累积漂移,用 IMU 补上 GPS 失锁时的空窗,再用地磁给航向加一道约束。它适合做多传感器融合入门、联邦滤波验证、或者给无人机/车载导航算法做前期仿真的人。不是生产级代码,但作为理解组合导航数据流和滤波器结构的起点,够用。
2. 联邦滤波器的骨架:从传感器模型到状态估计
2.1 为什么是联邦滤波而不是集中式卡尔曼
集中式卡尔曼把所有传感器量测堆进一个大矩阵,理论上最优,但实际工程里有两个硬伤:一是某个传感器故障会污染全局估计,二是量测维数一高,矩阵求逆的数值稳定性就往下掉。联邦滤波把系统拆成若干局部滤波器加一个主滤波器,每个局部滤波器独立处理自己的量测,主滤波器只做信息融合。好处很直接——GPS 丢了,IMU 和地磁的局部滤波器照常跑,主滤波器把坏掉那路的信息权重降下去就行。
这个仿真包里,典型结构是:
- 局部滤波器 1:IMU 做预测,GPS 位置/速度做更新
- 局部滤波器 2:IMU 做预测,地磁航向做更新
- 主滤波器:按信息守恒原则融合两个局部滤波器的状态估计和协方差
信息融合的核心公式是信息矩阵相加:
P_f^-1 = P_1^-1 + P_2^-1 x_f = P_f * (P_1^-1 * x_1 + P_2^-1 * x_2)其中P是协方差矩阵,x是状态向量。这个式子看着简单,但实操里P的条件数、局部滤波器的相关性处理,都是容易翻车的地方。
2.2 状态向量怎么定:15 维还是 9 维
打开GPS_ins_mag_int_sim.m,第一件要确认的事就是状态向量维度。常见做法是 15 维误差状态:
| 状态分量 | 维度 | 含义 |
|---|---|---|
| 位置误差 | 3 | 纬度、经度、高度 |
| 速度误差 | 3 | 东北天三向 |
| 姿态误差 | 3 | 俯仰、横滚、航向 |
| 加速度计零偏 | 3 | 三轴 |
| 陀螺仪零偏 | 3 | 三轴 |
如果脚本里只写了 9 维,那大概率是简化版,没估计零偏。零偏不估的后果是:IMU 积分误差会随时间线性增长,GPS 更新只能压住位置,压不住零偏本身,长时间跑下来姿态还是会歪。
检查方法很简单,在 MATLAB 命令窗口里看状态转移矩阵F的维度:
% 假设脚本里变量名为 F disp(size(F)); % 如果输出 15x15,说明是完整误差状态模型 % 如果输出 9x9,说明零偏没进状态向量提示:零偏建模时,通常用随机游走模型,即
b_dot = w_b,w_b是零均值高斯白噪声。对应的F矩阵里零偏那几行只有对角线上的 1,过程噪声矩阵Q里零偏对应项要设成小量,一般加速度计零偏噪声在1e-5 g量级,陀螺零偏在1e-4 deg/s量级。
2.3 地磁量测更新:航向角怎么算、怎么用
地磁参与组合导航,最直接的方式是提供航向观测。三轴磁力计读数先做倾斜补偿,再算磁航向:
% 假设 mag_body 是 3x1 机体坐标系磁力计读数 % roll, pitch 来自 IMU 姿态解算 % 倾斜补偿:把磁力计投影到水平面 mag_x_h = mag_body(1) * cos(pitch) + ... mag_body(2) * sin(roll) * sin(pitch) + ... mag_body(3) * cos(roll) * sin(pitch); mag_y_h = mag_body(2) * cos(roll) - mag_body(3) * sin(roll); % 磁航向(弧度) mag_heading = atan2(-mag_y_h, mag_x_h); % 磁偏角修正(如果脚本里有磁偏角参数) true_heading = mag_heading + declination;这段代码的逻辑是:先把机体坐标系下的磁力计读数旋转到水平坐标系,再用atan2求航向。参数说明:roll和pitch来自 IMU 的姿态角,declination是当地磁偏角,不修正的话航向会有几度到十几度的系统偏差。
量测更新时,把true_heading作为观测量,H矩阵对应姿态误差中的航向分量。注意航向角在 ±π 附近有跳变,做残差计算时要先归一化到 [-π, π]:
residual = wrapToPi(true_heading - predicted_heading);这个wrapToPi不加,滤波器在航向过零时会直接发散,这是血泪经验。
3. 把 .m 脚本跑起来:数据流、参数配置与结果验证
3.1 脚本结构拆解与运行前检查
拿到GPS_ins_mag_int_sim.m,别急着按 F5。先做三件事:
第一,确认 MATLAB 版本。脚本里如果用了readmatrix或timetable,需要 R2019a 以上;如果只有csvread和xlsread,老版本也能跑。在脚本开头看有没有verLessThan的判断。
第二,检查数据文件路径。仿真脚本通常有两种数据来源:一是内置的模拟数据生成函数,二是外部.mat或.csv文件。如果是外部文件,路径写死成绝对路径的话,换台机器就报错。常见做法是改成相对路径:
% 不推荐 load('D:\project\data\gps_data.mat'); % 推荐 data_dir = fullfile(fileparts(mfilename('fullpath')), 'data'); load(fullfile(data_dir, 'gps_data.mat'));mfilename('fullpath')返回当前脚本的完整路径,fileparts取目录部分,这样不管脚本放在哪,数据路径都跟着走。
第三,看采样率。GPS 通常 1Hz 或 10Hz,IMU 常见 100Hz 或 200Hz,地磁可能 10Hz 或 50Hz。如果脚本里用同一个时间向量处理所有传感器,那大概率是做了插值或者降采样。检查时间对齐逻辑:
% 假设 t_imu 是 IMU 时间戳,t_gps 是 GPS 时间戳 % 常见做法:以 IMU 时间为基准,GPS 量测做零阶保持 gps_idx = 1; for k = 1:length(t_imu) if gps_idx <= length(t_gps) && t_imu(k) >= t_gps(gps_idx) % 更新 GPS 量测 gps_idx = gps_idx + 1; end % IMU 预测 end这种写法比interp1更贴近实际嵌入式实现,因为真实系统里 GPS 来了就更新,没来就只跑预测。
3.2 噪声参数怎么设:从传感器手册到 Q/R 矩阵
过程噪声Q和量测噪声R直接决定滤波器是“信模型”还是“信量测”。设错了,要么滤波发散,要么估计滞后。
常见做法是查传感器手册里的噪声密度:
- 加速度计噪声密度:
100 ug/√Hz到300 ug/√Hz - 陀螺仪噪声密度:
0.01 deg/s/√Hz到0.1 deg/s/√Hz - GPS 位置噪声:
1 m到5 m(CEP) - 磁力计噪声:
0.5 uT到2 uT
然后按采样率换算成离散时间噪声方差:
% 加速度计噪声密度 200 ug/sqrt(Hz),采样率 100 Hz accel_noise_density = 200e-6 * 9.81; % 转成 m/s^2/sqrt(Hz) fs = 100; accel_var = accel_noise_density^2 * fs; % 离散方差 % 陀螺仪噪声密度 0.05 deg/s/sqrt(Hz) gyro_noise_density = 0.05 * pi/180; % 转成 rad/s/sqrt(Hz) gyro_var = gyro_noise_density^2 * fs;Q矩阵里对应位置误差的项,还要乘上dt^2或dt^4的系数,具体取决于状态转移的离散化方式。如果脚本里Q是手工填的常数,跑之前先看量级对不对——位置过程噪声一般在1e-4到1e-2量级,速度过程噪声在1e-3到1e-1。
R矩阵相对好设,GPS 位置方差直接填(2m)^2 = 4,磁航向方差填(3deg)^2转弧度。但要注意:如果 GPS 在城市环境跑,R要放大,否则滤波器会把多径误差当成真实位置。
3.3 结果验证:三条曲线说明一切
跑完脚本,至少要看三张图:
第一张,位置误差随时间变化。横轴时间,纵轴东北天三向误差。正常收敛的话,误差应该在 GPS 更新时刻被拉低,在 GPS 失锁区间缓慢上升。如果误差单调发散,先查Q是不是太小、R是不是太大。
第二张,航向误差。地磁参与后,航向误差应该比纯 IMU 积分小一个量级。如果航向误差在 ±180 度跳变,说明wrapToPi没加对地方。
第三张,协方差对角线。P矩阵的对角线应该收敛到一个稳态值,如果一直涨,说明过程噪声压不住量测更新。
% 画位置误差 figure; subplot(3,1,1); plot(t, pos_error(:,1)); ylabel('东向误差 (m)'); grid on; % ... 类似画北向和天向 % 画航向误差 figure; plot(t, heading_error * 180/pi); ylabel('航向误差 (deg)'); grid on;注意:仿真里如果用了
randn生成噪声,每次跑结果都不一样。想复现,在脚本开头加rng(42)固定随机种子。
4. 避坑与排查:那些让滤波器发散的小细节
4.1 现象:GPS 更新后位置反而跳变
原因:GPS 量测和 IMU 预测的时间戳没对齐,或者 GPS 坐标系和 IMU 坐标系没统一。常见的是 GPS 给的是经纬高,IMU 积分出来的是本地东北天坐标,中间少了一步大地坐标转平面坐标。
解决:在 GPS 更新前,先把经纬高转成东北天。可以用geodetic2enu,或者自己写简化公式:
% 参考点纬经高 lat0 = 39.9; lon0 = 116.4; h0 = 50; % 当前 GPS 纬经高 lat = gps_data(k,1); lon = gps_data(k,2); h = gps_data(k,3); % 简化转换(小范围适用) R_earth = 6378137; d_lat = (lat - lat0) * pi/180; d_lon = (lon - lon0) * pi/180; enu_e = d_lon * R_earth * cos(lat0 * pi/180); enu_n = d_lat * R_earth; enu_u = h - h0;4.2 现象:航向缓慢旋转,地磁像没起作用
原因:磁力计没做硬铁和软铁校准,或者磁偏角没修正。硬铁误差让磁力计圆心偏移,软铁误差让圆变成椭圆,两者都会让航向出现周期性偏差。
解决:仿真里如果磁力计数据是理想生成的,检查有没有加磁偏角。如果是从真实数据回放,先做椭球拟合校准:
% 简化硬铁校准:减去三轴均值 mag_cal = mag_raw - mean(mag_raw); % 软铁校准需要拟合椭球,仿真里一般不做4.3 现象:协方差矩阵不对称或非正定
原因:联邦滤波融合时,局部滤波器之间的相关性没处理,或者数值误差累积。信息融合公式里P_f^-1 = P_1^-1 + P_2^-1假设局部滤波器独立,实际中它们共享 IMU 预测,是相关的。
解决:工程上常用协方差交叉(Covariance Intersection)代替简单信息相加,或者给每个局部滤波器的信息矩阵乘一个保守系数。仿真里如果发散不严重,可以对P_f做对称化:
P_f = (P_f + P_f') / 2; % 再检查特征值 if min(eig(P_f)) < 0 P_f = P_f + eye(size(P_f)) * 1e-6; end4.4 现象:IMU 零偏估计不收敛,一直漂
原因:零偏的可观测性不够。如果载体一直静止或者匀速直线运动,加速度计零偏和姿态误差耦合,滤波器分不清是零偏还是姿态歪了。
解决:仿真里加一段机动,比如转弯或者加减速,让零偏变得可观测。或者降低零偏过程噪声,让滤波器“耐心”一点。
4.5 现象:脚本跑一半报错“矩阵维度不一致”
原因:状态向量维度改了,但H矩阵或者Q、R没跟着改。比如从 9 维扩到 15 维,H矩阵的列数还是 9。
解决:在脚本开头统一定义状态维度:
n_state = 15; % 后面所有矩阵初始化都用 n_state H_gps = zeros(3, n_state); H_gps(1:3, 1:3) = eye(3);这样改维度时只改一个地方,不用满脚本找。
5. 进阶玩法:把仿真脚本改成能跑真实数据的框架
仿真跑通只是第一步。真正有价值的是把GPS_ins_mag_int_sim.m改造成能灌真实传感器数据的框架。我一般会做三件事:
第一,把数据读取层抽象出来。定义一个load_sensor_data函数,输入是数据目录,输出是统一格式的结构体:
function data = load_sensor_data(data_dir) % 读取 IMU imu_raw = readmatrix(fullfile(data_dir, 'imu.csv')); data.imu.t = imu_raw(:,1); data.imu.acc = imu_raw(:,2:4); data.imu.gyro = imu_raw(:,5:7); % 读取 GPS gps_raw = readmatrix(fullfile(data_dir, 'gps.csv')); data.gps.t = gps_raw(:,1); data.gps.pos = gps_raw(:,2:4); % 读取磁力计 mag_raw = readmatrix(fullfile(data_dir, 'mag.csv')); data.mag.t = mag_raw(:,1); data.mag.field = mag_raw(:,2:4); end这样换数据集只需要改文件格式,滤波器核心不用动。
第二,加一个时间对齐模块。真实数据里 IMU 和 GPS 的时间戳很少完全对齐,常见做法是以 IMU 为基准,对 GPS 和磁力计做线性插值或者零阶保持。我一般用零阶保持,因为更接近嵌入式实现。
第三,加一个在线零偏估计的监控。把滤波器估计的零偏画出来,如果零偏在几分钟内漂了几十毫克或者几度每秒,说明Q设大了或者量测有问题。
| 改造点 | 仿真脚本原状 | 真实数据框架 |
|---|---|---|
| 数据来源 | 内置生成函数 | 外部 CSV/Mat 文件 |
| 时间对齐 | 统一时间向量 | 以 IMU 为基准插值 |
| 零偏监控 | 无 | 实时画图 |
| 故障处理 | 无 | GPS 失锁检测与隔离 |
最后说一个习惯:每次改完滤波器参数,先跑一段 60 秒的短数据,看协方差有没有收敛,再跑长数据。直接上长数据,发散了都不知道从哪查。从那以后我每次调组合导航参数,都强制先跑短序列验证收敛性,再放长线。希望帮到你。
本文还有配套的精品资源,点击获取