本文将简要回顾一下卡尔曼滤波理论,然后详细介绍如何在OpenCV中使用卡尔曼滤波进行跟踪,最后给两个程序实例。

1. 卡尔曼滤波理论回顾

对于一个动态系统,我们首先定义一组状态空间方程

状态方程:     

测量方程:      

xk是状态向量,zk是测量向量,Ak是状态转移矩阵,uk是控制向量,Bk是控制矩阵,wk是系统误差(噪声),Hk是测量矩阵,vk是测量误差(噪声)。wk和vk都是高斯噪声,即

整个卡尔曼滤波的过程就是个递推计算的过程,不断的“预测——更新——预测——更新……”

预测

预测状态值:              

预测最小均方误差:   

更新

测量误差:                   

测量协方差:                

最优卡尔曼增益:         

修正状态值:                

修正最小均方误差:     

2.OpenCV中的KalmanFilter详解

OpenCV中有两个版本的卡尔曼滤波方法KalmanFilter(C++)和CvKalman(C),用法差不太多,这里只介绍KalmanFilter。

C++版本中将KalmanFilter封装到一个类中,其结构如下所示:

  1. class CV_EXPORTS_W KalmanFilter
  2. {
  3. public:
  4. CV_WRAP KalmanFilter(); //构造默认KalmanFilter对象
  5. CV_WRAP KalmanFilter(int dynamParams, int measureParams, int controlParams=0, int type=CV_32F);  //完整构造KalmanFilter对象方法
  6. void init(int dynamParams, int measureParams, int controlParams=0, int type=CV_32F); //初始化KalmanFilter对象,会替换原来的KF对象
  7. CV_WRAP const Mat& predict(const Mat& control=Mat()); //计算预测的状态值
  8. CV_WRAP const Mat& correct(const Mat& measurement); //根据测量值更新状态值
  9. Mat statePre; //预测值 (x'(k)): x(k)=A*x(k-1)+B*u(k)
  10. Mat statePost; //状态值 (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
  11. Mat transitionMatrix; //状态转移矩阵 (A)
  12. Mat controlMatrix; //控制矩阵 B
  13. Mat measurementMatrix; //测量矩阵 H
  14. Mat processNoiseCov; //系统误差 Q
  15. Mat measurementNoiseCov; //测量误差 R
  16. Mat errorCovPre; //最小均方误差 (P'(k)): P'(k)=A*P(k-1)*At + Q)
  17. Mat gain; //卡尔曼增益 (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)
  18. Mat errorCovPost; //修正的最小均方误差 (P(k)): P(k)=(I-K(k)*H)*P'(k)
  19. // 临时矩阵
  20. Mat temp1;
  21. Mat temp2;
  22. Mat temp3;
  23. Mat temp4;
  24. Mat temp5;
  25. };
  26. enum
  27. {
  28. OPTFLOW_USE_INITIAL_FLOW = CV_LKFLOW_INITIAL_GUESSES,
  29. OPTFLOW_LK_GET_MIN_EIGENVALS = CV_LKFLOW_GET_MIN_EIGENVALS,
  30. OPTFLOW_FARNEBACK_GAUSSIAN = 256
  31. };

函数原型见:…..\OpenCV2\sources\modules\ocl\src\kalman.cpp

只有四个方法: 构造KF对象KalmanFilter(DP,MP,CP)、初始化KF对象init(DP,MP,CP)、预测predict( )、更新correct( )。除非你要重新构造KF对象,否则用不到init( )。

KalmanFilter(DP,MP,CP)和init( )就是赋值,没什么好说的。

注意:KalmanFilter结构体中并没有测量值,测量值需要自己定义,而且一定要定义,因为后面要用。

编程步骤

step1:定义KalmanFilter类并初始化

//构造KF对象

KalmanFilter KF(DP, MP, 0);

//初始化相关参数

KF.transitionMatrix                         转移矩阵 A

KF.measurementMatrix                  测量矩阵    H

KF.processNoiseCov                     过程噪声 Q

KF.measurementNoiseCov            测量噪声        R

KF.errorCovPost                            最小均方误差 P

KF.statePost                                系统初始状态 x(0)

Mat measurement                          定义初始测量值 z(0)

step2:预测

