卡尔曼滤波原理介绍与opencv库的参数调整和使用流程的简述

kalman filter 学习

1.直观解释

图片来自:https://build.openmodelica.org/Documentation/Buildings.Utilities.IO.Python27.Examples.KalmanFilter.html

1.1 为什么要滤波?

因为传感器等数据观测者会存在观测偏差与精度缺失,或是信号传输中产生噪声.

1.2 滤波的方式?

相当于根据样本值的分布,进行实际值的拟合.

1.3 滤波和预测的关系?

按照当前的拟合趋势,提前判断下一个点的位置,就实现了预测的功能.

2.原理介绍

核心思想是贝叶斯滤波,外加一些矩阵的基本操作.

2.1 核心思想

将当前运动状态视为一个正态分布 f k − N ( u k , σ k ) f_k - N(u_k,\sigma_k) fkN(uk,σk)

2.2 核心模型

运 动 模 型 : X k = f k − 1 ( X k − 1 ) + Q k 运动模型 : X_k = f_{k-1}(X_{k-1})+Q_k :Xk=fk1(Xk1)+Qk
观 测 模 型 : Y k = h ( X k ) + R k 观测模型: Y_k=h(X_k)+R_k :Yk=h(Xk)+Rk
其中
k k k代表当前时间戳,
X k − 1 X_{k-1} Xk1代表第 k − 1 k-1 k1次的状态向量,
f k f_k fk表示运动模型函数, h h h表示观测模型函数(本质是多项式的系数)

这是一个递推过程.

2.3 核心步骤

预测就是要确定下一时刻的目标概率分布,即确定相应的 u k u_k uk期望与 σ k \sigma_k σk方差
通过以下五个式子实现预测与更新:
u k − = F ∗ u k − 1 + u_k^-=F*u_{k-1}^+ uk=Fuk1+
σ k − = F ∗ σ k − 1 + ∗ F T + Q k \sigma_k^-=F*\sigma_{k-1}^+*F^T + Q_k σk=Fσk1+FT+Qk
K = σ k − ∗ H T ∗ ( H ∗ σ k − ∗ H T + R k ) − 1 K = \sigma_k^- *H^T*(H* \sigma_k^- *H^T+R_k)^{-1} K=σkHT(HσkHT+Rk)1
u k + = u k − + K ( y k − H ∗ u k − ) u_k^+=u_{k}^-+K(y_k - H*u_k^-) uk+=uk+K(ykHuk)
σ k + = ( I − K ∗ H ) ∗ σ k − \sigma_k^+=(I-K*H)*\sigma_{k}^- σk+=(IKH)σk
其中上下标±表示当前轮状态的后验概率与先验概率.
F F F运动模型矩阵,反应了运动函数多项式的系数
H H H观测模型矩阵,是用于状态向量向观测值转变的矩阵
K K K为卡尔曼增益,就是推导过程中的④与⑤式子的共同变量.

2.4 工作流程

预测
修正
上一帧的后验
这一帧的先验
这一帧的后验
作为下一帧的输入

2.5 参考资料

具体推导过程就不说了,这里给出一些参考资料:
哔站最强kalman推导(从贝叶斯滤波开始):https://space.bilibili.com/287989852/
相应的推导博客:https://blog.csdn.net/wq1psa78/article/details/105849353
油管最强kalman通俗演讲: https://www.youtube.com/watch?v=CaCcOwJPytQ&list=PLX2gX-ftPVXU3oUFNATxGXY90AULiqnWT
一个例子搞清楚(先验分布/后验分布/似然估计 : https://blog.csdn.net/qq_23947237/article/details/78265026

3.代码使用

主体kalman操作函数借用了opencv自带kalman.cpp

3.1 源代码

class CV_EXPORTS_W KalmanFilter{
public:
    CV_WRAP KalmanFilter();
    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.
     */
    void init( int dynamParams, int measureParams, int controlParams = 0, int type = CV_32F );
    /** @brief Computes a predicted state.
    @param control The optional input control
     */
    CV_WRAP const Mat& predict( const Mat& control = Mat() );

    /** @brief Updates the predicted state from the measurement.
    	@param measurement The measured system parameters
     */
    CV_WRAP const Mat& correct( const Mat& measurement );

    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)
};

