卡尔曼滤波原理及实现

滤波笔记一:卡尔曼滤波(Kalman Filtering)详解 卡尔曼滤波法的详解,包括公式推导,算法迭代步骤以及python源代码。 阅读详情
AI摘要

本文介绍卡尔曼滤波的基本原理及其在物体跟踪中的应用。通过实例详细解析卡尔曼滤波器的工作流程,包括先验估计与后验估计,并展示了如何使用OpenCV实现卡尔曼滤波。

原文链接:http://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/

卡尔曼滤波原理及实现

前一段时间,做项目研究了一下卡尔曼滤波,并且在项目当中实现了一

个物体跟踪的功能,所以,借着新鲜劲儿,本次博客对卡尔曼滤波进行一次整理。

  • 卡尔曼滤波是什么
  • 卡尔曼滤波能做什么
  • 卡尔曼滤波的工作原理
  • 举个栗子

卡尔曼滤波是什么

卡尔曼滤波适用于估计一个动态系统的最优状态。即便是观测到的系统状态参数含有噪声,观测值不准确,卡尔曼滤波也能够完成对状态真实值的最优估计。网上大多数的教程讲到卡尔曼的数学公式推导,会让人很头疼,难以把握其中的主线和思想。所以我参考了国外一位学者的文章,讲述卡尔曼滤波的工作原理,然后编写了一个基于OpenCV的小程序给大家做一下说明。下面的这个视频请大家先直观地看看热闹吧~
角度跟踪视频

卡尔曼滤波能做什么

假设我们手头有一辆DIY的移动小车。这辆车的外形是这样的:
这里写图片描述
这辆车可以在荒野移动,为了便于对它进行控制,需要知道它的位置以及移动速度。所以,建立一个向量,用来存储小车的位置和速度

xk−→=(p→,v→)

其实,一个系统的状态有很多,选择最关心的状态来建立这个状态向量是很重要的。例如,状态还有水库里面水位的高低、炼钢厂高炉内的温度、平板电脑上面指尖触碰屏幕的位置等等这些需要持续跟踪的物理量。好了,回归到正题,小车上面安装了GPS传感器,这个传感器的精度是10米。但是如果小车行驶的荒野上面有河流和悬崖的话,10米的范围就太大,很容易掉进去进而无法继续工作。所以,单纯靠GPS的定位是无法满足需求的。另外,如果有人说小车本身接收操控着发送的运动指令,根据车轮所转动过的圈数时能够知道它走了多远,但是方向未知,并且在路上小车打滑车轮空转的现象绝对是不可避免。所以,GPS以及车轮上面电机的码盘等传感器是间接地为我们提供了小车的信息,这些信息包含了很多的和不确定性。如果将所有这些信息综合起来,是否能够通过计算得到我们更想要的准确信息呢?答案是可以的!
这里写图片描述

 

卡尔曼滤波的工作原理

1.先验状态估计

以之前我们创建的状态变量为例,

x→=[pv]


下图表示的是一个状态空间图,为了研究方便,假如小车在一条绝对笔直的线路上面行驶,其位置和速度的方向是确定的,不确定的是大小。
这里写图片描述
卡尔曼滤波器发生作用的前提是小车的速度和位置量在其定义域内具有正态的高斯分布规律。每一个变量都具有一个平均值μ(这个值在变量的概率密度函数分布图的最中心位置,代表该数值是最可能发生的)和σ2(这个数值代表方差,表示变量的不确定性程度)。下图中横纵坐标分别是位置和速度,相互独立,也就是已知其中一个变量,无法推断出另外一个的结果或者变化规律。
这里写图片描述
下面请看另外一种情况:下图的位置和速度的分布不再像上图一样了,速度越大,位置也越大,这种情况下,两个变量相关(其实这种情况是很有可能发生的,因为速度越大的话,可能小车就远离我们,速度越小,表明靠近我们)。那么,通过协方差矩阵Σ就能够将几个变量的相关程度描述清楚。矩阵当中的某一个元素Σij表示的是状态向量第i个变量和第j个变量之间相关程度,Σij=Cov(xi,xj)=E[(xi−μi)(xj−μj)]
这里写图片描述
要注意的是协方差矩阵是一个对称阵。感兴趣的童鞋可以深入研究一下,协方差矩阵的特征值和特征向量所具有的几何意义:the directions in which the data varies the most.啥意思,就是哪个方向变化快,特征向量指哪儿。
协方差的特征向量是什么
协方差的几何意义
两个链接提供给大家,有兴趣的好好看看,知识点融会贯通了才有意思!
这里写图片描述
下面是重头戏:数学描述部分:
定义:

x^k=[positionvelocity]

 

Pk=[ΣppΣvpΣpvΣvv](1)


