kalman 滤波器

引言

最近在做激光雷达的建图LIO-SAM,数据是跑起来了,但是细节还不大清楚,这篇文章简单梳理kalman的使用,没有整理繁杂的推导过程。细节可参看参考文献部分,特别是第一个,化繁为简,写的很好。


一句话理解 kalman:

  • 使用上一次的最优结果预测当前值,并使用当前观测值进行修正,得到最优结果。

1 kalman 流程

image

  • 初始估计值

\[\hat{x}_0,\hat{P}_0 \]

  • 预测:根据上一次的最优估计值估算当前阶段的状态值与不确定性

\[\tilde{x}_{n}=F\hat{x}_{n-1} + Gu_n \\ \space \\ \tilde{P}_{n}=F\hat{P}_{n-1}F^T+Q \]

  • 更新:基于观测值与估计值进行修正,得到最优估计

\[K_n=\tilde{P}_{n}H^T(H\tilde{P}_{n}H^{T} + R)^{-1} \\ \space\\ \hat{P}_n = (I - K_nH)\tilde{P}_{n} \\ \space\\ \hat{x}_{n}=\tilde{x}_{n} + K_{n}(z_n - H\tilde{x}_{n}) \]

  • 符号说明
    • F: 转移矩阵
    • H:测量矩阵
    • Q:过程噪声
    • R:测量噪声
    • P:协方差 (更新阶段,此值有简化)
    • G: 是将输入转换为状态的矩阵
  • 完整的协方差计算方法如下:

\[\hat{P}_n = (I - K_nH)\tilde{P}_{n}(I - K_nH)^T+K_nRK_n^T \]

2 构建过程噪声矩阵Q

有了递推的公式,在实际使用过程中,一直对噪声的设置不大明白,看到第一篇参考文献的介绍才知道有具体的方法。
过程噪声方差对卡尔曼滤波性能有非常大的影响。过小的 q会造成滞后误差。而如果q的值过高,卡尔曼滤波会完全相信测量值,造成估计值噪声过大。

不同状态变量对应的过程噪声可以是独立的。这种情况下,过程噪声协方差矩阵 Q 是对角阵:

\[Q = \begin{bmatrix} q_{11}&0&\dots & 0\\ 0&q_{22}&\dots & 0\\ \vdots & \vdots& \ddots & \vdots \\ 0&0&\dots & q_{kk}\\ \end{bmatrix} \]

过程噪声也可以是相关的。比如匀速运动模型假设零加速度(a=0),但实际加速度的随机噪声 \(\sigma^2_a\) 会造成速度和位置产生对应的扰动。这种情况下,过程噪声在状态变量间就是存在互相关的。环境过程噪声有两种模型:

  • 离散噪声模型
  • 连续噪声模型

2.1 离散噪声模型-协方差构建

离散噪声模型假设噪声在每个采样点是不同的,但是在采样点之间是相同的(零阶保持)。
image
对匀速运动模型,过程噪声协方差具有下述形式:

\[Q=\begin{bmatrix} V(x) & CONV(x,v)\\ COV(v,x) & V(v) \end{bmatrix} \]

我们以模型的随机加速度方差 \(σ^2_a\)来表示位置和速度的协方差。矩阵元素计算:

\[\left\{\begin{matrix} V(v)=\sigma_v^2=E\left(v^2\right)-\mu_v^2=E\left(\left(a\Delta t\right)^2\right)-\left(\mu_a\Delta t\right)^2=\Delta t^2\left(E\left(a^2\right)-\mu_a^2\right)=\Delta t^2\sigma_a^2\\ V(x)=\sigma_x^2=E\left(x^2\right)-\mu_x^2=E\left(\left(\frac{1}{2}a\Delta t^2\right)^2\right)-\left(\frac{1}{2}\mu_a\Delta t^2\right)^2=\frac{\Delta t^4}{4}\left(E\left(a^2\right)-\mu_a^2\right)=\frac{\Delta t^4}{4}\sigma_a^2\\ COV(x,v)=COV(v,x)=E\left(xv\right)-\mu_x\mu_v=E\left(\frac{1}{2}a\Delta t^2a\Delta t\right)-\left(\frac{1}{2}\mu_a\Delta t^2\mu_a\Delta t\right)=\frac{\Delta t^3}{2}\left(E\left(a^2\right)-\mu_a^2\right)=\frac{\Delta t^3}{2}\sigma_a^2 \end{matrix}\right. \]

现在我们可以把结果填入 Q 矩阵:

\[Q=\sigma_a^2\begin{bmatrix}\frac{\Delta t^4}{4}&\frac{\Delta t^3}{2}\\\frac{\Delta t^3}{2}&\Delta t^2\end{bmatrix} \]

2.2 离散噪声模型-输入转移矩阵投影法

如果动态模型包含输入,我们可以更快捷地算出 Q矩阵。我们可以用输入转移矩阵把加速度方差 \(σ^2_a\) 投影到我们的动态模型里。

\[Q=G\sigma^{2}_aG^T \\ \]

式中 G 是控制矩阵(或输入转移矩阵)。
对匀速运动模型,G 矩阵为:

\[G=\begin{bmatrix} \Delta{t^2}/2 \\ \Delta{t} \end{bmatrix} \space \\ Q=G\sigma^{2}_aG^T=\sigma^{2}_aGG^T=\sigma_a^2\begin{bmatrix}\frac{\Delta t^4}{4}&\frac{\Delta t^3}{2}\\\frac{\Delta t^3}{2}&\Delta t^2\end{bmatrix} \]

2.3 连续噪声模型

连续噪声模型假设噪声随时间连续改变。
image

为了推导连续噪声模型的过程噪声协方差矩阵Qc,我们需要把离散过程噪声的协方差矩阵 Q 对时间进行积分。

\[\boldsymbol{Q}_c=\int_0^{\Delta t}\boldsymbol{Q}dt=\int_0^{\Delta t}\sigma_a^2\begin{bmatrix}\frac{t^4}{4}&\frac{t^3}{2}\\\frac{t^3}{2}&t^2\end{bmatrix}dt=\sigma_a^2\begin{bmatrix}\frac{\Delta t^5}{20}&\frac{\Delta t^4}{8}\\\frac{\Delta t^4}{8}&\frac{\Delta t^3}{3}\end{bmatrix} \]

2.4 噪声模型的选择

回答这个问题之前,你需要选择合适的过程噪声方差。你可以用统计和随机领域的公式去计算,或者根据经验手动选择一个合适的值(这种比较推荐)。

在雷达中,\(\sigma_{a}^{2}\) 依赖目标的特性和模型完整性。对可以机动的目标,比如飞机,\(\sigma_{a}^{2}\) 应该相对高;对不能机动的目标,比如火箭,可以用较小的 \(\sigma_{a}^{2}\)。模型完整性也是选择过程噪声方差的因素之一。如果你的模型包含了环境扰动,例如空气阻力,那么过程噪声的比例会比不包含要小。(译注:目标特性和模型完整性是一回事,一句话说就是过程噪声就是模型不准确性,模型近似程度越高就越不准确,噪声方差就要选得越大,大到一定程度KF就只相信测量值了,就起不到滤波效果了。)

一旦选定了过程噪声方差的值,接下来就要选择噪声模型了。应该选离散的还是连续的?

这个问题没有明确的答案。我推荐两种模型都尝试一下,实测看看哪种模型在你的滤波器中效果最好。当 \(\Delta t\) 非常小时,可以选取离散噪声模型,反之则最好选用连续噪声模型。

3 例程1 二维运动滤波

3.1 预测

\[\tilde{x} = F\hat{x}+u \]

经过时间 \(\Delta{t}\) 后的预测状态量为:

\[\tilde{x}=\begin{bmatrix} s_x+v_x\Delta t \\ s_y+v_y\Delta t \\ v_x \\ v_y \end{bmatrix} \]

对于匀速运行,加速度为0,不会对预测造成影响,故

\[u=0 \]

对于匀加速运动模型,引入加速度\(a_x,a_y\),u变为:

\[u=\begin{bmatrix} \frac{1}{2}a_x \Delta{t^2} \\ \space\\ \frac{1}{2}a_y \Delta{t^2} \\ \space\\ a_x \Delta{t} \\ \space \\ a_y \Delta{t} \\ \end{bmatrix} \]

  • 第一个预测公式为:

\[\tilde{x}=\begin{bmatrix} s_x+v_x\Delta t \\ s_y+v_y\Delta t \\ v_x \\ v_y \end{bmatrix} =\begin{bmatrix} 1 & 0 & \Delta t &0\\ 0 & 1 & 0 & \Delta t\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1\\ \end{bmatrix} \begin{bmatrix} s_x\\s_y\\v_x\\v_y \end{bmatrix} + \begin{bmatrix} 0\\0\\0\\0 \end{bmatrix} \]

  • 第二个预测公式

\[\tilde{P}_{n}=F\hat{P}_{n-1}F^T+Q \]

该公式中,P被称为状态协方差矩阵(state covariance matrix),表示系统的不确定程度,P在卡尔曼滤波器初始化时会很大,随着越来越多的数据注入滤波器中,不确定程度会逐渐减小;

Q是过程噪声协方差矩阵(process noise covariance matrix),即无法用 \(\tilde{x} = F\hat{x}+u\) 表示的噪声,比如车辆运动时突然到了上坡,这个影响是无法用之前的状态转移估计的。

  • P的初始化

以激光雷达为例,激光雷达只能测量点的位置,无法测量点的速度,因此对于激光雷达的协方差矩阵来说,对于位置信息,其测量较准确,不确定度较低;对于速度信息,不确定度较高。因此可以认为此处的 P为:

\[P=\begin{bmatrix} 1 & 0 & 0 &0\\ 0 & 1 & 0 & 0\\ 0 & 0 & 100 & 0\\ 0 & 0 & 0 & 100\\ \end{bmatrix} \]

  • Q 的初始化

3.2 更新

  • 第一个更新公式

\[y_n = z_n - H\tilde{x}_{n} \]

这个公式计算的是实际观测量 z 与状态量预测值 \(\tilde{x}\) 之间的差值 y。不同传感器的观测量一般不同,比如激光雷达测量的位置信号为 x方向和 y方向上的距离,毫米波雷达测量的是位置和角度信息。因此需要将状态量左乘一个矩阵H,才能与观测量进行相应的运算,H被称为测量矩阵(MeasurementMatrix)。激光雷达的观测量为:

\[z=\begin{bmatrix} x_m\\y_m \end{bmatrix} \]

其中 \(x_m\)\(y_m\) 分别表示 x 方向和 y方向上的测量值。由于 \(\tilde{x}\) 是一个 4x1的列向量,如果要与一个 2x1 的列向量 z 进行减运算,需要左乘一个 2x4 的矩阵:

\[\begin{bmatrix} \Delta{x}\\ \Delta{y} \end{bmatrix} = \begin{bmatrix} x_m\\ y_m \end{bmatrix} - \begin{bmatrix} 1 & 0 & 0 &0\\ 0 & 1 & 0 &0 \end{bmatrix} \begin{bmatrix} s_x+v_x\Delta t \\ s_y+v_y\Delta t \\ v_x \\ v_y \end{bmatrix} \]

意即,对于激光雷达而言,测量矩阵 H为:

\[H = \begin{bmatrix} 1 & 0 & 0 &0\\ 0 & 1 & 0 &0 \end{bmatrix} \]

  • kalman gain更新
    对于公式:

\[K_n=\tilde{P}_{n}H^T(H\tilde{P}_{n}H^{T} + R)^{-1} \]

R是测量噪声协方差矩阵(measurement noise covariance matrix),表示的是测量值与真值之间的差值。一般情况下,传感器厂家会提供该值

4 例程2

4.1 背景

假设车辆X方向移动的速度为20m/s,Y方向为10m/s,持续20s观察

import numpy as np
import matplotlib.pyplot as plt

m = 200 # Measurements
vx = 20 # in X
vy = 10 # in Y

mx = np.array(vx + np.random.randn(m)*1)
my = np.array(vy + np.random.randn(m)*1)
measurements = np.vstack((mx,my))

# initial object state (px,py, vx, vy)
x = np.matrix([[0.0, 0.0, 0.0, 0.0]]).T
# 物件的不确定因素: 先验估计的协方差矩阵
P = np.diag([1000.0,
             1000.0,
             1000.0,
             1000.0])
# 量测的时间间隔 𝑑𝑡 假设100ms
dt = 0.1  # time step between filter step
# 状态转移矩阵 𝐅
A = np.matrix([
    [1.0, 0.0, dt, 0.0],
    [0.0, 1.0, 0.0, dt],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0],
])
# 观测矩阵 𝐇
H = np.matrix([
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]
])