其中

CV_PROP_RW Mat statePre;是先验状态,代表 X k − X_k^- Xk
CV_PROP_RW Mat statePost是后验状态,代表 X k + X_k^+ Xk+
CV_PROP_RW Mat transitionMatrix是状态转移矩阵,代表 F F F
CV_PROP_RW Mat measurementMatrix是观测模型矩阵,代表 H H H
CV_PROP_RW Mat processNoiseCov,CV_PROP_RW Mat measurementNoiseCov是噪音,代表 R R R Q Q Q
CV_PROP_RW Mat gain是卡尔曼增益,代表 K K K

3.2 使用流程

初始化滤波器
先修正
再预测
图形化显示
3.2.1 初始化

初始化状态向量和测量向量:确定维度,及初始化这两个向量

        KF.init(stateSize, measSize, contrSize, CV_32F);
        state=cv::Mat(stateSize, 1, CV_32F);
        measure=cv::Mat(measSize, 1, CV_32F);

初始化状态转移矩阵与观测矩阵:

            KF.transitionMatrix = (Mat_<float>(stateSize,stateSize) <<
                    1,0,0,0,1,0,0,0,1,0,
                    0,1,0,0,0,1,0,0,0,1,
                    0,0,1,0,0,0,1,0,0,0,
                    0,0,0,1,0,0,0,1,0,0,
                    0,0,0,0,1,0,0,0,0,0,
                    0,0,0,0,0,1,0,0,0,0,
                    0,0,0,0,0,0,1,0,0,0,
                    0,0,0,0,0,0,0,1,0,0,
                    0,0,0,0,0,0,0,0,1,0,
                    0,0,0,0,0,0,0,0,0,1
                    );

            KF.measurementMatrix  = (Mat_<float>(measSize,stateSize,CV_32F)<<
                    1,0,0,0,0,0,0,0,0,0,
                    0,1,0,0,0,0,0,0,0,0,
                    0,0,1,0,0,0,0,0,0,0,
                    0,0,0,1,0,0,0,0,0,0
            );

设置噪音:

        /**设对角线为scalar的噪音*/
        setIdentity(KF.processNoiseCov, Scalar::all(1e-5));
        setIdentity(KF.measurementNoiseCov, Scalar::all(1e-1));
        setIdentity(KF.errorCovPost, Scalar::all(1));
3.2.2 修正

确定时间 d T dT dT

float deltaT = (timeStamp - m_lastTimeStamp) /1000;

更新状态转移矩阵

KF.transitionMatrix = (cv::Mat_<float>(stateSize,stateSize)
                    <<
                    1,0,0,0,deltaT,0     ,0     ,0     ,deltaT*deltaT/2,0              ,
                    0,1,0,0,0     ,deltaT,0     ,0     ,0              ,deltaT*deltaT/2,
                    0,0,1,0,0     ,0     ,deltaT,0     ,0,0,
                    0,0,0,1,0     ,0     ,0     ,deltaT,0,0,
                    0,0,0,0,1     ,0     ,0     ,0     ,0,0,
                    0,0,0,0,0     ,1     ,0     ,0     ,0,0,
                    0,0,0,0,0     ,0     ,1     ,0     ,0,0,
                    0,0,0,0,0     ,0     ,0     ,1     ,0,0,
                    0,0,0,0,0     ,0,0,0,1,0,
                    0,0,0,0,0     ,0,0,0,0,1
            );

载入观测值,进行修正

        measure= (Mat_<float>(measSize,1) <<bBox.x,bBox.y,bBox.width,bBox.height);
        KF.correct(measure);
3.2.3 预测
state = KF.predict();
3.2.4 图形化显示

