1. DVL/INS 松耦合滤波状态空间建模与实现

为什么选 15 维误差状态,而不是直接估导航量

松耦合的核心是:INS 独立做机械编排,输出位置、速度、姿态;DVL 只提供速度观测。滤波器不直接估计“绝对速度/位置”,而是估计INS 机械编排的误差,再用误差去修正 INS 输出。这样做的原因是:INS 误差在小失准角、短时间条件下近似线性,卡尔曼滤波的线性假设成立;而直接估计绝对导航量会引入大范围非线性与可观性耦合。

本例采用“位置误差 + 速度误差 + 姿态误差 + 加速度计零偏 + 陀螺零偏”的 15 维误差状态:

δx = [ δp(3) , δv(3) , φ(3) , b_a(3) , b_g(3) ]ᵀ

各状态物理含义:δp 为导航系(n 系,东北天)位置误差;δv 为 n 系速度误差;φ 为 n 系下的小失准角;b_a、b_g 为载体系(b 系)下加速度计与陀螺零偏。误差状态定义为“真实值 = INS 解算值 − 误差”,因此修正时用减法。

连续时间误差方程与离散化

忽略地球自转与曲率的高阶项(短时、局部作业的常用简化),误差传播方程可写为:

δṗ = δv
δv̇ = −[f^n ×] φ + C_b^n · b_a + w_v
φ̇  = −C_b^n · b_g + w_φ
ḃ_a = w_ba ,  ḃ_g = w_bg

其中 f^n 为比力在 n 系下的投影,[·×] 为反对称矩阵,C_b^n 为姿态矩阵。系统矩阵 F 由上述方程对 δx 求偏导得到,噪声矩阵 G 把过程噪声映射到状态。离散化采用一阶近似:

Φ ≈ I + F·Δt ,   Q_d ≈ G·Q_c·Gᵀ·Δt

过程噪声强度按传感器指标设定:加速度计噪声密度 σ_a、陀螺噪声密度 σ_g,以及零偏随机游走 σ_ba、σ_bg。它们决定 Q_d 的量级,直接影响滤波器对 INS 的信任程度。

观测方程:DVL 只约束速度

DVL 测得的是载体系速度 v_b^DVL,需用 INS 姿态投影到 n 系:v_n^DVL = C_b^n · v_b^DVL。观测残差为 INS 速度减 DVL 投影速度:

z = v_n^INS − C_b^n · v_b^DVL = H·δx + n_DVL
H = [ 0(3×3)  I(3×3)  0(3×3)  0(3×3)  0(3×3) ]

H 只有速度块为单位阵,说明 DVL 观测直接可观的是速度误差;姿态误差通过 δv 方程中的 −[f^n×]φ 项间接可观,零偏则通过更长时间的速度/姿态误差积累间接可观。这正是松耦合可观性结构的本质,也是本例必须把 H 写清楚的原因。

INS 机械编排 IMU → p, v, C_b^n 输出导航解(含误差) DVL 速度观测 v_b^DVL(载体系) 经 C_b^n 投影到 n 系 观测残差 z v_n^INS − C_b^n·v_b^DVL H = [0 I 0 0 0] 15 维误差 KF 预测:δx⁻ = Φ·δx P⁻ = ΦPΦᵀ + Q_d 增益:K = P⁻Hᵀ(HP⁻Hᵀ+R)⁻¹ 更新:δx = K·z P = (I−KH)P⁻ 状态:δp δv φ b_a b_g 误差反馈修正 p ← p − δp,v ← v − δv C ← C·(I−[φ×]),零偏回写 v_n^INS v_n^DVL z δx 闭环修正 INS 解 可观性 δv:直接 φ:经 f^n 耦合 b_a:经 φ 间接 b_g:弱可观 δp:无直接观测 靠耦合收敛

基础滤波循环实现

下面给出可直接运行的 Python 实现,包含状态转移矩阵构造、过程噪声离散化、观测更新与误差反馈。示例参数为教学假设值,运行结果为按给定输入直接推出的预期现象,不代表实测。

import numpy as np