# 量测noise的协方差矩阵 𝐑
ra = 0.9
R = np.matrix([[ra, 0.0],
               [0.0, ra]])

# 过程噪声的协方差矩阵 𝐐,输入转移矩阵投影法构建
sv = 0.5  # m/s^2
G = np.matrix([[0.5 * dt ** 2],
               [0.5 * dt ** 2],
               [dt],
               [dt]])
Q = G * G.T * sv ** 2

I = np.eye(4)


class States_Kalman():
    def __init__(self):
        self.initial()

    def savestates(self, x, Z, P, R, K):
        self.xt.append(float(x[0]))
        self.yt.append(float(x[1]))
        self.dxt.append(float(x[2]))
        self.dyt.append(float(x[3]))
        self.Zx.append(float(Z[0]))
        self.Zy.append(float(Z[1]))
        self.Px.append(float(P[0, 0]))
        self.Py.append(float(P[1, 1]))
        self.Pdx.append(float(P[2, 2]))
        self.Rdy.append(float(P[3, 3]))
        self.Rdx.append(float(R[0, 0]))
        self.Rdy.append(float(R[1, 1]))
        self.Kx.append(float(K[0, 0]))
        self.Ky.append(float(K[1, 0]))
        self.Kdx.append(float(K[2, 0]))
        self.Kdy.append(float(K[3, 0]))

    def initial(self):
        self.xt, self.yt = [], []
        self.dxt, self.dyt = [], []
        self.Zx, self.Zy = [], []
        self.Px, self.Py = [], []
        self.Pdx, self.Pdy = [], []
        self.Rdx, self.Rdy = [], []
        self.Kx, self.Ky = [], []
        self.Kdx, self.Kdy = [], []


