标准卡尔曼滤波器公式推导及算法实现

前言


题外话 :
本文是博主对几个滤波算法整理的笔记之一,原本一篇,但由于CSDN篇幅限制分为了几个部分,包括:
RC滤波器:RC滤波器数学公式推导及软件算法实现
陷波滤波器:陷波滤波器的数学模型推导及算法实现
以及本文的卡尔曼滤波器。

原文PDF下载链接:https://mp.csdn.net/mp_download/manage/download/UpDetailed


回到本文主题:卡尔曼滤波器
参考来源于B站DR_CAN的卡尔曼滤波器系列讲解:https://www.bilibili.com/video/BV1ez4y1X7eR/?spm_id_from=333.788
讲解由浅入深,循序渐进,图文并茂,给人印象深刻。

卡尔曼滤波(Kalman filter)是一种高效率的递归滤波器(自回归滤波器),它能够从一系列的不完全及包含噪声的测量中,估计动态系统的状态。卡尔曼滤波会根据各测量量在不同时间下的值,考虑各时间下的联合分布,再产生对未知变数的估计,因此会比只以单一测量量为基础的估计方式要准。卡尔曼滤波得名自主要贡献者之一的鲁道夫·卡尔曼。

维基百科:卡尔曼滤波

在知乎看到一网友对卡尔曼滤波的本质理解,也是有一定道理的:

  1. 基于前一时间点的系统状态的概率分布给出当前时间点的系统状态的概率分布预测 P 1 \mathbf{P}_1 P1
  2. 基于当前时间点的观测量的概率分布 Q \mathbf{Q} Q给出另一组对当前时间点系统状态的概率分布预测 P 2 \mathbf{P}_2 P2
    注意:如果传感器能直接把系统状态都观测了,那么我们直接用观测量的概率分布 Q \mathbf{Q} Q作为 P 2 \mathbf{P}_2 P2 就好了,但是有可能传感器观测的物理量量并不是直接的系统状态,而是其他物理量,这时我们需要通过做一个线性变换来获得 P 2 \mathbf{P}_2 P2 )
    最后,得到两个关于当前时间点系统状态的概率分布的预测 P 1 \mathbf{P}_1 P1 P 2 \mathbf{P}_2 P2 ,我们于是可以计算它们的联合概率分布。这个联合概率分布就是我们最终想要的校正过后的当前时间点的系统状态概率分布,有了它我们就可以进入下一个时间点继续迭代我们交替的预测-校正步骤了。

1、一些前置知识

