zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

卡尔曼滤波——包含C/C++实现方式及MPU6050实例

2023-04-18 16:13:42 时间

1.简介

简单的来说卡尔曼滤波的核心思想是:假定我们需要观测的系统输出是线性的,而噪声都满足高斯分布。这一刻系统的状态(最优估计)是这一刻的预测值和这一刻的测量值的加权平均,当得到最优估计之后,再将这一刻的最优估计和估计值进行对比,如果相差比较小,则说明估计比较准确,下次计算就加大估计值的权值,否则说明估计值不准确,下次计算就加大测量值的权值,循环以上过程,进行动态调整。

测量一个实际的参数时,可以根据以前的数据预测下一个数据的走向,我们把这个数作为预测值,与此同时还有一个传感器可以测量到这个参数,我们就把它作为测量值。如果对测量值和预测值的可信度没有把握就可以对半开,表示为下面这样:

输出值 = 0.5 * 测量值 + 0.5 * 预测值

如果这个测量值噪声有点大,不太可信,那就三七开吧,相信预测值多一点:

输出值 = 0.3 * 测量值 + 0.7 * 预测值

上面的式子换个写法的话:

输出值 = 0.3 * 测量值 + (1-0.3) * 预测值

= 预测值 + 0.3 *(测量值 - 预测值)

= 预测值 + K *(测量值 - 预测值)

这样我们用一个参数K就可以表示我们对数据的可信度(参数K越大,表示测量值越可信),我们总是希望输出值可信度越高越好,也就是不确定度越小越好。测量值和观测值都有不确定度,而卡尔曼滤波算法就是基于数值的不确定性调节这个参数K(卡尔曼增益)。在迭代不定次数后接近输出值不确定性最小的数值。

说实话很多时候对理论的东西不需要有太深的了解,主要是会用就行了,所以下面有大家用起来比较方便的的C和C++的版本代码,也有我实际做的MPU6050的输出滤波(C),是从工程当中抠出来的,但除了一些特定环境下的设置,算法部分是可以直接拿去用的。关于配置我也有一些建议

看下面的代码可能大家就会发现,估计噪声和测量噪声的方差是需要自己配置的,在实际工程当中中,这些数值可能得需要大家实测,但是我们可以从推导公式中发现,只要固定估计噪声和测量噪声的方差的比值,计算的结果就是一样的。实际上,我们在实际测试滤波效果的时候,调整比值就可以改变滤波的效果,这个比值越大,说明测量值越不可靠。

开始的时候还需要提供估计值的初值,这时最好把协方差矩阵的初始值设为非零数,也就是开始的状态基于测量值。(当然,如果你有非常可靠的状态初值,建议把协方差矩阵初始值设为零)

2.C语言版本

其中包含一维数据和二维数据的滤波(.c/.h)

/*
 * FileName : kalman_filter.h
 * Date     : 2022/3/31  
 */
#ifndef  _KALMAN_FILTER_H
#define  _KALMAN_FILTER_H

/* 
 * NOTES: n表示状态为n维
 *        测量总是为1维
 */

/* 1 维数据 */
typedef struct {
    float x;  /* 状态 */
    float A;  /* 先验状态估计 x(n)=A*x(n-1)+u(n),u(n)~N(0,q) */
    float H;  /* z(n)=H*x(n)+w(n),w(n)~N(0,r)   */
    float q;  /* 过程噪声协方差(预测) */
    float r;  /* 测量噪声协方差 */
    float p;  /* 估计误差协方差 */
    float gain;
} kalman1_state;

/* 2 维数据 */
typedef struct {
    float x[2];     /* 状态:[0]-角度[1]-角度差, 2x1 */
    float A[2][2];  /* X(n)=A*X(n-1)+U(n),U(n)~N(0,q), 2x2 */
    float H[2];     /* Z(n)=H*X(n)+W(n),W(n)~N(0,r), 1x2   */
    float q[2];     /* 过程噪声协方差(预测),2x1 [q0,0; 0,q1] */
    float r;        /* 测量噪声协方差 */
    float p[2][2];  /* 估计误差协方差,2x2 [p0 p1; p2 p3] */
    float gain[2];  /* 2x1 */
} kalman2_state;                   