KF.predict( )                                                 //返回的是下一时刻的状态值KF.statePost (k+1)

step3:更新

更新measurement;                                     //注意measurement不能通过观测方程进行计算得到,要自己定义!

更新KF   KF.correct(measurement)

最终的结果应该是更新后的statePost.

相关参数的确定

对于系统状态方程,简记为Y=AX+B,X和Y是表示系统状态的列向量,A是转移矩阵,B是其他项。

状态值(向量)只要能表示系统的状态即可,状态值的维数决定了转移矩阵A的维数,比如X和Y是N×1的,则A是N×N的。

A的确定跟X有关,只要保证方程中不相干项的系数为0即可,看下面例子

X和Y是二维的,

X和Y是三维的,

X和Y是三维的,但c和△ c是相关项

上面的1也可以是其他值。

下面对predict( ) 和correct( )函数介绍下,可以不用看,不影响编程。

  1. CV_EXPORTS const oclMat& KalmanFilter::predict(const oclMat& control)
  2. {
  3. gemm(transitionMatrix, statePost, 1, oclMat(), 0, statePre);
  4. oclMat temp;
  5. if(control.data)
  6. gemm(controlMatrix, control, 1, statePre, 1, statePre);
  7. gemm(transitionMatrix, errorCovPost, 1, oclMat(), 0, temp1);
  8. gemm(temp1, transitionMatrix, 1, processNoiseCov, 1, errorCovPre, GEMM_2_T);
  9. statePre.copyTo(statePost);
  10. return statePre;
  11. }

gemm( )是矩阵的广义乘法

void gemm(const GpuMat& src1, constGpuMat& src2, double alpha, const GpuMat& src3, double beta,GpuMat& dst, int flags=0, Stream& stream=Stream::Null())

dst = alpha · src1 · src2 +beta· src3

上面,oclMat()其实是uk,只不过默认为0,所以没赋值。整个过程就计算了x'和P’。(用x'代表x的预测值,用P'代表P的预测值)。GEMM_2_T表示对第2个参数转置。

x’(k)=1·A·x(k-1)

如果B非空, x'(k) = 1·B·u + 1·x'(k-1)

temp1 = 1·A·P(k-1) + 0·u(k)

P’(k) = 1· temp1·AT + 1· Qk= A·P(k-1)·AT + 1· Qk

可见,和第一部分的理论介绍完全一致。

  1. CV_EXPORTS const oclMat& KalmanFilter::correct(const oclMat& measurement)
  2. {
  3. CV_Assert(measurement.empty() == false);
  4. gemm(measurementMatrix, errorCovPre, 1, oclMat(), 0, temp2);
  5. gemm(temp2, measurementMatrix, 1, measurementNoiseCov, 1, temp3, GEMM_2_T);
  6. Mat temp;
  7. solve(Mat(temp3), Mat(temp2), temp, DECOMP_SVD);
  8. temp4.upload(temp);
  9. gain = temp4.t();
  10. gemm(measurementMatrix, statePre, -1, measurement, 1, temp5);
  11. gemm(gain, temp5, 1, statePre, 1, statePost);
  12. gemm(gain, temp2, -1, errorCovPre, 1, errorCovPost);
  13. return statePost;
  14. }

bool solve(InputArray src1, InputArray src2, OutputArray dst, int flags=DECOMP_LU)

求解线型最小二乘估计

temp2 = 1· H·P’ + 0·u(k)

temp3 = 1· temp2·HT + 1·R = H·P’·HT+ 1· R   也就是上面的Sk

temp = argmin||tem2- temp3||

K=temp

temp5 = -1· H·x’ + 1·zk        就是上面的y’。

x = 1·K·temp5 + 1·x’ = KT·y’ +x’

P =-1·K·temp2 + 1·P’ = -K·H·P’+P’ = (I- K·H) P’

也和第一部分的理论完全一致。

通过深入函数内部,学到了两个实用的函数哦。矩阵广义乘法gemm( )、最小二乘估计solve( )

补充

1)以例2为例,为什么状态值一般都设置成(x,y,△x,△y)?我们不妨设置成(x,y,△x),对应的转移矩阵也改成3×3的。可以看到仍能跟上,不过在x方向跟踪速度快,在y方向跟踪速度慢。进一步设置成(x,y)和2×2的转移矩阵,程序的跟踪速度简直是龟速。所以,简单理解,△x和△y严重影响对应方向上的跟踪速度。

