使用 C++ Eigen 包的最小二乘多项式拟合

通常在处理传感器数据(或信号)时,我们会发现数据通常不干净并且存在大量噪声。这种噪声使得执行进一步的数学运算变得更加困难,例如微分、积分、卷积等。此外,如果我们打算将这些信号用于控制自动驾驶汽车、机器人等实时操作,那么这种噪声会带来很大的挑战。手臂或工业厂房,因为噪声往往会在任何下游数学运算中放大。

在这种情况下,一种通用方法是平滑数据以去除噪声。我们寻求实时平滑这些数据,以用于控制工程中的应用,例如自动驾驶汽车或机器人的智能控制。已经开发了许多方法来实时平滑信号,例如卡尔曼滤波器、扩展卡尔曼滤波器及其变体。然而,设计卡尔曼滤波器需要了解系统动力学,这可能是已知的,也可能是未知的。在这种情况下,一种更简单的方法是对收到的最后一个数据点执行最小二乘多项式拟合。n
最小二乘多项式拟合的数学非常简单。考虑一组n个数据点
在这里插入图片描述
在这种情况下,阶多项式拟合k可以写成:
在这里插入图片描述
等式 1
在这种情况下,残差由下式给出

在这里插入图片描述
等式 2
最小二乘多项式拟合的目标是最小化R²。通常的方法是对等式 2 对系数a 进行偏导并使其为零。这导致了一个由k个方程组成的系统。这样的方程组以范德蒙矩阵方程的形式出现,可以简化并写成如下:
在这里插入图片描述
等式 3
在矩阵表示法中,我们可以将等式 3 写为
在这里插入图片描述
等式 4
等式 4 可以通过预乘T的转置来求解,因此解向量为
在这里插入图片描述
等式 5
最小二乘多项式拟合的实现
正如一开始所说,这里关注的应用是一个实时系统,它可能必须处理安全关键系统,例如自动驾驶汽车和机械臂。对于这样的系统,在目标通常是嵌入式系统的情况下,实现速度很重要。因此,在 C++ 中实现的编程语言的常见选择。在本文中,我们使用用 C++ 编写的 Eigen 包来求解方程 5。

对于从乘用车的速度曲线获得的样本数据点,下面提供了实现代码。我只是拟合了一个三次多项式。请注意,拟合更高次的多项式计算成本很高,可能根本不需要。
具体代码如下

#include <Eigen/Dense>
#include <iostream>
#include <cmath>
#include <vector>
#include <Eigen/QR>
#include "matplotlibcpp.h"

namespace plt = matplotlibcpp;

void polyfit(	const std::vector<double> &t,
		const std::vector<double> &v,
		std::vector<double> &coeff,
		int order

	     )
{
	// Create Matrix Placeholder of size n x k, n= number of datapoints, k = order of polynomial, for exame k = 3 for cubic polynomial
	Eigen::MatrixXd T(t.size(), order + 1);
	Eigen::VectorXd V = Eigen::VectorXd::Map(&v.front(), v.size());
	//std::cout<<"ceshi"<<std::endl;
	//std::cout<<V<<std::endl;
	Eigen::VectorXd result;

	// check to make sure inputs are correct
	assert(t.size() == v.size());
	assert(t.size() >= order + 1);
	// Populate the matrix
	for(size_t i = 0 ; i < t.size(); ++i)
	{
		for(size_t j = 0; j < order + 1; ++j)
		{
			T(i, j) = pow(t.at(i), j);
		}
	}
	std::cout<<T<<std::endl;
	
	// Solve for linear least square fit
	result  = T.householderQr().solve(V);
	coeff.resize(order+1);
	for (int k = 0; k < order+1; k++)
	{
		coeff[k] = result[k];
	}

}

