这篇文章,我们将使用史上最经典状态估计算法,也就是卡尔曼滤波来来解决相同的问题,我们将从原理、系统模型到C代码实现全过程讲解,让你一篇文章就搞懂如何实战卡尔曼滤波算法。
卡尔曼滤波算法
算法思想
当我们想要通过传感器的测量获取物体的状态(比如位置、速度或者角度)时,常常会遇到各种问题,比如传感器的误差、噪声等。这些问题会导致我们得到的数据不够准确或者不稳定。卡尔曼滤波的思想很简单,它不只是单纯地采用传感器提供的数据,还结合了我们对物体运动规律的认识或者经验。比如,如果我们知道物体的在做匀速运动,那么即使传感器提供的速度数据有问题,但是我们任然也可以通过这个运动规律来修正它。
让我们来想象一个常见的情景:
开车定位
假设你正在开车匀速直线行驶,我们可以通过车上的GPS系统来获取车的位置。但是由于GPS系统不够准确,你可能会得到一些不太可信的位置信息。这时候,你就可以使用卡尔曼滤波来纠正这些位置估计的错误。
卡尔曼滤波会首先根据你的车速和方向,预测你在下一个时刻的位置。然后,当GPS系统提供新的位置信息时,卡尔曼滤波会将这个观测值与预测值进行比较。如果观测值与预测值相符,那么卡尔曼滤波就会相信GPS系统提供的位置信息;但如果观测值与预测值有很大的偏差,那么卡尔曼滤波就会调整你的位置估计,使之更接近实际情况。
通过不断地重复这个过程,卡尔曼滤波可以逐渐提高对你的位置的准确度,即使GPS系统偶尔出错。这样,你就能更可靠地了解自己的位置,从而更安全地行驶。
算法步骤
卡尔曼滤波通过两个重要的步骤来实现:预测和更新,这个实现步骤和算法思想是息息相关的。
算法步骤
预测:
状态预测:
状态预测
协方差预测:
协方差预测
在预测步骤中,我们根据系统模型(由状态转移矩阵 F 和控制输入矩阵 B 描述)来预测下一个状态的值,并且根据过程噪声的协方差矩阵 Q 来估计预测的不确定性。
更新:
计算卡尔曼增益:
增益计算
更新状态估计:
更新状态估计
更新协方差:
更新协方差矩阵
在更新步骤中,我们将预测的状态值与实际观测值进行比较,根据观测噪声的协方差矩阵 R 来估计观测值的不确定性,并计算出一个卡尔曼增益,用来权衡预测值和观测值。最后,我们使用卡尔曼增益来更新状态的估计值和协方差矩阵,以获得更准确的状态估计值。
总的来说,卡尔曼滤波通过动态地结合系统模型和观测值,以及它们的不确定性信息,来得到对系统状态更准确的估计。这使得卡尔曼滤波在处理带有噪声的测量数据时能够提供更可靠的估计结果。
总结一下,首先利用系统的模型,基于上一次的最优结果来预测当前状态值,然后结合观测数据来进一步修正当前状态值,从而得到这一次最优结果值。
Q和R取值的意义
矩阵 Q 表示系统模型的误差的协方差,即在预测步骤中,我们对状态的预测有多不确定。一般来说,Q 的值越小,表示我们对系统模型的信任度越高,即我们认为系统的动态规律是比较可靠的,预测的结果也就更加可信。
如果系统的动态变化比较缓慢,那么 Q 的值可以设置得比较小;如果系统的动态变化很快,那么 Q 的值可能需要设置得比较大,以反映出系统的不确定性。通常情况下,Q 的取值是根据经验或者系统特性进行调整的。
矩阵 R 表示观测误差的协方差,即在更新步骤中,我们对观测值的信任程度。R 的取值大小取决于传感器的精度和环境噪声等因素。一般来说,R 的值越小,表示我们对观测值的信任度越高,即我们认为传感器的测量结果是比较可靠的,更新的结果也就更加可信。
如果传感器的精度很高,环境噪声很小,那么 R 的值可以设置得比较小;如果传感器存在较大的测量误差,或者环境噪声比较大,那么 R 的值可能需要设置得比较大,以反映出观测值的不确定性。同样,R 的取值通常是需要根据实际情况进行调整的。
陀螺仪加速度计滤波角度估计
要得到平衡车的倾斜角度,其实就是传感器绕着平行于轮轴的轴旋转的角度。这里我们以此陀螺仪旋转轴的角速度newGyro输出作为系统输入u,我们知道对角度积分能得到角度值,利用这个先验模型来预测更新系统状态。然后把加速度计估计重力加速度在物体局部坐标系下的投影反正切计算得到角度值作为观测值。这样我们就可以利用卡尔曼算法来得到更准确的角度估计。
下面我们就按照上面讲到的算法步骤按部就班来实现:
预测当前角度
我们知道,对角速度积分是旋转角度,于是有:
其中angle为绕轴旋转角度,bias表示角速度偏差值。newGyro是陀螺仪旋转轴的角速度值,这里作为系统输入。dt为传感器采样时间,比如0.01秒。
定义系统状态向量为:
那么系统的状态模型可以写成如下矩阵形式:
我们可以通过 上面公式来预测当前角度。
预测协方差矩阵
由协方差方程
其中由上面系统模型可知,
定义系统过程协方差噪声
注意,前面讲到,Q_angle和Q_bias是常数,表征角度值和角速度值的误差方差不确定性,具体可以有经验整定出来。
将Q和F代入公式有:
展开公式有:
测量方程
系统测量方差:
Zk是实际测量值,我们这里是加速度计算出来的角度值。H是测量系数矩阵,表示测量值与状态量xk的转换关系的,Vk是测量噪声。
其中H=[1 0] .因为测量值是角度,与角速度偏差bias无关,所以H第二项是0。
设加速度计角度计算值为newAngle,那么Zk=newAngle。
计算卡尔曼增益K
根据上面,
定义R=R_measure,为常数,表示角速度计角度的测量方差。因为观测只有一个数据,所以R是1*1矩阵。
所以代入R,P,H矩阵,可以得到:
K为2*1列向量,k0为第一项,k2为第二项。
更新状态估计
由更新状态估计公式可得:
展开矩阵:
更新协方差矩阵
根据上面公式,可得:
展开矩阵可得:
C代码实现
下面我们把上述过程转成C代码实现:
include
// 状态向量
typedef struct {
float angle; // 姿态角度
float bias; // 角速度偏差
} State;
// 卡尔曼滤波器结构
typedef struct {
State state; // 状态
float P[2][2]; // 协方差矩阵
float Q_angle; // 过程噪声协方差
float Q_bias;
float R_measure; // 观测噪声协方差
float dt; // 时间步长
} KalmanFilter;
// 初始化滤波器
void kalman_init(KalmanFilter *kf, float Q_angle, float Q_bias,
float R_measure, float dt) {
kf->state.angle = 0.0;
kf->state.bias = 0.0;
kf->P[0][0] = 0.0;
kf->P[0][1] = 0.0;
kf->P[1][0] = 0.0;
kf->P[1][1] = 0.0;
kf->Q_angle = Q_angle;
kf->Q_bias = Q_bias;
kf->R_measure = R_measure;
kf->dt = dt;
}
// 卡尔曼滤波算法
float kalman_filter(KalmanFilter kf, float new_angle, float new_Gyro) {
// 预测
//先验估计
kf->state.angle += kf->dt (new_Gyro - kf->state.bias);
//协方差估计
kf->P[0][0] += kf->dt (kf->dtkf->P[1][1] - kf->P[0][1]
+ kf->P[1][0] )+ kf->Q_angle;
kf->P[0][1] -= kf->dt * kf->P[1][1];
kf->P[1][0] -= kf->dt * kf->P[1][1];
kf->P[1][1] += kf->Q_bias;
// 更新
//角度差
float y = new_angle - kf->state.angle;
//K增益矩阵计算
float S = kf->P[0][0] + kf->R_measure;
float K[2];
K[0] = kf->P[0][0] / S;
K[1] = kf->P[1][0] / S;
//后验状态更新
kf->state.angle += K[0] * y;
kf->state.bias += K[1] * y;
//后验协方差误差更新
kf->P[0][0] -= K[0] * kf->P[0][0];
kf->P[0][1] -= K[0] * kf->P[0][1];
kf->P[1][0] -= K[1] * kf->P[0][0];
kf->P[1][1] -= K[1] * kf->P[0][1];
return kf->state.angle;
}
int main() {
// 设置卡尔曼滤波器参数
float Q_angle = 0.00005;
float Q_bias = 0.00015;
float R_measure = 0.5;
float dt = 0.005;//传感器实际采样频率
//代码效果参考:http://www.zidongmutanji.com/zsjx/101439.html
KalmanFilter kf;
kalman_init(&kf, Q_angle, Q_bias, R_measure, dt);
// 陀螺仪和加速度计数据
float gyro_data = 0.1; // 陀螺仪数据,通过I2C读取
float accel_data = 0.0; // 加速度计数据,通过I2C读取
float angle = kalman_filter(&kf, accel_data, gyro_data);
printf("Filtered Angle: %.2f\n", angle);
return 0;
}
通过调节超参数取值到Q_angle = 0.00005,Q_bias = 0.00015,R_measure = 0.5时,滤波效果比较不错。如下图:
滤波效果
我们将小车静置桌面下测得的实际效果数据。图中白色线是陀螺仪先验估计角度,黄色线是加速度计测量计算得到角度数据,而红色线是卡尔曼滤波融合之后的数据,红色线完美抑制了白线的漂移,同时滤掉了黄色加速度数据的毛刺,效果非常完美!