很多同学看了我之前的文章,都对kalman滤波器的原理有了理解,但我发现,在具体工程设计过程中,还是很多人都感觉到无从下手,一些参数也不知道如何选取。
这样吧。我这里举一些简单的例子,并用C++来一步一步的进行设计。然后,咱们一起看看最后实现的效果到底如何好吧。
案例1:室内温度测量
要求用kalman滤波器对一个房间的温度进行测量估计。这个房间的温度大概是25℃,而由于空气流动、光照等影响,室内温度会有小幅的随机波动。这个波动的方差嘛,就暂定为0.01好了。然后我们 用温度计每分钟进行一次测量,温度计的测量误差大概是0.5℃,也就是方差为0.25。
这是一个非常简单的案例,过程中所有的矩阵都是一维,也就是标量。
        先定义状态量,就是房间温度。房间温度有波动,所以会有系统噪声
。根据题意,
的协方差矩阵Q就等于0.01。于是状态方程就是:
再看量测方程。由于是直接测量,所以H阵等于1,而量测方差阵R根据题意,就等于0.25了。
由于涉及矩阵计算,所以这里用到了我自己用C++开发的矩阵类。需要的可以到我的空间,或者下面这个链接去下载:
https://download.csdn.net/download/weixin_38898944/90305952
开始C++程序设计了啊。嗯,注意是C++11,否则我的有些写法可能会报错。
先设计一个kalman滤波器的类。为了代码复用程度高,我把之前文章里的提到的基本型kalman滤波器拆分成两个类。一个是模型Model类,一个是kalman类。这样如果换一个需求场景,只要更改模型类就好了,kalman类不用变。先看kalman类。头文件内容:
#ifndef KALMAN_H
#define KALMAN_H
#include "matrix.h"
#include "model.h"
class Kalman
{
    size_t m_ns;    //状态维数
    size_t m_nm;    //量测维数
    Matrix Xkf;     //状态估计值
    Matrix Xpre;    //状态预测值
    Matrix Z;       //量测
    Matrix K;       //增益
    Matrix P;       //状态估计的协方差阵
    Matrix Ppre;    //状态预测的协方差阵
    Model *mp;      //模型,包含了状态方程、量测方程等
    Kalman();       //默认构造函数,私有化,不能调用
public:
    Kalman(size_t ns, size_t nm);           //构造函数,需要确定状态维数和量测维数
    void Init(const Matrix &X0, const Matrix &P0, Model *mp0);//初始化,初始化X0和P0,并与模型对象挂钩
    void SetMeasur(const Matrix &Zk);       //量测量进入滤波器
    void Iterator();                        //滤波器更新过程
    Matrix GetX() {return Xkf;}             //获取当前状态估计
};
#endif // KALMAN_H
 
源文件:
#include "kalman.h"
Kalman::Kalman()
{
}
Kalman::Kalman(size_t ns, size_t nm)
{
    m_ns = ns;
    m_nm = nm;
    K = Matrix(ns, nm);
    Xkf = Matrix(ns);
    P = Matrix(ns, ns);
    Ppre = Matrix(ns, ns);
    Z = Matrix(nm);
    Xpre = Matrix(ns);
}
void Kalman::Init(const Matrix &X0, const Matrix &P0, Model *mp0)
{
    Xkf = X0;
    P = P0;
    mp = mp0;
}
void Kalman::SetMeasur(const Matrix &Zk)
{
    Z = Zk;
}
void Kalman::Iterator()
{
    static Matrix I(Matrix::unit(m_ns));
    Matrix F(mp->GetF());
    Matrix H(mp->GetH());
    Matrix Q(mp->GetQ());
    Matrix R(mp->GetR());
    Xpre = mp->StateTrans(Xkf);
    Ppre = F*P*F.Trans() + Q;
    K = Ppre*H.Trans()*((H*Ppre*H.Trans() + R).Inv());
    Xkf = Xpre + K*(Z - H*Xpre);
    P = (I - K*H)*Ppre;
}
 
