当前位置: 首页 > news >正文

神奇的卡尔曼滤波,目标追踪的福音

前言

        卡尔曼滤波算法由匈牙利数学家Kalman提出,主要基于线性系统提出。这里我们将其用于汽车跟踪,并对其基本原理进行介绍。


神奇的卡尔曼滤波,目标追踪的福音

1. 背景知识

1.1 时间序列模型

1.2. 滤波

1.3. 线性动态系统 

2. 卡尔曼滤波理论知识 

2.1 预测

2.1.1 第一条公式:状态转移

2.1.2 第二条公式:协方差矩阵

2.2 更新

2.2.1 第三条公式:卡尔曼增益

2.2.2 第四条公式:观测矩阵

2.2.3 第五条公式:

3. 卡尔曼滤波的应用

3.1 代码

        3.1.1 获取场景数据。

        3.1.2 初始化

        3.1.3 开始预测与更新

        3.1.4 画图 


1.背景知识

1.1 时间序列模型

时间序列模型都可以用如下示意图表示:

这个模型包含两个序列,一个是黄色部分的状态序列,用X表示,一个是绿色部分的观测序列(又叫测量序列、证据序列、观察序列,不同的书籍有不同的叫法,在这里统一叫观测序列。)用Y表示。状态序列反应了系统的真实状态,一般不能被直接观测,即使被直接观测也会引进噪声;观测序列是通过测量得到的数据,它与状态序列之间有规律性的联系。

上面序列中,假设初始时间为t_{1}, 则X_{1},Y_{1}t_{1}时刻的状态值和观测值,X_{2},Y_{2}t_{2}时刻的状态值和观测值...,即随着时间的流逝,序列从左向右逐渐展开。  

常见的时间序列模型主要包括三个:隐尔马尔科夫模型,卡尔曼滤波,粒子滤波

1.2. 滤波

时间序列模型中包括预测和滤波两步

  • 预测:指用当前和过去的数据来求取未来的数据。对应上述序列图中,则是利用t_{1}时刻X_{1},Y_{1}的值,估计t_{2}时刻X_{2}值。
  • 滤波是用当前和过去的数据来求取当前的数据。对应上述序列图中,则是先通过上一步的预测步骤得到X_{2}的一个预测值,再利用t_{2}时刻Y_{2}的值对这个预测值进行纠正,得到最终的X_{2}估计值。(通俗讲,就是通过X_{1}预测一个值, 通过传感器测量一个值Y_{2}, 将两者进行融合得到最终的X_{2}值)        

1.3. 线性动态系统 

        卡尔曼滤波又称为基于高斯过程的线性动态系统(Linear Dynamic System, LDS)。

        · 这里的高斯是指:状态变量X_{t}和观测变量Y_{t}都符合高斯分布;(为什么是高斯分布?因为卡尔曼滤波估计的结果是一个概率,概率不是一个固定的值,而是一个范围,而这个范围符合高斯分布。)

        · 这里的线性是指:X_{t}可以通过X_{t-1}线性表示,Y_{t}可以通过X_{t}线性表示;

如果用数学表达式来表达这两层含义如下:

上面表达式中F是一个矩阵,常称作状态转移矩阵,保证了X_{t}X_{t-1}的线性关系(线性代数中,矩阵就是线性变换);w_{t-1}常称作噪声,其服从均值为0,方差为Q的高斯分布,保证了X_{t}服从高斯分布(因为高斯分布加上一个常数后依然是高斯分布)。

同样的关于X_{t}Y_{t},也可以得到如下表示, 其中矩阵H称作状态空间到观测空间的映射矩阵, rtrt称作噪声,其服从高斯分布:

2.卡尔曼滤波理论知识 

以下公式需要用到的变量含义如下:

  • x\hat{}_{k}:状态变量
  • \hat{P}_{k}: 状态变量的协方差矩阵
  • F_{k}:状态转移矩阵
  • B_{k}:控制矩阵
  • u_{k}:控制向量
  • w_{k}: 状态变量的噪声矩阵
  • Q_{k}:协方差矩阵的噪声矩阵

        学习卡尔曼滤波只需要弄懂下图这五个公式与他们之间的关系。

         预测中的两条公式,是通过上一时刻的状态预测下一时刻的状态的,通过这两条公式,我们得到的是带减号和上标的,这表示,这并不是最佳的估计值,减号表示它们还欠缺点什么东西,这个欠缺的东西呢就是通过观测值里面带来的信息,因为我们还需要用观测值中带来的信息修正误差,从而得到最优估计值,这正是更新这一步要做的事情。


        更新这三个公式,第一条公式是用来求卡尔曼增益K的,卡尔曼增益K是用来协同当前状态的观测值Z来更新XP的,经过更新后的值才是最佳的估计值,所以他们是不带减号下标的。


        我们可以看出,五条公式有三条(\hat{P}_{k}w_{k}Q_{k})都是有噪声尾随其后的,这些噪声不确定性会化为数值根据每一次迭代不断的更新,并用来修正充满了噪声观测值,从而使得观测值真实可靠。

