前言
卡尔曼滤波算法由匈牙利数学家Kalman提出,主要基于线性系统提出。这里我们将其用于汽车跟踪,并对其基本原理进行介绍。
神奇的卡尔曼滤波,目标追踪的福音
1.背景知识
1.1 时间序列模型
时间序列模型都可以用如下示意图表示:
这个模型包含两个序列,一个是黄色部分的状态序列,用X表示,一个是绿色部分的观测序列(又叫测量序列、证据序列、观察序列,不同的书籍有不同的叫法,在这里统一叫观测序列。)用Y表示。状态序列反应了系统的真实状态,一般不能被直接观测,即使被直接观测也会引进噪声;观测序列是通过测量得到的数据,它与状态序列之间有规律性的联系。
上面序列中,假设初始时间为, 则,是时刻的状态值和观测值,,是时刻的状态值和观测值...,即随着时间的流逝,序列从左向右逐渐展开。
常见的时间序列模型主要包括三个:隐尔马尔科夫模型,卡尔曼滤波,粒子滤波。
1.2. 滤波
时间序列模型中包括预测和滤波两步
- 预测:指用当前和过去的数据来求取未来的数据。对应上述序列图中,则是利用时刻,的值,估计时刻值。
- 滤波:是用当前和过去的数据来求取当前的数据。对应上述序列图中,则是先通过上一步的预测步骤得到的一个预测值,再利用时刻的值对这个预测值进行纠正,得到最终的估计值。(通俗讲,就是通过预测一个值, 通过传感器测量一个值, 将两者进行融合得到最终的值)
1.3. 线性动态系统
卡尔曼滤波又称为基于高斯过程的线性动态系统(Linear Dynamic System, LDS)。
· 这里的高斯是指:状态变量和观测变量都符合高斯分布;(为什么是高斯分布?因为卡尔曼滤波估计的结果是一个概率,概率不是一个固定的值,而是一个范围,而这个范围符合高斯分布。)
· 这里的线性是指:可以通过线性表示,可以通过线性表示;
如果用数学表达式来表达这两层含义如下:
上面表达式中F是一个矩阵,常称作状态转移矩阵,保证了和的线性关系(线性代数中,矩阵就是线性变换);常称作噪声,其服从均值为0,方差为Q的高斯分布,保证了服从高斯分布(因为高斯分布加上一个常数后依然是高斯分布)。
同样的关于和,也可以得到如下表示, 其中矩阵H称作状态空间到观测空间的映射矩阵, rtrt称作噪声,其服从高斯分布:
2.卡尔曼滤波理论知识
以下公式需要用到的变量含义如下:
- :状态变量
- : 状态变量的协方差矩阵
- :状态转移矩阵
- :控制矩阵
- :控制向量
- : 状态变量的噪声矩阵
- :协方差矩阵的噪声矩阵
学习卡尔曼滤波只需要弄懂下图这五个公式与他们之间的关系。
预测中的两条公式,是通过上一时刻的状态预测下一时刻的状态的,通过这两条公式,我们得到的是带减号和上标的,这表示,这并不是最佳的估计值,减号表示它们还欠缺点什么东西,这个欠缺的东西呢就是通过观测值里面带来的信息,因为我们还需要用观测值中带来的信息修正误差,从而得到最优估计值,这正是更新这一步要做的事情,也就是说更新这一步才做到了真正的预测值X,更新了没有修正的预测结果,可以这么理解。
更新这三个公式,第一条公式是用来求卡尔曼增益K的,卡尔曼增益K是用来协同当前状态的观测值Z来更新X(第二条公式)和P(第三条公式)的,经过更新后的值才是最佳的估计值,所以他们是不带减号下标的。
我们可以看出,五条公式有三条(、、)都是有噪声尾随其后的,这些噪声的不确定性会化为数值根据每一次迭代不断的更新,并用来修正充满了噪声的观测值,从而使得观测值真实可靠。
2.1 预测
2.1.1 第一条公式:状态转移
我们假设有这样一辆小车在以可变化的速度向前行驶,这个时候它有两个状态量:位置position和速度velocity(此后简略为p和v)。
若当前时刻为t,则为当前时刻的状态量,它有两个属性和,分别表示当前状态的位置和速度。 上面我们也说了滤波是预测当前的状态,那么我们如何预测当前的状态?自然是用过去的状态量预测当前的状态量,于是我们又有了,它也有两个属性与。
至此,我们根据位置与速度,速度与时间之间的关系,简单的推出它们的数学表达公式(左边的两个式子),如果我们用线性代数的知识:矩阵,来描述这个式子,那么它将变成一种更具有计算与表达价值的矩阵形式(右边的式子)。
但我们可以看出他依旧不是那么的简洁,这不符合优美的数学。所以我们接下来用状态转移矩阵,它代表我们如何从上一时刻的状态来推测当前时刻的状态,和控制矩阵,它表示上一时刻的状态控制量u如何作用于当前状态。如下图:
x之所以戴了尖帽子是因为这只是一个估计量,因为我们无法永远预测汽车的真实状态,只能尽量可能的估计推测出真实状态,这是因为真实状态包含了非常多的噪声,比如汽车突然减速加速,或者掉进了一个坑...等等。噪声越大,不确定性也就越大,所以我们待会还需要根据协方差矩阵来计算这个不确定性究竟有多大。
2.1.2 第二条公式:协方差矩阵
协方差矩阵P(这次是大写的)是符合高斯分布的,它在卡尔曼滤波中是用来表示p,v两个状态量之间的相关性,比如正相关、负相关,不相关,并且计算出系数,也就是如果是正相关,那么相关的程度用数字表达是多少。具体如下图:
那么下一个问题就是,我们通过让上一刻的协方差矩阵 推测到下一刻的协方差矩阵呢?这里可以通过协方差的性质:
来推出:(加上Q是因为这样的传递预测也同样是有噪声Q的)
2.2 更新
2.2.1 第三条公式:卡尔曼增益
2.2.2 第四条公式:观测矩阵
2.2.3 第五条公式:
3 卡尔曼滤波的应用
假设这样一个场景:有一辆小车始终以1m/s的速度匀速行驶在公路,一共行驶了20秒。我们通过卡尔曼滤波在有强烈噪声干扰的情况下试试下,小车能否被准确预测出来速度1m/s,且每个时刻对应距离(11s对应位于离原点11m的距离)
3.1 代码
3.1.1 获取场景数据。
# 假设条件;一个小车20秒里每秒匀速移动1米。
# 观测20个时刻状态的值
Z = np.array([x for x in range(1, 20+1)])
# 观测中的噪声
noise = np.random.randn(1, 20)
# 添加噪声
Z = Z + noise
Z = Z[0]
从加入了噪声的Z中可以看到,Z已经非常的混乱,那么我们的卡尔曼滤波是否还能够准确预测,达到我们理想中的情况呢?(速度1m/s,11s对应位于离原点11m的距离)
>>>print(Z)
>>>[-1.54066623 2.65314083 2.55081667 3.60778965 3.5172674 6.24392259
6.26421322 7.71828829 9.93166587 9.91688117 11.46280844 11.83685529
12.13700321 13.5197601 12.52724415 17.40105333 16.84530721 18.10129522
18.74874511 21.0930127 ]
3.1.2 初始化
# 初始状态
X = np.array([0, 0])
# 初始状态协方差矩阵
P = np.eye(2)
# 初始化状态转移矩阵
F = np.array([
[1, 1],
[0, 1]
])
# 初始化状态转移矩阵协方差
Q = np.eye(2) * 0.0001
# 观测矩阵
H = np.eye(2)
# 观测矩阵噪声方差
R = np.eye(2)
3.1.3 开始预测与更新
p = []
v = []
for i in range(len(Z)):
# 预测的两个式子
X_ = np.dot(F, X)
P_ = np.dot(F, P)
P_ = np.dot(P_, F.T) + Q
# 更新的三个式子
k1 = np.dot(P_, H.T)
k2 = np.dot(np.dot(H, P_), H.T) + R
K = np.dot(k1, np.linalg.inv(k2))
X = Z[i] - np.dot(H, X_)
X = X_ + np.dot(K, X)
P = np.eye(2) - np.dot(K, H)
P = np.dot(P, P_)
p.append(int(X[0]))
v.append(int(X[1]))
3.1.4 画图
(横轴位置p,纵轴速度v)
惊喜!我们可以从图中看出,只需要很少的迭代次数,卡尔曼滤波就能在极强的噪声干扰下依旧得出相对比较精准的结果!ps:有兴趣的伙伴可以试着增加场景的复杂度喔
完整的代码
import random
import matplotlib.pyplot as plt
import numpy as np
if __name__ == '__main__':
# 假设条件;一个小车20秒里每秒匀速移动1米。
# 观测20个时刻状态的值
Z = np.array([x for x in range(1, 20+1)])
# 观测中的噪声
noise = np.random.randn(1, 20)
# 添加噪声
Z = Z + noise
Z = Z[0]
print(Z)
# 初始状态
X = np.array([0, 0])
# 初始状态协方差矩阵
P = np.eye(2)
# 初始化状态转移矩阵
F = np.array([
[1, 1],
[0, 1]
])
# 初始化状态转移矩阵协方差
Q = np.eye(2) * 0.0001
# 观测矩阵
H = np.eye(2)
# 观测矩阵噪声方差
R = np.eye(2)
p = []
v = []
for i in range(len(Z)):
# 预测两个方差式子
X_ = np.dot(F, X)
P_ = np.dot(F, P)
P_ = np.dot(P_, F.T) + Q
k1 = np.dot(P_, H.T)
k2 = np.dot(np.dot(H, P_), H.T) + R
K = np.dot(k1, np.linalg.inv(k2))
X = Z[i] - np.dot(H, X_)
X = X_ + np.dot(K, X)
P = np.eye(2) - np.dot(K, H)
P = np.dot(P, P_)
p.append(int(X[0]))
v.append(int(X[1]))
plt.scatter(p, v)
plt.show()
完毕!
如果大家觉得本文对你有帮助的话,麻烦点赞+收藏,谢谢大家!