x→k是状态向量。Pk称为协方差。
下图描述了一个k-1时刻x→k−1和k时刻x→k的状态:我们不清楚到底哪一个状态是最准确的,但是卡尔曼滤波的状态更新方程会根据前一时刻的结果预测出一个下一时刻的分布情况:
这里写图片描述
定义Fk,这个矩阵表示的是前后两个状态之间是如何转换的:
pk=pk−1+δtvk−1
vk= vk−1
写成矩阵的形式就是:
x^k=[10δt1]x^k−1=Fkx^k−1(2)
状态向量的更新有了,但是还缺少状态向量之间相关性的更新,也就是协方差矩阵。

Cov(x)=Σ

Cov(Ax)=AΣAT

结合(2)得到:x^k=Fkx^k−1(先验状态估计向量)
Pk=FkPk−1FTk(先验状态估计协方差矩阵)
这里写图片描述
外部确定性影响
到目前为止,我们只是关心系统内部状态的更新,但是如果在外部对系统产生了确定影响,比如:小车的操控者发出一条刹车的指令,我们通过建模该指令,假如小车的加速度为a,根据运动学的公式,得到:
p^k=pk−1+δtvk−1+12aδt2
vk=vk−1+aδt 写成矩阵的形式就是:
x^k=Fkxk−1+[δt22δt]a=Fkxk−1+Bku→k
其中,Bk称为控制矩阵,uk−→是控制向量,由于本例子当中的控制实际上只包含了加速度,所以该向量包含元素的个数为1。
外部不确定性影响
现实世界往往是不那么好描述清楚的,就是存在不确定的外部影响,会对系统产生不确定的干扰。我们是无法对这些干扰进行准确的跟踪和量化的。所以,除了外界的确定项,还需要考虑不确定干扰项wk。
这里写图片描述
从而,得到最终先验估计的更新方程:
x^k=Fkxk−1+Bku→k+wk
每一次的状态更新,就是在原来的最优估计的基础上面,下一次的状态落在一个新的高斯分布区域,从坐标系上面看就像是一团云状的集合,最优的估计就在这个云团当中的某一处。所以,我们首先要弄清楚这个云团具有什么样的性质,也就是使用过程激励噪声协方差Qk来描述不确定干扰。
这里写图片描述
考虑到不确定的干扰之后,和不考虑这一因素相比,状态的期望不变,只是协方差产生变化,即wk的期望为0。
这里写图片描述
到此,先验估计的过程结束,总结一下,形成如下的公式:
x^k=Fkx^k−1+Bku→k+wk
P^k=FkP^k−1FTk+Qk(3)
重点来了:
先验估计x^k取决于如下三部分:一部分是上一次的最优估计值,一部分是确定性的外界影响值,另一部分是环境当中不确定的干扰。先验估计协方差矩阵Pk,首先是依据第k-1次卡尔曼估计(后验估计)的协方差矩阵进行递推,再与外界在这次更新中可能对系统造成的不确定的影响求和得到。

 

2.后验估计(量测更新)

到此,利用xk^

和Pk能够对系统进行粗略的跟踪,但是还不完善。因为安装在系统上面的传感器会对系统的状态进行测量反馈,纠正估计。下面看看传感器的量测更新是怎么样对最终的估计产生影响的。下图说明的是状态空间向观测空间的映射。
在这里需要说明一下,卡尔曼滤波器的观测系统的维数小于等于动态系统的维数,即观测量可以少于动态系统中状态向量所包含的元素个数。
这里写图片描述
注意,传感器的输出值不一定就是我们创建的状态向量当中的元素,有时候需要进行一下简单的换算。即使是,有可能单位也不对应,所以,需要一个转换。这个转换就是矩阵Hk,在一些文献当中也被称作状态空间到观测空间的映射矩阵。
这里写图片描述
得到的公式如下:
μ→expected=Hkx^k
Σexpected=HkPkHTk
那么,传感器对系统某些状态的测量真的准确吗?是不是也会有偏差呢?答案是肯定的。系统在某一个状态下,传感器输出一组观测值,当系统在另一个状态下,传感器会输出另外的观测值。反过来想,就是说根据读取到的观测向量,可以推测系统的实际状态。由于的存在,不同的系统状态造成当前的观测值的概率是不同的。所以,还需要一个观测噪声向量以及观测噪声协方差来衡量测量水平,我们将它们分别命名为v→k和Rk。下面的两张图说明这一点。
这里写图片描述
这里写图片描述
z→t=Hkx^k+v→k
观测向量zk→服从高斯分布,并且其平均值认为就是本次的量测值(z1,z2)。
下图看到的是两个云团,一个是状态预测值,另一个是观测值。那么到底哪一个具体的结果才是最好的呢?现在需要做的是对这两个结果进行合理的取舍,通过一种方法完成最终的卡尔曼预测。即:从图中粉红色云团和绿色云团当中找到一个最合适的点。
这里写图片描述
问题来了,怎么找?
首先来直观理解一下:考察观测向量zk→和先验估计x^k:存在两个事件:事件1传感器的输出是对系统状态真实值的完美测量,丝毫不差;事件2先验状态估计的结果就是系统状态真实值的完美预测,也是丝毫不差。但是大家读到这里心里非常清楚的一点是:两个事件的发生都是概率性的,不能完全相信其中的任何一个!!!!!!!
如果我们具有两个事件,如果都发生的话,从直觉或者是理性思维上讲,是不是认定两个事件发生就找到了那个最理想的估计值?好了,抽象一下,得到:两个事件同时发生的可能性越大,我们越相信它!要想考察它们同时发生的可能性,就是将两个事件单独发生的概率相乘。
两个云团的乘法
那么,下一步就是对两个云团进行重叠,找到重叠最亮的点(实际上我们能够把云团看做一帧图像,这帧图像上面的每一个像素具有一个灰度值,灰度值大小代表的是该事件发生在这个点的概率密度),最亮的点,从直觉上面讲,就是以上两种预测准确的最大化可能性。也就是得到了最终的结果。非常神奇的事情是,对两个高斯分布进行乘法运算,得到新的概率分布规律仍然符合高斯分布,然后就取下图当中蓝色曲线峰值对应的横坐标不就是结果了嘛。证明如下:
我们考察单随机变量的高斯分布,期望为μ,方差为σ2