这几个成员函数都非常直观,尤其注意那个Iterator()函数,滤波器运行需要的转移矩阵F,量测矩阵H以及状态协方差阵 Q和量测方差阵R都是从模型里获取的。所以模型变了该模型就好了,不需要改这个滤波器。一步预测那里并没有写成Xpre=H*X,而是让模型对象去更新,这样写是为了适应以后扩展成非线性kalman滤波器,也就是ekf的时候方便一些。
下面就来设计模型类。头文件:
#ifndef MODEL_H
#define MODEL_H
#include "matrix.h"
class Model
{
    Matrix Q;
    Matrix R;
public:
    Model(): Q(Matrix::unit(1)*0.01), R(Matrix::unit(1)*0.25){}
    Matrix StateTrans(Matrix &X);         //状态转移,这里就是Xpre=H*Xkf
    void StateUpdate(Matrix &X);          //状态转移后还加入噪声,这里就是X=H*X+w
    Matrix MeasurPre(const Matrix &X);    //量测预测,没有加入量测噪声
    Matrix Measur(const Matrix &X);       //量测,加入量测噪声
    Matrix GetF();                        //获取状态转移矩阵,如果是ekf,可以改写这个函数,
                                          //获取状态方程的雅各比矩阵
    Matrix GetH();                        //获取量测矩阵,如果是ekf,改写这个函数,
                                          //获取量测方程的雅各比矩阵
    Matrix GetQ() {return Q;}                //获取模型的状态方程协方差阵
    Matrix GetR() {return R;}                //获取模型的量测方程的协方差阵
};
#endif // MODEL_H
 
Q和R都是一维矩阵,且值分别是0.01和0.25。然后是源文件:
#include "model.h"
Matrix Model::StateTrans(Matrix &X)
{
    Matrix F(Matrix::ones(1, 1));
    return F*X;
}
void Model::StateUpdate(Matrix &X)
{
    X = StateTrans(X);
    X = X + Sqrtm(Q)*Matrix::randn(1, 1);
}
Matrix Model::MeasurPre(const Matrix &X)
{
    Matrix H(Matrix::ones(1, 1));
    return  H*X;
}
Matrix Model::Measur(const Matrix &X)
{
    return MeasurPre(X) + Sqrtm(R)*Matrix::randn(1, 1);
}
Matrix Model::GetF()
{
    return Matrix::ones(1, 1);
}
Matrix Model::GetH()
{
    return Matrix::ones(1, 1);
}
 
我的矩阵类里已经实现了矩阵的各类运算,包括矩阵加减乘,矩阵与标量的加减乘以及矩阵转置、矩阵求逆、矩阵元素开根号。并能够生成全零矩阵、全1矩阵、单位矩阵、(0,1)正态分布的随机矩阵等。所以以上代码看上去还是很好理解的。
有了这两个类,主程序就非常简单了:
#include "model.h"
#include "kalman.h"
#include <stdio.h>
int main()
{
    FILE *fp;
    fp = fopen("F:/data/data.txt", "w");
    Model M;
    Kalman kf(1, 1);
    Matrix X(Matrix::ones(1, 1)*25.0);
    Matrix Z(1);
    Matrix P0(Matrix::ones(1, 1)*0.01);
    kf.Init(X, P0, &M);
    for(size_t i = 0; i < 100; i++)
    {
        M.StateUpdate(X);
        Z = M.Measur(X);
        kf.SetMeasur(Z);
        kf.Iterator();
        fprintf(fp, "%lf,%lf,%lf\n", X(0), kf.GetX()(0), Z(0));
    }
    fclose(fp);
    return 0;
}
 
初值就设定为25℃,P的初值也取的跟Q一样。用kf.Init()函数初始化,并与模型对象挂钩。
主程序在运行kalman的同时也对室内温度实际值进行了仿真,并将温度实际值(X),kalman滤波值(kf.GetX()),和量测值(Z)保存到文件里。
        来看看效果吧
蓝线为实际温度,绿线为测量值,红线为滤波输出。喏,看出效果了吧。
先这样吧。最近有些忙,回头我再写一个状态变化情况的kalman滤波仿真。



