3.实例

例1 OpenCV自带的示例程序

  1. #include "opencv2/video/tracking.hpp"
  2. #include "opencv2/highgui/highgui.hpp"
  3. #include <iostream>
  4. #include <stdio.h>
  5. using namespace std;
  6. using namespace cv;
  7. //计算相对窗口的坐标值,因为坐标原点在左上角,所以sin前有个负号
  8. static inline Point calcPoint(Point2f center, double R, double angle)
  9. {
  10. return center + Point2f((float)cos(angle), (float)-sin(angle))*(float)R;
  11. }
  12. static void help()
  13. {
  14. printf( "\nExamle of c calls to OpenCV's Kalman filter.\n"
  15. " Tracking of rotating point.\n"
  16. " Rotation speed is constant.\n"
  17. " Both state and measurements vectors are 1D (a point angle),\n"
  18. " Measurement is the real point angle + gaussian noise.\n"
  19. " The real and the estimated points are connected with yellow line segment,\n"
  20. " the real and the measured points are connected with red line segment.\n"
  21. " (if Kalman filter works correctly,\n"
  22. " the yellow segment should be shorter than the red one).\n"
  23. "\n"
  24. " Pressing any key (except ESC) will reset the tracking with a different speed.\n"
  25. " Pressing ESC will stop the program.\n"
  26. );
  27. }
  28. int main(int, char**)
  29. {
  30. help();
  31. Mat img(500, 500, CV_8UC3);
  32. KalmanFilter KF(2, 1, 0); //创建卡尔曼滤波器对象KF
  33. Mat state(2, 1, CV_32F); //state(角度,△角度)
  34. Mat processNoise(2, 1, CV_32F);
  35. Mat measurement = Mat::zeros(1, 1, CV_32F); //定义测量值
  36. char code = (char)-1;
  37. for(;;)
  38. {
  39. //1.初始化
  40. randn( state, Scalar::all(0), Scalar::all(0.1) ); //
  41. KF.transitionMatrix = *(Mat_<float>(2, 2) << 1, 1, 0, 1); //转移矩阵A[1,1;0,1]
  42. //将下面几个矩阵设置为对角阵
  43. setIdentity(KF.measurementMatrix); //测量矩阵H
  44. setIdentity(KF.processNoiseCov, Scalar::all(1e-5)); //系统噪声方差矩阵Q
  45. setIdentity(KF.measurementNoiseCov, Scalar::all(1e-1)); //测量噪声方差矩阵R
  46. setIdentity(KF.errorCovPost, Scalar::all(1)); //后验错误估计协方差矩阵P
  47. randn(KF.statePost, Scalar::all(0), Scalar::all(0.1)); //x(0)初始化
  48. for(;;)
  49. {
  50. Point2f center(img.cols*0.5f, img.rows*0.5f); //center图像中心点
  51. float R = img.cols/3.f; //半径
  52. double stateAngle = state.at<float>(0); //跟踪点角度
  53. Point statePt = calcPoint(center, R, stateAngle); //跟踪点坐标statePt
  54. //2. 预测
  55. Mat prediction = KF.predict(); //计算预测值,返回x'
  56. double predictAngle = prediction.at<float>(0); //预测点的角度
  57. Point predictPt = calcPoint(center, R, predictAngle); //预测点坐标predictPt
  58. //3.更新
  59. //measurement是测量值
  60. randn( measurement, Scalar::all(0), Scalar::all(KF.measurementNoiseCov.at<float>(0))); //给measurement赋值N(0,R)的随机值
  61. // generate measurement
  62. measurement += KF.measurementMatrix*state; //z = z + H*x;
  63. double measAngle = measurement.at<float>(0);
  64. Point measPt = calcPoint(center, R, measAngle);
  65. // plot points
  66. //定义了画十字的方法,值得学习下
  67. #define drawCross( center, color, d ) \
  68. line( img, Point( center.x - d, center.y - d ), \
  69. Point( center.x + d, center.y + d ), color, 1, CV_AA, 0); \
  70. line( img, Point( center.x + d, center.y - d ), \
  71. Point( center.x - d, center.y + d ), color, 1, CV_AA, 0 )
  72. img = Scalar::all(0);
  73. drawCross( statePt, Scalar(255,255,255), 3 );
  74. drawCross( measPt, Scalar(0,0,255), 3 );
  75. drawCross( predictPt, Scalar(0,255,0), 3 );
  76. line( img, statePt, measPt, Scalar(0,0,255), 3, CV_AA, 0 );
  77. line( img, statePt, predictPt, Scalar(0,255,255), 3, CV_AA, 0 );
  78. //调用kalman这个类的correct方法得到加入观察值校正后的状态变量值矩阵
  79. if(theRNG().uniform(0,4) != 0)
  80. KF.correct(measurement);
  81. //不加噪声的话就是匀速圆周运动,加了点噪声类似匀速圆周运动,因为噪声的原因,运动方向可能会改变
  82. randn( processNoise, Scalar(0), Scalar::all(sqrt(KF.processNoiseCov.at<float>(0, 0)))); //vk
  83. state = KF.transitionMatrix*state + processNoise;
  84. imshow( "Kalman", img );
  85. code = (char)waitKey(100);
  86. if( code > 0 )
  87. break;
  88. }
  89. if( code == 27 || code == 'q' || code == 'Q' )
  90. break;
  91. }
  92. return 0;
  93. }