extern void kalman1_init(kalman1_state *state, float init_x, float init_p);
extern float kalman1_filter(kalman1_state *state, float z_measure);
extern void kalman2_init(kalman2_state *state, float *init_x, float (*init_p)[2]);
extern float kalman2_filter(kalman2_state *state, float z_measure);

#endif  /*_KALMAN_FILTER_H*/
/*
 * FileName : kalman_filter.c
 * Date     : 2022/3/31  
 */
 
#include "kalman_filter.h"

/*
 * @brief   
 *   初始化结构体kalman1_state
 *    我在这个init函数中设置了一些默认值:
 *    a = 1;
 *    h = 1;
 *
 *   NOTES: 请根据自己的要求更改 A,H,q,r 
 * @inputs  
 *   state - 卡尔曼结构体
 *   init_x -初始x状态值
 *   init_p -初始估计误差协方差
 * @outputs 
 * @retval  
 */
void kalman1_init(kalman1_state *state, float init_x, float init_p)
{
    state->x = init_x;
    state->p = init_p;
    state->A = 1;
    state->H = 1;
    state->q = 2e2;//10e-6;  /* 预测噪声协方差 */
    state->r = 5e2;//10e-5;  /* 测量误差协方差 */
}

/*
 * @brief   
 *   1维卡尔曼滤波
 * @inputs  
 *   state - 卡尔曼结构体
 *   z_measure - 测量值
 * @outputs 
 * @retval  
 *   估计结果
 */
float kalman1_filter(kalman1_state *state, float z_measure)
{
    /* 预测 */
    state->x = state->A * state->x;
    state->p = state->A * state->A * state->p + state->q;  /* p(n|n-1)=A^2*p(n-1|n-1)+q */

    /* 测量 */
    state->gain = state->p * state->H / (state->p * state->H * state->H + state->r);
    state->x = state->x + state->gain * (z_measure - state->H * state->x);
    state->p = (1 - state->gain * state->H) * state->p;

    return state->x;
}

/*
 * @brief   
 *   初始化结构体kalman1_state
 *   我在这个init函数中设置了一些默认值:
 *     A = {{1, 0.1}, {0, 1}};
 *     H = {1,0}; 
 *    
 *   NOTES: 请根据自己的要求更改 A,H,q,r 
 *
 * @inputs  
 * @outputs 
 * @retval  
 */
void kalman2_init(kalman2_state *state, float *init_x, float (*init_p)[2])
{
    state->x[0]    = init_x[0];
    state->x[1]    = init_x[1];
    state->p[0][0] = init_p[0][0];
    state->p[0][1] = init_p[0][1];
    state->p[1][0] = init_p[1][0];
    state->p[1][1] = init_p[1][1];
    //state->A       = {{1, 0.1}, {0, 1}};
    state->A[0][0] = 1;
    state->A[0][1] = 0.1;
    state->A[1][0] = 0;
    state->A[1][1] = 1;
    //state->H       = {1,0};
    state->H[0]    = 1;
    state->H[1]    = 0;
    //state->q       = {{10e-6,0}, {0,10e-6}};  /* 测量噪声协方差 */
    state->q[0]    = 10e-7;
    state->q[1]    = 10e-7;
    state->r       = 10e-7;  /* 估计误差协方差 */
}

/*
 * @brief   
 *   2维卡尔曼滤波
 * @inputs  
 *   state - 卡尔曼结构体
 *   z_measure - 测量值
 * @outputs 
 *   state->x[0] - 更新状态值,如角度、速度 
 *   state->x[1] - 更新状态值,如差角,加速度
 *   state->p - 更新估计误差约定矩阵
 * @retval  
 *   返回值等于state->x[0],所以可能是角度或速度
 */
