1. 状态转移矩阵
从当前状态到下一个状态的转换.
例子: 假设跟踪车辆形式
-
车辆的状态矩阵, 位置变量 $p_t$; 速度变量 $v_t$.
$$ x_t=\begin{bmatrix} p_t \\ v_t \end{bmatrix} $$ -
假设车辆有加速度变量 $u_t$; 这里 $\Delta t$ 是单位时间; 这里就可以从上一个单位时间的 $p_{t-1}v_{t-1}$ 推导出当前的 $p_tv_t$.
$$ \begin{aligned}p_t=&p_{t-1}+v_{t-1}\times\Delta t+u_t\times\frac{\Delta t^2}{2}\\v_t=&v_{t-1}+u_t\times\Delta t\end{aligned} $$ -
这里可以发现输出变量是输入变量的线性组合, 所以卡尔曼滤波器是线性滤波器, 只能描述状态与状态之间的线性关系. 由于是线性关系, 所以可以将公式写成矩阵的形式.
$$ \left[\begin{array}{c}p_t \\v_t\end{array}\right]=\left[\begin{array}{cc}1 & \Delta t \\0 & 1\end{array}\right]\left[\begin{array}{c}p_{t-1} \\v_{t-1}\end{array}\right]+\left[\begin{array}{c}\frac{\Delta t^2}{2} \\\Delta t\end{array}\right] u_t $$ -
提取公式中带时间参数矩阵之后, 就可以简化公式获得卡尔曼滤波器中的状态预测公式.
$$ \begin{aligned}&F_t=\left[\begin{array}{cc}1 & \Delta t \\0 & 1\end{array}\right], B_t=\left[\begin{array}{c}\frac{\Delta t^2}{2} \\\Delta t\end{array}\right]\\&\hat{x}_t^{-}=F_t \hat{x}_{t-1}+B_t u_t\end{aligned} \quad\quad \text{(1)} $$ -
这里的 $\hat{x}_t^{-}$ 表示, 这个值有 hat 符号是预测值, 并不是真实值; 右上角减号表示这个值是从 t-1 时刻的参数估计而来, 还需要 t 时刻的观测值进行修正.
2. 协方差矩阵
协方差描述的是变量之间的相关程度.
-
首先, 可以使用中心值核方差来描述一个变量的分布情况.

-
而对于存在两个变量的分布情况, 需要用到二维坐标系. 可以在两个轴分别描述两个变量的中心值和方差. 如果两个变量没有明显线性关系时, 分布情况图为左一; 两变量分布正相关时, 图为中间; 分布为负相关时, 图为右一. 可以发现这三种情况用中间值和方差是无法体现的.

-
为了描述两变量之间的相关程度, 使用了协方差. 协方差矩阵的公式如下
$$ \operatorname{cov}(x, x)=\left[\begin{array}{ll}\sigma_{11} & \sigma_{12} \\\sigma_{12} & \sigma_{22}\end{array}\right] $$- 其中, $\sigma_{12}$ 表示的就是两变量的协方差.
-
还是拿车辆速度举例, 由 $P^-_t$ 来表示不确定性在各个时间段内的传递关系, 是当前时刻先验估计协方差.
$$ P^-_t=F_tP_{t-1}F_t^T+Q_t \quad\quad \text{(2)} $$- $F_t$ 表示的是状态转换矩阵里带时间变量的 $F_t$ 矩阵. 之所以两边都乘, 是因为协方差矩阵的性质. 协方差的更新方法, 如果要将每一个点都乘以矩阵 $A$, 公式如下:
- $Q$ 是状态转移协方差, 用来描述状态转换矩阵和实际过程之间的误差. 这个值是可以设置的参数, 值越大, 表示我们对预测值的信任值越高. 因为这样会增大 $P^-_t$.
3. 观测矩阵
描述系统实际状态, 与观测到的数值之间的关系.
-
例1, 地球上的汽车位置为 [经度, 纬度, 海拔], 但是地图导航软件常常默认海拔为 0, 也就是不需要观测数据中的海拔信息.
那么地图需要的观测数据 $Z_t$ 如下:
$$ Z_t=\begin{vmatrix} 经度 & 纬度 & 海拔 \\ 1 & 0 & 0\\ 0 & 1 & 0 \\ 0 & 0 & 0 \end{vmatrix}.\begin{vmatrix}经度值\\纬度值\\海拔值 \end{vmatrix} $$ -
例2, 还是车辆速度问题, 如果有个测距仪在每个时间段进行测距, 获得标量 $Z_t$.

