卡尔曼滤波的原理介绍
- 什么是卡尔曼滤波
在处理车辆轨迹数据时,轨迹点实际上是对车辆实际“状态”的一种“观测”信息。由于误差的存在,观测数据可能会与车辆的实际状态存在一定的偏差。
如何更精确地获取车辆的实际状态呢?考虑前面小节中所提及的判断车辆轨迹是否出现漂移的方法,主要将某个轨迹点与前面的轨迹的位置比较,查看是否存在明显不合理的瞬移。这种思路其实就是根据车辆之前的轨迹,预判车辆接下来可能的位置,如果记录的下一个轨迹点远超出预期,那么就可以断定这条轨迹出现了异常。
这种方法与卡尔曼滤波(Kalman filter)的思路有很大相似性。卡尔曼滤波是一种线性二次估计算法,用于对线性动态系统进行状态估计。它主要是结合以前的状态估计(即预测的当前轨迹点的位置)和当前的观测数据(记录的当前位置轨迹点),来进行当前状态的最优估计。
实现过程是使用上一次最优结果预测当前值,同时使用观测值修正当前值,得到最优结果。这种方法可以有效地减少噪声的影响,从而更精确地估计出车辆的实际状态。
卡尔曼滤波能够帮助推测出车辆的实际状态,对于任何存在不确定性信息的动态系统,都可以利用卡尔曼滤波来预测系统下一步的行为。即使存在噪声信息干扰,卡尔曼滤波通常也能有效地揭示实际发生的情况,找出现象之间不易察觉的相关性。
在实践中,卡尔曼滤波可以对轨迹数据进行平滑处理,减少数据噪声的影响。
在对车辆轨迹使用卡尔曼滤波算法进行平滑处理时,每次处理的是一条轨迹,轨迹中的每一个轨迹点可认为是算法中的一帧。卡尔曼滤波器算法的工作过程中,对每一帧数据的经纬左边进行处理,大致分为预测和更新两个步骤:
- 预测阶段:根据过去的状态估计和系统的动态模型,预测当前的状态和误差协方差。
- 更新阶段:根据预测的状态和当前的观测数据,通过卡尔曼增益(Kalman Gain)来更新状态估计和误差协方差。
通过这种方式,卡尔曼滤波器可以有效地解决噪声干扰下的预测和滤波问题,即在不完全、含有误差的信息中估计一个动态系统的状态。
- 卡尔曼滤波的预测阶段
首先介绍卡尔曼滤波的预测阶段。对一辆车,车辆在行驶过程中会有位置、速度、加速度等等状态,这些状态是不管是否测量都会客观存在的。通过这些状态,可以建立系统的状态方程,通过每一帧的状态来预测下一帧的状态。可以将第 k k k帧的车辆的状态表示为:
x ⃗ k = [ a k b k ζ k η k ] \vec x_k = \begin{bmatrix} a_k \\ b_k \\ \zeta_k \\ \eta_k \end{bmatrix} xk= akbkζkηk
其中,为了方便表示,也避免字母之间发生干扰,用 a a a、 b b b表示车辆的x与y坐标, ζ \zeta ζ、 η \eta η表示车辆在x方向与y方向上的速度。
假设第 k k k帧与第 k − 1 k-1 k−1帧之间的时间差为 Δ t \Delta t Δt。如果在没有其他外力干扰(加减速,转弯)的情况下,车辆会保持匀速直线运动。所以可以写出第 k k k帧的车辆的状态与第 k − 1 k-1 k−1帧的车辆的状态之间的数学关系:
a k = a k − 1 + Δ t × ζ k − 1 a_k=a_{k-1}+\Delta t × \zeta_{k-1} ak=ak−1+Δt×ζk−1
b k = b k − 1 + Δ t × η k − 1 b_k=b_{k-1}+\Delta t × \eta_{k-1} bk=bk−1+Δt×ηk−1
ζ k = ζ k − 1 \zeta_{k} = \zeta_{k-1} ζk=ζk−1
η k = η k − 1 \eta_{k} = \eta_{k-1} ηk=ηk−1
这里,可以写成矩阵的形式:
x ⃗ k = [ a k b k ζ k η k ] = [ 1 0 Δ t 0 0 1 0 Δ t 0 0 1 0 0 0 0 1 ] [ a k − 1 b k − 1 ζ k − 1 η k − 1 ] = F x ⃗ k − 1 \vec x_k = \begin{bmatrix} a_k \\ b_k \\ \zeta_k \\ \eta_k \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} a_{k-1} \\ b_{k-1} \\ \zeta_{k-1} \\ \eta_{k-1} \end{bmatrix} = F \vec x_{k-1} xk= akbkζkηk = 10000100Δt0100Δt01 ak−1bk−1ζk−1ηk−1 =Fxk−1
其中, F F F被称为状态转移矩阵。
然而,上面的情况显然是理想状态下的结果。如果能够知道车辆的加减速等信息时,就可以用一定的方法计算出车辆更精准的状态。这些额外输入的信息可以用 u ⃗ k \vec u_{k} uk表示,这些信息对车辆的四个状态的影响可以用矩阵 B u ⃗ k B \vec u_{k} Buk表示,那么式[@eq:状态转移矩阵]中可以增加 B u ⃗ k B \vec u_{k} Buk一项来表示这些输入信息的影响。
即使考虑了以上所述的输入信息影响,理论模型计算出的结果仍然不能完全准确地描述现实世界的情况,还会存在一定的误差。例如,在追踪车辆轨迹数据时,车辆的轮胎可能会打滑,或者粗糙的地面可能会降低其移动速度。这些不可预测的因素可能会影响预测结果的准确性。因此,每个预测步骤后都需要添加一些新的随机项 w ⃗ k \vec w_{k} wk,以模拟与"世界"相关的所有不确定性。
在此场景中, w ⃗ k \vec w_{k} wk包含四个误差项,分别对应 x ⃗ k \vec x_k xk中的四个状态,每个状态的误差项都是一个随机变量,服从均值为0的正态分布(高斯分布)。
当定义多个随机变量的方差时,除了随机变量本身的方差,随机变量之间可能存在一定的相关性,通常会采用协方差矩阵来表示,记为字母 Q Q Q,即 w ⃗ k ∼ N ( 0 ; Q k ) \vec w_k \sim N(0;Q_k) wk∼N(0;Qk)。 Q Q Q矩阵也被称为系统过程噪声的协方差矩阵。
在引入 B u ⃗ k − 1 B \vec u_{k-1} Buk−1和 w ⃗ k − 1 \vec w_{k-1} wk−1之后,第 k k k帧的车辆状态估计可以表示为:
x ^ k − = F x ^ k − 1 + B u ⃗ k − 1 + w ⃗ k − 1 \hat x^-_k = F \hat x_{k-1}+B \vec u_{k-1} +\vec w_{k-1} x^k−=Fx^k−1+Buk−1+wk−1
这就是卡尔曼滤波的系统状态方程。注意,在这里使用 x ^ k − \hat x^-_k x^k−来表示 k k k时刻 x ⃗ k \vec x_k xk的预测值(带有负号是因为它还未经过第二步的修正)。
状态方程表示的是从上一帧的状态估计 x ^ k − 1 \hat x_{k-1} x^k−1和输入信息 u ⃗ k − 1 \vec u_{k-1} uk−1经过一定的数学关系,推导出下一帧的状态 x ^ k \hat x_k x^k。
在车辆轨迹数据中,大多数情况下可能并不能知道额外输入的信息 u ⃗ k \vec u_{k} uk,这一项也可以省略。此时,状态方程可以简化为:
x ^ k − = F x ^ k − 1 + u ⃗ k − 1 \hat x^-_k = F \hat x_{k-1}+\vec u_{k-1} x^k−=Fx^k−1+uk−1
此时,还需要计算 x ⃗ k \vec x_k xk四个状态之间的协方差,以便后续对状态进行更新修正,它可以用来表示这四个状态之间的相关性。这个矩阵用字母 P P P表示,它也被称为状态协方差矩阵。
P k = [ Σ a a Σ a b Σ a ζ Σ a η Σ b a Σ b b Σ b ζ Σ b η Σ ζ a Σ ζ b Σ ζ ζ Σ ζ η Σ η a Σ η b Σ η ζ Σ η η ] P_k=\begin{bmatrix} \Sigma_{aa} & \Sigma_{ab} & \Sigma_{a\zeta} & \Sigma_{a\eta} \\ \Sigma_{ba} & \Sigma_{bb} & \Sigma_{b\zeta} & \Sigma_{b\eta}\\ \Sigma_{\zeta a} & \Sigma_{\zeta b} & \Sigma_{\zeta\zeta} & \Sigma_{\zeta\eta}\\ \Sigma_{\eta a} & \Sigma_{\eta b} & \Sigma_{\eta\zeta} & \Sigma_{\eta\eta} \end{bmatrix} Pk= ΣaaΣbaΣζaΣηaΣabΣbbΣζbΣηbΣaζΣbζΣζζΣηζΣaηΣbηΣζηΣηη
其中, Σ a a \Sigma_{aa} Σaa代表变量 a a a的方差, Σ a b \Sigma_{ab} Σab代表 a a a与 b b b之间的协方差,以此类推。
在系统状态方程中,由简化后的状态方程可以推出 k k k与 k − 1 k-1 k−1帧之间状态协方差矩阵 P P P的关系:
P k − = F Cov ( x ^ k − 1 , x ^ k − 1 ) F ⊺ + Cov ( w k , w k ) = F P k − 1 F ⊺ + Q k \begin{align*} P^-_k&=F \text{Cov}(\hat x_{k-1}, \hat x_{k-1}) F^\intercal+\text{Cov}(w_k, w_k)\\ &=F P_{k-1} F^\intercal+Q_k \end{align*} Pk−=FCov(x^k−1,x^k−1)F⊺+Cov(wk,wk)=FPk−1F⊺+Qk
上式中,用 P k − P^-_k Pk−表示对 k k k时刻状态协方差矩阵的预测值(还没有进行下一步的更新),用 P k − 1 P_{k-1} Pk−1表示 k − 1 k-1 k−1时刻状态协方差矩阵的估计值,用 Q k Q_k Qk表示系统过程噪声的协方差矩阵。
上述的过程如下所示。
- 卡尔曼滤波的更新阶段
在车辆轨迹的场景下,GPS定位系统是一种传感器,它返回的经纬度信息是观测到的数值,一定程度上它反映了车辆真实状态的信息。这里可以用 z k z_k zk来表示 k k k时刻的传感器观测值,也就是轨迹数据中的经纬度位置信息。
z k = [ l o n k l a t k ] z_k=\begin{bmatrix} lon_k \\ lat_k \end{bmatrix} zk=[lonklatk]
在卡尔曼滤波的更新阶段,需要对前面预测的状态 x ^ k − \hat x^-_k x^k−和当前的观测数据 z k z_k zk进行比较,从而更新状态估计和误差协方差。
数据中所观测到的GPS传感器数值 z k z_k zk应该与车辆状态有关,实际上,经纬度就是前面所定义的车辆状态中的前两个状态,所以可以用一个矩阵 H H H来表示这种关系,得到推出观测方程,也就是从前面所预测的车辆状态估计出应该从传感器处观测到的数值。
z ^ k = [ a k b k ] + v ⃗ k = [ 1 0 0 0 0 1 0 0 ] [ a k b k ζ k η k ] + v ⃗ k = H x ⃗ k + v ⃗ k \hat z_k = \begin{bmatrix} a_k \\ b_k \end{bmatrix}+ \vec v_k = \begin{bmatrix} 1 &0&0&0 \\ 0&1&0&0 \end{bmatrix} \begin{bmatrix} a_k \\ b_k \\ \zeta_k \\ \eta_k \end{bmatrix} +\vec v_k = H \vec x_{k} +\vec v_k z^k=[akbk]+vk=[10010000] akbkζkηk +vk=Hxk+vk
在观测方程中,与前面状态方程类似,引入 v ⃗ k ∼ N ( 0 ; R k ) \vec v_k \sim N(0;R_k) vk∼N(0;Rk)来代表传感器测量时产生的噪声误差, R R R代表测量噪声协方差。
在卡尔曼滤波的更新阶段,需要将预测的状态估计 x ^ k \hat x_k x^k与观测值 z k z_k zk进行比较,从而得到更精确的状态估计。可以通过计算两者之间的误差来得到更精确的状态估计,这个误差可以用 e k e_k ek来表示。
e k = z k − z ^ k = z k − H x ^ k − e_k = z_k - \hat z_k = z_k - H \hat x^-_{k} ek=zk−z^k=zk−Hx^k−
卡尔曼滤波的更新阶段,则依据卡尔曼增益来更新状态估计和误差协方差。修正状态的公式如下:
x ^ k = x ^ k − + K k e k = x ^ k − + K k ( z k − H x ^ k − ) \hat x_k = \hat x^-_k +K_k e_k = \hat x^-_k +K_k (z_k-H \hat x^-_{k}) x^k=x^k−+Kkek=x^k−+Kk(zk−Hx^k−)
其中, K k K_k Kk就是卡尔曼增益。实际上,在更新的这一步可以选择更相信上一步理论模型所预测的结果,还是更相信传感器检测到的数值。而 K k K_k Kk的出现就是为了调整这两者之间更相信哪一个,也可以理解为权重,其计算方法如下。
K k = P k − H ⊺ ( H P k − H ⊺ + R ) − 1 K_k = P^-_k H^\intercal (H P^-_k H^\intercal +R)^{-1} Kk=Pk−H⊺(HPk−H⊺+R)−1
下一步,需要更新协方差矩阵,以便在下一帧中对轨迹进行预测:
P k = ( I − K k H ) P k − P_k = (I-K_k H) P^-_k Pk=(I−KkH)Pk−
上述的过程如下所示。
纵观整个卡尔曼滤波算法,有 R R R和 Q Q Q两个协方差矩阵的大小需要设定,而 K k K_k Kk的取值则是由 R R R和 Q Q Q来决定。
可以看到, K k K_k Kk的取值大小与 R R R有一定的关系。如果 R R R取得越小,那么 K k K_k Kk的取值就越大,也就是说更相信传感器检测到的数值;反之,如果 R R R取得越大,那么 K k K_k Kk的取值就越小,更新后的状态就更加接近于理论模型所预测的结果。
基于卡尔曼滤波的轨迹数据平滑处理
基于前面所介绍的卡尔曼滤波算法,可以对车辆轨迹数据进行平滑处理,从而减少数据噪声的影响。利用pykalman包,可以很方便地实现卡尔曼滤波算法。但在使用时,还需要定义卡尔曼滤波的模型,并将轨迹数据输入到模型中。将这一过程封装为函数:
import numpy as np
from pykalman import KalmanFilter
def Kalman_traj_smooth(data, process_noise_std, measurement_noise_std):
'''
使用卡尔曼滤波器对轨迹数据进行平滑处理
参数
----
data : DataFrame
轨迹数据,包含time、lon、lat三列
process_noise_std : float or list
过程噪声标准差,如果是list,则认为是过程噪声协方差矩阵的对角线元素
measurement_noise_std : float or list
观测噪声标准差,如果是list,则认为是观测噪声协方差矩阵的对角线元素
返回
----
data : DataFrame
平滑后的轨迹数据
'''
# 拷贝数据,避免修改原始数据
data = data.copy()
# 轨迹数据转换为numpy数组
observations = data[['lon', 'lat']].values
timestamps = data['time']
# F-状态转移矩阵
transition_matrix = np.array([[1, 0, 1, 0],
[0, 1, 0, 1],
[0, 0, 1, 0],
[0, 0, 0, 1]])
# H-观测矩阵
observation_matrix = np.array([[1, 0, 0, 0],
[0, 1, 0, 0]])
# R-观测噪声协方差矩阵
# 如果measurement_noise_std是list,则认为是观测噪声协方差矩阵的对角线元素
if isinstance(measurement_noise_std, list):
observation_covariance = np.diag(measurement_noise_std)**2
else:
observation_covariance = np.eye(2) * measurement_noise_std**2
# Q-过程噪声协方差矩阵
# 如果process_noise_std是list,则认为是过程噪声协方差矩阵的对角线元素
if isinstance(process_noise_std, list):
transition_covariance = np.diag(process_noise_std)**2
else:
transition_covariance = np.eye(4) * process_noise_std**2
# 初始状态
initial_state_mean = [observations[0, 0], observations[0, 1], 0, 0]
# 初始状态协方差矩阵
initial_state_covariance = np.eye(4) * 1
# 初始化卡尔曼滤波器
kf = KalmanFilter(
transition_matrices=transition_matrix,
observation_matrices=observation_matrix,
initial_state_mean=initial_state_mean,
initial_state_covariance=initial_state_covariance,
observation_covariance=observation_covariance,
transition_covariance=transition_covariance
)
# 使用卡尔曼滤波器进行平滑处理
# 先创建变量存储平滑后的状态
smoothed_states = np.zeros((len(observations), 4))
# 将初始状态存储到平滑后的状态中
smoothed_states[0, :] = initial_state_mean
# 从第二个状态开始,进行循环迭代
current_state = initial_state_mean
current_covariance = initial_state_covariance
for i in range(1, len(observations)):
# 计算时间间隔
dt = (timestamps.iloc[i] - timestamps.iloc[i - 1]).total_seconds()
# 更新状态转移矩阵
kf.transition_matrices = np.array([[1, 0, dt, 0],
[0, 1, 0, dt],
[0, 0, 1, 0],
[0, 0, 0, 1]])
# 根据当前状态的预测情况与观测结果进行状态估计
current_state, current_covariance = kf.filter_update(
current_state, current_covariance, observations[i]
)
# 将平滑后的状态存储到变量中
smoothed_states[i, :] = current_state
# 将平滑后的数据结果添加到原始数据中
data['lon'] = smoothed_states[:, 0]
data['lat'] = smoothed_states[:, 1]
return data
上面的函数中,定义了一个卡尔曼滤波器,以及将轨迹数据输入到卡尔曼滤波器中的方法,得到平滑后的轨迹数据。在定义卡尔曼滤波器时,需要定义状态转移矩阵、观测矩阵、观测噪声协方差矩阵、过程噪声协方差矩阵、初始状态、初始状态协方差矩阵等参数,这些参数决定了卡尔曼滤波器的工作效果。
在车辆轨迹数据中,除了经纬度信息以外,可能还会自带有速度、方向角等,这些信息也可以用来辅助卡尔曼滤波器的工作,但也需要对应地修改卡尔曼滤波器的参数。
在上面的函数中,卡尔曼滤波器的有些参数是可以调整的,比如观测噪声标准差(measurement_noise_std)、过程噪声标准差(process_noise_std)等。如果更加相信数据观测的结果,可以适当减小观测噪声标准差;如果更加相信理论模型的结果,可以适当减小过程噪声标准差。通常,这两者之间的取值比例关系会影响平滑后的轨迹数据的平滑程度。
接下来,使用上面的函数对一条轨迹数据进行平滑处理。首先,读取测试数据,代码如下:
import pandas as pd
traj = pd.read_csv('Data/测试轨迹.csv')
traj['time'] = pd.to_datetime(traj['time'])
traj
结果如下所示。这一数据包含了一车一次出行的轨迹数据。
用前面所写的卡尔曼滤波算法对轨迹数据进行平滑处理:
traj_smoothed = Kalman_traj_smooth(traj,
process_noise_std = 0.01,
measurement_noise_std = 1)
此时得到的轨迹数据格式与原始数据一致,只是经纬度信息发生了变化,变成了平滑后的结果。可以将平滑后的轨迹数据与原始轨迹数据进行对比,代码如下:
import matplotlib.pyplot as plt
# 显示中文
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig = plt.figure(1,(5,5),dpi=300)
ax = plt.subplot(111)
#绘制原始轨迹与平滑后的轨迹
plt.plot(traj['lon'], traj['lat'],label = '原始轨迹')
plt.plot(traj_smoothed['lon'], traj_smoothed['lat'],label = '平滑后轨迹')
plt.legend()
plt.show()
结果如下所示。可以看到,平滑后的轨迹数据相比原始轨迹数据更加平滑。