float kalman2_filter(kalman2_state *state, float z_measure)
{
    float temp0 = 0.0f;
    float temp1 = 0.0f;
    float temp = 0.0f;

    /* 预测 */
    state->x[0] = state->A[0][0] * state->x[0] + state->A[0][1] * state->x[1];
    state->x[1] = state->A[1][0] * state->x[0] + state->A[1][1] * state->x[1];
    /* p(n|n-1)=A^2*p(n-1|n-1)+q */
    state->p[0][0] = state->A[0][0] * state->p[0][0] + state->A[0][1] * state->p[1][0] + state->q[0];
    state->p[0][1] = state->A[0][0] * state->p[0][1] + state->A[1][1] * state->p[1][1];
    state->p[1][0] = state->A[1][0] * state->p[0][0] + state->A[0][1] * state->p[1][0];
    state->p[1][1] = state->A[1][0] * state->p[0][1] + state->A[1][1] * state->p[1][1] + state->q[1];

    /* 测量 */
    /* gain = p * H^T * [r + H * p * H^T]^(-1), H^T 意思是转置. */
    temp0 = state->p[0][0] * state->H[0] + state->p[0][1] * state->H[1];
    temp1 = state->p[1][0] * state->H[0] + state->p[1][1] * state->H[1];
    temp  = state->r + state->H[0] * temp0 + state->H[1] * temp1;
    state->gain[0] = temp0 / temp;
    state->gain[1] = temp1 / temp;
    /* x(n|n) = x(n|n-1) + gain(n) * [z_measure - H(n)*x(n|n-1)]*/
    temp = state->H[0] * state->x[0] + state->H[1] * state->x[1];
    state->x[0] = state->x[0] + state->gain[0] * (z_measure - temp); 
    state->x[1] = state->x[1] + state->gain[1] * (z_measure - temp);

    /* 更新 p: p(n|n) = [I - gain * H] * p(n|n-1) */
    state->p[0][0] = (1 - state->gain[0] * state->H[0]) * state->p[0][0];
    state->p[0][1] = (1 - state->gain[0] * state->H[1]) * state->p[0][1];
    state->p[1][0] = (1 - state->gain[1] * state->H[0]) * state->p[1][0];
    state->p[1][1] = (1 - state->gain[1] * state->H[1]) * state->p[1][1];

    return state->x[0];
}

3.C++版本

关于输出角度和角加速度数值的滤波,也可以按自己的需求更改

/*
 * FileName : kalman.cpp
 * Date     : 2022/3/31  
 */

#include "Kalman.h"

Kalman::Kalman() {
    /* 可以根据自己的情况设定下列值 */
    Q_angle = 0.001f;
    Q_bias = 0.003f;
    R_measure = 0.03f;

    angle = 0.0f; // 重置角度
    bias = 0.0f; // 重置误差

    P[0][0] = 0.0f; // 因为我们假设偏差为0,并且我们知道起始角度(使用setAngle),所以误差协方差矩阵是这样设置的
    P[0][1] = 0.0f;
    P[1][0] = 0.0f;
    P[1][1] = 0.0f;
};

// 角度单位: 度 角速度单位: 度/秒 时间单位: 秒
float Kalman::getAngle(float newAngle, float newRate, float dt) {
    // 卡尔曼滤波模块

    // 离散卡尔曼滤波器时间更新方程-时间更新
    // 更新xhat - 提前预测状态
    /* 第一步 */
    rate = newRate - bias;
    angle += dt * rate;

    // 更新估计误差协方差-提前预测误差协方差
    /* 第二步 */
    P[0][0] += dt * (dt*P[1][1] - P[0][1] - P[1][0] + Q_angle);
    P[0][1] -= dt * P[1][1];
    P[1][0] -= dt * P[1][1];
    P[1][1] += Q_bias * dt;

    // 离散卡尔曼滤波器测量更新方程-测量更新
    // 计算卡尔曼增益-估算卡尔曼增益
    /* 第四步 */
    float S = P[0][0] + R_measure; 
    /* 第五步 */
    float K[2]; //卡尔曼增益-这是一个2x1的向量
    K[0] = P[0][0] / S;
    K[1] = P[1][0] / S;

    // 计算角度和偏差-用测量zk更新估计(newAngle)
    /* 第三步 */
    float y = newAngle - angle; 
    /* 第六步 */
    angle += K[0] * y;
    bias += K[1] * y;

    // 计算估计误差协方差-更新误差协方差
    /* 第七步 */
    float P00_temp = P[0][0];
    float P01_temp = P[0][1];

    P[0][0] -= K[0] * P00_temp;
    P[0][1] -= K[0] * P01_temp;
    P[1][0] -= K[1] * P00_temp;
    P[1][1] -= K[1] * P01_temp;

    return angle;
};