2.1 预测

2.1.1 第一条公式:状态转移

        我们假设有这样一辆小车在以可变化的速度向前行驶,这个时候它有两个状态量:位置position和速度velocity(此后简略为p和v)。

        若当前时刻为t,则x_{t}为当前时刻的状态量,它有两个属性p_{t}v_{t},分别表示当前状态的位置和速度。 上面我们也说了滤波是预测当前的状态,那么我们如何预测当前的状态?自然是用过去的状态量预测当前的状态量,于是我们又有了x_{t-1},它也有两个属性p_{t-1}v_{t-1}

        至此,我们根据位置与速度,速度与时间之间的关系,简单的推出它们的数学表达公式(左边的两个式子),如果我们用线性代数的知识:矩阵,来描述这个式子,那么它将变成一种更具有计算与表达价值的矩阵形式(右边的式子)。

        但我们可以看出他依旧不是那么的简洁,这不符合优美的数学。所以我们接下来用F_{t}状态转移矩阵,它代表我们如何从上一时刻的状态来推测当前时刻的状态,和B_{t}控制矩阵,它表示上一时刻的状态控制量u如何作用于当前状态。如下图:

         x之所以戴了尖帽子是因为这只是一个估计量,因为我们无法永远预测汽车的真实状态,只能尽量可能的估计推测出真实状态,这是因为真实状态包含了非常多的噪声,比如汽车突然减速加速,或者掉进了一个坑...等等。噪声越大,不确定性也就越大,所以我们待会还需要根据协方差矩阵来计算这个不确定性究竟有多大。

2.1.2 第二条公式:协方差矩阵

        协方差矩阵P(这次是大写的)是符合高斯分布的,它在卡尔曼滤波中是用来表示p,v两个状态量之间的相关性,比如正相关(我大你也大)、负相关(我大你却小),不相关(我大不大都不管你的事),并且计算出系数,也就是如果是正相关,那么相关的程度用数字表达是多少。具体如下图:

        那么下一个问题就是,我们通过让上一刻的协方差矩阵P_{t-1} 推测到下一刻的协方差矩阵P_{t}呢?这里可以通过协方差的性质:

来推出:(加上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()

完毕!

如果大家觉得本文对你有帮助的话,麻烦点赞+收藏,谢谢大家!

相关文章:

  • Web服务器配置与管理
  • [Linux]进程信号(信号入门 | 信号产生的方式 | 信号捕捉初识)
  • 【精通内核】CPU控制并发原理CPU中断控制内核解析
  • 【深度学习】Python 快速入门
  • 10、Java 方法的递归调用详解(递归调用的分析和案例:阶乘、斐波那契、猴子吃桃)
  • 【图解HTTP】HTTP首部
  • 还搞不明白“响应式编程”,那得看这篇
  • “蔚来杯“2022牛客暑期多校训练营10 EFI题解
  • fetch的简单使用
  • 程序员应了解的那些事(111)~进程、线程及堆栈关系
  • NTU 课程笔记:向量和矩阵
  • ElasticSearch Python API教程
  • Linux (五)- mv 命令
  • 20220831-basic-cuda-interface
  • 3.4背景图片位置
  • (ckeditor+ckfinder用法)Jquery,js获取ckeditor值
  • 10个确保微服务与容器安全的最佳实践
  • Akka系列(七):Actor持久化之Akka persistence
  • Angularjs之国际化
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • Java|序列化异常StreamCorruptedException的解决方法
  • LeetCode算法系列_0891_子序列宽度之和
  • SpiderData 2019年2月23日 DApp数据排行榜
  • 初识 beanstalkd
  • 大整数乘法-表格法
  • - 概述 - 《设计模式(极简c++版)》
  • 关于使用markdown的方法(引自CSDN教程)
  • 快速体验 Sentinel 集群限流功能,只需简单几步
  • 理解在java “”i=i++;”所发生的事情
  • 三栏布局总结
  • 使用parted解决大于2T的磁盘分区
  • 走向全栈之MongoDB的使用
  • 阿里云移动端播放器高级功能介绍
  • !$boo在php中什么意思,php前戏
  • # Java NIO(一)FileChannel
  • #define
  • #在 README.md 中生成项目目录结构
  • $jQuery 重写Alert样式方法
  • %@ page import=%的用法
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (02)Hive SQL编译成MapReduce任务的过程
  • (12)目标检测_SSD基于pytorch搭建代码
  • (33)STM32——485实验笔记
  • (Note)C++中的继承方式
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (四)图像的%2线性拉伸
  • (转)iOS字体
  • (转)大型网站架构演变和知识体系
  • (转)负载均衡,回话保持,cookie
  • (转)树状数组
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • ./configure,make,make install的作用
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .Net 6.0 处理跨域的方式
  • .net Application的目录