接触过传感器数据的同学一定不可避免见到一个名字“卡尔曼滤波”。这是何方神圣?请看后面分晓。很多时候看不懂一个算法是因为里面很多概念上的问题你没了解,就直接看细节了当然看不懂。最关键的事就是你得先了解卡尔曼滤波到底有啥用,它的初衷是什么?接下来我就是想讲讲破解卡尔曼滤波的一些概念上的认知障碍这个事。
卡尔曼滤波做的事就是:举个例子,已知上个时刻飞机的位置,知道现在这个时刻收到的雷达测量的飞机的位置。用前面两个数据来估计此时飞机的位置。精简的说就是知道上个时刻状态,又知道测量数据,融合这两个数据来求当前状态。
你一定会问现在知道当前时刻的测量数据那么我认为当前状态就是测量数据不就好了么?换句话说:“你一定会觉得雷达测量到的飞机位置不就是当前飞机的位置嘛?为何要用卡尔曼滤波来估计飞机当前的位置?”。
答:现在这个时刻收到的雷达信号测量的飞机的位置还真不一定是飞机当前的真实位置。首先雷达信号测量有误差。其次你想想我现在收到雷达信号,那是之前发射过去然后返回的信号。这个过程是不是要时间?这段时间飞机说不定以超2倍音速飞行,说不定直接坠机,这些都有可能**。也就是说即使收到测量数据但是还是不确飞机位置在哪**。于是我得需要根据前一个时刻的位置估计出当前时刻的飞机位置 结合 测量数据 综合考虑来 估计当前飞机位置。这就是卡尔曼滤波的作用。
那么你一定会问根据前一个时刻估计的飞机位置怎么就可以估计现在这个时刻的飞机位置了?
答:卡尔曼认为所有的状态变化(位置变化)都是线性的。什么叫做线性?上个时刻位置是0.3,速度是0.2。那么我估计下个时刻的位置就是0.5。这就叫做线性。
接下来你一定会问那并不是所有的状态变化都是线性的怎么办?你像风速变化它就不是线性的。
答:恭喜你发明了新的算法。事实上别人已经为这个算法命名了叫做扩展卡尔曼滤波。现在我们要学习的是卡尔曼滤波。你只需要记住卡尔曼滤波就是认为所有变化都是线性的。
那么现在我知道了怎么用上个时刻飞机的位置估计当前时刻的飞机位置,也知道了还得借助当前时刻收到的测量数据来综合考虑来估计当前飞机的位置。那么怎么综合考虑呢?这就涉及到一个比例。到底这两个数据占比多少?这就是卡尔曼滤波的核心精髓。卡尔曼滤波算法要动态的调这个比例。(有种中庸之道的调调,既不只信测量数据,也不只信上个时刻的位置所估算的当前时刻位置。)
如果不理解滤波思路那么今天学会了一个卡尔曼滤波明天还有一个通尔曼滤波要你学。如果学会这些滤波算法的思路,把他们联系在一起然后记忆那就简单多了。滤波算法本质上就是利用多个数据来融合估计真实状态。下面举两个从浅到深的例子。
下面有个一个飞机只会水平飞行。我们已知上个时刻飞机的位置 x 1 x_1 x1和速度 v 1 v_1 v1.而且雷达可以测量到 t 2 t_2 t2此时飞机的位置 x 2 ∗ x_2^* x2∗。那么我们估计飞机的位置那就有两种方法:“一是认为飞机是匀速( v 2 = v 1 v_2=v_1 v2=v1)直线运动得到现在飞机位置为 x 1 + v 2 ( t 2 − t 1 ) x_1+v_2(t_2-t_1) x1+v2(t2−t1),另一种是认为飞机位置就是测量值 x 2 ∗ x_2^* x2∗”。如果按第一种那么万一飞机不是匀速走呢?如果按第二种万一雷达很不准呢?我们得采取一种折中的方法。
我们认为飞机在时刻 t 2 t_2 t2的位置为 x 2 r e a l = ( x 1 + v 2 ( t 2 − t 1 ) ) + α [ x 2 ∗ − ( x 1 + v 2 ( t 2 − t 1 ) ) ] x_2^{real}=(x_1+v_2(t_2-t_1))+\alpha [x_2^* - (x_1+v_2(t_2-t_1))] x2real=(x1+v2(t2−t1))+α[x2∗−(x1+v2(t2−t1))]。这个 α \alpha α需要我们自己设置。如果我们相信雷达不准那就 α \alpha α赋值很低比如为0.1.如果我们相信雷达很准那么就设置 α \alpha α为很接近1的值比如0.9。你会发现 α \alpha α越接近0,那么所估计的位置越是接近认为飞机匀速走的那个思路。越接近1那么所估计的位置越接近雷达测量值(等于1就是认为飞机在时刻 t 2 t_2 t2的位置就是雷达测量值)。
但是飞机的速度怎么估计啊?因为飞机不一定是匀速走。我们得根据
现在我想估计飞机的速度。
速度估计的方式有两种:
第N次算出的估计值=上次的估计值+ 因子a ×(测量值-上次估计值)
。我们知道真实的速度肯定可以这么表达 v 2 r e a l = v 1 + β × ( v 2 ∗ − v 1 r e a l ) v_2^{real}=v_1+\beta \times (v_2^*-v_1^{real}) v2real=v1+β×(v2∗−v1real)。至于这个 β \beta β到底取多少这是我们自己赋值。比如我们觉得测量的值是更准确的,那 β \beta β可以设置大一点比如0.9。如果我们觉得雷达不准那就 β \beta β设置小一点比如0.1。在前面我们一直做的假设是飞机是匀速走的。但是飞机很可能会发生加速或者减速。这就不得不考虑加速度的问题了。
在高中我们学过位移与速度和加速度之间的关系是: x 2 = x 1 + v 1 ( t 2 − t 1 ) + 1 2 a 2 ( t 2 − t 1 ) 2 x_2=x_1+v_1(t_2-t_1)+\frac 1 2 a_2(t_2-t_1)^2 x2=x1+v1(t2−t1)+21a2(t2−t1)2。其中 a 2 a_2 a2是从 t 1 t_1 t1到 t 2 t_2 t2这段时间的加速度。
所以飞机的位置可以融合速度、加速度这样来估计: x 2 r e a l = ( x 1 r e a l + v 2 r e a l ( t 2 − t 1 ) + 1 2 a 2 ( t 2 − t 1 ) 2 ) ) + α [ x 2 ∗ − ( x 1 r e a l + v 2 r e a l ( t 2 − t 1 ) + 1 2 a 2 ( t 2 − t 1 ) 2 ) ] x_2^{real}=(x_1^{real}+v_2^{real}(t_2-t_1)+\frac 1 2 a_2 (t_2-t_1)^2))+\alpha [x_2^* - (x_1^{real}+v_2^{real}(t_2-t_1)+\frac 1 2 a_2 (t_2-t_1)^2)] x2real=(x1real+v2real(t2−t1)+21a2(t2−t1)2))+α[x2∗−(x1real+v2real(t2−t1)+21a2(t2−t1)2)]。
可以看到和前面那个例子的位置估计公式:
x 2 r e a l = ( x 1 r e a l + v 2 r e a l ( t 2 − t 1 ) ) + α [ x 2 ∗ − ( x 1 + v 2 r e a l ( t 2 − t 1 ) ) ] x_2^{real}=(x_1^{real}+v_2^{real}(t_2-t_1))+\alpha [x_2^* - (x_1+v_2^{real}(t_2-t_1))] x2real=(x1real+v2real(t2−t1))+α[x2∗−(x1+v2real(t2−t1))]相比,只是将原来的根据速度估计位移变成了根据速度和加速度来估计位移。
在前面提到了我们的速度可以根据测量数进行动态更新:
v 2 r e a l = v 1 r e a l + β × ( v 2 ∗ − v 1 r e a l ) v_2^{real}=v_1^{real}+\beta \times (v_2^*-v_1^{real}) v2real=v1real+β×(v2∗−v1real),其中 v 2 ∗ = x 2 m e a s u r e − x 1 r e a l t 2 − t 1 v_2^*=\frac {x_2^{measure}-x_1^{real}}{t_2-t_1} v2∗=t2−t1x2measure−x1real。
当然我们的加速度也是可以根据测量数据进行动态更新的
a 2 r e a l = a 1 r e a l + γ × ( a 2 ∗ − a 1 r e a l ) a_2^{real}=a_1^{real}+\gamma \times(a_2^*-a_1^{real}) a2real=a1real+γ×(a2∗−a1real)。其中 a 2 ∗ = v 2 r e a l − v 1 r e a l t 2 − t 1 a_2^*=\frac{v_2^{real}-v_1^{real}}{t_2-t_1} a2∗=t2−t1v2real−v1real,这个是根据加速度的定义所得来的,加速度=速度变化量除以时间间隔。
可以看到滤波思路是一环扣一环,它要平衡每个环节中的 根据之前状态所估计出当前状态值 和 测量值之间的在最终结果中的占比。
而那些滤波算法要做的事就是设计合理的占比到底取多少比较合适。而且是需要根据不同情况动态的设置占比。前面提到的例子中占比变量有三个 α , β , γ \alpha,\beta,\gamma α,β,γ,你学习滤波算法时只需要关注这几个占比值是怎么计算的就可以了。
像卡尔曼滤波,拓展的卡尔曼滤波,Particle Filter,贝叶斯滤波等等都是前面提到滤波思路这种形式。
我们先把前面总结的滤波算法规律复制过来看看:
前面我们提到了那些种类繁多的滤波算法,无非就是提出了各种确定 α , β , γ \alpha,\beta,\gamma α,β,γ具体取值的计算方法而已。
我们看看卡尔曼滤波是怎么确定这三个值的。
于是卡尔曼滤波可以这么写:
那么 K n K_n Kn怎么计算呢?
K n = 根 据 上 个 状 态 算 出 当 前 状 态 这 种 方 法 的 方 差 p n − 1 根 据 上 个 状 态 算 出 当 前 状 态 这 种 方 法 的 方 差 p n − 1 + 测 量 仪 器 的 方 差 K_{n}= \frac{根据上个状态算出当前状态这种方法的方差p_{n-1}}{根据上个状态算出当前状态这种方法的方差p_{n-1}+测量仪器的方差} Kn=根据上个状态算出当前状态这种方法的方差pn−1+测量仪器的方差根据上个状态算出当前状态这种方法的方差pn−1
橘黄色虚线是根据市上个状态计算出当前状态的估计值,绿色虚线是测量值,紫色虚线是最终结果。而 K n K_n Kn这个数字就是调节紫色虚线到底是靠近另外两条线中的哪个。
现在我们得到了新的位置估计值,那么根据这个来估计下一个位置它的方差 p n − 1 p_{n-1} pn−1是多少呢?由于根据上个状态算出当前状态这个数字在最终结果的占比只占了 1 − K n 1-K_n 1−Kn这么多。所以方差 p n = ( 1 − k n ) p n − 1 p_{n}=(1-k_n)p_{n-1} pn=(1−kn)pn−1
"""
@司南牧
"""
import numpy as np
# 模拟数据
t = np.linspace(1,100,100)
a = 0.5
position = (a * t**2)/2
position_noise = position+np.random.normal(0,120,size=(t.shape[0]))
import matplotlib.pyplot as plt
plt.plot(t,position,label='truth position')
plt.plot(t,position_noise,label='only use measured position')
# 初试的估计导弹的位置就直接用GPS测量的位置
predicts = [position_noise[0]]
position_predict = predicts[0]
predict_var = 0
odo_var = 120**2 #这是我们自己设定的位置测量仪器的方差,越大则测量值占比越低
v_std = 50 # 测量仪器的方差
for i in range(1,t.shape[0]):
dv = (position[i]-position[i-1]) + np.random.normal(0,50) # 模拟从IMU读取出的速度
position_predict = position_predict + dv # 利用上个时刻的位置和速度预测当前位置
predict_var += v_std**2 # 更新预测数据的方差
# 下面是Kalman滤波
position_predict = position_predict*odo_var/(predict_var + odo_var)+position_noise[i]*predict_var/(predict_var + odo_var)
predict_var = (predict_var * odo_var)/(predict_var + odo_var)**2
predicts.append(position_predict)
plt.plot(t,predicts,label='kalman filtered position')
plt.legend()
plt.show()
数学期望值与平均值的区别?
大家平常把这两个等价为一个好像并没什么很大影响。事实上他们两个确实差不多。但是又有微小的差异。
假设有个2个硬币:1元,5元。你说它平均值是3元这个没错。但是你不能说它期望值是3元。因为这个硬币它整体我们已经可以看完了。而且期望值是针对那些样本整体我们没法看完的情况,我们对他们整体的平均值的一个估计。平均值一般是用希腊字母 μ \mu μ(读音:miu)表示,期望值一般用 E E E表示。这个符号很关键。因为作者写公式的时候就隐含了这些信息。他默认你知道这个概念认知。
方差与标准差?
方差就是一个数。这个数可以衡量样本集中各个样本值与平均值之间的波动大小。(还记得平均值用什么符号表示么?平均值用 μ \mu μ)。怎么衡量与平均值波动大小?很简单我减去平均值然后平方不就好了么。然后由于是要衡量整个样本集中的样本,那就得把那个平方求和然后除以样本个数(关于到底除N还是N-1,可以看看这篇文章Why divide the sample variance by N-1?)。我们熟悉的方差公式出来啦: σ 2 = 1 N ∑ n = 1 N ( x n − μ ) 2 \sigma ^{2}= \frac{1}{N} \sum _{n=1}^{N} \left( x_{n}- \mu \right) ^{2} σ2=N1∑n=1N(xn−μ)2。注意: σ 2 \sigma ^{2} σ2(含义:sigma的平方)这个表示方差。N表示样本的个数。 μ \mu μ是样本集的平均值。 x n x_n xn表示第n个样本。
标准差也是一个数。它就是方差开根号。和方差没啥区别。还记得方差是什么吗?它是sigma的平方。标准差是方差开根号,也就是说标准差就是sigma。即用符号表示就是 σ \sigma σ,这就是标准差。
标准差有什么用?
衡量样本值与平均值之间的波动大小这个你肯定高中就知道了。我想说的是另外一个作用:根据样本估计真实值的范围。
假设我们抽样了溶液x的样本数据。测量他们的电压。然后他们的分布如下所示。根据这些我们可以得出一个结论:溶液x电压值在 [ 0 , 1 σ ] [0,1σ] [0,1σ]之间的概率是34.1%。溶液x的电压在 [ − 1 σ , 1 σ ] [-1σ,1σ] [−1σ,1σ]之间的概率是68.2%。也就是说溶液x的电压大概率在 [ − 1 σ , 1 σ ] [-1σ,1σ] [−1σ,1σ]范围内?而 σ σ σ就是标准差。是不是很有用?我们可以根据所收集的很多测量值,算他们的均值和标准差来估计真实值的范围。这个原理就是滤波算法的很常用的原理。
正态分布不清楚?
下面这个图就是正态分布。横坐标是在本例中是飞机的速度v,纵坐标是当前速度是v的概率。其实这些你编程就调用一个服从正态分布的随机函数就是了。正态分布函数用公式表示就是 f ( v ) = 1 2 π σ 2 e − ( v − μ ) 2 2 σ 2 f( v) = \frac{1}{\sqrt[]{2 \pi \sigma ^{2}}}e^{\frac{- \left( v- \mu \right) ^{2}}{2 \sigma ^{2}}} f(v)=2πσ21e2σ2−(v−μ)2。 f ( v ) f(v) f(v)这个函数,表示的是当前速度为 v v v的概率是 f ( v ) f(v) f(v)。还记得 μ , σ \mu , \sigma μ,σ是什么吗? u u u是平均值, σ \sigma σ是标准差。 μ , σ \mu , \sigma μ,σ这两个是常数,你编程的时候需要告诉程序,告诉它生成的随机数平均值是多少,标准差是多少。一般生成正态分布随机数代码是这么写的v = normalrand(u,s)
参考文献:
[1] http://web.mit.edu/kirtley/kirtley/binlustuff/literature/control/Kalman%20filter.pdf
[2] https://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/
[3] https://en.wikipedia.org/wiki/Kalman_filter
[4] http://www.cl.cam.ac.uk/~rmf25/papers/Understanding%20the%20Basis%20of%20the%20Kalman%20Filter.pdf
[5] https://www.kalmanfilter.net/alphabeta.html