假设下面曲线
y = k x + b + w y=kx+b+w y=kx+b+w
其中a,b为曲线的参数,w为高斯噪声。假设我们有N个关于x,y的观测数据点,想根据这些数据点求出直线的参数。
状态变量是需要求解的参数。对于拟合一条直线 y = k x + b y=kx+b y=kx+b,状态变量为待求的参数 k , b k,b k,b。
状态方程可以表示为
x k = x k − 1 + b + w k x_{k}=x_{k-1}+b+w_{k} xk=xk−1+b+wk
虽然点(x,y)是二维的,转换为观测其实只有一维。表示如下
h ( x k ) = k x + b − y + v k h(x_{k})=kx+b-y+v_{k} h(xk)=kx+b−y+vk
卡尔曼滤波观测方程中H矩阵如下
H k = ∂ h ∂ x k ∣ x ˉ k H k = [ ∂ h ∂ k ∂ h ∂ k ] = [ x 1 ] H_{k}=\frac{\partial h}{\partial x_{k}}|_{\bar{x}_{k}}\\ H_{k}=\begin{bmatrix} \frac{\partial h}{\partial k}&\frac{\partial h}{\partial k} \end{bmatrix} =\begin{bmatrix} x &1 \end{bmatrix} Hk=∂xk∂h∣xˉkHk=[∂k∂h∂k∂h]=[x1]
设第k次观测的坐标值为 ( x 0 , y 0 ) (x_{0},y_{0}) (x0,y0)
将观测值代入得到
第k次观测的 H k H_{k} Hk矩阵为
H k = [ x 0 1 ] H_{k}=\begin{bmatrix} {x}_{0}&1 \end{bmatrix} Hk=[x01]
#include "opencv2/video/tracking.hpp"
#include "opencv2/highgui.hpp"
#include
using namespace cv;
using namespace std;
int main(int, char**)
{
double theta=-M_PI/4;
double c=10;
double k=-tan(theta);
double b=-c/sin(theta);
cout<<"ori theta ="<(2, 2) << 1, 0, 0, 1);
setIdentity(KF.measurementMatrix);
setIdentity(KF.processNoiseCov, Scalar::all(1e-5));
setIdentity(KF.measurementNoiseCov, Scalar::all(1e-1));
setIdentity(KF.errorCovPost, Scalar::all(1));
randn(KF.statePost, Scalar::all(0), Scalar::all(0.1));
for ( int i=0; i(0,0)=x_data[i];
KF.measurementMatrix.at(0,1)=1;
measurement=y_data[i];
KF.correct(measurement);
cout<<" k= "<(0,0)<<" b= "<(1,0)<
注意:噪声一定不能设置得太大,否则不会收敛,得不得想要的结果。ps:被坑惨了...
namespace cv
{
KalmanFilter::KalmanFilter() {}
KalmanFilter::KalmanFilter(int dynamParams, int measureParams, int controlParams, int type)
{
init(dynamParams, measureParams, controlParams, type);
}
void KalmanFilter::init(int DP, int MP, int CP, int type)
{
CV_Assert( DP > 0 && MP > 0 );
CV_Assert( type == CV_32F || type == CV_64F );
CP = std::max(CP, 0);
statePre = Mat::zeros(DP, 1, type);
statePost = Mat::zeros(DP, 1, type);
transitionMatrix = Mat::eye(DP, DP, type);
processNoiseCov = Mat::eye(DP, DP, type);
measurementMatrix = Mat::zeros(MP, DP, type);
measurementNoiseCov = Mat::eye(MP, MP, type);
errorCovPre = Mat::zeros(DP, DP, type);
errorCovPost = Mat::zeros(DP, DP, type);
gain = Mat::zeros(DP, MP, type);
if( CP > 0 )
controlMatrix = Mat::zeros(DP, CP, type);
else
controlMatrix.release();
temp1.create(DP, DP, type);
temp2.create(MP, DP, type);
temp3.create(MP, MP, type);
temp4.create(MP, DP, type);
temp5.create(MP, 1, type);
}
const Mat& KalmanFilter::predict(const Mat& control)
{
CV_INSTRUMENT_REGION()
// update the state: x'(k) = A*x(k)
statePre = transitionMatrix*statePost;
if( !control.empty() )
// x'(k) = x'(k) + B*u(k)
statePre += controlMatrix*control;
// update error covariance matrices: temp1 = A*P(k)
temp1 = transitionMatrix*errorCovPost;
// P'(k) = temp1*At + Q
gemm(temp1, transitionMatrix, 1, processNoiseCov, 1, errorCovPre, GEMM_2_T);
// handle the case when there will be measurement before the next predict.
statePre.copyTo(statePost);
errorCovPre.copyTo(errorCovPost);
return statePre;
}
const Mat& KalmanFilter::correct(const Mat& measurement)
{
CV_INSTRUMENT_REGION()
// temp2 = H*P'(k)
temp2 = measurementMatrix * errorCovPre;
// temp3 = temp2*Ht + R
gemm(temp2, measurementMatrix, 1, measurementNoiseCov, 1, temp3, GEMM_2_T);
// temp4 = inv(temp3)*temp2 = Kt(k)
solve(temp3, temp2, temp4, DECOMP_SVD);
// K(k)
gain = temp4.t();
// temp5 = z(k) - H*x'(k)
temp5 = measurement - measurementMatrix*statePre;
// x(k) = x'(k) + K(k)*temp5
statePost = statePre + gain*temp5;
// P(k) = P'(k) - K(k)*temp2
errorCovPost = errorCovPre - gain*temp2;
return statePost;
}
}
以下为公式对应源码部分
预测对应predict函数
状态方程更新
x ˉ k = F k x ^ + u k \bar{x}_{k}=F_{k}\hat{x}+u_{k} xˉk=Fkx^+uk
对应
// update the state: x'(k) = A*x(k)
statePre = transitionMatrix*statePost;
if( !control.empty() )
// x'(k) = x'(k) + B*u(k)
statePre += controlMatrix*control;
P更新
P ˉ k = F P ^ k F T + R k \bar{P}_{k}=F\hat{P}_{k}F^T+R_{k} Pˉk=FP^kFT+Rk
对应
// update error covariance matrices: temp1 = A*P(k)
temp1 = transitionMatrix*errorCovPost;
// P'(k) = temp1*At + Q
gemm(temp1, transitionMatrix, 1, processNoiseCov, 1, errorCovPre, GEMM_2_T);
(1)先计算卡尔曼增益 K K K
K = P ˉ k H k T ( H k P ˉ k H k T + Q ) − 1 K=\bar{P}_{k}H_{k}^T(H_{k}\bar{P}_{k}H_{k}^T+Q)^{-1} K=PˉkHkT(HkPˉkHkT+Q)−1
对应
// temp2 = H*P'(k)
temp2 = measurementMatrix * errorCovPre;
// temp3 = temp2*Ht + R
gemm(temp2, measurementMatrix, 1, measurementNoiseCov, 1, temp3, GEMM_2_T);
// temp4 = inv(temp3)*temp2 = Kt(k)
solve(temp3, temp2, temp4, DECOMP_SVD);
// K(k)
gain = temp4.t();
其中H矩阵是对应measurementMatrix是需要手动输入的
H = ∂ h ∂ x k ∣ x ˉ k H=\frac{\partial h}{\partial x_{k}}|_{\bar{x}_{k}}\\ H=∂xk∂h∣xˉk
(2)计算后验分布
计算增量
x ^ k = x ˉ k + K ( z k − H k x ˉ k ) \hat{x}_{k}=\bar{x}_{k}+K(z_{k}-H_{k}\bar{x}_{k}) x^k=xˉk+K(zk−Hkxˉk)
对应
// temp5 = z(k) - H*x'(k)
temp5 = measurement - measurementMatrix*statePre;
// x(k) = x'(k) + K(k)*temp5
statePost = statePre + gain*temp5;
更新P后验
P ^ k = ( I − K H k ) P ˉ k \hat{P}_{k}=(I-KH_{k})\bar{P}_{k} P^k=(I−KHk)Pˉk
对应
// P(k) = P'(k) - K(k)*temp2
errorCovPost = errorCovPre - gain*temp2;
这是我自己按照公式实现的代码,对应关系更加明显
#include "ceres/ceres.h"
#include
#include
using namespace std;
using Eigen::MatrixXd;
using Eigen::VectorXd;
const int STATE_NUMBER=2;
const int MEASUREMENT_NUMBER=1;
const double SIGMA=0.01;
int main(int argc, char** argv)
{
double k=1.0, b=2.0; // 真实参数值
int N=100; // 数据点
double w_sigma=1; // 噪声Sigma值
cv::RNG rng; // OpenCV随机数产生器
vector x_data, y_data; // 数据
cout<<"generating data: "<