def plot_x(states_kalman):
    fig = plt.figure(figsize=(16, 9))
    plt.step(range(len(measurements[0])), measurements[0], label='$measured v_x$')
    plt.step(range(len(measurements[0])), measurements[1], label='$measured v_y$')

    plt.axhline(vx, color='k', label='$ture v_x$')
    plt.axhline(vy, color='k', label='$ture v_y$')

    plt.step(range(len(measurements[0])), states_kalman.dxt, label='$estimate v_x$', linewidth=2)
    plt.step(range(len(measurements[0])), states_kalman.dyt, label='$estimate v_y$', linewidth=2)

    plt.xlabel('Filter Step')
    plt.ylabel('Velocity')
    plt.title('Estimate (Elements from State Vectir $x$)')
    plt.legend(loc='best', prop={'size': 11})
    plt.show()


states_kalman = States_Kalman()

for n in range(len(measurements[0])):
    # Time update (Prediction)
    # 1. project the state ahead
    x = A * x
    # 2. Project the error covariance ahead
    P = A * P * A.T + Q

    # Measurement update (Correction)
    # 1. Compute the Kalman Gain
    S = H * P * H.T + R
    K = (P * H.T) * np.linalg.pinv(S)
    # 2. update the estimate by z
    Z = measurements[:, n].reshape(2, 1)
    y = Z - H * x
    x = x + K * y
    # 3. update the error covariance
    P = (I - K * H) * P

    ## Save states (for plot results)
    states_kalman.savestates(x, Z, P, R, K)

plot_x(states_kalman)

image

4.2 参数调试

若只更改Q为单位阵,协方差矩阵偏大,则更相信量测值,起不到很好的滤波作用,效果如下:
image

Reference

  1. kalman filter from the ground up
  2. 协方差外插
  3. 从贝叶斯滤波到扩展卡尔曼滤波
  4. SLAM基础-扩展卡尔曼滤波
  5. Error state Kalman Filter 学习笔记
posted @ 2025-06-24 00:15  wangnb  阅读(140)  评论(0)    收藏  举报