简介:本资源面向本硕博等教研学习人群,提供基于UKF(无迹卡尔曼滤波)的6自由度火箭飞行预测跟踪与状态估计完整MATLAB实现,解决利用加速计、陀螺仪和GPS多源数据融合进行位置、速度及姿态估计的问题,适合导航制导、状态估计方向的中高级学习者。压缩包共8个文件,约188KB,包含6个m脚本文件、1个txt说明文档和1个avi操作录像,脚本涵盖主运行入口、仿真、估计、动力学方程及误差与真值绘图等模块,txt提供辅助说明,视频演示完整操作流程。已有399人学习下载。读者可获取可直接运行的工程代码,结合录屏快速理解UKF在火箭6自由度模型中的预测与更新流程,掌握多传感器融合估计的实现思路与误差对比方法,并借助绘图脚本直观验证位置、速度和姿态的估计精度,为相关课题研究或课程设计提供可复用的参考方案。
1. 从一条加速度计曲线说起:UKF 在 6 自由度火箭状态估计里到底解决什么问题
火箭上升段的状态估计有个反直觉的地方:GPS 明明能给位置,陀螺仪明明能给角速度,但把这两路数据直接拼起来用,姿态角会在几十秒内漂到没法看。原因不复杂——加速度计测的是比力,不是惯性加速度,要减掉重力还得知道当前姿态;而姿态又依赖角速度积分,角速度积分又依赖零偏估计。这是一个典型的非线性耦合系统,卡尔曼滤波的线性化假设在这里站不住。
无迹卡尔曼滤波(UKF)的思路是不对非线性函数做雅可比线性化,而是选一组确定性采样点(Sigma 点)穿过非线性函数,用变换后的点集去逼近均值和协方差。对 6 自由度火箭来说,状态量通常取位置、速度、姿态四元数、陀螺零偏,量测量是 GPS 位置/速度和加速度计比力。这套东西适合做火箭上升段、再入段或者任何高动态飞行器的组合导航,也适合做无人机、导弹的同类问题。下面按建模、实现、调参、排错的顺序把它讲透。
2. 6 自由度火箭动力学建模与 UKF 状态量选取
2.1 状态向量怎么定:15 维还是 16 维
火箭 6 自由度指三轴平动加三轴转动。工程上最常用的状态向量是 16 维:
x = [p(3), v(3), q(4), bg(3), ba(3)]p 是 NED 或 ECEF 下的位置,v 是速度,q 是机体到导航系的姿态四元数,bg 是陀螺零偏,ba 是加速度计零偏。四元数用 4 维表示但只有 3 个自由度,协方差是 15×15,这个细节后面误差状态处理时会讲。
选四元数而不是欧拉角,是因为火箭俯仰角会跨过 ±90°,欧拉角在万向节死锁附近数值会炸。选误差状态(error-state)而不是直接状态,是因为四元数归一化约束会让协方差矩阵奇异。常见做法是:名义状态用四元数传播,误差状态用 3 维旋转向量,UKF 在 15 维误差空间里跑。
2.2 连续时间动力学方程
导航系取 NED,重力模型用简单的常值加高度修正即可,火箭上升段时间短,J2 项影响有限。
import numpy as np def dynamics(x, u, dt, g=9.80665): """ x: [p(3), v(3), q(4), bg(3), ba(3)] 16维 u: [omega_m(3), acc_m(3)] 陀螺和加速度计原始测量 dt: 积分步长 """ p = x[0:3]; v = x[3:6]; q = x[6:10] bg = x[10:13]; ba = x[13:16] # 去零偏 omega = u[0:3] - bg acc_b = u[3:6] - ba # 四元数转旋转矩阵 (机体->导航) R = quat_to_rot(q) # 比力转到导航系,减重力 a_nav = R @ acc_b + np.array([0, 0, g]) # 四元数微分 dq = 0.5 * q ⊗ [0, omega] Omega = np.array([[0, -omega[0], -omega[1], -omega[2]], [omega[0], 0, omega[2], -omega[1]], [omega[1], -omega[2], 0, omega[0]], [omega[2], omega[1], -omega[0], 0]]) dq = 0.5 * Omega @ q # 零偏建模为随机游走,均值为0 dx = np.zeros(16) dx[0:3] = v dx[3:6] = a_nav dx[6:10] = dq return x + dx * dt这段代码里quat_to_rot把四元数转成 3×3 旋转矩阵,Omega是四元数乘法的矩阵形式。注意重力项加在导航系 z 轴向下为正,NED 下重力是 +g。零偏用随机游走建模,过程噪声 Q 里给对应的小方差。
2.3 量测方程:GPS 和加速度计怎么进 UKF
量测分两路。GPS 给位置和速度,直接线性:
z_gps = [p; v] + noise加速度计给的是机体比力,量测方程是非线性的:
z_acc = R(q)^T * (a_nav - g_nav) + ba + noise这里 a_nav 是导航系真实加速度,实际实现时用上一时刻速度差分近似,或者干脆把加速度计只用于姿态观测。很多工程实现里加速度计不直接进 UKF 量测,而是用来做姿态初始化或者辅助重力对齐,因为高动态下比力里混着振动和推力噪声,直接进滤波会污染协方差。
| 量测源 | 维度 | 更新频率 | 噪声量级(典型) |
|---|---|---|---|
| GPS 位置 | 3 | 5–20 Hz | 水平 1.5 m,垂直 3 m |
| GPS 速度 | 3 | 5–20 Hz | 0.1 m/s |
| 加速度计 | 3 | 100–1000 Hz | 0.05–0.5 m/s² |
| 陀螺仪 | 3 | 100–1000 Hz | 0.001–0.01 rad/s |
提示:GPS 和 IMU 频率差一个数量级,UKF 预测步按 IMU 频率跑,量测步按 GPS 到达时刻触发,中间用零阶保持处理 IMU 数据。
3. UKF 的 Sigma 点生成、预测与更新实现
3.1 无迹变换的三个参数怎么设
UKF 核心是无迹变换。给定 n 维状态和协方差 P,生成 2n+1 个 Sigma 点:
def sigma_points(x, P, alpha=1e-3, beta=2.0, kappa=0.0): n = len(x) lam = alpha**2 * (n + kappa) - n # 矩阵平方根,用 Cholesky S = np.linalg.cholesky((n + lam) * P) pts = np.zeros((2*n + 1, n)) pts[0] = x for i in range(n): pts[i+1] = x + S[:, i] pts[n+i+1] = x - S[:, i] # 均值权重和协方差权重 Wm = np.full(2*n+1, 1.0 / (2*(n+lam))) Wc = Wm.copy() Wm[0] = lam / (n + lam) Wc[0] = lam / (n + lam) + (1 - alpha**2 + beta) return pts, Wm, Wcalpha控制 Sigma 点离均值的散布,通常取 1e-3 到 1e-1,太小会让 Cholesky 数值不稳,太大会让高阶项误差变大。beta对高斯分布取 2 最优,它把先验的峰度信息带进协方差权重。kappa一般取 0 或 3-n,n 是状态维数。15 维误差状态时,kappa 取 0 就行。
3.2 预测步:Sigma 点穿过动力学
def ukf_predict(x, P, u, dt, Q, alpha=1e-3, beta=2.0, kappa=0.0): n = len(x) pts, Wm, Wc = sigma_points(x, P, alpha, beta, kappa) # 每个 Sigma 点传播 pts_pred = np.array([dynamics(pt, u, dt) for pt in pts]) # 加权均值 x_pred = np.sum(Wm[:, None] * pts_pred, axis=0) # 四元数归一化 x_pred[6:10] /= np.linalg.norm(x_pred[6:10]) # 加权协方差 + 过程噪声 P_pred = np.zeros((n, n)) for i in range(2*n+1): d = pts_pred[i] - x_pred P_pred += Wc[i] * np.outer(d, d) P_pred += Q return x_pred, P_pred这里有个坑:四元数在加权平均后必须重新归一化,否则协方差会慢慢发散。更严谨的做法是在误差状态空间做加权,名义四元数单独传播。过程噪声 Q 按连续时间谱密度乘 dt 离散化,陀螺零偏和加速度计零偏对应的 Q 块给 1e-8 到 1e-6 量级。
3.3 更新步:GPS 量测进来怎么算卡尔曼增益
def ukf_update(x, P, z, h_func, R, alpha=1e-3, beta=2.0, kappa=0.0): n = len(x) pts, Wm, Wc = sigma_points(x, P, alpha, beta, kappa) # 量测传播 z_pts = np.array([h_func(pt) for pt in pts]) z_pred = np.sum(Wm[:, None] * z_pts, axis=0) # 量测协方差和交叉协方差 m = len(z) Pzz = np.zeros((m, m)); Pxz = np.zeros((n, m)) for i in range(2*n+1): dz = z_pts[i] - z_pred dx = pts[i] - x Pzz += Wc[i] * np.outer(dz, dz) Pxz += Wc[i] * np.outer(dx, dz) Pzz += R K = Pxz @ np.linalg.inv(Pzz) x_upd = x + K @ (z - z_pred) P_upd = P - K @ Pzz @ K.T x_upd[6:10] /= np.linalg.norm(x_upd[6:10]) return x_upd, P_updh_func对 GPS 就是取状态里的位置和速度,对加速度计就是前面那个非线性量测方程。R是量测噪声协方差,GPS 位置给对角 2.25、9,速度给 0.01。卡尔曼增益 K 的维度是 n×m,更新后同样要归一化四元数。
注意:Pzz 求逆前检查条件数,GPS 丢星时 R 会变得很大,Pzz 接近奇异,用
np.linalg.pinv或者加对角正则更稳。
4. 用加速度计、陀螺仪和 GPS 数据跑通完整流程
4.1 数据对齐与时间戳处理
三路数据时间戳不同步是常态。IMU 通常 200 Hz 以上,GPS 5–20 Hz。做法是把 GPS 时间戳作为量测触发点,IMU 数据缓存在队列里,每次 GPS 到达时把两帧之间的 IMU 数据依次做预测。
def run_filter(imu_data, gps_data, x0, P0, Q, R_gps): x, P = x0.copy(), P0.copy() gps_idx = 0 for k in range(len(imu_data)): t_imu = imu_data[k]['t'] u = np.hstack([imu_data[k]['gyro'], imu_data[k]['acc']]) dt = t_imu - imu_data[k-1]['t'] if k > 0 else 0.005 x, P = ukf_predict(x, P, u, dt, Q) # 检查是否有 GPS 量测落在当前时刻 if gps_idx < len(gps_data) and gps_data[gps_idx]['t'] <= t_imu: z = np.hstack([gps_data[gps_idx]['pos'], gps_data[gps_idx]['vel']]) x, P = ukf_update(x, P, z, h_gps, R_gps) gps_idx += 1 return x, Pdt用相邻 IMU 时间戳差分,比固定步长更准。GPS 量测用<=判断,保证不丢帧。如果 GPS 有延迟,可以在时间戳上加一个固定偏移补偿。
4.2 初始对准:静止段估零偏和初始姿态
火箭起飞前有一段静止或低速段,用这段数据做初始对准。陀螺零偏取静止段均值,加速度计零偏同理。初始姿态用加速度计测的重力方向反推:
def init_attitude(acc_static): # 静止时 acc 测的是 -g 在机体的投影 g_b = -acc_static / np.linalg.norm(acc_static) # 构造从机体到导航的旋转,使 g_b 对齐 [0,0,1] v1 = g_b v2 = np.array([0, 0, 1.0]) axis = np.cross(v1, v2) if np.linalg.norm(axis) < 1e-8: return np.array([1, 0, 0, 0]) axis /= np.linalg.norm(axis) angle = np.arccos(np.clip(np.dot(v1, v2), -1, 1)) return np.hstack([np.cos(angle/2), axis * np.sin(angle/2)])初始协方差 P0 位置给 10 m²,速度给 1 m²/s²,姿态给 0.1 rad²,零偏给静止段方差。P0 给太小会让滤波器对初始误差不敏感,给太大会让收敛慢。
4.3 完整调用与结果验证
# 初始化 x0 = np.zeros(16); x0[6:10] = init_attitude(acc_static) x0[10:13] = gyro_bias_static x0[13:16] = acc_bias_static P0 = np.diag([10]*3 + [1]*3 + [0.01]*4 + [1e-4]*3 + [1e-3]*3) Q = np.diag([0]*3 + [0.01]*3 + [1e-6]*4 + [1e-8]*3 + [1e-6]*3) R_gps = np.diag([2.25]*3 + [0.01]*3) x_est, P_est = run_filter(imu_data, gps_data, x0, P0, Q, R_gps)验证方法:把估计轨迹和 GPS 原始轨迹画在一起看位置误差,把估计姿态和陀螺积分姿态对比看漂移。位置 RMSE 在 GPS 噪声量级以内算正常,姿态在无外部观测时靠加速度计重力对齐能压住横滚和俯仰,偏航会慢慢漂,这是可观测性问题,不是滤波器 bug。
| 验证项 | 正常范围 | 异常表现 | 排查方向 |
|---|---|---|---|
| 位置 RMSE | < 3 m | 持续增大 | Q/R 比例、GPS 延迟 |
| 速度 RMSE | < 0.3 m/s | 振荡 | 加速度计零偏未估 |
| 横滚/俯仰 | < 2° | 缓慢漂移 | 重力模型、初始对准 |
| 偏航 | 无观测时漂移 | 快速发散 | 正常,需磁力计辅助 |
| 新息 | 白噪声 | 有偏或相关 | 量测模型错、时间戳错 |
5. 调参、发散排查与高动态下的几个实用技巧
5.1 过程噪声 Q 和量测噪声 R 的整定顺序
先定 R,再调 Q。R 从传感器手册拿,GPS 位置方差取水平精度的平方,速度取 0.1²。Q 的整定看新息序列:新息均值不为零说明量测模型有偏,新息自相关说明 Q 太小。经验上陀螺零偏的 Q 给 1e-8,加速度计零偏给 1e-6,姿态过程噪声给 1e-6。Q 太大会让滤波器过度信任 IMU,姿态跟着陀螺漂;Q 太小会让 GPS 更新时修正过猛,轨迹出现台阶。
5.2 协方差发散和 Cholesky 失败的三种处理
Sigma 点生成时 Cholesky 要求 P 正定。数值误差累积会让 P 失去正定性,报LinAlgError。处理办法:一是每次更新后做P = (P + P.T) / 2强制对称;二是加一个小的对角正则P += 1e-9 * np.eye(n);三是改用平方根 UKF,直接传播协方差的 Cholesky 因子,数值稳定性更好。高动态下推荐平方根版本,代价是代码复杂度上升。
5.3 高动态段加速度计不进量测的取舍
火箭推力段振动大,加速度计输出里混着结构振动和推力偏心,直接进 UKF 量测会让姿态估计抖。常见做法是加速度计只用于初始对准和低速段的重力观测,高速段靠陀螺积分加 GPS 位置差分修正姿态。如果非要用,先做低通滤波,截止频率设在 10–20 Hz,再把滤波后的比力进量测,R 给大一点。
5.4 用新息卡方检验做量测异常剔除
GPS 多路径或者丢星时,量测会跳变。用新息卡方检验剔除异常点:
def chi2_gate(z, z_pred, Pzz, dof=6, threshold=16.81): # 6 自由度,99% 置信度阈值约 16.81 innovation = z - z_pred d2 = innovation.T @ np.linalg.inv(Pzz) @ innovation return d2 < thresholddof是量测维数,GPS 位置加速度共 6 维。阈值查卡方分布表,99% 对应 16.81,95% 对应 12.59。检验不通过就跳过这次更新,只做预测。这个技巧在 GPS 信号遮挡场景下能明显减少轨迹跳变。
5.5 姿态估计的可观测性边界
6 自由度火箭在无磁力计、无星敏感器时,偏航角不可观测。加速度计只能观测横滚和俯仰,GPS 位置差分能观测速度方向但观测不到绕速度轴的旋转。工程上要么加磁力计,要么在发射前用已知航向做初始对准,飞行中接受偏航缓慢漂移。如果任务要求偏航精度,必须引入外部航向观测,这是物理限制,调参解决不了。
本文还有配套的精品资源,点击获取