在实际项目中,常常需要对数据进行滤波,这里分享卡尔曼滤波算法应用。
卡尔曼滤波(KF)属于线性滤波器,使用均值和方差描述系统状态,将多个符合高斯分布的不确定信息源进行数据融合的一种最优状态估计算法。
使用前提:
线性:系统状态方程为线性(比例性、齐次性)的系统,非线性则需要使用扩展卡尔曼滤波(EKF)。
KF和EKF最大的区别在于:EKF的 F k F_k Fk和 H k H_k Hk都是使用雅各比矩阵表示
高斯分布:系统噪声服从高斯分布
传感器噪声
ADC采样噪声
X ^ k = F k X ^ k − 1 + B k U ⃗ k \hat{X}_k=F_k\hat{X}_{k-1}+B_k\vec{U}_k X^k=FkX^k−1+BkUk
P k = F k P k − 1 F k T + Q k P_k=F_kP_{k-1}F^T_k+Q_k Pk=FkPk−1FkT+Qk
K ′ = P k H k T H k P k H k T + R k K'=\frac {P_kH^T_k}{H_kP_kH^T_k+R_k} K′=HkPkHkT+RkPkHkT
X k ′ ^ = X ^ k + K ′ ( Z ⃗ k − H k X ^ k ) \hat{X_k'}=\hat{X}_k+K'(\vec{Z}_k-H_k\hat{X}_k) Xk′^=X^k+K′(Zk−HkX^k)
P k ′ = P k − K ′ H k P k {P_k}'=P_k-K'H_kP_k Pk′=Pk−K′HkPk
X ^ k \hat{X}_k X^k为 k k k时刻系统最优的状态向量
F k F_k Fk为状态转移矩阵,表示用 k − 1 k-1 k−1时刻的状态向量估算 k k k时刻的状态向量
B k B_k Bk为输入控制矩阵,代表着控制向量 U ⃗ k \vec{U}_k Uk和状态向量 X ^ k \hat{X}_k X^k的关系
U ⃗ k \vec{U}_k Uk代表着控制向量,如加速度、角加速度等
P k P_k Pk为状态向量的协方差矩阵,代表着状态向量每个元素之间的关系
Q k Q_k Qk为预测状态的高斯噪声协方差矩阵,它用来衡量模型的准确度,模型越准确其值越小
Z ⃗ k \vec{Z}_k Zk为 k k k时刻测量值的状态向量
H k H_k Hk为转换矩阵,它将状态向量 X ^ k \hat{X}_k X^k映射到测量值所在的向量空间 Z ⃗ k \vec{Z}_k Zk
R k R_k Rk为测量值的高斯噪声协方差矩阵,代表着传感器的测量误差
在一阶系统下, F k F_k Fk的值为1,无外部控制向量 U ⃗ k \vec{U}_k Uk的作用。所以,代入公式 X ^ k = F k X ^ k − 1 + B k U ⃗ k \hat{X}_k=F_k\hat{X}_{k-1}+B_k\vec{U}_k X^k=FkX^k−1+BkUk得出:
X ^ k = X ^ k − 1 \hat{X}_k=\hat{X}_{k-1} X^k=X^k−1
在一阶系统下, F k F_k Fk的值为1,所以转置矩阵 F k T F^T_k FkT的值也为1。所以,代入公式 P k = F k P k − 1 F k T + Q k P_k=F_kP_{k-1}F^T_k+Q_k Pk=FkPk−1FkT+Qk得出:
P k = P k − 1 + Q k P_k=P_{k-1}+Q_k Pk=Pk−1+Qk
在一阶系统下, H k H_k Hk的值为1。所以,代入公式 K ′ = P k H k T H k P k H k T + R k K'=\frac {P_kH^T_k}{H_kP_kH^T_k+R_k} K′=HkPkHkT+RkPkHkT得出:
K ′ = P k P k + R k K'=\frac {P_k}{P_k+R_k} K′=Pk+RkPk
在一阶系统下, H k H_k Hk的值为1。所以,代入公式 X k ′ ^ = X ^ k + K ′ ( Z ⃗ k − H k X ^ k ) \hat{X_k'}=\hat{X}_k+K'(\vec{Z}_k-H_k\hat{X}_k) Xk′^=X^k+K′(Zk−HkX^k)得出:
X k ′ ^ = X ^ k + K ′ ( Z ⃗ k − X ^ k ) \hat{X_k'}=\hat{X}_k+K'(\vec{Z}_k-\hat{X}_k) Xk′^=X^k+K′(Zk−X^k)
在一阶系统下, H k H_k Hk的值为1。所以,代入公式 P k ′ = P k − K ′ H k P k {P_k}'=P_k-K'H_kP_k Pk′=Pk−K′HkPk得出:
P k ′ = P k − K ′ P k {P_k}'=P_k-K'P_k Pk′=Pk−K′Pk
typedef struct{
float last_P;
float Q;
float R;
float out;
float now_P;
float Kg;
}kalman_t;
kalman_t my_kalman;
float kalman_filter_init(void)
{
my_kalman.last_P = 1.0;
my_kalman.Q = 0;
my_kalman.R = 0.01;
my_kalman.out = 0;
my_kalman.now_P = 0;
my_kalman.Kg = 0;
}
float kalman_filter_calc(kalman_t *kfp, float input)
{
kfp->now_P = kfp->last_P + kfp->Q;
kfp->Kg = kfp->now_P / (kfp->now_P + kfp->R);
kfp->out += kfp->Kg * (input - kfp->out);
kfp->last_P = (1 - kfp->Kg) * kfp->now_P;
return kfp->out;
}

以经典卡尔曼滤波模型:加速度计、陀螺仪进行角度数据融合为例。
首先,根据系统建立系统方程:
a n g l e k = a n g l e k − 1 − Q _ b i a s k − 1 ∗ Δ t + G y r o k ∗ Δ t + 1 2 ∗ ( G y r o k − G y r o k − 1 ) ∗ Δ t 2 angle_k = angle_{k-1} - Q\_bias_{k-1}*\Delta{t}+Gyro_k*\Delta{t}+\frac{1}{2}*(Gyro_k-Gyro_{k-1})*{\Delta _{t}}^{2} anglek=anglek−1−Q_biask−1∗Δt+Gyrok∗Δt+21∗(Gyrok−Gyrok−1)∗Δt2
Q _ b i a s k = Q _ b i a s k − 1 Q\_bias_{k} = Q\_bias_{k-1} Q_biask=Q_biask−1
用矩阵的方式表示为:
∣
a
n
g
l
e
k
Q
_
b
i
a
s
k
∣
=
∣
1
−
Δ
t
0
1
∣
∣
a
n
g
l
e
k
−
1
Q
_
b
i
a
s
k
−
1
∣
+
∣
Δ
t
0
∣
G
y
r
o
k
+
∣
1
2
∗
Δ
t
2
0
∣
(
G
y
r
o
k
−
G
y
r
o
k
−
1
)
a n g l e angle angle为角度,如pitch、roll、yaw
Q _ b i a s Q\_bias Q_bias为陀螺仪零漂,固定值
由于 Δ t 2 {\Delta_{t}}^{2} Δt2非常小,所以忽略不计,所以 B k B_k Bk为0
F
k
=
∣
1
−
Δ
t
0
1
∣
F_k=
所以,预测状态方程的C代码如下:
angle += (gyro - Q_bias) * dt;//预测角度等于上次最优角度加上
//角速度减去零漂后的积分角度
由于高斯分布中的每个数据点的协方差可以用
C
o
v
(
x
)
=
Σ
Cov(x)=\Sigma
Cov(x)=Σ表示,所以高斯噪声协方差矩阵
Q
k
=
∣
C
o
v
(
a
n
g
l
e
,
a
n
g
l
e
)
C
o
v
(
Q
_
b
i
a
s
,
a
n
g
l
e
)
C
o
v
(
a
n
g
l
e
,
Q
_
b
i
a
s
)
C
o
v
(
Q
_
b
i
a
s
,
Q
_
b
i
a
s
)
∣
Q_k=
因为加速度计和陀螺仪噪声相互独立,所以:
Q
k
=
∣
C
o
v
(
a
n
g
l
e
,
a
n
g
l
e
)
0
0
C
o
v
(
Q
b
i
a
s
,
Q
b
i
a
s
)
∣
Q_k=
假设预测状态协方差矩阵
P
k
=
∣
a
k
b
k
c
k
d
k
∣
P_k=
代入预测状态协方差方程得出:
P
k
=
∣
a
k
−
1
−
(
c
k
−
1
+
b
k
−
1
)
∗
Δ
t
+
Δ
t
2
b
k
−
1
−
d
k
−
1
∗
Δ
t
c
k
−
1
−
d
k
−
1
∗
Δ
t
d
k
−
1
∣
+
∣
C
o
v
(
a
n
g
l
e
,
a
n
g
l
e
)
0
0
C
o
v
(
Q
_
b
i
a
s
,
Q
_
b
i
a
s
)
∣
P_k=
所以,预测状态协方差方程的C代码如下:
PP[0][0] = PP[0][0] + Q_angle - (PP[0][1] - PP[1][0])*dt;
PP[0][1] = PP[0][1] - PP[1][1]*dt;
PP[1][0] = PP[1][0] - PP[1][1]*dt;
PP[1][1] = PP[1][0] + Q_gyro;
首先建立测量方程 Z k = H k X k + V k Z_k=H_kX_k+V_k Zk=HkXk+Vk
Z k Z_k Zk为 k k k时刻实际观测量
X k X_k Xk为 k k k时刻状态量
V k V_k Vk为 k k k时刻的噪声
由于传感器数据自带噪声,所以 V k = 0 V_k=0 Vk=0
所以测量方程可以用矩阵表示为:
Z
k
=
∣
1
0
∣
∣
X
k
0
∣
Z_k=
所以转换矩阵
H
k
=
∣
1
0
∣
H_k=
代入卡尔曼增益方程得出:
K
′
=
∣
a
b
c
d
∣
∣
1
0
∣
∣
1
0
∣
∣
a
b
c
d
∣
∣
1
0
∣
+
R
_
a
n
g
l
e
=
a
+
c
a
+
R
_
a
n
g
l
e
K'=\frac{
所以卡尔曼增益方程的C代码如下:
K_0 = PP[0][0] / (PP[0][0] + R_angle); //angle增益
K_1 = PP[1][0] / (PP[0][0] + R_angle); //Q_bias增益
代入更新最优状态方程得出:
∣
a
n
g
l
e
Q
_
b
i
a
s
∣
=
∣
a
n
g
l
e
Q
_
b
i
a
s
∣
+
∣
K
0
K
1
∣
(
a
n
g
l
e
z
−
∣
1
0
∣
∣
a
n
g
l
e
Q
_
b
i
a
s
∣
)
所以更新最优状态方程的C代码如下:
Q_bias += K_1 * (angle_now - angle);
angle += K_0 * (angle_now - angle);
代入更新状态协方差方程得出:
∣
a
k
′
b
k
′
c
k
′
d
k
′
∣
=
(
∣
1
0
0
1
∣
−
∣
K
0
K
1
∣
∣
1
0
∣
)
∗
∣
a
k
b
k
c
k
d
k
∣
所以更新状态协方差的C代码如下:
PP[0][0] = PP[0][0] - K_0 * PP[0][0];
PP[0][1] = PP[0][1] - K_0 * PP[0][1];
PP[1][0] = PP[1][0] - K_1 * PP[0][0];
PP[1][1] = PP[1][1] - K_1 * PP[0][1];
static float Q_angle = 0.001; //角度噪声的协方差
static float Q_gyro = 0.003; //角速度噪声的协方差
static float R_angle = 0.5; //加速度计测量噪声的协方差
static float dt = 0.01; //采样周期即计算任务周期10ms
float kalman_acc_gyro(float angle_now, float gyro)
{
static float Q_bias;
static float K_0,K_1;
static float PP[2][2] = {{1, 0}, {0, 1}};
static float angle;
angle += (gyro - Q_bias) * dt;
PP[0][0] += Q_angle - (PP[0][1] - PP[1][0])*dt;
PP[0][1] -= PP[1][1]*dt;
PP[1][0] -= PP[1][1]*dt;
PP[1][1] += Q_gyro;
K_0 = PP[0][0] / (PP[0][0] + R_angle);
K_1 = PP[1][0] / (PP[0][0] + R_angle);
Q_bias += K_1 * (angle_now - angle);
angle += K_0 * (angle_now - angle);
PP[0][0] -= K_0 * PP[0][0];
PP[0][1] -= K_0 * PP[0][1];
PP[1][0] -= K_1 * PP[0][0];
PP[1][1] -= K_1 * PP[0][1];
return angle;
}
void test(float acc_x, float acc_y, float acc_z,
float gyro_x, float gyro_y, float gyro_z)
{
float roll = (atan(acc_x/acc_z))*180/3.14159;
kalman_acc_gyro(roll, gyro_y);
}

这样就完成了卡尔曼滤波的简单分析和基础应用,实际项目中不同系统的传递函数不同,所以需要进行调整设计,但是5大核心公式都不会变。扩展卡尔曼其实也是利用线性去模拟非线性,具体的应用大家有兴趣可以自行查文献实验,这里不做分析介绍。水平有限,这里只做自己的理解分析,有错误的地方,请大佬们不吝赐教!