简介:面向低成本组合导航系统研发的GPS/INS松组合MATLAB实现包,适合导航专业学生、算法工程师及无人机、自动驾驶、航海等领域的开发者。压缩包共两个文件,包含一个M脚本和一个MAT数据文件,整体仅618KB。核心脚本基于卡尔曼滤波融合GPS与惯性导航输出,用于位置估计与误差校正;数据文件提供预计算模拟数据,可用于测试和验证算法在不同动态场景下的性能。松组合方式允许两个系统独立完成数据解算后在输出层融合,既保留卫星定位的长期稳定性,又发挥惯导短期精度高的特点,可有效抑制单传感器误差并降低计算负担。已有一百七十人学习下载,代码结构简洁,便于快速阅读理解松组合架构、掌握滤波融合实现细节,并在此基础上扩展改进。
1. GPS/INS 松组合:低成本导航系统里最值得先跑的方案
做低成本的组合导航,第一版算法我通常不建议直接上紧组合。GPS/INS 松组合把卫星定位和惯性导航当作两个独立模块,GPS 出位置,INS 出姿态和短时高动态轨迹,最后由卡尔曼滤波在导航坐标系里做一次融合。这套架构在无人机、车载、农业机械上都验证过,实现成本低、故障隔离清晰,而且对惯性器件的要求不高——MEMS 级别的 IMU 配上普通 GPS 接收机就能跑出可用的结果。它的核心价值在于:GPS 给 INS 提供长期漂移校正,INS 给 GPS 补足高动态和遮挡环境下的连续性。本文要拆的这份kalman_GPS_INS_position_sp_NFb.m就是这类系统里最典型的实现——15 维误差状态卡尔曼滤波,位置观测量,NED 导航坐标系,MATLAB 全流程可跑。适合刚接触组合导航、想弄懂松组合滤波结构和参数设置逻辑的人。
2. 误差状态卡尔曼滤波:松组合的数学骨架
2.1 状态向量选 15 维还是 9 维
松组合的滤波对象不是位置、速度、姿态本身,而是它们的误差。原因是误差的传播近似线性,卡尔曼滤波的线性高斯假设更容易满足。这份代码采用标准 15 维误差状态,比 9 维多了陀螺零偏和加速度计零偏的估计,这在低成本 MEMS 器件上非常关键——不估计零偏,姿态误差会随时间二次方增长,几分钟后位置就发散。
| 状态分组 | 变量 | 维度 | 单位 | 说明 |
|---|---|---|---|---|
| 位置误差 | δr_n | 3 | m | NED 坐标系下的位置误差 |
| 速度误差 | δv_n | 3 | m/s | NED 速度误差 |
| 姿态误差 | ψ_n | 3 | rad | 等效旋转矢量小角度近似 |
| 陀螺零偏 | δb_g | 3 | rad/s | 体坐标系下三轴陀螺零偏 |
| 加速度计零偏 | δb_a | 3 | m/s² | 体坐标系下三轴加速度计零偏 |
对应的状态向量写作x = [δr_n; δv_n; ψ_n; δb_g; δb_a]。9 维状态里没有零偏项,系统只能在滤波过程中被动吸收器件误差,但无法把零偏从姿态误差中分离出来。15 维虽然增加了计算量,但对 MEMS 器件来说,零偏才是最主要的误差源,估计出来并反馈到机械编排里,才能保证长时间递推不飘。
2.2 误差传播方程与状态转移矩阵
松组合的时间更新基于惯性误差传播模型。忽略地球自转和科里奥利力的简化形式可以写成:
δr_dot = δv δv_dot = -skew(f_n) * ψ + C_b^n * δb_a ψ_dot = -C_b^n * δb_g δb_g_dot = 0 δb_a_dot = 0其中f_n是导航坐标系下的比力,C_b^n是体坐标系到导航坐标系的旋转矩阵,skew()构造反对称矩阵。这段关系在代码里对应误差状态转移矩阵F的构建。完整推导还包含ω_en、ω_ie等地球自转项,但低动态场景下省略它们对结果影响很小,反而让矩阵更直观。MATLAB 里的实现如下:
function F = ins_error_state_matrix(Cbn, fb) % 构建 15 维误差状态转移矩阵(连续时间) % Cbn: 体坐标系到导航坐标系的旋转矩阵 % fb: 体坐标系下的比力,m/s^2 fn = Cbn * fb; % 比力投影到导航系 F = zeros(15, 15); % 位置误差对速度误差 F(1:3, 4:6) = eye(3); % 速度误差对姿态误差:δv_dot = -skew(f_n) * ψ F(4:6, 7:9) = -skew(fn); % 速度误差对加速度计零偏:δv_dot = C_b^n * δb_a F(4:6, 13:15) = Cbn; % 姿态误差对陀螺零偏:ψ_dot = -C_b^n * δb_g F(7:9, 10:12) = -Cbn; % 其余项保持为零:零偏随机游走由过程噪声体现 endF矩阵第 1 到 3 行是位置误差的导数,等于速度误差;第 4 到 6 行里只有姿态误差和加计零偏两个输入,没有位置误差项,说明速度误差不会直接由位置误差耦合进来;第 7 到 9 行姿态误差只受陀螺零偏影响。离散化时用一阶泰勒展开:Phi = eye(15) + F * dt,dt 是惯导更新周期。如果 IMU 输出的是角增量和速度增量而非角速率和比力,机械编排里要额外做圆锥补偿和划船补偿,但误差状态转移矩阵仍然用比力和角速率形式。
2.3 观测方程与可观测性
这份代码用 GPS 位置作为唯一观测量,观测方程极其简洁:
z = p_gps - p_ins H = [ I_3 O_3 O_3 O_3 O_3 ]z是 GPS 位置与 INS 推算位置的差,H矩阵只在前 3 列有值。这里有个工程上常被忽略的问题:只用位置观测时,陀螺零偏和加速度计零偏不是完全可观的。直观解释是,位置误差对姿态误差的敏感度需要载体有加速度变化才能体现——匀速直线飞行时,位置误差和姿态误差耦合在同一个方向上,滤波无法区分。所以松组合系统在上电初始化的前几十秒最好做加减速或转弯机动,否则零偏估计收敛得很慢,甚至不收敛。
量测更新采用标准卡尔曼滤波五步:
% 计算卡尔曼增益 K = P_pred * H' / (H * P_pred * H' + R); % 状态更新 x_upd = x_pred + K * (z - H * x_pred); % 协方差更新(Joseph 形式,数值稳定性好) P_upd = (eye(15) - K * H) * P_pred * (eye(15) - K * H)' + K * R * K';Joseph 形式的协方差更新比简化的(I - K*H)*P数值稳定性更好,尤其在 P 矩阵条件数较大的时候。R矩阵在这里是对角阵,对角线是 GPS 位置误差的方差,通常设(2~5m)^2。如果 GPS 接收机输出的精度指标是HDOP * 1.5m之类的值,可以把水平方向的方差设得比垂直方向小,因为垂直方向定位精度通常更差。
3. 从 ode500.mat 到 kalman_GPS_INS_position_sp_NFb.m 的完整复现
3.1 数据文件与字段约定
压缩包里的ode500.mat是预先准备的测试数据。文件名里的 500 大概率指仿真时长 500 秒或数据点数 500 个。这类 mat 文件里通常包含的字段有:IMU 采样时间戳imu_time、三轴角速率gyro(rad/s)或角增量、三轴比力acc(m/s²)、GPS 时间戳gps_time、GPS 位置gps_pos(或者经纬度高程)。拿到数据后第一件事是检查变量名,而不是直接跑脚本:
% 查看 mat 文件内有哪些变量、维度多大 whos('-file', 'ode500.mat'); % 加载后查看每个变量的前几行 load('ode500.mat'); disp(names); % 变量总览 fprintf('IMU 数据点数: %d\n', length(imu_time)); fprintf('GPS 数据点数: %d\n', length(gps_time));这一步很关键。不同来源的 mat 文件里 IMU 和 GPS 的时间戳可能不是同一坐标系下的,有的用 GPS 周秒,有的用本地仿真时间,有的直接以采样序号代替。确认清楚后再做时间同步:GPS 更新频率通常是 1~10Hz,IMU 是 100~400Hz,松组合里常见做法是 IMU 每次采样都做时间更新,GPS 数据到达的时刻做量测更新,GPS 时刻如果没有对应 IMU 采样点,取最近的 IMU 状态做量测更新。
3.2 机械编排与卡尔曼滤波主循环
主脚本的核心结构是:IMU 采样循环里先做惯性机械编排,然后做卡尔曼时间更新,遇到 GPS 观测就叠加量测更新和反馈校正。下面是一段可以直接替换进kalman_GPS_INS_position_sp_NFb.m的主循环框架:
%% 初始化 x = zeros(15, 1); % 误差状态初值 P = diag([... (10)^2 * ones(1,3), ... % 位置误差 10m (0.1)^2 * ones(1,3), ... % 速度误差 0.1m/s (0.1*pi/180)^2 * ones(1,3), ... % 姿态误差 0.1deg (0.02*pi/180/3600)^2 * ones(1,3),... % 陀螺零偏 0.02deg/h (1e-4*9.8)^2 * ones(1,3)]); % 加计零偏 1e-4g % 过程噪声功率谱密度,根据 IMU 数据手册或 Allan 方差填 sigma_g = 0.02 * pi / 180 / 3600; % 陀螺角度随机游走 sigma_a = 1e-4 * 9.8; % 加速度计速度随机游走 Q = diag([zeros(1,3), ... % 位置误差过程噪声约等于 0 sigma_a^2 * ones(1,3), ... % 速度误差 sigma_g^2 * ones(1,3), ... % 姿态误差 (1e-6)^2 * ones(1,3), ... % 陀螺零偏的随机游走 (1e-7)^2 * ones(1,3)]); % 加计零偏的随机游走 %% 主循环 for k = 1 : length(imu_time) - 1 dt = imu_time(k+1) - imu_time(k); % --- 机械编排(简化,未含圆锥/划船补偿)--- gyro = gyro_data(k, :)'; % 角速率 rad/s fb = accel_data(k, :)'; % 比力 m/s^2 % 姿态更新:一阶近似 omega_b = skew(gyro); Cbn = Cbn * (eye(3) + omega_b * dt); % 速度更新:比力方程,忽略地球自转项 vel = vel + (Cbn * fb + [0; 0; 9.794]) * dt; % 位置更新 pos = pos + vel * dt; % --- 卡尔曼时间更新 --- F = ins_error_state_matrix(Cbn, fb); Phi = eye(15) + F * dt; G = [zeros(3); Cbn; zeros(3); zeros(3); eye(3)]; Qd = Phi * G * Q * G' * Phi' * dt; x = Phi * x; P = Phi * P * Phi' + Qd; % --- GPS 位置量测更新 --- if gps_valid(k) z = gps_pos_gps(k, :)'; % GPS 位置 z_ins = pos; % INS 推算位置 innovation = z - z_ins; H = [eye(3), zeros(3, 12)]; R = diag([2.5^2, 2.5^2, 5^2]); % 水平 2.5m, 垂直 5m K = P * H' / (H * P * H' + R); x = x + K * (innovation - H * x); P = (eye(15) - K*H) * P * (eye(15) - K*H)' + K*R*K'; % --- 反馈校正 --- pos = pos - x(1:3); vel = vel - x(4:6); % 姿态修正:小角度近似 psi = x(7:9); Cbn = (eye(3) - skew(psi)) * Cbn; % 零偏反馈到陀螺和加速度计 gyro_bias = gyro_bias + x(10:12); acc_bias = acc_bias + x(13:15); % 误差状态清零(反馈后剩余状态量零) x(1:6) = 0; x(7:15) = 0; end % 记录结果 pos_history(k, :) = pos'; vel_history(k, :) = vel'; end代码里出现的关键参数都值得展开解释。P矩阵的初值代表对初始误差的信任程度,惯性导航系统上电时位置误差通常来自 GPS 单点定位精度(约 10 米),姿态误差来自对准精度(MEMS 器件用陀螺罗盘对准约 0.1 度,静态粗对准约 1 度),零偏误差来自标定残余。Q矩阵里的sigma_g和sigma_a是惯性器件的噪声密度,严格讲应该从 Allan 方差曲线读零偏不稳定性,但大多数低成本项目没有条件做完整标定,直接用数据手册里的角度随机游走和速度随机游走值凑合也可以。
反馈校正时机要特别注意。松组合里有两种校正方式:开环和闭环。开环模式下滤波器只在后台估计误差,不把估计值送回去修正机械编排;闭环模式下每次量测更新后立刻用误差估计值修正位置、速度和姿态,然后清零误差状态。上面代码用的是闭环。闭环的好处是机械编排始终工作在校正后的状态上,线性化误差小,不会出现误差大到超出小角度假设的情况;代价是如果滤波发散,校正会把发散误差注入机械编排,导致系统直接崩掉。低成本的场景下我建议用闭环,但要在代码里写一个发散检测。
3.3 时间同步与数据对齐
多数初学松组合的人踩的第一个坑就是 GPS 和 IMU 时间不同步。GPS 接收机输出的位置带的是信号接收时刻,但很多模块内部有 100~200ms 的处理延迟,如果不补偿,载体的速度乘以延迟时间就是几米到几十米的位置误差。常见做法是用 GPS 的 PPS 秒脉冲对齐前端采样时刻,在导航解算里用线性插值把 GPS 位置内插到 IMU 时刻:
function gps_pos_interp = interpolate_gps(gps_time, gps_pos, t_target) % 找到 t_target 附近的 GPS 时间戳 idx = find(gps_time <= t_target, 1, 'last'); if idx >= length(gps_time) || idx < 1 gps_pos_interp = gps_pos(idx, :); return; end % 线性插值位置 ratio = (t_target - gps_time(idx)) / (gps_time(idx+1) - gps_time(idx)); gps_pos_interp = gps_pos(idx, :) + ratio * (gps_pos(idx+1, :) - gps_pos(idx, :)); end线性插值对 1Hz 的 GPS 数据来说精度一般,5Hz 以上才勉强够用。更正规的方案是在滤波器的状态里增加 GPS 延迟时间参数,但那套结构更接近紧组合或半紧组合,松组合阶段做插值就够了。另一个容易被忽略的坑是 GPS 位置输出格式——有的接收机输出经纬度(WGS84),要先用椭球模型转成当地 NED 坐标,再进滤波器;如果拿经纬度直接减 NED 位置,量测更新永远不会收敛。
4. 参数整定与典型故障排查
4.1 从静态数据估计初始方差
拿到一个新 IMU,第一个动作是采一段静止数据,用 Allan 方差或简单统计方法估计零偏稳定性和噪声密度。没有 Allan 方差工具也可以用最笨的方法:静止 10 分钟,把陀螺和加速度计输出的均值和标准差算出来,均值就是零偏估计,标准差是噪声密度的下界:
% 静止数据中取前 1000 个样本 gyro_mean = mean(gyro_static); gyro_std = std(gyro_static); acc_mean = mean(acc_static); acc_std = std(acc_static); fprintf('陀螺零偏: [%.3f %.3f %.3f] deg/h\n', gyro_mean * 180 / pi * 3600); fprintf('陀螺噪声: [%.4f %.4f %.4f] deg/s\n', gyro_std * 180 / pi); fprintf('加计零偏: [%.4f %.4f %.4f] mg\n', acc_mean / 9.8 * 1000);注意这种静态统计得到的零偏包含了常值零偏和随机游走,真正进滤波器做状态估计的应该是随机游走部分——常值零偏应该在机械编排前扣除。P矩阵初值里的零偏方差应该用 Allan 方差曲线上的零偏不稳定性点,而不是静态数据的标准差,否则会低估零偏不确定性。
4.2 松组合发散的典型特征与定位
松组合系统跑出来的位置误差曲线如果出现以下几类特征,排查方向完全不同:
- 误差随时间线性增长并周期性跳变:大概率是 GPS 数据坐标系没对齐,或者位置用了经纬度,INS 用了 NED,量测更新在给滤波器注入错误信息。看
innovation序列,如果它呈锯齿状且均值不为零,基本就是这个原因。 - 误差很快发散且姿态异常:检查陀螺零偏初始值。便宜的 MEMS 陀螺零偏可能到每秒几十度,不扣掉直接进机械编排,姿态几秒就翻转了。把初始姿态误差设到 0.1 度以内,零偏初值设到静态估计值的附近。
- 滤波不发散但误差偏大:看是不是 GPS 和 IMU 时间不同步。低速场景不明显,高速场景下几百毫秒的延迟对应几米到几十米的误差,直接表现为位置波动变大。
- 协方差 P 收敛但位置误差不收敛:可观测性问题。载体长时间匀速直线飞行,零偏和姿态误差耦合,P 矩阵的收敛是自欺欺人——滤波以为自己估计得很好,实际误差状态根本分不开。让载体做机动即可验证:机动后误差是否显著减小。
画图查看时有个小技巧:把Z - H*x(新息)序列画出来,它的均值应当接近零,且标准差应该落在sqrt(H*P*H' + R)预估的范围内。如果新息持续偏大,说明 R 设置过小或模型错误,而不是简单调大 R 掩盖问题。
4.3 R 和 Q 的粗调顺序
参数整定的顺序通常是:先调 R(量测噪声),再调 Q(过程噪声)。R 可以根据 GPS 接收机的 CEP 值估算,水平 2~3 米,垂直 4~6 米,这类信息手册里都有。调 Q 时,先固定 R,调整陀螺和加计的噪声密度,看位置误差的平稳段是否收敛到合理值;如果位置误差震荡幅度大,说明 Q 偏大或 R 偏小,滤波响应过激;如果位置误差缓慢漂移再被拉回,说明 Q 偏小或 R 偏大,滤波反应迟钝。松组合里 Q 的数值级对结果影响没有紧组合敏感,调一两个数量级通常就能看到明显变化。
5. GPS 中断处理与自适应滤波技巧
5.1 量测更新门控
低成本的 GPS 模块在城市峡谷或树荫下会频繁丢星,丢星期间gps_valid信号拉低,此时直接跳过量测更新即可。但直接跳过会带来一个问题:GPS 恢复的瞬间,新息可能非常大,滤波器会被一个异常观测拉偏。常见做法是做新息门控:
% 新息门控:超过 3 倍标准差就跳过量测更新 innov = z - z_ins; S = H * P_pred * H' + R; if innov' * inv(S) * innov < 9 % 通过检测,执行量测更新 else % 可能发生跳变,丢弃该观测,继续纯惯性递推 warning('GPS 新息超限,已丢弃: %.2f m', norm(innov)); endinnov' * inv(S) * innov服从卡方分布,自由度为观测维数,阈值取卡方分布 99% 分位点(3 维时约为 11.3),比固定 3 倍标准差更严格也更合理。这种门控对 GPS 多路径干扰特别有效,因为多路径误差可以到几十米,远超正常噪声水平。
5.2 基于新息的自适应 R
GPS 定位精度在不同环境变化很大:开阔场地 2 米,城市峡谷 10 米,树荫下 20 米。固定 R 会导致滤波在 GPS 精度差的区域过度信任观测,把多路径误差当成真实位置拉进去。自适应 R 的思路是统计一段时间窗口内的新息方差,动态调整 R:
% 滑动窗口内估计新息方差 window_size = 50; innov_history = [innov_history, innovation(1:3)]; if size(innov_history, 2) > window_size innov_history(:, 1) = []; end R_estimated = var(innov_history, 0, 2) / (H * P_pred * H' + eye(3)); R = max(R_min, min(R_estimated, R_max));这段代码的思路是:新息的理论协方差是H*P*H' + R,实际统计的新息方差如果明显大于理论值,说明 R 被低估,放大 R;如果接近理论值,说明 R 合理。注意对 R 做上下限截断,防止自适应发散——下限取 GPS 模块手册标称精度,上限取 3~5 倍标称精度即可。自适应 R 的代价是引入了非线性,理论上破坏了卡尔曼滤波的最优性,但工程上现在的组合导航产品基本都这么干,鲁棒性远收益大于理论损失。
5.3 从松组合到紧组合的演进路径
做完松组合再回头看紧组合,会发现松组合的很多代码可以直接复用:误差状态模型基本不变,变化的是观测方程从位置/速度换成了伪距、伪距率或载波相位。紧组合的核心优势是 GPS 卫星少于 4 颗时仍可工作,但代价是滤波状态里要增加接收机钟差、钟漂,观测模型涉及卫星位置计算和电离层对流层修正,代码复杂度翻倍。松组合跑通之后往紧组合走,建议分三步:先把量测更新从位置扩展到位置+速度,再改成伪距观测,最后再加载波相位平滑。每一步都能独立测试,避免一次性引入过多变量。对于低成本导航项目,松组合永远是最合适的起步方案——先让系统转起来,再谈精度。
本文还有配套的精品资源,点击获取