def skew(v):
    return np.array([[0, -v[2], v[1]],
                     [v[2], 0, -v[0]],
                     [-v[1], v[0], 0]])

def build_F(f_n, C_bn, dt):
    """15 维误差状态转移矩阵,一阶离散化"""
    F = np.zeros((15, 15))
    I3 = np.eye(3)
    F[0:3, 3:6] = I3                      # δṗ = δv
    F[3:6, 6:9] = -skew(f_n)              # δv̇ 中姿态误差项
    F[3:6, 9:12] = C_bn                   # 加速度计零偏
    F[6:9, 12:15] = -C_bn                 # 陀螺零偏
    return np.eye(15) + F * dt

def build_Qd(C_bn, dt, sa=0.02, sg=0.002, sba=1e-4, sbg=1e-5):
    """过程噪声:速度/姿态由传感器噪声密度驱动,零偏为随机游走"""
    G = np.zeros((15, 12))
    G[3:6, 0:3] = C_bn
    G[6:9, 3:6] = C_bn
    G[9:12, 6:9] = np.eye(3)
    G[12:15, 9:12] = np.eye(3)
    q = np.diag([sa**2]*3 + [sg**2]*3 + [sba**2]*3 + [sbg**2]*3)
    return G @ q @ G.T * dt

def kf_step(dx, P, f_n, C_bn, v_ins, v_dvl_b, R, dt):
    Phi = build_F(f_n, C_bn, dt)
    Qd = build_Qd(C_bn, dt)
    # 预测
    dx = Phi @ dx
    P = Phi @ P @ Phi.T + Qd
    # 观测:DVL 载体系速度投影到 n 系
    v_dvl_n = C_bn @ v_dvl_b
    H = np.zeros((3, 15)); H[0:3, 3:6] = np.eye(3)
    z = v_ins - v_dvl_n
    S = H @ P @ H.T + R
    K = P @ H.T @ np.linalg.inv(S)
    # 更新
    dx = dx + K @ (z - H @ dx)
    P = (np.eye(15) - K @ H) @ P
    return dx, P, z

# ---- 教学示例:单步验证 ----
dt = 0.05
C_bn = np.eye(3)                       # 假设载体系与导航系对齐
f_n = np.array([0.0, 0.0, 9.81])       # 静止时比力近似为重力反作用
P = np.diag([1.0]*3 + [0.25]*3 + [0.01]*3 + [1e-4]*3 + [1e-6]*3)
dx = np.zeros(15)
R = np.diag([0.01**2]*3)               # DVL 速度噪声方差 (m/s)^2
v_ins = np.array([0.10, -0.05, 0.02])  # INS 速度含误差
v_dvl_b = np.array([0.0, 0.0, 0.0])    # DVL 静止输出

dx, P, z = kf_step(dx, P, f_n, C_bn, v_ins, v_dvl_b, R, dt)
print("残差 z =", np.round(z, 4))
print("δv 估计 =", np.round(dx[3:6], 4))
print("P_vv 对角 =", np.round(np.diag(P)[3:6], 5))

预期现象:残差 z 等于 INS 速度与 DVL 投影速度之差,即 [0.10, −0.05, 0.02];由于 H 只作用于速度块,δv 估计会向残差方向靠拢,P 的速度对角元在更新后下降。若把 R 调大,δv 估计更保守、P 下降更慢;把 R 调小则更信任 DVL。这正是后续调参的抓手。

建模检查三件事:① 误差定义方向统一(本例为“INS 减真值”),反馈时用减法;② H 的列块位置必须与状态排列一致,否则速度观测会错误地修正姿态或零偏;③ Q_d 与 R 的量纲要匹配,速度噪声用 (m/s)²,零偏随机游走用 (m/s²)²·s 与 (rad/s)²·s。
本例忽略了地球自转、曲率半径与杆臂,仅适用于短时、低速、局部作业的入门验证。若直接用于长航时或高动态场景,需在 F 中补入舒拉项与哥氏项,并把 DVL 杆臂速度纳入观测模型,否则残差会出现系统性偏差。