void Kalman::setAngle(float angle) { this->angle = angle; }; //用于设置角度时,应将此设置为起始角度
float Kalman::getRate() { return this->rate; }; 

/* 这些是用来调整卡尔曼滤波器的 */
void Kalman::setQangle(float Q_angle) { this->Q_angle = Q_angle; };
void Kalman::setQbias(float Q_bias) { this->Q_bias = Q_bias; };
void Kalman::setRmeasure(float R_measure) { this->R_measure = R_measure; };

float Kalman::getQangle() { return this->Q_angle; };
float Kalman::getQbias() { return this->Q_bias; };
float Kalman::getRmeasure() { return this->R_measure; };
/*
 * FileName : kalman.h
 * Date     : 2022/3/31  
 */

#ifndef _Kalman_h_
#define _Kalman_h_
class Kalman {
public:
    Kalman();
    // 角度的单位:度 速率的单位:度/秒 时间的单位:秒
    float getAngle(float newAngle, float newRate, float dt);//主滤波函数 dt:采样频率
    void setAngle(float angle); // 用于设置角度时,应将此设置为起始角度
    float getRate(); // 返回无偏估计

    /* 这些是用来调整卡尔曼滤波器的 */
    void setQangle(float Q_angle);
    /**
     * setQbias(float Q_bias)
     * 在Kalman.cpp的默认值是 (0.003f)  
     * 提高这个值来更紧密地跟踪输入,
     * 降低这个值来平滑卡尔曼滤波的结果。
     */
    void setQbias(float Q_bias);
    void setRmeasure(float R_measure);

