第4章 扩展卡尔曼滤波(EKF):非线性系统建模与目标跟踪实战

各位同学,欢迎来到第四章。说实话,卡尔曼滤波(KF)在工程界已经封神了——只要系统是线性的、噪声是高斯的,它几乎是无敌的。但现实世界哪有那么多线性系统?你想想看,雷达测距、视觉SLAM、无人机姿态估计,哪个不是非线性的?

我刚开始做目标跟踪那会儿,天真地以为套个标准KF就能搞定。结果呢?跟踪误差大得离谱,目标稍微转个弯就直接跟丢。后来我才明白——线性化才是关键。扩展卡尔曼滤波(EKF),说白了就是给KF装了个“线性化眼镜”,让它能处理非线性问题。

核心思想:EKF通过对非线性函数进行一阶泰勒展开(雅可比矩阵),将非线性系统近似为线性系统,然后套用标准KF的框架。

4.1 非线性系统建模

我们先看一个典型场景:雷达跟踪匀速转弯的目标。状态向量通常包含位置和速度:

x = [px, py, vx, vy]^T

但雷达的观测是距离r和方位角θ,它们与状态的关系是非线性的:

r = sqrt(px^2 + py^2)
θ = atan2(py, px)

你看,这里出现了开方和反正切。标准KF要求观测方程是线性的(即z = Hx),但这里显然不是。怎么办?

我个人的习惯是:先写出完整的非线性模型,再考虑线性化。系统模型分两部分:

  • 状态方程(运动模型):x_k = f(x_{k-1}, u_k) + w_k
  • 观测方程(测量模型):z_k = h(x_k) + v_k

其中f和h都是非线性函数,w_k和v_k是高斯噪声。嗯,这里要注意:噪声假设仍然是高斯的,只是函数关系是非线性的。

避坑指南:我曾经在项目中把非线性系统直接套用标准KF,结果滤波器发散。后来发现,当非线性程度较强时(比如目标做急转弯),一阶线性化可能不够,得考虑二阶EKF或无迹卡尔曼滤波(UKF)。

4.2 EKF线性化过程

EKF的核心就两步:预测和更新。但每一步都需要计算雅可比矩阵。

4.2.1 预测步骤

首先,用非线性函数f预测状态:

x_pred = f(x_est, u)

然后,计算状态转移矩阵F(即f对x的雅可比矩阵):

F = ∂f/∂x | x = x_est

协方差预测:

P_pred = F * P_est * F^T + Q

这里Q是过程噪声协方差。我刚开始学的时候总搞混:F是时变的,每个时刻都要重新计算,不像标准KF里F是常数矩阵。

4.2.2 更新步骤

计算观测矩阵H(即h对x的雅可比矩阵):

H = ∂h/∂x | x = x_pred

然后就是标准KF的更新公式:

K = P_pred * H^T * (H * P_pred * H^T + R)^(-1)
x_est = x_pred + K * (z - h(x_pred))
P_est = (I - K * H) * P_pred

你看,除了F和H需要实时计算,其他步骤和标准KF一模一样。说白了,EKF就是用雅可比矩阵把非线性问题“掰弯”成线性问题。

重要提醒:雅可比矩阵的计算一定要仔细!我见过太多人把偏导数算错,导致滤波器性能极差。建议先用符号计算验证,再写代码。

4.3 EKF在目标跟踪中的应用

我们用一个完整的例子来说明。假设你要跟踪一个做匀速圆周运动的目标,状态向量为:

x = [px, py, vx, vy]^T

运动模型(匀速转弯,CTRV模型):

px_k = px_{k-1} + vx_{k-1} * dt
py_k = py_{k-1} + vy_{k-1} * dt
vx_k = vx_{k-1} * cos(ω*dt) - vy_{k-1} * sin(ω*dt)
vy_k = vx_{k-1} * sin(ω*dt) + vy_{k-1} * cos(ω*dt)

其中ω是转弯角速度。观测模型(雷达):

r = sqrt(px^2 + py^2)
θ = atan2(py, px)

下面给出EKF的核心代码(Python风格):

import numpy as np

def ekf_predict(x, P, F, Q):
    # 状态预测
    x_pred = f(x)  # 非线性函数
    # 协方差预测
    P_pred = F @ P @ F.T + Q
    return x_pred, P_pred

def ekf_update(x_pred, P_pred, z, H, R):
    # 卡尔曼增益
    S = H @ P_pred @ H.T + R
    K = P_pred @ H.T @ np.linalg.inv(S)
    # 状态更新
    z_pred = h(x_pred)  # 非线性观测
    x_est = x_pred + K @ (z - z_pred)
    # 协方差更新
    P_est = (np.eye(len(x_pred)) - K @ H) @ P_pred
    return x_est, P_est

# 计算雅可比矩阵(以观测模型为例)
def compute_H(x):
    px, py = x[0], x[1]
    r = np.sqrt(px**2 + py**2)
    H = np.array([
        [px/r, py/r, 0, 0],
        [-py/(r**2), px/(r**2), 0, 0]
    ])
    return H

实战经验:我在做无人机跟踪项目时,发现EKF对初始状态非常敏感。如果初始位置偏差太大,线性化误差会累积,导致滤波器发散。我的建议是:先用几帧数据做初始化,或者用多假设方法。

4.4 知识体系与核心逻辑

下面这张图总结了EKF在目标跟踪中的完整流程:

EKF目标跟踪核心流程 初始状态 x₀, P₀ 预测步骤 x_pred = f(x_est, u) P_pred = F·P·Fᵀ + Q 计算雅可比矩阵 F = ∂f/∂x, H = ∂h/∂x 更新步骤 K = P_pred·Hᵀ·(H·P_pred·Hᵀ+R)⁻¹ x_est = x_pred + K·(z - h(x_pred)) 输出 x_est, P_est 关键点 • 雅可比矩阵时变 • 一阶线性近似 • 需要良好初始化 循环迭代

4.5 常见问题与调试技巧

最后,分享几个我在项目中踩过的坑:

  • 雅可比矩阵计算错误:这是最常见的错误。我建议用数值微分验证解析结果,比如用中心差分法。
  • 噪声协方差Q和R的调参:Q太大,滤波器会震荡;Q太小,会滞后。我一般用试错法,先设大Q,再逐步减小。
  • 非线性程度判断:如果目标运动剧烈,一阶EKF可能不够。这时候可以考虑迭代EKF(IEKF)或UKF。

个人经验:我曾经在车载雷达项目中,因为EKF的雅可比矩阵算错了一个符号,导致跟踪结果完全相反。排查了整整两天才发现。所以,写代码时一定要单元测试雅可比矩阵!

好了,这一章的内容就到这里。EKF虽然经典,但也不是万能的。下一章我们会聊聊更强大的无迹卡尔曼滤波(UKF),它不需要计算雅可比矩阵,处理非线性问题更鲁棒。但在此之前,建议你把EKF的代码亲手写一遍,感受一下线性化的威力。

可可精品资料库