,概率密度函数为:

N(x,μ,σ)=1σ2π−−√e−(x−μ)22σ2(4)


如果存在两个这样的高斯分布,只不过期望和方差不同,当两个分布相乘,得到什么结果?
这里写图片描述
是不是下式成立

N(x,μ0,σ0)⋅N(x,μ1,σ1)=?N(x,μ′,σ′)(5)


将(4)带入(5),对照(4)的形式,求得
μ′ = μ0+k(μ1- μ0)
σ′2=σ20- σ′2
其中,k=σ20σ20+σ21
以上是单变量概率密度函数的计算结果,如果是多变量的,那么,就变成了协方差矩阵的形式:

K=Σ0(Σ0+Σ1)−1(6)

 

μ→′=μ→0+K(μ→1−μ→0)(7)

 

Σ′=Σ0−KΣ0(8)


K称为卡尔曼增益,在下一步将会起到非常重要的作用。
好了,马上就要接近真相!
卡尔曼估计
下面就是对传感器的量测结果和根据k-1时刻预测得到的结果进行融合。(由于刚才的推导是两个单变量,并且处在同一个空间内,下面的推导为了方便起见,我们将先验状态估计对应的结果映射到观测向量空间进行统一的运算)
第一个要解决的问题是:(7)和(8)两个式子中那个平均值和方差都对应多少?

(μ0,Σ0)=(Hkx^k,HkPkHTk)(9)

 

(μ1,Σ1)=(zk,Rk)(10)


将(9)和(10)代入(6)、(7)、(8)三个式子,整理得到:

Hkx^′k=Hkx^k+K(zk→−Hkx^k)(11)

 

HkP′kHTk=HkPkHTk−KHkPkHTk(12)


其中,卡尔曼增益的表达式为:K=HkPkHTk(HkPkHTk+Rk)−1
最后一步,更新结果:
x^′k=x^k+K′(zk→-Hkx^k)(13)
P′k=Pk-K′HkPk(14)
K′=PkHTk(HkPkHTk+Rk)−1(15)
x^′k就是第k次卡尔曼预测结果,P′k是该结果的协方差矩阵,它们都作为下一次预测的初始值参与到新的预测当中。总体上来讲,卡尔曼滤波的步骤大致分为两步,第一步是时间更新,也叫作先验估计,第二步是量测更新,也叫作后验估计,而当前的卡尔曼过程的后验估计结果不仅可以作为本次的最终结果,还能够作为下一次的先验估计的初始值。下图是卡尔曼滤波的工作流程:
卡尔曼滤波工作流程
对卡尔曼滤波原理的理解,我参考了这篇文章,图片取自该文章,该文的图片和公式颜色区分是一大亮点,在此表示对作者的感谢。

 

举个栗子

这部分重点讲解一下利用OpenCV如何实现一个对三维空间内物体的二维平面跟踪。
背景:跟踪一个移动速度大小和方向大致保持不变的物体,检测该物体的传感器是通过对物体拍摄连续帧图像,经过逐个像素的分析计算,得到x、y方向的速度,将两个速度构成一个二维的列向量作为观测向量。
下面看一下OpenCV当中对卡尔曼滤波的支持和提供的接口说明。
OpenCV2.4.13-KalmanFilter
下面是参与到卡尔曼滤波的一些数据结构,它们代表的意义在其后面用英文进行了描述。
OpenCV将以下的成员封装在了KalmanFilter当中,我们使用时候,可以直接实例化一个对象,例如:

KalmanFilter m_KF;
  • 1
//CV_PROP_RW Mat statePre;           //!< predicted state (x'(k)): x(k)=A*x(k-1)+B*u(k)
//CV_PROP_RW Mat statePost;          //!< corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
//CV_PROP_RW Mat transitionMatrix;   //!< state transition matrix (A)
//CV_PROP_RW Mat controlMatrix;      //!< control matrix (B) (not used if there is no control)
//CV_PROP_RW Mat measurementMatrix;  //!< measurement matrix (H)
//CV_PROP_RW Mat processNoiseCov;    //!< process noise covariance matrix (Q)
//CV_PROP_RW Mat measurementNoiseCov;//!< measurement noise covariance matrix (R)
//CV_PROP_RW Mat errorCovPre;        //!< priori error estimate covariance matrix (P'(k)): P'(k)=A*P(k-1)*At + Q)*/
//CV_PROP_RW Mat gain;               //!< Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)
//CV_PROP_RW Mat errorCovPost;       //!< posteriori error estimate covariance matrix (P(k)): P(k)=(I-K(k)*H)*P'(k)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

KalmanFilter类的成员函数具有如下几个:

KalmanFilter
init
predict
correct
  • 1
  • 2
  • 3
  • 4

方法很简单,但是需要知道如何使用:
程序当中,我单独写了一个类,在我的计算线程(就是获取到量测结果的线程)当中对该类进行实例化并且调用其中的方法。量测结果存放在

m_dispacementVector[0] = m_xSpeed;
m_dispacementVector[1] = m_ySpeed;

当中。
步骤一:

CV_WRAP KalmanFilter( int dynamParams, int measureParams, int controlParams = 0, int type = CV_32F );

    /** @brief Re-initializes Kalman filter. The previous content is destroyed.

    @param dynamParams Dimensionality of the state.
    @param measureParams Dimensionality of the measurement.
    @param controlParams Dimensionality of the control vector.
    @param type Type of the created matrices that should be CV_32F or CV_64F.
     */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在卡尔曼滤波类的构造函数成员列表当中首先告知OpenCV过程状态向量的维度和观测向量的维度:

m_KF(4,2,0)
  • 1

对m_KF成员的初始化:

1状态向量初始化xk

 

我想对物体的位置信息和速度信息进行跟踪,由于是二维的,所以位置信息x、y方向两个变量,速度信息x、y方向两个变量,从而m_KF.statePre和m_KF.statePost是一个四维列向量。

state=⎡⎣⎢⎢⎢⎢xyvxvy⎤⎦⎥⎥⎥⎥


该向量在初始化时设置为零。

 

2状态转移矩阵初始化Fk

 

在计算机屏幕上面,我自定义了一个该物体的运动空间,具有横纵坐标,后面会看到这个空间。由于相机的帧率是30fps,所以相邻帧时间间隔δt≈30ms

,被测物体的实际速度大约为10mm/s,所以在如此短的时间内,该物体能够认为是做匀速直线运动,故得到状态转移方程

Fk=⎡⎣⎢⎢⎢10000100δt0100δt01⎤⎦⎥⎥⎥

 

m_KF.transitionMatrix = (Mat_<float>(4, 4) << 1, 0, 0.03, 0,0,1,0,0.03,0,0,1,0,0,0,0,1);
  • 1

3过程噪声激励协方差矩阵Qk

 

setIdentity(m_KF.processNoiseCov, Scalar::all(1e-5));//Q
  • 1

认为过程激励噪声比较弱,并且每个分量相互之间不存在相关系。

4观测矩阵Hk

 

setIdentity(m_KF.measurementMatrix);
  • 1

初始化得到:

Hk=[00001001]


由于传感器只是检测到了两个方向的速度,对位移没有检测,所以要将矩阵前两列初始化为0。

 

5预测估计协方差矩阵Pk

 

setIdentity(m_KF.errorCovPost, Scalar::all(1));
  • 1

初始化为单位阵。

6测量噪声协方差矩阵Rk

 

setIdentity(m_KF.measurementNoiseCov, Scalar::all(1e-1));//R
  • 1

步骤二:
先验估计:

m_prediction = m_KF.predict();
  • 1

步骤三:
后验估计:
首先需要告知卡尔曼滤波器最新的传感器数据:

    m_dispacementVector[0] = m_xSpeed;
    m_dispacementVector[1] = m_ySpeed;
  • 1
  • 2
m_pKalman->updateMeasurements(m_dispacementVector);
  • 1
  • 2
void kalmanFilter::updateMeasurements(double p[])
{
    m_measurement = (Mat_<float>(2, 1) << p[0], p[1]);
}
  • 1
  • 2
  • 3
  • 4