程序结果

例2  跟踪鼠标位置

在我介绍粒子滤波的博文“学习Opencv2——粒子滤波Condensation算法”里,有个例3,是跟踪鼠标位置。现在我们用卡尔曼滤波来实现。

  1. #include "opencv2/video/tracking.hpp"
  2. #include "opencv2/highgui/highgui.hpp"
  3. #include <stdio.h>
  4. using namespace cv;
  5. using namespace std;
  6. const int winHeight=600;
  7. const int winWidth=800;
  8. Point mousePosition= Point(winWidth>>1,winHeight>>1);
  9. //mouse event callback
  10. void mouseEvent(int event, int x, int y, int flags, void *param )
  11. {
  12. if (event==CV_EVENT_MOUSEMOVE) {
  13. mousePosition = Point(x,y);
  14. }
  15. }
  16. int main (void)
  17. {
  18. RNG rng;
  19. //1.kalman filter setup
  20. const int stateNum=4; //状态值4×1向量(x,y,△x,△y)
  21. const int measureNum=2; //测量值2×1向量(x,y)
  22. KalmanFilter KF(stateNum, measureNum, 0);
  23. KF.transitionMatrix = *(Mat_<float>(4, 4) <<1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1); //转移矩阵A
  24. setIdentity(KF.measurementMatrix); //测量矩阵H
  25. setIdentity(KF.processNoiseCov, Scalar::all(1e-5)); //系统噪声方差矩阵Q
  26. setIdentity(KF.measurementNoiseCov, Scalar::all(1e-1)); //测量噪声方差矩阵R
  27. setIdentity(KF.errorCovPost, Scalar::all(1)); //后验错误估计协方差矩阵P
  28. rng.fill(KF.statePost,RNG::UNIFORM,0,winHeight>winWidth?winWidth:winHeight); //初始状态值x(0)
  29. Mat measurement = Mat::zeros(measureNum, 1, CV_32F); //初始测量值x'(0),因为后面要更新这个值,所以必须先定义
  30. namedWindow("kalman");
  31. setMouseCallback("kalman",mouseEvent);
  32. Mat image(winHeight,winWidth,CV_8UC3,Scalar(0));
  33. while (1)
  34. {
  35. //2.kalman prediction
  36. Mat prediction = KF.predict();
  37. Point predict_pt = Point(prediction.at<float>(0),prediction.at<float>(1) ); //预测值(x',y')
  38. //3.update measurement
  39. measurement.at<float>(0) = (float)mousePosition.x;
  40. measurement.at<float>(1) = (float)mousePosition.y;
  41. //4.update
  42. KF.correct(measurement);
  43. //draw
  44. image.setTo(Scalar(255,255,255,0));
  45. circle(image,predict_pt,5,Scalar(0,255,0),3); //predicted point with green
  46. circle(image,mousePosition,5,Scalar(255,0,0),3); //current position with red
  47. char buf[256];
  48. sprintf_s(buf,256,"predicted position:(%3d,%3d)",predict_pt.x,predict_pt.y);
  49. putText(image,buf,Point(10,30),CV_FONT_HERSHEY_SCRIPT_COMPLEX,1,Scalar(0,0,0),1,8);
  50. sprintf_s(buf,256,"current position :(%3d,%3d)",mousePosition.x,mousePosition.y);
  51. putText(image,buf,cvPoint(10,60),CV_FONT_HERSHEY_SCRIPT_COMPLEX,1,Scalar(0,0,0),1,8);
  52. imshow("kalman", image);
  53. int key=waitKey(3);
  54. if (key==27){//esc
  55. break;
  56. }
  57. }
  58. }

