先上代码
https://github.com/blueskit/KalmanFilter
namespace FusionFiltering { /// <summary> /// Simple implementation of the Kalman Filter for 1D data. /// Originally written in JavaScript by Wouter Bulten /// /// https://github.com/wouterbulten/kalmanjs/blob/master/contrib/java/KalmanFilter.java /// </summary> public class FilterKalman { private double A = 1; private double B = 0; private double C = 1; private double R; private double Q; private double cov = double.NaN; private double x = double.NaN; /// <summary> /// Constructor /// </summary> /// <param name="R">R Process noise</param> /// <param name="Q">Q Measurement noise</param> /// <param name="A">A State vector</param> /// <param name="B">B Control vector</param> /// <param name="C">C Measurement vector</param> public FilterKalman(double R, double Q, double A, double B, double C) { this.R = R; this.Q = Q; this.A = A; this.B = B; this.C = C; this.cov = double.NaN; this.x = double.NaN; // estimated signal without noise } /// <summary> /// Constructor /// </summary> /// <param name="R">R Process noise</param> /// <param name="Q">Q Measurement noise</param> public FilterKalman(double R, double Q) { this.R = R; this.Q = Q; } /// <summary> /// Filters a measurement /// </summary> /// <param name="measurement">The measurement value to be filtered</param> /// <param name="u">The controlled input value</param> /// <returns>The filtered value</returns> public double filter(double measurement, double u) { if (double.IsNaN(this.x)) { this.x = (1 / this.C) * measurement; this.cov = (1 / this.C) * this.Q * (1 / this.C); } else { double predX = (this.A * this.x) + (this.B * u); double predCov = ((this.A * this.cov) * this.A) + this.R; // Kalman gain double K = predCov * this.C * (1 / ((this.C * predCov * this.C) + this.Q)); // Correction this.x = predX + K * (measurement - (this.C * predX)); this.cov = predCov - (K * this.C * predCov); } return this.x; } /// <summary> /// Filters a measurement /// </summary> /// <param name="measurement">The measurement value to be filtered</param> /// <returns>The filtered value</returns> public double filter(double measurement) { double u = 0; if (double.IsNaN(this.x)) { this.x = (1 / this.C) * measurement; this.cov = (1 / this.C) * this.Q * (1 / this.C); } else { double predX = (this.A * this.x) + (this.B * u); double predCov = ((this.A * this.cov) * this.A) + this.R; // Kalman gain double K = predCov * this.C * (1 / ((this.C * predCov * this.C) + this.Q)); // Correction this.x = predX + K * (measurement - (this.C * predX)); this.cov = predCov - (K * this.C * predCov); } return this.x; } /// <summary> /// Set the last measurement. /// </summary> /// <returns>return The last measurement fed into the filter</returns> public double lastMeasurement() { return this.x; } /// <summary> /// Sets measurement noise /// </summary> /// <param name="noise">The new measurement noise</param> public void setMeasurementNoise(double noise) { this.Q = noise; } /// <summary> /// Sets process noise /// </summary> /// <param name="noise">The new process noise</param> public void setProcessNoise(double noise) { this.R = noise; } } }
using System; using System.Linq; namespace FusionFiltering { public class ProgramTest { /// <summary> /// kalman滤波测试1 /// </summary> [System.Diagnostics.Conditional("DEBUG")] public static void TestKalmanFilter1() { Console.WriteLine("FilterKalman Usage"); //标准误差 测量误差 FilterKalman test = new FilterKalman(0.001, 20); double[] testData = { 66, 64, 63, 63, 63, 66, 65, 67, 58 ,99,200,65,55,75,85,88,86,89,84,83}; foreach (var x in testData) { Console.WriteLine("Input data: {0:#,##0.00}, Filtered data:{1:#,##0.000}", x, Math.Round(test.filter(x))); } } /// <summary> /// Example Usage with controlled input /// </summary> [System.Diagnostics.Conditional("DEBUG")] public static void TestKalmanFilterWithControlled() { Console.WriteLine("FilterKalman Usage with controlled input"); //标准误差 测量误差 状态向量 控制向量 测量向量 FilterKalman test = new FilterKalman(0.008, 0.1, 1, 1, 1); double[] testData = { 66, 64, 63, 63, 63, 66, 65, 67, 58, 99, 200, 65, 55, 75, 85, 88, 86, 89, 84, 83 }; double u = 0.2; foreach (var x in testData) { Console.WriteLine("Input data: {0:#,##0.00}, Filtered data:{1:#,##0.000}", x,Math.Round( test.filter(x, u))); } } } }
https://github.com/Singerwall/filter/blob/master/filter.c
/** @copyright XunFang Communication Tech Limited. All rights reserved. 2013. * @file filter.c * @author hzp * @version V1.0.0 * @date 12/28/2017 * @brief 旋转编码器的驱动 */ /** * @brief 将32位的主机数据流转换为网络流 * @details Q:过程噪声,Q增大,动态响应变快,收敛稳定性变坏 R:测量噪声,R增大,动态响应变慢,收敛稳定性变好 * @param ResrcData 需要过滤的数据 * @param * @return 过滤之后的数据 */ double KalmanFilter(const double ResrcData,double ProcessNiose_Q,double MeasureNoise_R) { double R = MeasureNoise_R; double Q = ProcessNiose_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),Q=噪声 /* * 卡尔曼滤波的五个重要公式 */ kg=p_mid/(p_mid+R); //kg为kalman filter,R 为噪声 x_now=x_mid+kg*(ResrcData-x_mid); //估计出的最优值 p_now=(1-kg)*p_mid; //最优值对应的covariance p_last = p_now; //更新covariance 值 x_last = x_now; //更新系统状态值 return x_now; } /** * @brief 滑动加权滤波算法 * @details * @gram p_buff 采样缓存队列 * @gram value 采样值 * @gram n_sample 采样次数 设定好不能改动 * @retval 无 */ float huadongjiaquan(float *p_buff,float value, int n_sample) { float temp; float sum=0; for(int i=1;i < n_sample; i++) { p_buff[i-1] = p_buff[i]; sum += p_buff[i] * i; } p_buff[n_sample-1] = value; sum += value*n_sample; temp = sum /(((n_sample+1)/2 * n_sample)-1); return temp; }
#ifndef _filter_h_include_ #define _filter_h_include_ #include <stdint.h> double KalmanFilter(const double ResrcData,double ProcessNiose_Q,double MeasureNoise_R) #endif /* _algorithm_h_include_ */
卡尔曼滤波器的介绍 :
为了可以更加容易的理解卡尔曼滤波器,这里会应用形象的描述方法来讲解,而不是像大多数参考书那样罗列一大堆的数学公式和数学符号。但是,他的5条公式是其核心内容。结合现代的计算机,其实卡尔曼的程序相当的简单,只要你理解了他的那5条公式。
在介绍他的5条公式之前,先让我们来根据下面的例子一步一步的探索。
假设我们要研究的对象是一个房间的温度。根据你的经验判断,这个房间的温度是恒定的,也就是下一分钟的温度等于现在这一分钟的温度(假设我们用一分钟来做时间单位)。假设你对你的经验不是100%的相信,可能会有上下偏差几度。我们把这些偏差看成是高斯白噪声(White Gaussian Noise),也就是这些偏差跟前后时间是没有关系的而且符合高斯分布(Gaussian Distribution)。另外,我们在房间里放一个温度计,但是这个温度计也不准确的,测量值会比实际值偏差。我们也把这些偏差看成是高斯白噪声。
好了,现在对于某一分钟我们有两个有关于该房间的温度值:你根据经验的预测值(系统的预测值)和温度计的值(测量值)。下面我们要用这两个值结合他们各自的噪声来估算出房间的实际温度值。
假如我们要估算k时刻的实际温度值。首先你要根据k-1时刻的温度值,来预测k时刻的温度。因为你相信温度是恒定的,所以你会得到k时刻的温度预测值是跟k-1时刻一样的,假设是23度,同时该值的高斯噪声的偏差是5度(5是这样得到的:如果k-1时刻估算出的最优温度值的偏差是3,你对自己预测的不确定度是4度,他们平方相加再开方,就是5)。然后,你从温度计那里得到了k时刻的温度值,假设是25度,同时该值的偏差是4度。
由于我们用于估算k时刻的实际温度有两个温度值,分别是23度和25度。究竟实际温度是多少呢?相信自己还是相信温度计呢?究竟相信谁多一点,我们可以用他们的协方差(covariance)来判断。因为Kg=5^2/(5^2+4^2),所以Kg=0.61,我们可以估算出k时刻的实际温度值是:23+0.61*(25-23)=24.22度。可以看出,因为温度计的协方差(covariance)比较小(比较相信温度计),所以估算出的最优温度值偏向温度计的值。
现在我们已经得到k时刻的最优温度值了,下一步就是要进入k+1时刻,进行新的最优估算。到现在为止,好像还没看到什么自回归的东西出现。对了,在进入k+1时刻之前,我们还要算出k时刻那个最优值(24.22度)的偏差。算法如下:((1-Kg)*5^2)^0.5=3.12。这里的5就是上面的k时刻你预测的那个23度温度值的偏差,得出的3.12就是进入k+1时刻以后k时刻估算出的最优温度值的偏差(对应于上面的3)。
就是这样,卡尔曼滤波器就不断的把协方差(covariance)递归,从而估算出最优的温度值。他运行的很快,而且它只保留了上一时刻的协方差(covariance)。上面的Kg,就是卡尔曼增益(Kalman Gain)。他可以随不同的时刻而改变他自己的值,是不是很神奇!
下面就要言归正传,讨论真正工程系统上的卡尔曼。
卡尔曼滤波器算法 :
在这一部分,我们就来描述源于Dr Kalman 的卡尔曼滤波器。下面的描述,会涉及一些基本的概念知识,包括概率(Probability),随机变量(Random Variable),高斯或正态分配(Gaussian Distribution)还有State-space Model等等。但对于卡尔曼滤波器的详细证明,这里不能一一描述。
首先,我们先要引入一个离散控制过程的系统。该系统可用一个线性随机微分方程(Linear Stochastic Difference equation)来描述:
X(k)=A X(k-1)+B U(k)+W(k)
再加上系统的测量值:
Z(k)=H X(k)+V(k)
上两式子中,X(k)是k时刻的系统状态,U(k)是k时刻对系统的控制量。A和B是系统参数,对于多模型系统,他们为矩阵。Z(k)是k时刻的测量值,H是测量系统的参数,对于多测量系统,H为矩阵。W(k)和V(k)分别表示过程和测量的噪声。他们被假设成高斯白噪声(White Gaussian Noise),他们的协方差(covariance)分别是Q,R(这里我们假设他们不随系统状态变化而变化)。
对于满足上面的条件(线性随机微分系统,过程和测量都是高斯白噪声),卡尔曼滤波器是最优的信息处理器。下面我们结合他们的协方差来估算系统的最优化输出(类似上一节那个温度的例子)。
首先我们要利用系统的过程模型,来预测下一状态的系统。假设现在的系统状态是k,根据系统的模型,可以基于系统的上一状态而预测出现在状态:
X(k|k-1)=A X(k-1|k-1)+B U(k) ……….. (1)
式(1)中,X(k|k-1)是利用上一状态预测的结果,X(k-1|k-1)是上一状态最优的结果,U(k)为现在状态的控制量,如果没有控制量,它可以为0。
到现在为止,我们的系统结果已经更新了,可是,对应于X(k|k-1)的协方差还没更新。我们用P表示协方差(covariance):
P(k|k-1)=A P(k-1|k-1) A’+Q ……… (2)
式(2)中,P(k|k-1)是X(k|k-1)对应的协方差,P(k-1|k-1)是X(k-1|k-1)对应的协方差,A’表示A的转置矩阵,Q是系统过程的协方差。式子1,2就是卡尔曼滤波器5个公式当中的前两个,也就是对系统的预测。
现在我们有了现在状态的预测结果,然后我们再收集现在状态的测量值。结合预测值和测量值,我们可以得到现在状态(k)的最优化估算值X(k|k):
X(k|k)= X(k|k-1)+Kg(k) (Z(k)-H X(k|k-1)) ……… (3)
其中Kg为卡尔曼增益(Kalman Gain):
Kg(k)= P(k|k-1) H’ / (H P(k|k-1) H’ + R) ……… (4)
到现在为止,我们已经得到了k状态下最优的估算值X(k|k)。但是为了要令卡尔曼滤波器不断的运行下去直到系统过程结束,我们还要更新k状态下X(k|k)的协方差:
P(k|k)=(I-Kg(k) H)P(k|k-1) ……… (5)
其中I 为1的矩阵,对于单模型单测量,I=1。当系统进入k+1状态时,P(k|k)就是式子(2)的P(k-1|k-1)。这样,算法就可以自回归的运算下去。
卡尔曼滤波器的原理基本描述了,式子1,2,3,4和5就是他的5 个基本公式。根据这5个公式,可以很容易用计算机编程实现。
在上面的例子中,过程误差和测量误差设定为4是为了讨论的方便。实际中,温度的变化速度以及温度计的测量误差都没有这么大。
假设如下一个系统:
-
房间内连续两个时刻温度差值的标准差为0.02度
-
温度计的测量值误差的标准差为0.5度
-
房间温度的真实值为24度
-
对温度的初始估计值为23.5度,误差的方差为1
卡尔曼滤波的实质是由量测值重构系统的状态向量。它以“预测—实测—修正”的顺序递推,根据系统的量测值来消除随机干扰,再现系统的状态,或根据系统的量测值从被污染的系统中恢复系统的本来面目。