接着完成后验估计:

m_KF.correct(m_measurement);
  • 1

KalmanFilter.h:

#include <QObject>
#include "opencv2/video/tracking.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
class kalmanFilter:public QObject
{
    Q_OBJECT
public:
    kalmanFilter();
    ~kalmanFilter();
    void initKalman();
    void kalmanPredict();
    void updateMeasurements(double p[]);
    void kalmamCorrect();
    double *returnResult();
    void drawArrow(Point start, Point end, Scalar color, int alpha, int len);
private:
    KalmanFilter m_KF;
    Mat m_state;
    Mat m_postCorrectionState;
    Mat m_processNoise;
    Mat m_measurement;
    Mat m_img;
    Mat m_prediction;
    Point2f m_PointCenter;
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

KalmanFilter.cpp:

#include "kalmanFilter.h"
#include <iostream>
#include <fstream>
//CV_PROP_RW Mat statePre;           //!< predicted state (x'(k)): x(k)=A*x(k-1)+B*u(k)
//CV_PROP_RW Mat statePost;          //!< corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
//CV_PROP_RW Mat transitionMatrix;   //!< state transition matrix (A)
//CV_PROP_RW Mat controlMatrix;      //!< control matrix (B) (not used if there is no control)
//CV_PROP_RW Mat measurementMatrix;  //!< measurement matrix (H)
//CV_PROP_RW Mat processNoiseCov;    //!< process noise covariance matrix (Q)
//CV_PROP_RW Mat measurementNoiseCov;//!< measurement noise covariance matrix (R)
//CV_PROP_RW Mat errorCovPre;        //!< priori error estimate covariance matrix (P'(k)): P'(k)=A*P(k-1)*At + Q)*/
//CV_PROP_RW Mat gain;               //!< Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)
//CV_PROP_RW Mat errorCovPost;       //!< posteriori error estimate covariance matrix (P(k)): P(k)=(I-K(k)*H)*P'(k)

using namespace cv;
using namespace std;
kalmanFilter::kalmanFilter()
:m_KF(4,2,0)
, m_state(4,1,CV_32F)
, m_processNoise(4, 1, CV_32F)
, m_measurement(2,1,CV_32F)
, m_img(300, 300, CV_8UC3)
, m_PointCenter(m_img.cols*0.5f, m_img.rows)
{
    m_KF.transitionMatrix = (Mat_<float>(4, 4) << 1, 0, 0.03, 0,0,1,0,0.03,0,0,1,0,0,0,0,1);//A
    setIdentity(m_KF.measurementMatrix);
    setIdentity(m_KF.processNoiseCov, Scalar::all(1e-5));//Q
    setIdentity(m_KF.measurementNoiseCov, Scalar::all(1e-1));//R
    setIdentity(m_KF.errorCovPost, Scalar::all(1));
}
kalmanFilter::~kalmanFilter()
{
}

void kalmanFilter::initKalman()
{
    m_state = (Mat_<float>(4, 1) << 0,0,0,0);
    m_KF.statePre = (Mat_<float>(4, 1) << m_PointCenter.x, m_PointCenter.y, 0, 0);
    m_KF.statePost = (Mat_<float>(4, 1) << m_PointCenter.x, m_PointCenter.y, 0, 0);
    m_KF.measurementMatrix = (Mat_<float>(2, 4) << 0,0, 1, 0,0,0,0,1);
}

void kalmanFilter::updateMeasurements(double p[])
{
    m_measurement = (Mat_<float>(2, 1) << p[0], p[1]);
}

void kalmanFilter::kalmanPredict()
{
    m_prediction = m_KF.predict();
}

void kalmanFilter::kalmamCorrect()
{
    m_KF.correct(m_measurement);
     m_postCorrectionState = m_KF.statePost;
     //show the result
     m_img = Scalar::all(0);
     //measured result
     drawArrow(Point(m_KF.statePost.at<float>(0, 0), m_KF.statePost.at<float>(1, 0)),
         Point(m_KF.statePost.at<float>(0, 0) + m_measurement.at<float>(0, 0), m_KF.statePost.at<float>(1, 0) + m_measurement.at<float>(1, 0)),
         Scalar(0, 0, 255), 20, 15);//B G R
     putText(m_img, "measurement", Point(m_KF.statePost.at<float>(0, 0) + m_measurement.at<float>(0, 0), m_KF.statePost.at<float>(1, 0) + m_measurement.at<float>(1, 0)-5), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255), 2);
     //predicted result
     drawArrow(Point(m_KF.statePost.at<float>(0, 0), m_KF.statePost.at<float>(1, 0)),
         Point(m_KF.statePost.at<float>(0, 0) + m_KF.statePost.at<float>(2, 0), m_KF.statePost.at<float>(1, 0) + m_KF.statePost.at<float>(3, 0)),
         Scalar(0,255, 0), 20, 15);
     putText(m_img, "Kalman", Point(m_KF.statePost.at<float>(0, 0) + m_KF.statePost.at<float>(2, 0)-10, m_KF.statePost.at<float>(1, 0) + m_KF.statePost.at<float>(3, 0)-5), FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0), 2);
     imshow("Kalman", m_img);
     ofstream myfile("example.txt", ios::app);
     myfile << "measure" << "   "<<m_measurement.at<float>(0, 0) << "   " << m_measurement.at<float>(1, 0)<<"   ";
     myfile << "kalman" << "    " << m_KF.statePost.at<float>(2, 0) << "    " << m_KF.statePost.at<float>(3, 0) << endl;
}