    float getQangle();
    float getQbias();
    float getRmeasure();
private:
    /*卡尔曼滤波器的变量 */
    float Q_angle; // 处理加速度计的噪声方差
    float Q_bias; // 陀螺仪偏差的处理噪声方差
    float R_measure; // 测量噪声方差——这实际上是测量噪声的方差
    float angle; // 由卡尔曼滤波器计算的角度- 2x1状态向量的一部分
    float bias; // 由卡尔曼滤波器计算的陀螺偏置部分-2x1状态向量
    float rate; // 无偏估计计算从速率和计算偏差-我们必须调用getAngle来更新速率
    float P[2][2]; // 误差协方差矩阵-这是一个2x2矩阵
#endif

4.MPU6050滤波实例,亲测有效

#ifndef __KALMAN_H
#define __KALMAN_H
#include "math.h"

#define Pi 	3.1415926
//噪声参数
#define dt           0.012   //kalman滤波器采样时间
#define R_angle      0.5
#define Q_angle      0.001   //角度数据置信度
#define Q_gyro       0.003   //角速度数据置信度

static float Angle_x_temp;  
static float Angle_y_temp;  
static float Angle_z_temp;

static float Angle_X_Final; 
static float Angle_Y_Final; 
static float Angle_Z_Final; 


static short aacx, aacy, aacz;		//加速度传感器原始数据
static short gyrox, gyroy, gyroz;	//陀螺仪原始数据

double KalmanFilter(const double ResrcData, double ProcessNiose_Q, double MeasureNoise_R);
void Angle_Calculate(void);
void Kalman_Filter_X(float Accel,float Gyro);
void Kalman_Filter_Y(float Accel,float Gyro);
void Kalman_Filter_Z(float Accel, float Gyro);

#endif
/***********************卡尔曼滤波算法***************************/

#include "Kalman.h"

float Accel_x;	     
float Accel_y;	     
float Accel_z;	     

float Gyro_x;		 
float Gyro_y;            
float Gyro_z;		 

//其他地方已经定义
//float Angle_gy;    
//static float Angle_x_temp;  
//static float Angle_y_temp;  
//static float Angle_z_temp;

//static float Angle_X_Final; 
//static float Angle_Y_Final; 
//static float Angle_Z_Final; 


char  C_0 = 1;
float Q_bias_x, Q_bias_y, Q_bias_z;
float Angle_err_x, Angle_err_y, Angle_err_z;
float PCt_0, PCt_1, E;
float K_0, K_1, t_0, t_1;
float Pdot[4] = { 0,0,0,0 };
float PP[2][2] = { { 1, 0 },{ 0, 1 } };

//卡尔曼理论算法
double KalmanFilter(const double ResrcData, double ProcessNiose_Q, double MeasureNoise_R)
{
	double R = MeasureNoise_R;    //测量噪声  R 增大,动态响应变慢,收敛稳定性变好
	double Q = ProcessNiose_Q;    //过程噪声  Q 增大,动态响应变快,收敛稳定性变坏
	static double x_last;
	double x_mid = x_last;
	double x_now;
	static double p_last;
	double p_mid;
	double p_now;
	double kg;    //卡尔曼增益
        
        /*******预测过程*******/
	x_mid = x_last;           //x_last=x(k-1|k-1),x_mid=x(k|k-1)
	p_mid = p_last + Q;       //p_mid=p(k|k-1),p_last=p(k-1|k-1)
        
        /*******更新过程*******/
	kg = p_mid / (p_mid + R); 
	x_now = x_mid + kg*(ResrcData - x_mid);
	p_now = (1 - kg)*p_mid;
        
        
	p_last = p_now; 
	x_last = x_now; 
	return x_now;
}

//主调用函数
void Angle_Calculate(void)
{
	
	//deg = rad*180/3.14
	float x=0, y=0, z=0;

        //初始变量赋值给计算变量
	Accel_x = aacx; 
	Accel_y = aacy; 
	Accel_z = aacz; 
	Gyro_x = gyrox;  
	Gyro_y = gyroy;  
	Gyro_z = gyroz;  

	//根据实际给定的限制计算
        //MPU6050初始化设置范围为2g时,灵敏度 16384 LSB/g
	if (Accel_x<32764) x = Accel_x / 16384;
	else              x = 1 - (Accel_x - 49152) / 16384;

	
	if (Accel_y<32764) y = Accel_y / 16384;
	else              y = 1 - (Accel_y - 49152) / 16384;

	
	if (Accel_z<32764) z = Accel_z / 16384;
	else              z = (Accel_z - 49152) / 16384;

	//计算与x轴的夹角
	Angle_x_temp = (atan2(z , y)) * 180 / Pi;
	Angle_y_temp = (atan2(x , z)) * 180 / Pi;
	Angle_z_temp = (atan2(y , x)) * 180 / Pi;

	
	if (Accel_y<32764) Angle_y_temp = +Angle_y_temp;
	if (Accel_y>32764) Angle_y_temp = -Angle_y_temp;
	if (Accel_x<32764) Angle_x_temp = +Angle_x_temp;
	if (Accel_x>32764) Angle_x_temp = -Angle_x_temp;
	if (Accel_z<32764) Angle_z_temp = +Angle_z_temp;
	if (Accel_z>32764) Angle_z_temp = -Angle_z_temp;
	
	
        
	if (Gyro_x<32768) Gyro_x = -(Gyro_x / 16.4);    //去除零点偏移,计算角速度值,负号为方向处理 
	
	if (Gyro_x>32768) Gyro_x = +(65535 - Gyro_x) / 16.4;
	
	if (Gyro_y<32768) Gyro_y = -(Gyro_y / 16.4);
	
	if (Gyro_y>32768) Gyro_y = +(65535 - Gyro_y) / 16.4;
	
	if (Gyro_z<32768) Gyro_z = -(Gyro_z / 16.4);
	
	if (Gyro_z>32768) Gyro_z = +(65535 - Gyro_z) / 16.4;

	
	Kalman_Filter_X(Angle_x_temp, Gyro_x);  ;
	Kalman_Filter_Y(Angle_y_temp, Gyro_y);  
	Kalman_Filter_Z(Angle_z_temp, Gyro_z);  
}


void Kalman_Filter_X(float Accel, float Gyro) 
{
        /*****状态估计*****/
	Angle_X_Final += (Gyro - Q_bias_x) * dt;    //先验估计

	Pdot[0] = Q_angle - PP[0][1] - PP[1][0];   

	Pdot[1] = -PP[1][1];
	Pdot[2] = -PP[1][1];
	Pdot[3] = Q_gyro;

        /******方差估计*******/
	PP[0][0] += Pdot[0] * dt;      
	PP[0][1] += Pdot[1] * dt;   
	PP[1][0] += Pdot[2] * dt;
	PP[1][1] += Pdot[3] * dt;
        
        /*****残差*****/
	Angle_err_x = Accel - Angle_X_Final;	

	PCt_0 = C_0 * PP[0][0];
	PCt_1 = C_0 * PP[1][0];

	E = R_angle + C_0 * PCt_0;
        
        
        /*****最优增益*****/
	K_0 = PCt_0 / E;
	K_1 = PCt_1 / E;

	t_0 = PCt_0;
	t_1 = C_0 * PP[0][1];
        
        /*****数据更新*****/
	PP[0][0] -= K_0 * t_0;		 
	PP[0][1] -= K_0 * t_1;
	PP[1][0] -= K_1 * t_0;
	PP[1][1] -= K_1 * t_1;

	Angle_X_Final += K_0 * Angle_err_x;	 
	Q_bias_x += K_1 * Angle_err_x;	 
	Gyro_x = Gyro - Q_bias_x;	 
}

void Kalman_Filter_Y(float Accel, float Gyro) 		
{
	Angle_Y_Final += (Gyro - Q_bias_y) * dt; 

	Pdot[0] = Q_angle - PP[0][1] - PP[1][0];  

	Pdot[1] = -PP[1][1];
	Pdot[2] = -PP[1][1];
	Pdot[3] = Q_gyro;

	PP[0][0] += Pdot[0] * dt;    
	PP[0][1] += Pdot[1] * dt;    
	PP[1][0] += Pdot[2] * dt;
	PP[1][1] += Pdot[3] * dt;

	Angle_err_y = Accel - Angle_Y_Final;	

	PCt_0 = C_0 * PP[0][0];
	PCt_1 = C_0 * PP[1][0];

	E = R_angle + C_0 * PCt_0;

	K_0 = PCt_0 / E;
	K_1 = PCt_1 / E;

	t_0 = PCt_0;
	t_1 = C_0 * PP[0][1];

	PP[0][0] -= K_0 * t_0;		 
	PP[0][1] -= K_0 * t_1;
	PP[1][0] -= K_1 * t_0;
	PP[1][1] -= K_1 * t_1;

	Angle_Y_Final += K_0 * Angle_err_y;	 
	Q_bias_y += K_1 * Angle_err_y;	 
	Gyro_y = Gyro - Q_bias_y;	 
}

void Kalman_Filter_Z(float Accel, float Gyro) 
{
	Angle_Z_Final += (Gyro - Q_bias_z) * dt; 

	Pdot[0] = Q_angle - PP[0][1] - PP[1][0]; 

	Pdot[1] = -PP[1][1];
	Pdot[2] = -PP[1][1];
	Pdot[3] = Q_gyro;

	PP[0][0] += Pdot[0] * dt;   
	PP[0][1] += Pdot[1] * dt;    
	PP[1][0] += Pdot[2] * dt;
	PP[1][1] += Pdot[3] * dt;

	Angle_err_z = Accel - Angle_Z_Final;	

	PCt_0 = C_0 * PP[0][0];
	PCt_1 = C_0 * PP[1][0];

	E = R_angle + C_0 * PCt_0;

	K_0 = PCt_0 / E;
	K_1 = PCt_1 / E;

	t_0 = PCt_0;
	t_1 = C_0 * PP[0][1];

	PP[0][0] -= K_0 * t_0;		 
	PP[0][1] -= K_0 * t_1;
	PP[1][0] -= K_1 * t_0;
	PP[1][1] -= K_1 * t_1;

	Angle_Z_Final += K_0 * Angle_err_z;	 
	Q_bias_z += K_1 * Angle_err_z;	 
	Gyro_z = Gyro - Q_bias_z;	 
}