黄色为上一帧的位置,绿色为实际(观测值)位置,红色为预测位置.
在这里插入图片描述

        /**获取预测位置,[x,y,w,h] 左上点坐标 宽 高*/
        cv::Mat& state = kalman_filter.state;
        /**预测位置偏差太大,筛选不要*/
        if(abs(state.at<float>(0)-faces[i].x)>faces[i].x){
            continue;
        }
        /**用于打印预测框 @红色red*/
        cv::Rect predRect;
        predRect.width = state.at<float>(2);
        predRect.height = state.at<float>(3);

        cv::Point center;
        center.x = state.at<float>(0)+predRect.width / 2;
        center.y = state.at<float>(1)+predRect.height / 2;

        predRect.x = state.at<float>(0);
        predRect.y = state.at<float>(1);

        cv::circle(frame, center, 2, CV_RGB(255,0,0), -1);
        cv::rectangle(frame, predRect, CV_RGB(255,0,0), 2);
        /**评估预测误差,直接采用差的平方和*/
        Mat predict_box = state(Range(0,4),Range(0,1)).clone().t();
        Mat Real_box = (Mat_<float >(1,4)<<faces[i].x,faces[i].y,faces[i].width,faces[i].height);
        cout<<"|predict_box : "<<predict_box<<endl;
        cout<<"|Real_box    : "<<Real_box<<endl;
        Mat result ;
        cv::pow(Mat(predict_box-Real_box),2,result);

        int err= 0;
        for(int i = 0 ; i < 4 ; i++){
            err += pow(predict_box.ptr<float>(0)[i]-Real_box.ptr<float>(0)[i],2);
        }
        err_sum+=err;
        cout<<"|------- err : "<<err<<endl;
        cout<<"|------- aver_err : "<<err_sum/cur_frame<<endl;

3.3 参数设置

这里最主要的就是状态转移矩阵和观测矩阵的设置,现在用一个匀加速模型作为例子,进行分析.

3.3.1 运动模型

首先确定运动模型(牛顿运动学公式 - 本质是进行泰勒展开!):
X k = X k − 1 + V k − 1 ∗ d t + 1 2 ∗ a k − 1 ∗ d t 2 X_k = X_{k-1} + V_{k-1}*dt+\frac{1}{2}*a_{k-1}*dt^2 Xk=Xk1+Vk1dt+21ak1dt2

3.3.2 状态向量

然后确定我们需要的向量:
[ x y w h v x v y v w v h a x a y ] \begin{bmatrix} x \\ y \\ w \\ h \\ v_x\\ v_y\\ v_w\\ v_h\\ a_x\\ a_y\\ \end{bmatrix} xywhvxvyvwvhaxay
上面定义了一个可以进行匀加速预测的模型,可以进行位置的确定和状态框的缩放.

3.3.3 状态转移矩阵