double * kalmanFilter::returnResult()
{
    double result[4];
    for (int i = 0; i < 4; i++)
    {
        result[i] = m_postCorrectionState.at<double>(i, 1);
    }
    return result;
}

void kalmanFilter::drawArrow(Point start, Point end, Scalar color,int alpha,int len)
{
    const double PI = 3.1415926;
    Point arrow; 
    double angle = atan2((double)(start.y - end.y), (double)(start.x - end.x));
    line(m_img, start, end, color,2);
    arrow.x = end.x + len * cos(angle + PI * alpha / 180);
    arrow.y = end.y + len * sin(angle + PI * alpha / 180);
    line(m_img, end, arrow, color,1);
    arrow.x = end.x + len * cos(angle - PI * alpha / 180);
    arrow.y = end.y + len * sin(angle - PI * alpha / 180);
    line(m_img, end, arrow, color,1);
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

下面是录制的程序运行效果图(抱歉物体未显示):
这里写图片描述
参考资料:
卡尔曼滤波器OpenCV自带例子
教程
卡尔曼滤波的直觉理解

卡尔曼滤波介绍 卡尔曼滤波的简单介绍 阅读详情

相关推荐

卡尔曼滤波【Kalman Filter】详解

对于卡尔曼滤波器,我们几乎可以下这么一个定论:只要是存在不确定信息的动态系统,卡尔曼滤波就可以对系统下一步要做什么做出有根据的推测。即便有噪声信息干扰,卡尔曼滤波通常也能很好的弄清楚究竟发生了什么,找出现象间不易察觉的相关性。 因此卡尔曼滤波非常适合不断变化的系统,它的优点还有内存占用较小(只需保留前一个状态)、速度快,是实时问题和嵌入式系统的理想选择。

weixin_44606353的博客 4753

卡尔曼滤波(Kalman Filter)概念介绍及详细公式推导

卡尔曼滤波(Kalman filter)是一种高效率的递归滤波器(自回归滤波器),它能够从一系列的不完全及包含噪声的测量中,估计动态系统的状态。卡尔曼滤波会根据各测量量在不同时间下的值,考虑各时间下的联合分布,再产生对未知变数的估计,因此会比只以单一测量量为基础的估计方式要准。卡尔曼滤波得名自主要贡献者之一的鲁道夫·卡尔曼。​ 卡尔曼滤波的算法是二步骤的程序。

泽渊的博客 9万+

卡尔曼滤波原理详解与Python实现

卡尔曼滤波是一种高效的递归估计算法,通过"预测-更新"循环从含噪声测量中估计系统状态。其核心是线性状态空间模型和高斯噪声假设,利用卡尔曼增益动态调整预测与测量的权重。算法包含预测和更新两个步骤,通过协方差矩阵量化估计不确定性。文章给出了Python实现示例,在一维位置跟踪中,卡尔曼滤波显著降低了均方误差(观测误差1.0降至滤波误差0.1左右),验证了其在噪声环境下的优越估计性能。该算法广泛应用于导航、机器人、目标跟踪等领域。

电脑管家 817

卡尔曼滤波

目录1.卡尔曼滤波入门2.学卡尔曼滤波的必备知识2.1.状态空间表达式2.2.高斯分布2.3.方差2.4.超参数2.5.卡尔曼直观图解3.卡尔曼滤波3.1.卡尔曼公式理解3.2.调节超参数3.2.1.Q和R的取值3.2.2.P0和X0的取值3.2.3.卡尔曼滤波的使用...

qq_45113223的博客 5万+

卡尔曼滤波通俗易懂原理及其python实现

kalman滤波的核心思想 根据k-1时刻的最优估计值来预测k时刻的预测值 再根据k时刻的测量值和k时刻的预测值,得到此时刻的最佳估计值 循环迭代 需要说明的是,不管是根据上一时刻的预测值还是本时刻的测量值,都是有误差存在的 举例 假设我们要研究房间里的温度,以分钟为单位。因为房间里的温度变化不大,因此根据k-1时刻的最优估计值来预测k时刻的预测值时,我们由经验认为: = 但是这个预测是有误差的,我们把误差定义为高斯白噪声。另外,房间里放置...

希望和大家多多交流技术! 6419

高中生能看懂的详细通俗讲解卡尔曼滤波Kalman Filter原理及Python实现教程

接触过传感器数据的同学一定不可避免见到一个名字“卡尔曼滤波”。这是何方神圣?请看后面分晓。很多时候看不懂一个算法是因为里面很多概念上的问题你没了解,就直接看细节了当然看不懂。最关键的事就是你得先了解卡尔曼滤波到底有啥用,它的初衷是什么?接下来我就是想讲讲破解卡尔曼滤波的一些概念上的认知障碍这个事。 破解概念上的认知枷锁:卡尔曼滤波做的事 卡尔曼滤波做的事就是:举个例子,已知上个时刻飞机的位置,知道...

@司南牧|知乎|博客|易懂教程|李韬 3万+

卡尔曼滤波原理及c++实现

卡尔曼滤波是一种最优估计算法。用处:1)利用可测量值估算无法测量的量。2)对有测量噪声的物理量进行估计。常用于制导和导航、计算机视觉等。状态估计器[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wyl6RMng-1671698056041)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20221222145125380.png)]