这时候, 观测矩阵为 $H=\begin{vmatrix}1&0\end{vmatrix}$, 因为车辆的状态矩阵中有用的只有位置变量 $p_t$.
获得 $Z_t$ 公式如下:
$$ Z_t=Hx_t+c $$-
$c$ 描述观测值和预测值的残差, 因为观测值也不一定准确. 这时候, $Z_t$ (观测值)和 $H\hat{x}_t^-$ (预测值) 都是已知的, 所以可以算出:
$$ c = Z_t-H\hat{x}_t^- $$ -
测量噪声的协方差为 $R$, 可以手动设定的参数.
-
4. 状态更新
-
计算出卡尔曼系数 $K_t$, 也叫滤波增益系数, 它的作用有两个
-
权衡预测状态协方差 $P^-_t$ 和 观察值的残差协方差 $R$ 的大小, 来决定观测值的权重. (相信预测模型还是观测值, 如果相信预测模型多一点, 那么 $K_t$ 就会小一点; 反之则 $K_t$ 就会大一点)
$$ K_t=\frac{P_t^{-} H^T}{H P_t^{-} H^T+R}\quad\quad \text{(3)} $$ -
将残差的表现形式从观测域转换为状态域. 因为, 观察值和状态值的维度可能就是不同的, 所以需要转换. 举车辆速度的例子来说, 观测值只有车辆的位置没有速度, 但是车辆状态有速度和位置. 这时候, $K_t$ 中包含了协方差矩阵 $P^-_t$ 的信息, 所以可以利用位置和速度的相关性, 从位置的残差 $c$ 推算出速度的残差.
-
-
计算当前状态, 由 状态预测公式 $\hat{x}_t^{-}$+ 卡尔曼系数 $K_t$ * 观测值和预测值的残差 $c$ 获得.
$$ \hat{x}_t=\hat{x}_t^{-}+K_t\left(z_t-H \hat{x}_t^{-}\right) \quad\quad \text{(4)} $$ -
更新最佳估计值的噪声协方差矩阵 $P_t$, 在下一时刻作为 $P_{t-1}$ 用于 $P^-_t$ 的计算.
$$ P_t=(I-K_tH)P^-_t \quad\quad \text{(5)} $$
5. 实际使用
-
需要调试的参数
-
$\Delta t$: 其实修改的是转态转移矩阵, 不同的运动状态可以使用不同的矩阵