根据上述的运动学公式我们不难定义相应的状态转移矩阵 F F F
F = [ 1 0 0 0 d t 0 0 0 1 2 d t 2 0 0 1 0 0 0 d t 0 0 0 1 2 d t 2 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 ] F = \left [ \begin{matrix} 1 & 0& 0& 0& dt& 0 & 0 & 0 & \frac{1}{2}dt^2 & 0 \\ 0&1&0&0&0 &dt&0 &0 &0 &\frac{1}{2}dt^2\\ 0&0&1&0&0 &0 &dt&0 &0&0\\ 0&0&0&1&0&0 &0 &dt&0 &0\\ 0&0&0&0&1&0&0 &0 &dt&0 \\ 0&0&0&0&0&1&0&0 &0 &dt\\ 0&0&0&0&0&0&1&0&0 &0 \\ 0&0&0&0&0&0&0&1&0&0 \\ 0&0&0&0&0&0&0&0&1&0 \\ 0&0&0&0&0&0&0&0&0&1\\ \end{matrix} \right ] F=1000000000010000000000100000000001000000dt0001000000dt0001000000dt0001000000dt00010021dt2000dt00010021dt2000dt0001
具体运算过程如下:
[ x k y k w k h k v x k v y k v w k v h k a x k a y k ] = [ 1 0 0 0 d t 0 0 0 1 2 d t 2 0 0 1 0 0 0 d t 0 0 0 1 2 d t 2 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 d t 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 ] ∗ [ x k − 1 y k − 1 w k − 1 h k − 1 v x k − 1 v y k − 1 v w k − 1 v h k − 1 a x k − 1 a y k − 1 ] \left [ \begin{matrix} x^k \\ y^k \\ w^k \\ h^k \\ v_x^k\\ v_y^k\\ v_w^k\\ v_h^k\\ a_x^k\\ a_y^k\\ \end{matrix} \right ] = \left [ \begin{matrix} 1 & 0& 0& 0& dt& 0 & 0 & 0 & \frac{1}{2}dt^2 & 0 \\ 0&1&0&0&0 &dt&0 &0 &0 &\frac{1}{2}dt^2\\ 0&0&1&0&0 &0 &dt&0 &0&0\\ 0&0&0&1&0&0 &0 &dt&0 &0\\ 0&0&0&0&1&0&0 &0 &dt&0 \\ 0&0&0&0&0&1&0&0 &0 &dt\\ 0&0&0&0&0&0&1&0&0 &0 \\ 0&0&0&0&0&0&0&1&0&0 \\ 0&0&0&0&0&0&0&0&1&0 \\ 0&0&0&0&0&0&0&0&0&1\\ \end{matrix} \right ] * \left [ \begin{matrix} x ^{k-1}\\ y ^{k-1}\\ w^{k-1} \\ h ^{k-1}\\ v_x^{k-1}\\ v_y^{k-1}\\ v_w^{k-1}\\ v_h^{k-1}\\ a_x^{k-1}\\ a_y^{k-1}\\ \end{matrix} \right ] xkykwkhkvxkvykvwkvhkaxkayk=1000000000010000000000100000000001000000dt0001000000dt0001000000dt0001000000dt00010021dt2000dt00010021dt2000dt0001xk1yk1wk1hk1vxk1vyk1vwk1vhk1axk1ayk1
拿出第一行进行解释:
x k = 1 ∗ x k − 1 + v x k − 1 ∗ d t + 1 2 a x k − 1 ∗ d t 2 x^k=1*x^{k-1}+v_x^{k-1}*dt+\frac{1}{2}a^{k-1}_x*dt^2 xk=1xk1+vxk1dt+21axk1dt2

3.3.4 观测矩阵

观测矩阵的作用就是把当前状态向量转化为观测向量
这里就是把10维转为4维
[ x y w h ] = [ 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 ] ∗ [ x k y k w k h k v x k v y k v w k v h k a x k a y k ] \left [ \begin{matrix} x \\ y \\ w \\ h \end{matrix} \right ] = \left [ \begin{matrix} 1 & 0& 0& 0& 0& 0 & 0 & 0 &0 & 0 \\ 0&1&0&0&0 &0&0 &0 &0 &0\\ 0&0&1&0&0 &0 &0&0 &0&0\\ 0&0&0&1&0&0 &0 &0&0 &0\\ \end{matrix} \right ] * \left [ \begin{matrix} x ^{k}\\ y ^{k}\\ w^{k} \\ h ^{k}\\ v_x^{k}\\ v_y^{k}\\ v_w^{k}\\ v_h^{k}\\ a_x^{k}\\ a_y^{k}\\ \end{matrix} \right ] xywh=1000010000100001000000000000000000000000xkykwkhkvxkvykvwkvhkaxkayk

参考资料
卡尔曼滤波运动模型 : https://www.docin.com/p-2004245093.html
卡尔曼滤波的理解以及参数调整 : https://blog.csdn.net/u013453604/article/details/50301477

4.实现效果展示

其他学习资料:
https://www.kalmanfilter.net/

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值