ziyu2020的博客 2392

通俗理解卡尔曼滤波器原理及matlab中实现

卡尔曼滤波器又叫最佳线性滤波器,有很多好处,比如说:实现简单,而且又是一个纯时域的滤波器,不需要进行频域变换,所以在工程上有很多应用。 假设有一辆小汽车在路上行驶,用位置和速度来表示当前的状态,x=【】,u向前或向后的加速度, 另外驾驶员也可以踩刹车或者踩油门,u表示汽车的控制量,如果没有踩油门,也没有踩刹车,那么u=0。 p t就是间隔时间。 我们观察这两个公式,会发现:输出变量是两个输入变量的...

weixin_43434305的博客 1822

室内定位——卡尔曼滤波原理、Matlab与javascript实现

前言:在基于Beacon的室内定位中,由于获取的RSSI值中含有大量噪声,故对RSSI值进行去噪是非常有必要的。

GIS 5518

卡尔曼滤波原理与MATLAB实现完整指南

MATLAB是一种广泛应用于科学计算、信号处理和控制系统设计的高级语言和交互式环境。它以其矩阵运算为核心,语法简洁,功能强大。卡尔曼滤波的实现通常依赖于MATLAB的内置函数和脚本编写能力。一个典型的MATLAB函数结构如下:% MYKALMANFILTER 是一个示例函数,演示卡尔曼滤波的基本函数结构% 输入:% input1 - 初始状态估计% input2 - 初始协方差矩阵% 输出:% output1 - 更新后的状态估计% output2 - 更新后的协方差矩阵% 函数体。

weixin_34511754的博客 1102

修复zookeeper未授权访问漏洞

【代码】修复zookeeper未授权访问漏洞。

qq_28392947的博客 4068

卡尔曼滤波器原理及代码实现

卡尔曼滤波(Kalman filtering)是一种利用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的算法。由于观测数据中包括系统中的噪声和干扰的影响,所以最优估计也可看作是滤波过程。 卡尔曼滤波器也叫做最佳线性滤波器,其优点有很多:简单、占用内存小、速度快。同时卡尔曼滤波器还是时域滤波器(不需要进行频域的变换)。 用一个简单的例子来介绍卡尔曼滤波器的原理: 假设一台汽车在路上行驶,要描述这台汽车的状态一般通过位置和速度来表示: x表示当前状态,p代表当前位置,v代表当前速度。同时用

pomelo的博客 1839

专项1.CCS代码补全与快捷键优化全攻略

本文全面解析CCS开发环境的代码补全功能与快捷键优化技巧,包括内容辅助触发机制、快捷键自定义配置、编码效率提升方法等实用内容。通过详细步骤指导开发者优化CCS配置,实现高效的代码自动补全和个性化快捷键设置,显著提升嵌入式开发效率。

kotlin6android的博客 223

一文搞懂卡尔曼滤波:从原理到代码实现

卡尔曼滤波。(代码非常详细、非常齐全)1、卡尔曼滤波的含义是现时刻的最佳估计为在前一时刻的最佳估计的基础上根据现时刻的观测值作线性修正。2、卡尔曼滤波在数学上是一种线性最小方差统计估算方法,它是通过处理一系列带有误差的实际测量数据而得到物理参数的最佳估算。1、包含噪声的,对物体位置的观察序列(可能有偏差)预测出物体的位置的坐标及速度。3、这个估计可以是对当前目标位置的估计(滤波),也可以是对于将来位置的估计(预测),也可以是对过去位置的估计(插值或平滑)。