-
$R$: 测量噪声的协方差, $R$ 取值越小收敛越快, $R$ 取值越大收敛越慢;
-
$Q$: 状态转移协方差矩阵, 值越大, 表示我们对预测值的信任值越高; 当状态转换过程为已确定时, $Q$ 的取值越小越好; 转换状态不确定的时候, $Q$ 可以去随机值, 随时间变化, 这时滤波器变为自适应卡尔曼滤波器.
-
-
使用测距仪获得单位时间车辆距离测距仪的距离, 预测车辆速度.
import numpy as np import matplotlib.pyplot as plt N = 100 gt_data = np.arange(0, N) noise = np.random.randn(1, N) # 制造一些观测误差 noise = noise * 20 gt_data_n = (noise + gt_data).reshape(N) gt_data_n_list = (noise + gt_data).tolist()[0] delta_t = 1 X = np.zeros((2, 1)) # 状态矩阵 P = np.array([[1, 0], [0, 1]]) # 状态协方差矩阵 F = np.array([[1, delta_t], [0, 1]]) # 状态转移矩阵, 初始单位时间为1 Q = np.array([[0.0001, 0], [0, 0.0001]]) # 状态转移协方差矩阵 H = np.array([[1, 0]]) # 观测矩阵 <- 注意这里数组必须套在数组里面, 不然无法转置 R = 10 # 观测噪声方差矩阵 <- 由于距离的噪声是一维的所以这里不用矩阵了 B = np.array([[0.5 * np.sqrt(delta_t)], [delta_t]]) # 控制量 U = 0 distances = [] speeds = [] for i in range(N): X_ = F @ X + B * U # 状态预测公式 P_ = F @ P @ F.T + Q # 预测状态协方差公式 K = (P_ @ H.T) @ np.linalg.inv(H @ P @ H.T + R) # 更新卡尔曼系数 X = X_ + K @ (gt_data_n_list[i] - H @ X_) # 更新状态预测公式 P = (np.eye(2) - K @ H) @ P_ # 更新状态协方差矩阵 distances.append(float(X[0])) speeds.append(float(X[1])) # 画图 plt.scatter(distances, speeds) plt.xlabel('distances') plt.ylabel('speeds') plt.axhline(1) # 画一条辅助线方便观察 plt.show()
-
对于有车有固定加速度的情况
import numpy as np import matplotlib.pyplot as plt N = 100 gt_data = np.arange(0, N) gt_data = np.square(gt_data) noise = np.random.randn(1, N) # 制造一些观测误差 noise = noise * 20 gt_data_n = (noise + gt_data).reshape(N) gt_data_n_list = (noise + gt_data).tolist()[0] delta_t = 1.42 X = np.zeros((2, 1)) # 状态矩阵 P = np.array([[1, 0], [0, 1]]) # 状态协方差矩阵 F = np.array([[1, delta_t], [0, 1]]) # 状态转移矩阵, 初始单位时间为1 Q = np.array([[0.0001, 0], [0, 0.0001]]) # 状态转移协方差矩阵 H = np.array([[1, 0]]) # 观测矩阵 <- 注意这里数组必须套在数组里面, 不然无法转置 R = 1 # 观测噪声方差矩阵 <- 由于距离的噪声是一维的所以这里不用矩阵了 B = np.array([[0.5 * np.sqrt(delta_t)], [delta_t]]) # 控制量 U = 1 distances = [] speeds = [] for i in range(N): X_ = F @ X + B * U # 状态预测公式 P_ = F @ P @ F.T + Q # 预测状态协方差公式 K = (P_ @ H.T) @ np.linalg.inv(H @ P @ H.T + R) # 更新卡尔曼系数 X = X_ + K @ (gt_data_n_list[i] - H @ X_) # 更新状态预测公式 P = (np.eye(2) - K @ H) @ P_ # 更新状态协方差矩阵 distances.append(float(X[0])) speeds.append(float(X[1])) # 画图 # 画预测值 plt.plot(distances, speeds, "x", label="prec") # 画真实值 y=(2x)^(1/2) true_y = np.sqrt(2 * gt_data) plt.plot(gt_data, true_y, label="gt") # 画观测值 true_y_n = np.sqrt(2 * gt_data_n) plt.plot( gt_data_n, true_y_n, "o", label="obs", ) plt.xlabel('distances') plt.ylabel('speeds') plt.legend() plt.show() print()
-
如果车的加速度是非线性变化 (前50s, a=1; 后50s, a=3) 的情况, 无法准确预测
import numpy as np import matplotlib.pyplot as plt N = 100 a_1 = 1 a_2 = 3 def cul_true_data(time1, time2): speed1 = a_1 * time1 speed2 = (a_1 * time1)[-1] + a_2 * time2 return np.append(speed1, speed2) gt_time = np.arange(0, N // 2) # 单位时间 0-50 gt_distance = 0.5 * np.square(a_1 * gt_time) # 加速度 a1 last_speed = (a_1 * gt_time)[-1] # gt_time_2 = np.arange(N // 2, N) # 单位时间 50-100 gt_distance_2 = gt_distance[-1] + last_speed * gt_time + 0.5 * np.square( a_2 * gt_time) # a2 gt_data_merge = np.append(gt_distance, gt_distance_2) noise = np.random.randn(1, N) # 制造一些观测误差 noise = noise * 5 gt_data_n = (noise + gt_data_merge).reshape(N) gt_data_n_list = (noise + gt_data_merge).tolist()[0] delta_t = 1.5 X = np.zeros((2, 1)) # 状态矩阵 P = np.array([[1, 0], [0, 1]]) # 状态协方差矩阵 F = np.array([[1, delta_t], [0, 1]]) # 状态转移矩阵, 初始单位时间为1 Q = np.array([[0.0001, 0], [0, 0.0001]]) # 状态转移协方差矩阵 H = np.array([[1, 0]]) # 观测矩阵 <- 注意这里数组必须套在数组里面, 不然无法转置 R = 1 # 观测噪声方差矩阵 <- 由于距离的噪声是一维的所以这里不用矩阵了 B = np.array([[0.5 * np.sqrt(delta_t)], [delta_t]]) # 控制量 U = 2 distances = [] speeds = [] for i in range(N): X_ = F @ X + B * U # 状态预测公式 P_ = F @ P @ F.T + Q # 预测状态协方差公式 K = (P_ @ H.T) @ np.linalg.inv(H @ P @ H.T + R) # 更新卡尔曼系数 X = X_ + K @ (gt_data_n_list[i] - H @ X_) # 更新状态预测公式 P = (np.eye(2) - K @ H) @ P_ # 更新状态协方差矩阵 distances.append(float(X[0])) speeds.append(float(X[1])) # 画图 # 画预测值 plt.plot(distances, speeds, "x", label="prec") # 画真实值 true_y = cul_true_data(gt_time, gt_time) plt.plot(gt_data_merge, true_y, label="gt") plt.xlabel('distances') plt.ylabel('speeds') plt.legend() plt.show() print()
-
更复杂的应用可以看这里
6. 总结
优点:
- 卡尔曼滤波器是一种最优的滤波器,具有最小均方误差,能够有效地消除噪声和抖动。
- 卡尔曼滤波器不仅可以估计状态,还可以估计状态的协方差,对于系统的不确定性有一定的鲁棒性。
- 卡尔曼滤波器可以实时地进行状态估计,响应速度较快。
缺点:
- 卡尔曼滤波器的理论基础是线性高斯系统,对于非线性、非高斯的系统效果可能不佳。
- 卡尔曼滤波器需要准确的先验信息,如果先验信息不准确,则滤波结果也会不准确。
- 卡尔曼滤波器计算量较大,对计算能力有一定要求。
参考资料
1. 1. 1. 卡尔曼滤波器的原理以及在matlab中的实现_哔哩哔哩_bilibili
- 调参