多传感器融合与运动状态估计 - 第4节

一、为什么需要扩展卡尔曼滤波?

做自动驾驶定位的朋友都知道,现实世界没有完美的线性系统。

标准的卡尔曼滤波,说白了只能处理线性问题。比如速度乘以时间等于位移,这种关系很直白。但我们的车辆运动呢?航向角变化、转弯半径、加速度非线性...这些都不是简单的线性关系能描述的。

我记得刚入行那会儿,天真地以为直接用标准KF就能搞定定位。结果呢?车辆一转弯,估计位置直接飘到马路牙子上去了。嗯,那次教训挺深刻的。

所以就有了扩展卡尔曼滤波——EKF。它的核心思想其实很朴素:把非线性问题,在局部近似成线性问题来处理。

一句话总结:EKF = 标准KF + 线性化近似。线性化的工具就是雅可比矩阵。

二、非线性系统长什么样?

我们先看看自动驾驶中典型的非线性系统。

2.1 状态方程的非线性

车辆的运动模型,通常写成这样:

x_k = f(x_{k-1}, u_k) + w_k

这里的f()不是矩阵乘法,而是一个非线性函数。比如CTRV(恒定转率和速度)模型:

状态向量: [x, y, v, ψ, ψ_dot]
其中:
  x, y   = 位置
  v      = 速度
  ψ      = 航向角
  ψ_dot  = 角速度

预测方程:
  x_k = x_{k-1} + (v/ψ_dot) * [sin(ψ + ψ_dot*Δt) - sin(ψ)]
  y_k = y_{k-1} + (v/ψ_dot) * [-cos(ψ + ψ_dot*Δt) + cos(ψ)]
  ψ_k = ψ_{k-1} + ψ_dot * Δt
  ...

你看,这里面有sin、cos,还有除法。标准KF的线性假设完全失效。

2.2 观测方程的非线性

传感器观测也是非线性的。比如毫米波雷达测量目标:

观测向量: [ρ, θ, ρ_dot]
其中:
  ρ      = 距离
  θ      = 方位角
  ρ_dot  = 距离变化率

观测方程:
  ρ = sqrt(px² + py²)
  θ = atan2(py, px)
  ρ_dot = (px*vx + py*vy) / ρ

又是开方、又是反正切。这些操作在标准KF里根本没法直接处理。

我的经验:在实际项目中,80%的传感器融合问题都涉及非线性。如果你发现标准KF结果不对劲,先检查一下模型是不是线性的。

三、雅可比矩阵:线性化的核心工具

EKF怎么处理非线性?答案就是泰勒展开。

我们把非线性函数f(x)在某个点附近展开,只取一阶项:

f(x) ≈ f(x₀) + J * (x - x₀)

这里的J就是雅可比矩阵。它本质上是一个偏导数矩阵。

3.1 雅可比矩阵长什么样?

假设状态向量有n维,观测向量有m维:

雅可比矩阵 J 的形状是 m × n

J[i][j] = ∂f_i / ∂x_j

也就是第i个输出对第j个输入的偏导数

举个例子。对于上面提到的雷达观测方程:

观测函数 h(x):
  ρ = sqrt(px² + py²)
  θ = atan2(py, px)
  ρ_dot = (px*vx + py*vy) / ρ

雅可比矩阵 H (3×4):
  H[0][0] = ∂ρ/∂px = px / ρ
  H[0][1] = ∂ρ/∂py = py / ρ
  H[0][2] = ∂ρ/∂vx = 0
  H[0][3] = ∂ρ/∂vy = 0

  H[1][0] = ∂θ/∂px = -py / ρ²
  H[1][1] = ∂θ/∂py = px / ρ²
  H[1][2] = ∂θ/∂vx = 0
  H[1][3] = ∂θ/∂vy = 0

  H[2][0] = ∂ρ_dot/∂px = (vx*ρ² - px*(px*vx+py*vy)) / ρ³
  ...(后面略)

注意:雅可比矩阵必须在当前状态估计值处计算。每次预测和更新时都要重新算,不能复用上一次的值。我曾经因为这个bug排查了整整两天...

3.2 代码实现示例

下面是一个简化的EKF预测步骤实现:

def predict_ekf(x, P, u, dt):
    """
    EKF预测步骤
    x: 当前状态 (4x1)
    P: 协方差矩阵 (4x4)
    u: 控制输入
    dt: 时间间隔
    """
    # 1. 状态预测(非线性)
    x_pred = motion_model(x, u, dt)
    
    # 2. 计算雅可比矩阵 F
    F = compute_jacobian_F(x, u, dt)
    
    # 3. 协方差预测(线性化后)
    Q = process_noise_matrix(dt)
    P_pred = F @ P @ F.T + Q
    
    return x_pred, P_pred

def compute_jacobian_F(x, u, dt):
    """
    CTRV模型的雅可比矩阵
    """
    v = x[2]      # 速度
    psi = x[3]    # 航向角
    psi_dot = x[4] # 角速度
    
    F = np.eye(5)
    
    if abs(psi_dot) > 0.001:
        # 非零角速度情况
        F[0, 2] = (np.sin(psi + psi_dot*dt) - np.sin(psi)) / psi_dot
        F[0, 3] = (v/psi_dot) * (np.cos(psi + psi_dot*dt) - np.cos(psi))
        # ... 其他偏导数
    else:
        # 近似直线运动
        F[0, 2] = dt * np.cos(psi)
        F[0, 3] = -v * dt * np.sin(psi)
    
    return F

四、EKF在定位中的完整流程

搞清楚了雅可比矩阵,EKF的流程其实和标准KF很像,只是多了线性化这一步。

4.1 核心步骤

  1. 初始化:设定初始状态x₀和协方差P₀
  2. 预测:用非线性模型预测状态,用雅可比矩阵传播协方差
  3. 更新:计算观测雅可比矩阵,计算卡尔曼增益,更新状态和协方差
  4. 重复:回到步骤2

4.2 更新步骤详解

def update_ekf(x_pred, P_pred, z, R):
    """
    EKF更新步骤
    z: 实际观测值
    R: 观测噪声协方差
    """
    # 1. 计算观测雅可比矩阵 H
    H = compute_jacobian_H(x_pred)
    
    # 2. 预测观测值(非线性)
    z_pred = observation_model(x_pred)
    
    # 3. 计算卡尔曼增益
    S = H @ P_pred @ H.T + R
    K = P_pred @ H.T @ np.linalg.inv(S)
    
    # 4. 更新状态
    y = z - z_pred  # 残差
    x_updated = x_pred + K @ y
    
    # 5. 更新协方差
    I = np.eye(len(x_pred))
    P_updated = (I - K @ H) @ P_pred
    
    return x_updated, P_updated

关键点:EKF的更新步骤中,残差y的计算用的是真实观测值减去预测观测值,而不是减去H*x_pred。因为观测模型是非线性的,直接用H*x_pred会引入误差。

五、EKF的局限性

说实话,EKF虽然好用,但也不是万能的。我在项目中踩过不少坑:

  • 线性化误差:泰勒展开只取一阶项,如果非线性程度很高,误差会累积
  • 雅可比计算复杂:对于高维系统,手动推导偏导数容易出错
  • 对初始值敏感:如果初始状态估计偏差太大,线性化点选得不好,滤波器可能发散

避坑指南:我曾经在一个项目中,因为车辆急转弯导致航向角变化剧烈,EKF直接炸了。后来加了角度归一化处理,并且在角速度大的时候降低预测步长,才稳定下来。

六、本章小结

EKF是处理非线性系统的经典方法。它的核心就两件事:

  • 用雅可比矩阵把非线性模型线性化
  • 在标准KF框架下完成预测和更新

虽然现在有UKF、粒子滤波等更先进的方法,但EKF凭借计算量小、实现简单的优势,仍然是工业界的主流选择。我个人的建议是:先掌握EKF,再根据实际需求考虑更复杂的滤波器。

嗯,这一章的内容就到这里。记住,纸上得来终觉浅,建议你动手写一个完整的EKF定位程序,跑一跑真实数据,感受一下非线性带来的影响。

EKF定位核心逻辑流程图 初始状态 x₀, P₀ 预测步骤 非线性状态预测: x_pred = f(x, u) 雅可比矩阵F → 协方差预测: P_pred = F·P·Fᵀ + Q 有观测? 是 更新步骤 观测雅可比H → 卡尔曼增益K 状态更新: x = x_pred + K·(z - h(x_pred)) 否 输出 x, P

可可精品资料库