2503_93853778的博客 696

配置cts performance最优范围

这里写自定义目录标题cts例如此案例中,我们多测几次,发现Measured frame rate 的平均值为average=(380+389+380+371)/4=380 cts by VinterYoung Expected achievable frame rates for OMX.MTK.VIDEO.DECODER.HEVC video/hevc 1280x720: [75.0, 262.0]. Measured frame rate: [380.5645006622224, 389.3983545

o343196469的博客 1318

STM32CubeMx + FatFs 实战:SD卡DMA传输中断回调问题深度解析

本文深度解析了在STM32CubeMx环境下,结合FatFs文件系统并使用DMA进行SD卡读写时,程序可能出现的“卡死”问题。核心原因在于HAL库的弱函数回调机制未被正确处理,导致DMA传输完成中断后,状态机锁死。文章提供了两种实战修复方案:直接修改HAL库文件或在用户代码中重写回调函数,并给出了关键的配置检查点与调试技巧,帮助开发者彻底解决SD卡DMA传输中断回调失效的难题。

weixin_29176179的博客 330

卡尔曼滤波原理+matlab实现(有各个变量的详细解释噢!)

前言 写这篇文章是因为自己经过一段时间初步学完了卡尔曼滤波,把自己的一些理解记录下来,针对新手学习,把自己的曾经的困扰以及后来如何理解记录下来,希望能对你有一些启示。文中都用大白话阐述,能够便于理解,就是纯心得记录。因为本人也是小白一枚,如果文章中有出现错误的地方,希望大佬斧正。 ** 卡尔曼滤波解决了什么问题? ** 卡尔曼滤波器是一种线性的滤波器,所以在算法中都强制假设变量间都是线性关系,那有人要问了?那要解决非线性问题怎么办?那就要用到扩展卡尔曼滤波,这是卡尔曼滤波器的扩展版,解决了非线性的问题。 卡

weixin_44885615的博客 5万+

黑猫带你学eMMC协议第1篇:全网最全emmc协议中文详讲,这份学习框架图,你值得拥有!!!(持续更新中...)

黑猫带你学eMMC协议第7篇:全网最全emmc写保护详解(write protect) 黑猫带你学eMMC协议第8篇:emmc可靠写详解(Write Reliability) 黑猫带你学eMMC协议第9篇:emmc的分区配置(partitioning) 黑猫带你学eMMC协议第10篇:eMMC命令队列详解(CMDQ) 黑猫带你学eMMC协议第11篇:eMMC擦除详解(erase/trim/discard/sanitize/secure erase/secure trim…) 黑猫带你学eMMC协议第12篇:

黑猫奥利奥的博客 2万+

卡尔曼滤波器原理及实现

转载:https://www.cnblogs.com/zhoug2020/p/8445135.html 想象你在黄昏时分看着一仅仅小鸟飞行穿过浓密的丛林。你仅仅能隐隐约约、断断续续地瞥见小鸟运动的闪现。你试图努力地猜測小鸟在哪里以及下一时刻它会出如今哪里,才不至于失去它的行踪。或者再想象你是二战中的一名雷达操作员,正在跟踪一个微弱的游移目标。这个目标每隔10秒钟在屏幕上闪烁一次。 或者回到更远的从...

大琪爱吃鱼的博客 943

7.6 GPU页表更新机制

本文分析了AMDGPU驱动中BO到VA映射的页表更新实现机制。核心流程通过amdgpu_vm_bo_update()函数触发,支持CPU直接写入和SDMA异步命令两种更新模式。系统根据VRAM可见性自动选择最优更新策略,处理三种主要场景:命令提交、BO迁移和BO销毁。关键实现包括DMA地址数组处理、权限标志合并和BO迁移状态转换,最终通过amdgpu_vm_update_range()完成硬件页表更新。该机制有效平衡了更新延迟与吞吐量,为GPU虚拟内存管理提供基础支撑。

图形/计算GPU的驱动与虚拟化技术分享园地 2466

请尽可能地详细说明一个VLA项目的立项训练到落地的全流程

一个机器人VLA项目的全流程是一个复杂的系统工程,它深度融合了机器人学 (Robotics)计算机视觉 (CV)和自然语言处理 (NLP)。其核心挑战在于高质量数据的获取和从仿真到现实的有效迁移。与纯粹的软件AI项目相比,它增加了硬件、物理交互和安全性的维度,但其最终能让AI走出虚拟世界、在物理世界中产生实际价值,这也是其巨大的魅力所在。

具身机器人和通用AI 303
上一篇: OpenCV中矩阵类详解之一:Mat
下一篇: 学习OpenCV——Kalman滤波
HFKuAng
博客等级 码龄11年 70粉丝 13原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值