int main()
{
	// time value
	std::vector<double> time = {0, 0.0192341804504395, 0.0394501686096191,  0.059575080871582, 0.0790810585021973, 0.0792751312255859, 0.0987141132354736,  0.119336366653442,  0.138712167739868,  0.159000158309937,  0.178890228271484,   0.19960618019104,  0.219112157821655,   0.23919415473938,  0.259442090988159,  0.279186248779297,  0.299112319946289,  0.319219350814819,  0.339494228363037,  0.339675188064575,  0.359552145004272,   0.37941837310791,  0.399189233779907,  0.419828176498413,  0.439810276031494,  0.459331274032593,  0.479461193084717,  0.499663114547729,  0.519809246063232,  0.539092063903809,  0.559118270874023,  0.579315185546875,  0.598889112472534,  0.619685173034668,  0.638863086700439,  0.639052152633667,  0.658920288085938,  0.679149150848389,  0.699787139892578,   0.71905517578125,   0.73898720741272,  0.739143371582031,  0.758654117584229,  0.779210329055786,  0.799195289611816,  0.819046258926392,  0.839539289474487,   0.85923433303833,   0.87903618812561,  0.899263143539429,  0.919251203536987,  0.939138174057007,  0.959244251251221,  0.979074239730835,  0.998935222625732,   1.01904726028442,    1.0387852191925,   1.03895926475525,   1.05906510353088,   1.07873225212097,   1.09908628463745,   1.11907029151917,   1.13899827003479,   1.15879201889038};
	// velocity value
	std::vector<double> velocity = {1.8, 1.86, 2.03, 2.08, 2.14, 2.14, 2.25, 2.36, 2.42, 2.59,  2.7, 2.81, 2.87, 3.04, 3.15, 3.26, 3.32, 3.43, 3.54, 3.54,  3.6, 3.71, 3.83, 3.94, 4.11, 4.22, 4.33, 4.44, 4.56, 4.67, 4.78, 4.84, 4.84, 4.89, 4.89, 4.89, 4.95, 5.01, 5.06, 5.06, 5.06, 5.06, 5.01, 5.06, 5.12, 5.18, 5.18, 5.23, 5.23, 5.23, 5.29, 5.34, 5.29,  5.4,  5.4, 5.46, 5.51, 5.51, 5.51, 5.46,  5.4, 5.34, 5.34, 5.34};

	// placeholder for storing polynomial coefficient
        std::vector<double> coeff;
	polyfit(time, velocity, coeff, 3);

	std::vector<double> fitted_velocity;
	std::cout<< "Printing fitted values" << std::endl;
	for(int p = 0; p < time.size(); ++ p)
	{
		double vfitted = coeff[0] + coeff[1]*time.at(p) + coeff[2]*(pow(time.at(p), 2)) +coeff[3]*(pow(time.at(p), 3)) ;
		std::cout<< vfitted<<", ";
		fitted_velocity.push_back(vfitted);
	}
	std::cout<<std::endl;


	/*原始数据*/
	plt::figure();
	//plt::subplot(2,2,1);
	plt::title("originaldata");
	plt::plot(time,velocity);
	plt::figure();
	/*最小二乘拟合后的数据*/
	//plt::subplot(2,2,2);
	plt::title("polyfitdata");
	plt::plot(time,fitted_velocity);
	plt::show();
	return 0;
}

CmakeList配置:

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)  
PROJECT(polyfit)  
set(CMAKE_CXX_STANDARD 11)
INCLUDE_DIRECTORIES("/usr/include/eigen3") #("${EIGEN3_INCLUDE_DIR}")  
#SET(EIGEN3_INCLUDE_DIR "$ENV{EIGEN3_INCLUDE_DIR}")  
#IF(NOT EIGEN3_INCLUDE_DIR)  
#    MESSAGE( FATAL_ERROR "Please point the environment variable EIGEN3_INCLUDE_DIR to the include directory of your Eigen3 installation.")  
#ENDIF()  
#INCLUDE_DIRECTORIES(/usr/include/eigen3) #("${EIGEN3_INCLUDE_DIR}")  
  
#ADD_EXECUTABLE(test test.cpp)  
#include_directories(src)
# 找到src目录及其子目录下的所有c文件和h文件
#file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp" "src/*.c" "src/*.cc")
set(sources src/eigen_polyfit.cpp)

add_executable(polyfit ${sources})
#file(GLOB_RECURSE PROJECT_HEADERS "src/*.hpp" "src/*.h" "src/*.hh")

#add_executable(polyfit ${PROJECT_SOURCES}  ${PROJECT_HEADERS})

target_link_libraries(polyfit python2.7)

使用matplotlib-cpp可视化效果如下图:

拟合后的效果:
拟合后的效果
原始数据:
在这里插入图片描述
完整工程代码见https://github.com/JackJu-HIT/Polyfit.git。

如果觉得文章还不错,可以关注下我的微信公众号:
在这里插入图片描述

  • 7
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
最小二乘法是一种数学优化技术,用于寻找函数的最佳拟合参数。在多项式拟合中,我们可以使用最小二乘法来计算多项式系数,以使拟合的多项式函数最接近给定数据点。具体来说,对于给定的数据点集 $P=\{(x_1,y_1),(x_2,y_2),...,(x_n,y_n)\}$,我们可以选择一个$m$阶多项式函数$f(x)=a_0+a_1x+a_2x^2+...+a_mx^m$,其中$a_0,a_1,...,a_m$是多项式系数。我们的目标是找到这些系数,使得多项式函数$f(x)$最小化与数据点的误差平方和,即$\sum_{i=1}^{n}(y_i-f(x_i))^2$。 在Python中,我们可以使用NumPy库中的polyfit函数进行多项式拟合。该函数的语法如下: ```python import numpy as np # x和y是数据点的横纵坐标,m是多项式的阶次 # 返回多项式系数 np.polyfit(x, y, m) ``` 在C/C++中,我们可以自己编写最小二乘法的代码来进行多项式拟合。具体来说,我们可以使用矩阵运算来求解多项式系数。以下是一个C++的示例代码: ```c++ #include <iostream> #include <Eigen/Dense> // 需要Eigen库支持 using namespace Eigen; // x和y是数据点的横纵坐标,m是多项式的阶次 // 返回多项式系数 VectorXd polyfit(VectorXd x, VectorXd y, int m) { int n = x.size(); MatrixXd X(n, m+1); for (int i = 0; i < n; i++) { for (int j = 0; j <= m; j++) { X(i, j) = pow(x(i), j); } } VectorXd Y = y; VectorXd w = X.colPivHouseholderQr().solve(Y); return w; } int main() { VectorXd x(5); x << 1, 2, 3, 4, 5; VectorXd y(5); y << 1, 4, 9, 16, 25; VectorXd w = polyfit(x, y, 2); std::cout << "多项式系数为:" << w.transpose() << std::endl; return 0; } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jack Ju

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

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

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

打赏作者

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

抵扣说明:

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

余额充值