结果

例3

  1. #include "opencv2/video/tracking.hpp"
  2. #include <opencv2/legacy/legacy.hpp> //#include "cvAux.h"
  3. #include <opencv2/highgui/highgui.hpp>
  4. #include <opencv2/core/core.hpp>
  5. #include <stdio.h>
  6. using namespace cv;
  7. using namespace std;
  8. int main( )
  9. {
  10. float A[10][3] =
  11. {
  12. 10, 50, 15.6,
  13. 12, 49, 16,
  14. 11, 52, 15.8,
  15. 13, 52.2, 15.8,
  16. 12.9, 50, 17,
  17. 14, 48, 16.6,
  18. 13.7, 49, 16.5,
  19. 13.6, 47.8, 16.4,
  20. 12.3, 46, 15.9,
  21. 13.1, 45, 16.2
  22. };
  23. const int stateNum=3;
  24. const int measureNum=3;
  25. KalmanFilter KF(stateNum, measureNum, 0);
  26. KF.transitionMatrix = *(Mat_<float>(3, 3) <<1,0,0,0,1,0,0,0,1); //转移矩阵A
  27. setIdentity(KF.measurementMatrix); //测量矩阵H
  28. setIdentity(KF.processNoiseCov, Scalar::all(1e-5)); //系统噪声方差矩阵Q
  29. setIdentity(KF.measurementNoiseCov, Scalar::all(1e-1)); //测量噪声方差矩阵R
  30. setIdentity(KF.errorCovPost, Scalar::all(1));
  31. Mat measurement = Mat::zeros(measureNum, 1, CV_32F);
  32. //初始状态值
  33. KF.statePost = *(Mat_<float>(3, 1) <<A[0][0],A[0][1],A[0][2]);
  34. cout<<"state0="<<KF.statePost<<endl;
  35. for(int i=1;i<=9;i++)
  36. {
  37. //预测
  38. Mat prediction = KF.predict();
  39. //计算测量值
  40. measurement.at<float>(0) = (float)A[i][0];
  41. measurement.at<float>(1) = (float)A[i][1];
  42. measurement.at<float>(2) = (float)A[i][2];
  43. //更新
  44. KF.correct(measurement);
  45. //输出结果
  46. cout<<"predict ="<<"\t"<<prediction.at<float>(0)<<"\t"<<prediction.at<float>(1)<<"\t"<<prediction.at<float>(2)<<endl;
  47. cout<<"measurement="<<"\t"<<measurement.at<float>(0)<<"\t"<<measurement.at<float>(1)<<"\t"<<measurement.at<float>(2)<<endl;
  48. cout<<"correct ="<<"\t"<<KF.statePost.at<float>(0)<<"\t"<<KF.statePost.at<float>(1)<<"\t"<<KF.statePost.at<float>(2)<<endl;
  49. }
  50. system("pause");
  51. }

结果如下

这里预测值和上一个状态值一样,原因是转移矩阵A是单位阵,如果改成非单位阵,结果就不一样了。

转载自:

学习OpenCV2——卡尔曼滤波(KalmanFilter)详解相关推荐

  1. OpenCV--卡尔曼滤波(KalmanFilter)详解【转载】

    本文将简要回顾一下卡尔曼滤波理论,然后详细介绍如何在OpenCV中使用卡尔曼滤波进行跟踪,最后给两个程序实例. 1. 卡尔曼滤波理论回顾 对于一个动态系统,我们首先定义一组状态空间方程 状态方程: 测 ...

  2. 卡尔曼滤波原理详解(一)

    卡尔曼滤波原理详解(一) 前言 数据融合的思想 例子引入 卡尔曼增益推导 前言 本文是对卡尔曼滤波学习的记录,主要参照了DR_CAN老师的视频进行学习.视频专栏链接:DR_CAN卡尔曼滤波视频专栏.虽 ...

  3. 卡尔曼滤波原理详解(二)

    卡尔曼滤波原理详解(二) 前言 卡尔曼增益推导 总结 前言 本文是对卡尔曼滤波学习的记录,主要参照了DR_CAN老师的视频进行学习.视频专栏链接:DR_CAN卡尔曼滤波视频专栏.虽然网上有很多卡尔曼滤 ...

  4. java多线程学习-java.util.concurrent详解

    http://janeky.iteye.com/category/124727 java多线程学习-java.util.concurrent详解(一) Latch/Barrier 博客分类: java ...

  5. ELK学习笔记之Logstash详解

    0x00 Logstash概述 官方介绍:Logstash is an open source data collection engine with real-time pipelining cap ...

  6. expect学习笔记及实例详解【转】

    1. expect是基于tcl演变而来的,所以很多语法和tcl类似,基本的语法如下所示: 1.1 首行加上/usr/bin/expect 1.2 spawn: 后面加上需要执行的shell命令,比如说 ...

  7. Java中大数据数组,Java基础学习笔记之数组详解

    摘要:这篇Java开发技术栏目下的"Java基础学习笔记之数组详解",介绍的技术点是"java基础学习笔记.基础学习笔记.Java基础.数组详解.学习笔记.Java&qu ...

  8. Python基础学习之 os 模块详解

    Python基础学习之 os 模块详解 文章目录 Python基础学习之 os 模块详解 1. 路径操作 1.1 os.chdir(),切换当前工作目录: 1.2 os.getcwd(),返回工作目录 ...

  9. Activiti工作流学习之流程图应用详解

    Activiti工作流学习之流程图应用详解 1.目的 了解Activiti工作流是怎样应用流程图的. 2.环境准备 2.1.相关软件及版本 jdk版本:Jdk1.7及以上 IDE:eclipse 数据 ...

最新文章

  1. 百度第七期智能对话训练营来了!
  2. cdialog创建后马上隐藏_隐藏你的小秘密,这款神器就是玩的这么6!
  3. mysql分类和事务回滚
  4. 沐创密码芯片获奖!中国电子学会年度技术发明一等奖,颁给国产集成电路公司...
  5. 天猫11.11:手机淘宝 521 性能优化项目揭秘
  6. html制作选择题题库,HTML与网页制作测试题库
  7. ZkServer服务启动的逻辑-QuorumPeer.start
  8. sed行文本处理工具
  9. 解读文献里的那些图——流式细胞术
  10. StreamInsight 编程模型之适配器
  11. 怎样在计算机桌面上安装驱动器,怎么用韩博士驱动助理安装电脑驱动
  12. 微信模拟地理位置_微信电脑版伪装地理位置的方法
  13. 如何把桌面路径设置到D盘
  14. MATLAB图中图绘制(局部放大图)
  15. 拿到offer后 还应该继续去面试?
  16. 不知道虚拟化?看这篇就够了!
  17. C/C++实现双目矫正(不使用OpenCV内部函数)及矫正源码解析
  18. 获取大麦网孟鹤堂演出数据并播报和在右下角弹窗提示
  19. 【Flutter】如何完成一个透明沉浸式状态栏
  20. sirius java_sirius

热门文章

  1. 动态路由协议的分类、动静态路由优缺点、RIP简介、组播单播广播详解(附图)
  2. MySQL 处理重复数据
  3. Quartz.NET常用方法 01
  4. Qt Installer Framework翻译(5-2)
  5. nginx 1.16 配置反向代理,http,https,ssl
  6. 风变编程课 囚徒困境 答案_当您对所有这些有用的在线编程课程感到不知所措时,如何摆脱困境...
  7. code craft_Craft.io,设计和代码
  8. 常见的排序算法(面试经常碰到)
  9. centos mysql安装
  10. C++生成一个随机网络