递归算法(Recursive Processing

在这里插入图片描述
在这里插入图片描述

数据融合(Data fusion)

在这里插入图片描述

相关数学基础

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

状态空间方程与观测器

在这里插入图片描述
在这里插入图片描述

2、状态空间方程

在这里插入图片描述

3、估计模型与测量估计的数据融合

在这里插入图片描述

4、卡尔曼增益的推导(Kalman gain)

在这里插入图片描述
在这里插入图片描述

5、先验估计协方差的求解

在这里插入图片描述

6、后验估计协方差的化简

在这里插入图片描述

7、标准卡尔曼滤波算法实现及验证

在这里插入图片描述

一维标准卡尔曼算法实现及验证

在这里插入图片描述

#include <iostream>
#include <fstream>
#include <vector>
#include <cstdlib>  // For random number generation
#include <ctime>    // For seeding the random number generator
using namespace std;

typedef struct
{
    float P_k_prev;       //上次估算协方差
    float P_k_now;        //当前估算协方差
    float out_prev;       //上一时刻卡尔曼滤波器输出
    float out_new;        //当前时刻卡尔曼滤波器输出
    float Kk;             //卡尔曼增益 
    float Q;              //过程噪声协方差
    float R;              //观测噪声协方差
}klman_filter_s;//Kalman Filter parameter

klman_filter_s klman_filter;
void kalman_filter_init(klman_filter_s* klman_filter)
{
    klman_filter->P_k_prev = 0.02;   //非0
    klman_filter->P_k_now = 0;
    klman_filter->out_prev = 0;
    klman_filter->out_new = 0;
    klman_filter->Kk = 0;
    klman_filter->Q = 0.001;
    klman_filter->R = 0.1;
}

void kalman_filter_calc(klman_filter_s * klman_filter,float input)
{
    /* 
     *先验估计噪声的协方差更新:
     *k时刻的先验估计噪声协方差 = k-1时刻后验估计噪声协方差 + 过程噪声协方差 
     */
    klman_filter->P_k_now = klman_filter->P_k_prev + klman_filter->Q;
    /* 卡尔曼增益计算:
     * k时刻的卡尔曼增益 = k时刻先验估计噪声协方差/(k时刻先验估计噪声协方差 + 测量噪声协方差) 
     */
    klman_filter->Kk = klman_filter->P_k_now / (klman_filter->P_k_now + klman_filter->R);
    /*
     * 模型估计与测量估计的数据融合:(k时刻先验估计值=k-1时刻估计值,过程噪声为0)
     * k时刻估计值 = k-1时刻的估计值 + 卡尔曼增益 *(输入值 - k-1时刻的估计值) 
     */
    klman_filter->out_new = klman_filter->out_prev + klman_filter->Kk * (input - klman_filter->out_prev);
    /* 后验估计噪声的协方差更新:
     * k时刻的后验估计噪声协方差 = ( 1 - klman_filter->Kk )* k时刻的先验估计噪声协方差
     * 在下一时刻后验估计噪声协方差 = 当前时刻的后验估计噪声协方差 
     */
    klman_filter->P_k_prev = (1 - klman_filter->Kk) * klman_filter->P_k_now;
    klman_filter->out_prev = klman_filter->out_new;
}

void saveToCSV(const vector<double>& data, const string& filename) {
    ofstream file(filename);

    if (file.is_open()) {
        for (const double& value : data) {
            file << value << endl;
        }
        file.close();
        cout << "Data saved to " << filename << endl;
    }
    else {
        cerr << "Unable to open the file: " << filename << endl;
    }
}

int main() {
    int numSamples = 1000;   // Number of samples
    vector<double> inputSignal(numSamples);
    vector<double> noisySignal(numSamples);
    vector<double> filteredSignal(numSamples);

    // Seed the random number generator
    srand(time(nullptr));

    // Generate input signal with noise
    for (int i = 0; i < numSamples; ++i) {
        if (numSamples<100)
        {
            inputSignal[i] =  ((float)rand() / RAND_MAX - 0.5);
        }
        else if (numSamples>=100 && numSamples < 500) {
            inputSignal[i] = 0.5 * ((float)rand() / RAND_MAX - 0.5);
        }
        else if (numSamples >= 500 && numSamples < 700) {
            inputSignal[i] = 1.5 * ((float)rand() / RAND_MAX - 0.5);
        }
        else
        {
            inputSignal[i] = 5 * ((float)rand() / RAND_MAX - 0.5);
        }  
    }

    kalman_filter_init(&klman_filter);

    for (int i = 0; i < numSamples; ++i) {
        double Xn = inputSignal[i];
        kalman_filter_calc(&klman_filter, Xn);
        filteredSignal[i] = klman_filter.out_new;
    }

    // Save data to CSV files
    std::string filename1 = "KPF_before_Data.csv";
    saveToCSV(inputSignal, "input_signal.csv");
    std::string filename2 = "KPF_after_Data.csv";
    saveToCSV(filteredSignal, "filtered_signal.csv");

    return 0;
}

在这里插入图片描述

二维标准卡尔曼算法实现及验证

为了方便验证,我将原本可以模块化的矩阵运行函数写在了一个文件里,以下是源码:

#include <fstream>
#include <vector>
#include <cstdlib>  // For random number generation
#include <ctime>    // For seeding the random number generator
using namespace std;

/************************************该部分可以封装成模块*******************************************/
//申请最大矩阵内存
#define MATSIZE 50
//结构体
typedef struct Matrix
{
    int row, col;		//row为行,col
    Matrix() {};        //这是默认构造函数。在这里,构造函数没有实际操作。
    Matrix(int _row, int _col) :row(_row), col(_col) {} //这是带参数的构造函数。
	                                                    //当你创建一个带有行数和列数参数的 Matrix 对象时,
                                                        //这个构造函数会设置矩阵的行数和列数。
    double data[MATSIZE];
};

void InitMatrix(Matrix* matrix, int col, int row, int value)				//初始化一个矩阵
{
	if (col > 0 && row > 0)
	{
		matrix->col = col;
		matrix->row = row;
		memset(matrix->data, value, sizeof(double) * col * row);
		return;
	}
	else
		return;
}

void ValueMatrix(Matrix* matrix, double* array) 		//给矩阵赋值
{
	if (matrix->data != NULL)
	{
		memcpy(matrix->data, array, matrix->col * matrix->row * sizeof(double));
	}
}

int SizeMatrix(Matrix* matrix)
{
	return matrix->col * matrix->row;
}

void CopyMatrix(Matrix* matrix_A, Matrix* matrix_B)
{
	matrix_B->col = matrix_A->col;
	matrix_B->row = matrix_A->row;
	memcpy(matrix_B->data, matrix_A->data, SizeMatrix(matrix_A) * sizeof(double));
}

void PrintMatrix(Matrix* matrix)
{
	for (int i = 0; i < SizeMatrix(matrix); i++)
	{
		//printf("%lf\t", matrix->data[i]);
		cout << "matrix :" << matrix->data[i] << endl;
		if ((i + 1) % matrix->row == 0)
			//printf("\n");
			cout << endl;
	}

}

/* 加法 */
void AddMatrix(Matrix matrix_A, Matrix matrix_B, Matrix* matrix_C)
{
	if (matrix_A.col == matrix_B.col && matrix_A.row == matrix_B.row)
	{
		matrix_C->row = matrix_A.row;
		matrix_C->col = matrix_B.col;

		for (int i = 0; i < matrix_A.row; i++)
		{
			for (int j = 0; j < matrix_A.col; j++)
			{
				matrix_C->data[i * matrix_C->col + j] = \
					matrix_A.data[i * matrix_A.col + j] + matrix_B.data[i * matrix_A.col + j];
			}
		}
	}
	else
	{
		//printf("不可相加\n");
		cout <<  "不可相加" << endl;
	}
}


/* 减法 */
void SubMatrix(Matrix matrix_A, Matrix matrix_B, Matrix* matrix_C)
{
	if (matrix_A.col == matrix_B.col && matrix_A.row == matrix_B.row)
	{
		matrix_C->row = matrix_A.row;
		matrix_C->col = matrix_A.col;

		for (int i = 0; i < matrix_A.row; i++)
		{
			for (int j = 0; j < matrix_A.col; j++)
			{
				matrix_C->data[i * matrix_C->col + j] = \
					matrix_A.data[i * matrix_A.col + j] - matrix_B.data[i * matrix_A.col + j];
			}
		}
	}
	else
	{
		//printf("不可相减\n");
		cout << "不可相减" << endl;
	}
}

/* 乘法 */
void MulMatrix(Matrix matrix_A, Matrix matrix_B, Matrix* matrix_C)
{
	matrix_C->row = matrix_A.row;

	matrix_C->col = matrix_B.col;
	memset(matrix_C->data, 0, MATSIZE * sizeof(double));
	if (matrix_A.col == matrix_B.row)		//列==行
	{

		for (int i = 0; i < matrix_A.row; i++)
		{
			for (int j = 0; j < matrix_B.col; j++)
			{
				for (int k = 0; k < matrix_A.row; k++)
				{
					matrix_C->data[i * matrix_C->col + j] += \
						matrix_A.data[i * matrix_A.col + k] * matrix_B.data[k * matrix_B.col + j];
				}
			}
		}
	}
	else
	{
		//printf("不可相乘\n");
		cout << "不可相乘" << endl;
	}
}

//逆矩阵
void InvMatrix(Matrix matrix_A, Matrix* matrix_C)
{
	matrix_C->col = matrix_A.col;
	matrix_C->row = matrix_A.row;

	double f, k;
	if (matrix_A.col == 2 && matrix_A.row == 2)		//列==行
	{
		f = matrix_A.data[3] * matrix_A.data[0] - matrix_A.data[1] * matrix_A.data[2];
		if (f == 0) { 
			//printf("不可逆\n"); 
			cout << "不可逆" << endl;
			return; 
		}
		else
			k = 1 / f;
		matrix_C->data[0] = matrix_A.data[3] * k;
		matrix_C->data[1] = -matrix_A.data[1] * k;
		matrix_C->data[2] = -matrix_A.data[2] * k;
		matrix_C->data[3] = matrix_A.data[0] * k;
	}
	else
	{
		//printf("不可逆\n");
		cout << "不可逆" << endl;
		return;
	}
}


/* 矩阵转置
 * 条件为方阵
*/
void TransMatrix(Matrix matrix, Matrix* matrixTemp)
{
	matrixTemp->col = matrix.row;
	matrixTemp->row = matrix.col;

	if (matrix.col > 0 && matrix.row > 0)
	{

		for (int i = 0; i < matrix.col; i++)
		{
			for (int j = 0; j < matrix.row; j++)
			{
				matrixTemp->data[i * matrixTemp->col + j] = matrix.data[j * matrix.col + i];
			}
		}
		return;
	}
	else
	{
		//printf("转置的矩阵必须为方阵\n");
		cout << "转置的矩阵必须为方阵" << endl;
	}
}
/************************************该部分可以封装成模块*******************************************/

//Matrix A(2,2);

//typedef struct
//{
//	Matrix A;               //状态矩阵
//	Matrix B;               //输入矩阵
//	Matrix H;               //观测矩阵
//
//	Matrix P_k_prev;       //上次估算协方差
//	Matrix P_k_now;        //当前估算协方差
//	Matrix input;          //输入
//	Matrix out_prev;       //上一时刻卡尔曼滤波器输出
//	Matrix out_new;        //当前时刻卡尔曼滤波器输出
//	Matrix Kk;             //卡尔曼增益 
//	Matrix Q;              //过程噪声协方差
//	Matrix R;              //观测噪声协方差
//}klman_filter_s;//Kalman Filter parameter
//
//klman_filter_s klman_filter;

/* 状态转移矩阵 */
double a[4] = {
   1,1,
   0,1
};

/* 过程噪声协方差矩阵 */
double q[4] = {
   0.1,0,
   0,0.1
};

/* 观测噪声协方差矩阵 */
double r[4] = {
   1,0,
   0,1
};

/* 观测噪声协方差矩阵 */
double h[4] = {
   1,0,
   0,1
};

/* 输入矩阵 */
double b[4] = {
   1,0,
   0,1
};

/* 初始位置与速度 */
double X0[2] = {
   0,
   1
};

/* 状态估计协方差矩阵P初始化 */
double P0[4] = {
   1,0,
   0,1
};

/* 单位矩阵 */
double i[4] = {
   1,0,
   0,1
};

/* 零矩阵 */
double o[4] = {
   0,0,
   0,0
};

/* 零矩阵 */
double o1[2] = {
   0,0
};

Matrix Ii(2,2);
Matrix A(2, 2);
Matrix B(2, 2);
Matrix H(2, 2);
Matrix P_k_prev(2, 2);
Matrix P_k_now(2, 2);
Matrix input(2, 1);
Matrix out_prev(2, 1);
Matrix out_new(2, 1);
Matrix Kk(2, 1);
Matrix Q(2, 2);
Matrix R(2, 2);

void kalman_filter_init(void)
{
	/* 初始化矩阵并赋值 */
	ValueMatrix(&A, a);
	ValueMatrix(&B, b);
	ValueMatrix(&H, h);

	ValueMatrix(&P_k_prev, P0); 
	ValueMatrix(&P_k_now, o);
	ValueMatrix(&input, o1);
	ValueMatrix(&out_prev, o1);
	ValueMatrix(&out_new, o1);
	ValueMatrix(&Kk, o1);
	ValueMatrix(&Q, q);
	ValueMatrix(&R, r);

	ValueMatrix(&Ii, i);
}

void kalman_filter_calc(float xinput,float yinput)
{
	Matrix temp1;
	Matrix temp2;
	Matrix temp3;
	Matrix temp4;
	Matrix temp5;
	Matrix temp6;
	/* 测量值赋值*/
	input.data[0] = xinput;
	input.data[1] = yinput;
    /* 
     *先验估计噪声的协方差更新:
     *k时刻的先验估计噪声协方差 = k-1时刻后验估计噪声协方差 + 过程噪声协方差 
     */
	MulMatrix(A, P_k_prev, &temp1);// A * P(k-1)
	TransMatrix(A, &temp2);                      // A 的转置
	MulMatrix(temp1, temp2, &temp3);                           // A * P(k-1)*A^T
	AddMatrix(temp3, Q, &P_k_now); //P(k)^-=A * P(k-1)*A^T+Q
    /* 卡尔曼增益计算:
     * k时刻的卡尔曼增益 = k时刻先验估计噪声协方差/(k时刻先验估计噪声协方差 + 测量噪声协方差) 
     */
	TransMatrix(H, &temp1);                      // H 的转置
	MulMatrix(P_k_now, temp1, &temp2);           // P(k)^*H^T
	MulMatrix(H, P_k_now, &temp3); // H*P(k)^-
	MulMatrix(temp3, temp1, &temp4);                           // H*P(k)^-*H^T
	AddMatrix(temp4, R, &temp5);                 //H*P(k)^-*H^T+R
	InvMatrix(temp5, &temp6);                                  //求(H*P(k)^-*H^T+R)的逆
	MulMatrix(temp2, temp6, &Kk);                // Kk
    /*
     * 模型估计与测量估计的数据融合:(k时刻先验估计值=k-1时刻估计值,过程噪声为0)
     * k时刻估计值 = k-1时刻的估计值 + 卡尔曼增益 *(输入值 - k-1时刻的估计值) 
     */
	MulMatrix(H, out_prev, &temp1);           // H*X(k-1)
	SubMatrix(input, temp1, &temp2);                        //Z(k)-H*X(k-1)
	MulMatrix(Kk, temp2, &temp3);                           //K(k)*( Z(k)-H*X(k-1))
	AddMatrix(out_prev, temp3,&out_new);      // X(k-1)+  K(k)*( Z(k)-H*X(k-1))               
    /* 后验估计噪声的协方差更新:
     * k时刻的后验估计噪声协方差 = ( 1 - klman_filter->Kk )* k时刻的先验估计噪声协方差
     * 在下一时刻后验估计噪声协方差 = 当前时刻的后验估计噪声协方差 
     */
	MulMatrix(Kk, H, &temp1);           // K(k)*H
	SubMatrix(Ii, temp1, &temp2);                                   //I-K(k)*H
	MulMatrix(temp2, P_k_now, &P_k_prev);   // (I-K(k)*H)*P(k)^-

	CopyMatrix(&out_new, &out_prev);   // out_prev = out_new;
}

void saveToCSV(const vector<double>& data, const string& filename) {
    ofstream file(filename);

    if (file.is_open()) {
        for (const double& value : data) {
            file << value << endl;
        }
        file.close();
        cout << "Data saved to " << filename << endl;
    }
    else {
        cerr << "Unable to open the file: " << filename << endl;
    }
}

int main() {
    int numSamples = 1000;   // Number of samples
    vector<double> inputSignalx(numSamples);
    vector<double> inputSignaly(numSamples);
    vector<double> noisySignal(numSamples);
    vector<double> filteredSignalx(numSamples);
    vector<double> filteredSignaly(numSamples);

    // Seed the random number generator
    srand(time(nullptr));

    // Generate input signal with noise
    for (int i = 0; i < numSamples; ++i) {
        if (numSamples<100)
        {
            inputSignalx[i] =  ((float)rand() / RAND_MAX - 0.5);
        }
        else if (numSamples>=100 && numSamples < 500) {
            inputSignalx[i] = 0.5 * ((float)rand() / RAND_MAX - 0.5);
        }
        else if (numSamples >= 500 && numSamples < 700) {
            inputSignalx[i] = 1.5 * ((float)rand() / RAND_MAX - 0.5);
        }
        else
        {
            inputSignalx[i] = 5 * ((float)rand() / RAND_MAX - 0.5);
        }  
    }

	for (int i = 0; i < numSamples; ++i) {
		if (numSamples < 100)
		{
			inputSignaly[i] = ((float)rand() / RAND_MAX - 0.5);
		}
		else if (numSamples >= 100 && numSamples < 500) {
			inputSignaly[i] = 0.5 * ((float)rand() / RAND_MAX - 0.5);
		}
		else if (numSamples >= 500 && numSamples < 700) {
			inputSignaly[i] = 1.5 * ((float)rand() / RAND_MAX - 0.5);
		}
		else
		{
			inputSignaly[i] = 5 * ((float)rand() / RAND_MAX - 0.5);
		}
	}

    kalman_filter_init();

    for (int i = 0; i < numSamples; ++i) {
        double xn = inputSignalx[i];
        double yn = inputSignaly[i];
        kalman_filter_calc(xn,yn);
        filteredSignalx[i] = out_new.data[0];//
        filteredSignaly[i] = out_new.data[1];//
    }

    // Save data to CSV files
    std::string filename1 = "KPF_before_Data.csv";
    saveToCSV(inputSignalx, "input_x_signal.csv");
	saveToCSV(inputSignaly, "input_y_signal.csv");
    std::string filename2 = "KPF_after_Data.csv";
    saveToCSV(filteredSignalx, "filtered_signalx.csv");
    saveToCSV(filteredSignaly, "filtered_signaly.csv");

    return 0;
}

速度估计结果:
在这里插入图片描述
位置估计结果:
在这里插入图片描述
以上初步实现估计,参数可能有待优化。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
卡尔曼滤波器是一种用于估计系统状态的数学算法。根据引用和引用提供的参考视频,可以推导卡尔曼滤波器公式卡尔曼滤波器的五个公式包括: 1. 预测状态更新公式:用于根据系统的状态方程和过程噪声来预测系统的状态。 2. 预测误差协方差更新公式:用于根据系统的协方差矩阵、状态转移矩阵和过程噪声协方差矩阵来更新预测误差协方差。 3. 修正观测更新公式:用于根据观测方程和观测噪声来修正预测的状态值。 4. 修正误差协方差更新公式:用于根据观测方程、预测误差协方差和观测噪声协方差来更新修正后的误差协方差。 5. 卡尔曼增益公式:用于根据预测误差协方差和观测噪声协方差来计算卡尔曼增益,该增益决定了预测值和观测值在估计中的权重。 参考和提供的视频可以帮助你更详细地了解这些公式的数学推导过程。此外,引用中的GitHub项目提供了一个使用卡尔曼滤波器进行多目标跟踪的实例,可以进一步帮助你理解卡尔曼滤波器的应用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Kalman Filter卡尔曼滤波公式推导和视频行人跟踪应用](https://blog.csdn.net/Ybc_csdn/article/details/124409364)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hlpinghcg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值