Apollo9.0 PNC源码学习之Control模块(二)—— controller解析

前面文章:Apollo9.0 PNC源码学习之Control模块(一)
本文将对具体控制器以及原理做一个剖析
在这里插入图片描述

1 PID控制器

1.1 PID理论基础

如下图所示,PID各参数(Kp,Ki,Kd)的作用:
车辆实际运动轨迹和期望运动轨迹
车辆实际运动轨迹和期望运动轨迹
目标轨迹和车辆实际运动轨迹任何闭环控制系统的首要任务是要稳、准、快的响应命令。PID的主要工作就是如何实现这一任务。
PID控制器的比例单元 ( P) 、积分单元(I)和微分单元(D)分别对应目前误差、过去累计误差及未来误差。若是不知道受控系统的特性,一般认为PID控制器是最适用的控制器。

P:增大比例加快系统的响应,它的作用于输出值较快,但不能很好稳定在一个理想的数值。Kp过大,会产生超调,并产生振荡。
I:在P的基础上消除余差,对稳定后有累积误差的系统进行误差修整,减小稳态误差
D:可以使系统超调量减小,减小振荡,增加稳定性。

位置式PID:当前系统的实际位置,与你想要达到的预期位置的偏差,进行PID控制
在这里插入图片描述当采样时间足够小时,能够获得最够精确的结果,离散控制过程与连续过程非常接近。

位置式PID在积分项达到饱和时,误差仍然会在积分作用下继续累积,一旦误差开始反向变化,系统需要一定时间从饱和区退出,所以在u(k)达到最大和最小时,要停止积分作用,并且要有积分限幅和输出限幅。
抗积分饱和:如果上一次的输出控制量超过了饱和值,饱和值为正,则这一次只积分负的偏差,饱和值为负,则这一次只积分正的偏差,从而避免系统长期留在饱和区!

1.2 Apollo的经典PID源码

modules/control/control_component/proto/pid_conf.proto

syntax = "proto2";package apollo.control;message PidConf {optional bool integrator_enable = 1;optional double integrator_saturation_level = 2;optional double kp = 3;optional double ki = 4;optional double kd = 5;optional double kaw = 6 [default = 0.0];optional double output_saturation_level = 7;
}

pid_controller.h

#pragma once#include "modules/control/control_component/proto/pid_conf.pb.h"namespace apollo {
namespace control {class PIDController {public:// 初始化pid控制器void Init(const PidConf &pid_conf);// 设置pid参数void SetPID(const PidConf &pid_conf);// 重置pidvoid Reset();// 重置积分项void Reset_integral();// PID控制实现(误差+采样时间)virtual double Control(const double error, const double dt);// 默认虚析构函数virtual ~PIDController() = default;// 获取积分器(Integrator)的过饱和状态int IntegratorSaturationStatus() const;// 检查积分器(Integrator)是否处于保持状态bool IntegratorHold() const;// 设置积分器(Integrator)是否保持当前值void SetIntegratorHold(bool hold);protected:double kp_ = 0.0;double ki_ = 0.0;double kd_ = 0.0;double kaw_ = 0.0;double previous_error_ = 0.0;double previous_output_ = 0.0;double integral_ = 0.0;double integrator_saturation_high_ = 0.0;double integrator_saturation_low_ = 0.0;bool first_hit_ = false;bool integrator_enabled_ = false;bool integrator_hold_ = false;int integrator_saturation_status_ = 0;// Only used for pid_BC_controller and pid_IC_controllerdouble output_saturation_high_ = 0.0;double output_saturation_low_ = 0.0;int output_saturation_status_ = 0;
};}  // namespace control
}  // namespace apollo

pid_controller.cc

#include "modules/control/control_component/controller_task_base/common/pid_controller.h"#include <cmath>#include "cyber/common/log.h"namespace apollo {
namespace control {double PIDController::Control(const double error, const double dt) {// 如果dt小于等于0,使用上一次输出if (dt <= 0) {AWARN << "dt <= 0, will use the last output, dt: " << dt;return previous_output_;}double diff = 0;  // 差值double output = 0;// 输出// 如果是第一次运行if (first_hit_) {first_hit_ = false;} else {// 计算差值diff = (error - previous_error_) / dt;}// 积分 如果不启用积分,积分置为零if (!integrator_enabled_) {integral_ = 0;} else if (!integrator_hold_) {// 计算积分integral_ += error * dt * ki_;// 在积分之前应用Ki,以避免在稳态时改变Ki时的阶梯效应if (integral_ > integrator_saturation_high_) {// 如果积分大于饱和上限,将积分设置为饱和上限integral_ = integrator_saturation_high_;// 设置饱和状态为1integrator_saturation_status_ = 1;} else if (integral_ < integrator_saturation_low_) {// 如果积分小于饱和下限,将积分设置为饱和下限integral_ = integrator_saturation_low_;// 设置饱和状态为-1integrator_saturation_status_ = -1;} else {// 如果积分在饱和范围内,设置饱和状态为0integrator_saturation_status_ = 0;}}// 更新previous_error_previous_error_ = error;// 计算输出output = error * kp_ + integral_ + diff * kd_;  // Ki already applied// 更新previous_output_previous_output_ = output;return output;
}
// 重置积分项
void PIDController::Reset_integral() {integral_ = 0.0;integrator_saturation_status_ = 0;
}
// 重置PID控制器
void PIDController::Reset() {previous_error_ = 0.0;previous_output_ = 0.0;integral_ = 0.0;first_hit_ = true;integrator_saturation_status_ = 0;output_saturation_status_ = 0;
}
// 初始化控制器
void PIDController::Init(const PidConf &pid_conf) {previous_error_ = 0.0;previous_output_ = 0.0;integral_ = 0.0;first_hit_ = true;integrator_enabled_ = pid_conf.integrator_enable();integrator_saturation_high_ =std::fabs(pid_conf.integrator_saturation_level());integrator_saturation_low_ =-std::fabs(pid_conf.integrator_saturation_level());integrator_saturation_status_ = 0;integrator_hold_ = false;output_saturation_high_ = std::fabs(pid_conf.output_saturation_level());output_saturation_low_ = -std::fabs(pid_conf.output_saturation_level());output_saturation_status_ = 0;SetPID(pid_conf);
}
// 设置PID参数
void PIDController::SetPID(const PidConf &pid_conf) {kp_ = pid_conf.kp();ki_ = pid_conf.ki();kd_ = pid_conf.kd();kaw_ = pid_conf.kaw();
}
// 积分饱和状态
int PIDController::IntegratorSaturationStatus() const {return integrator_saturation_status_;
}
// 是否积分保持
bool PIDController::IntegratorHold() const { return integrator_hold_; }
// 设置积分保持(true or false)
void PIDController::SetIntegratorHold(bool hold) { integrator_hold_ = hold; }}  // namespace control
}  // namespace apollo

以上对经典PID的源码进行了剖析,额外对百度Apollo采用其他PID算法进行源码讲解

1.3 pid_IC_controller

pid_IC_controller.h

#pragma once#include "modules/control/control_component/proto/pid_conf.pb.h"#include "modules/control/control_component/controller_task_base/common/pid_controller.h"namespace apollo {
namespace control {
// 该类继承于PIDController
class PIDICController : public PIDController {public:// PIDICController控制virtual double Control(const double error, const double dt);// 输出饱和状态virtual int OutputSaturationStatus();private:
};}  // namespace control
}  // namespace apollo

pid_IC_controller.cc

#include "modules/control/control_component/controller_task_base/common/pid_IC_controller.h"#include <cmath>
#include <iostream>#include "cyber/common/log.h"
#include "modules/common/math/math_utils.h"namespace apollo {
namespace control {double PIDICController::Control(const double error, const double dt) {// 如果dt小于等于0,使用上一次输出if (dt <= 0) {AWARN << "dt <= 0, will use the last output";return previous_output_;}double diff = 0;  // 差值double output = 0;// 输出// 如果是第一次运行if (first_hit_) {first_hit_ = false;} else {diff = (error - previous_error_) / dt;}// integral clamping// 积分限幅if (!integrator_enabled_) {integral_ = 0;} else {double u = error * kp_ + integral_ + error * dt * ki_ + diff * kd_;// 如果error和u同一方向 输出u在饱和范围外,不更新积分项if (((error * u) > 0) &&((u > output_saturation_high_) || (u < output_saturation_low_))) {} else {// Only update integral then// 仅仅更新积分integral_ += error * dt * ki_;}}// 更新previous_error_previous_error_ = error;output = error * kp_ + integral_ + diff * kd_;if (output >= output_saturation_high_) {output_saturation_status_ = 1;} else if (output <= output_saturation_low_) {output_saturation_status_ = -1;} else {output_saturation_status_ = 0;}// 限制output在输出饱和范围内output = common::math::Clamp(error * kp_ + integral_ + diff * kd_,output_saturation_high_,output_saturation_low_);  // Ki already appliedprevious_output_ = output;return output;
}
// 返回输出饱和状态
int PIDICController::OutputSaturationStatus() {return output_saturation_status_;
}}  // namespace control
}  // namespace apollo
1.4 pid_BC_controller

反向计算pid
pid_BC_controller.h

#pragma once#include "modules/control/control_component/proto/pid_conf.pb.h"#include "modules/control/control_component/controller_task_base/common/pid_controller.h"namespace apollo {
namespace control {class PIDBCController : public PIDController {public:virtual double Control(const double error, const double dt);virtual int OutputSaturationStatus();private:
};}  // namespace control
}  // namespace apollo

pid_BC_controller.cc

#include "modules/control/control_component/controller_task_base/common/pid_BC_controller.h"#include <cmath>#include "cyber/common/log.h"
#include "modules/common/math/math_utils.h"namespace apollo {
namespace control {
// PIDBCController控制
double PIDBCController::Control(const double error, const double dt) {// 如果dt小于等于0,使用上一次输出if (dt <= 0) {AWARN << "dt <= 0, will use the last output";return previous_output_;}double diff = 0;double output = 0;if (first_hit_) {first_hit_ = false;} else {diff = (error - previous_error_) / dt;}// backward calculation 反向计算if (!integrator_enabled_) { // 如果积分器未启用,则将积分设置为0integral_ = 0;} else {double u = error * kp_ + integral_ + error * dt * ki_ + diff * kd_;// 本人认为aw_term是计算超出u的值,然后根据aw_term判断输出饱和状态double aw_term = common::math::Clamp(u, output_saturation_high_,output_saturation_low_) -u;if (aw_term > 1e-6) {output_saturation_status_ = -1;} else if (aw_term < -1e-6) {output_saturation_status_ = 1;} else {output_saturation_status_ = 0;}// 计算积分integral_ += kaw_ * aw_term + error * dt;}// 更新previous_error_previous_error_ = error;// 限制输出在输出饱和状态内output = common::math::Clamp(error * kp_ + integral_ + diff * kd_,output_saturation_high_,output_saturation_low_);  // Ki already appliedprevious_output_ = output;return output;
}
// 返回输出饱和状态
int PIDBCController::OutputSaturationStatus() {return output_saturation_status_;
}}  // namespace control
}  // namespace apollo

2 超前滞后控制器leadlag_controller

主要用于倒车运动控制

2.1 超前滞后控制器源码解析

leadlag_controller.h

#pragma once#include "modules/control/control_component/proto/leadlag_conf.pb.h"namespace apollo {
namespace control {class LeadlagController {public:// 初始化void Init(const LeadlagConf &leadlag_conf, const double dt);// 设置leadlag alpha, beta and tauvoid SetLeadlag(const LeadlagConf &leadlag_conf);// 双线性变换离散化方法(T型积分:曲线包围面积用梯形估算)// 把连续的控制器系数离散化void TransformC2d(const double dt);// 重置超前滞后控制器void Reset();// 超前滞后也是类似PID输入参数error和dtvirtual double Control(const double error, const double dt);// 获取饱和状态int InnerstateSaturationStatus() const;protected:// 连续时间控制系数double alpha_ = 0.0;double beta_ = 0.0;double tau_ = 0.0;double Ts_ = 0.01;  // 默认0.01s// 离散时间控制系数double kn1_ = 0.0;double kn0_ = 0.0;double kd1_ = 0.0;double kd0_ = 0.0;// Inner (intermedia) state in discrete-time domain at Direct Form IIdouble previous_output_ = 0.0;double previous_innerstate_ = 0.0;double innerstate_ = 0.0;double innerstate_saturation_high_ = 0.0;double innerstate_saturation_low_ = 0.0;int innerstate_saturation_status_ = 0;// 是否启用从连续时间到离散时间的转换bool transfromc2d_enabled_ = false;
};}  // namespace control
}  // namespace apollo

leadlag_controller.cc

#include "modules/control/control_component/controller_task_base/common/leadlag_controller.h"#include <cmath>#include "cyber/common/log.h"namespace apollo {
namespace control {double LeadlagController::Control(const double error, const double dt) {// check if the c2d transform passed during the initilization// 判断连续转离散是否成功if (!transfromc2d_enabled_) {// 失败则重新进行TransformC2dTransformC2d(dt);if (!transfromc2d_enabled_) { // 再次失败,则发出警告AWARN << "C2d transform failed; will work as a unity compensator, dt: "<< dt;return error;  // treat the Lead/Lag as a unity proportional controller}}// check if the current sampling time is valid// 检查步长dt是否小于等于零if (dt <= 0.0) {AWARN << "dt <= 0, will use the last output, dt: " << dt;return previous_output_;}double output = 0.0;// 计算内部状态innerstate_innerstate_ = (error - previous_innerstate_ * kd0_) / kd1_;  // calculate// the inner (intermedia) state under the Direct form II for the Lead / Lag// compensator factorization// 进行innerstate幅值判断:高于状态饱和上限,则等于状态饱和上限,并将状态饱和状态置1;// 低于状态饱和下限,则等于状态饱和下限,并将状态饱和状态置-1;其余情况状态饱和状态置0if (innerstate_ > innerstate_saturation_high_) {innerstate_ = innerstate_saturation_high_;innerstate_saturation_status_ = 1;} else if (innerstate_ < innerstate_saturation_low_) {innerstate_ = innerstate_saturation_low_;innerstate_saturation_status_ = -1;} else {innerstate_saturation_status_ = 0;}// 计算输出output = innerstate_ * kn1_ + previous_innerstate_ * kn0_;// 更新previous_innerstate_previous_innerstate_ = innerstate_;// 更新previous_output_previous_output_ = output;return output;
}void LeadlagController::Reset() {previous_output_ = 0.0;previous_innerstate_ = 0.0;innerstate_ = 0.0;innerstate_saturation_status_ = 0;
}void LeadlagController::Init(const LeadlagConf &leadlag_conf, const double dt) {// 前一输出previous_output_ = 0.0;// 前一内部状态previous_innerstate_ = 0.0;// 内部状态innerstate_ = 0.0;// 内部状态饱和上限innerstate_saturation_high_ =std::fabs(leadlag_conf.innerstate_saturation_level());// 内部状态饱和下限innerstate_saturation_low_ =-std::fabs(leadlag_conf.innerstate_saturation_level());// 内部状态饱和标志innerstate_saturation_status_ = 0;// 设置leadlagSetLeadlag(leadlag_conf);// 连续转离散 TransformC2d函数的作用是将连续形式的传递函数转换成离散形式的传递函数TransformC2d(dt);
}void LeadlagController::SetLeadlag(const LeadlagConf &leadlag_conf) {alpha_ = leadlag_conf.alpha();beta_ = leadlag_conf.beta();tau_ = leadlag_conf.tau();
}void LeadlagController::TransformC2d(const double dt) {if (dt <= 0.0) {AWARN << "dt <= 0, continuous-discrete transformation failed, dt: " << dt;transfromc2d_enabled_ = false;} else {double a1 = alpha_ * tau_;double a0 = 1.00;double b1 = beta_ * tau_;double b0 = beta_;Ts_ = dt;kn1_ = 2 * b1 + Ts_ * b0;kn0_ = Ts_ * b0 - 2 * b1;kd1_ = 2 * a1 + Ts_ * a0;kd0_ = Ts_ * a0 - 2 * a1;if (kd1_ <= 0.0) {AWARN << "kd1 <= 0, continuous-discrete transformation failed, kd1: "<< kd1_;transfromc2d_enabled_ = false;} else {transfromc2d_enabled_ = true;}}
}
// 返回内部饱和状态
int LeadlagController::InnerstateSaturationStatus() const {return innerstate_saturation_status_;
}}  // namespace control
}  // namespace apollo
2.2 超前滞后控制器原理讲解

leadlag传递函数
H ( s ) = β τ s + 1 τ α s + 1 H(s)=\beta \frac{\tau s+1}{\tau \alpha s+1} H(s)=βταs+1τs+1
其中,
τ \tau τ:时间常数
α \alpha α:超前滞后调节系数
β \beta β:开环增益系数
( 1 − α ) τ (1-\alpha)\tau (1α)τ:需要超前或滞后的时间

α < 1 \alpha<1 α<1,有 τ > α τ \tau>\alpha\tau τ>ατ:超前补偿
α > 1 \alpha>1 α>1,有 τ < α τ \tau<\alpha\tau τ<ατ:滞后补偿

采用双线性变换,T为采样周期
s = 2 T z − 1 z + 1 s=\frac{2}{T} \frac{z-1}{z+1} s=T2z+1z1
在这里插入图片描述从上述公式来看,和代码里是一致的:
α 1 = α τ , α 0 = 1.00 , b 1 = β τ , b 0 = β \alpha1=\alpha\tau,\alpha0=1.00,b1=\beta\tau,b0=\beta α1=ατ,α0=1.00,b1=βτ,b0=β

k n 1 = 2 β τ + T β kn_1=2\beta\tau+T\beta kn1=2βτ+
k n 0 = T β − 2 β τ kn_0=T\beta-2\beta\tau kn0=2βτ
k d 1 = 2 α τ + T kd_1=2\alpha\tau+T kd1=2ατ+T
k d 0 = T − 2 α τ kd_0=T-2\alpha\tau kd0=T2ατ

double a1 = alpha_ * tau_;
double a0 = 1.00;
double b1 = beta_ * tau_;
double b0 = beta_;
Ts_ = dt;
kn1_ = 2 * b1 + Ts_ * b0;
kn0_ = Ts_ * b0 - 2 * b1;
kd1_ = 2 * a1 + Ts_ * a0;
kd0_ = Ts_ * a0 - 2 * a1;
if (kd1_ <= 0.0) {AWARN << "kd1 <= 0, continuous-discrete transformation failed, kd1: "<< kd1_;transfromc2d_enabled_ = false;
} else {transfromc2d_enabled_ = true;
}

相位角和幅值
超前-滞后补偿器的传递函数相角为:
在这里插入图片描述式中,计算公式参考和差化积
在这里插入图片描述最大相角在极点 1 α τ \frac{1}{\alpha\tau} ατ1和零点 1 τ \frac{1}{\tau} τ1之间,其值取决于 α \alpha α的大小

在这里插入图片描述在这里插入图片描述参数分析

在这里插入图片描述在工程实践中,可以通过提高系统其他环节的放大系数或增加比例放大器加以补偿,Apollo的leadlag采用后者的处理方法。但是 β \beta β如果过大,容易引起饱和

innerstate_ = (error - previous_innerstate_ * kd0_) / kd1_;

在这里插入图片描述

3 Apollo的模型参考自适应控制MRAC

MRAC原理:一般用于百度Apollo横向控制
参考博客
MRAC控制系统的基本结构图
在这里插入图片描述控制器和受控对象组成内环,这一部分称之为可调系统,由参考模型和自适应机构组成外环。

该系统是在常规的反馈控制回路上再附加一个参考模型和控制器参数的自动调节回路而形成。在该系统中,参考模型的输出或状态相当于给定一个动态性能指标,目标信号同时加在可调系统与参考模型上,通过比较受控对象与参考模型的输出或状态来得到两者之间的误差信息,按照一定的规律(自适应律)来修正控制器的参数(参数自适应)或产生一个辅助输入信号(信号综合自适应),从而使受控制对象的输出尽可能地跟随参考模型的输出

在这里插入图片描述MRAC在无人驾驶控制算法中,往往会和上层控制算法联合起来使用,上层控制算法包含MPC、LQR、Stanley等。一般横向控制算法不会单独使用MRAC控制器,MRAC只是起到一个辅助调节的作用。

MRAC控制器收敛的并不是上层控制算法(MPC等)给出的参考方向盘转角值,而是收敛期望转向机算出来的参考转角值,让真实转向机的角度跟随期望转向机的角度。期望转向机的模型就是MRAC算法中的MR(参考模型),期望转向机的系数(阻尼比等)往往是设计者给定的,不一定要是接近于真实转向机,如果参考模型接近于真实模型,反而使得此算法变得没有意义。

在这里插入图片描述假设黑色线表示这一时刻上层控制算法MPC算出来的方向盘转角期望值,蓝色线代表在无MRAC作用时(单用MPC控制),真实转向机对MPC给出的参考值的跟随情况。而红色线代表是这一时刻用期望的转向机模型算出来的转角期望值。MRAC算法收敛的不是黑色线与蓝色线的误差,而是收敛蓝色线与红色线的误差。

为什么要用MRAC控制器?

为了让同一套上层控制算法(MPC等)在面对不同的真实转向机时,不受硬件参数差异的影响。比如现在有两台车,一台是博世的转向机,一台是国产转向机,两台转向机的阻尼比等系数都不同(模型不同),如果我设计一个期望的转向机参考模型,并用MRAC算法控制的话,那么这两台建模不同的转向机都会变成我设计的期望转向机的模型。这就是模型参考的意义。

设计的期望转向机的模型对信号的收敛速度应该是要低于真实转向机的。因为要使一个收敛快的转向机变得收敛慢是容易实现的,但要使一个收敛慢的转向机变得收敛快,往往难以克服机械硬件的限制

4 一维、二维插值算法

4.1 一维插值算法

interpolation_1d.h

#pragma once#include <memory>
#include <utility>
#include <vector>#include "Eigen/Core"
#include "unsupported/Eigen/Splines"namespace apollo {
namespace control {class Interpolation1D {public:typedef std::vector<std::pair<double, double>> DataType;Interpolation1D() = default;// 初始化bool Init(const DataType& xy);// 仅在[x_min,x_max]之间插值x// 对于超出范围的x,将返回起始或结束y值。double Interpolate(double x) const;private:// 将X值缩小到[0,1]double ScaledValue(double x) const;Eigen::RowVectorXd ScaledValues(Eigen::VectorXd const& x_vec) const;double x_min_ = 0.0;double x_max_ = 0.0;double y_start_ = 0.0;double y_end_ = 0.0;// 一维“点”的样条曲线std::unique_ptr<Eigen::Spline<double, 1>> spline_;
};}  // namespace control
}  // namespace apollo

interpolation_1d.cc

#include "modules/control/control_component/controller_task_base/common/interpolation_1d.h"#include <algorithm>#include "cyber/common/log.h"namespace apollo {
namespace control {const double kDoubleEpsilon = 1e-6;
// 初始化插值器
bool Interpolation1D::Init(const DataType& xy) {if (xy.empty()) {AERROR << "empty input.";return false;}auto data(xy);std::sort(data.begin(), data.end());Eigen::VectorXd x(data.size());Eigen::VectorXd y(data.size());for (unsigned i = 0; i < data.size(); ++i) {x(i) = data[i].first;y(i) = data[i].second;}x_min_ = data.front().first;x_max_ = data.back().first;y_start_ = data.front().second;y_end_ = data.back().second;// 在此处进行插值拟合。X值需要在此处进行缩放到[0, 1]spline_.reset(new Eigen::Spline<double, 1>(Eigen::SplineFitting<Eigen::Spline<double, 1>>::Interpolate(y.transpose(),// 插值多项式不超过三次,但接受短向量static_cast<Eigen::DenseIndex>(std::min<size_t>(x.size() - 1, 3)),ScaledValues(x))));return true;
}
// 使用插值器进行插值
double Interpolation1D::Interpolate(double x) const {if (x < x_min_) {return y_start_;}if (x > x_max_) {return y_end_;}// 提取插值值时,x值也需要进行缩放return (*spline_)(ScaledValue(x))(0);
}
// x就被转换成了一个0到1之间的值
double Interpolation1D::ScaledValue(double x) const {// 避免除以零的错误if (std::fabs(x_max_ - x_min_) < kDoubleEpsilon) {return x_min_;}return (x - x_min_) / (x_max_ - x_min_);
}
// 原向量中元素经过ScaledValue函数处理后的结果
Eigen::RowVectorXd Interpolation1D::ScaledValues(Eigen::VectorXd const& x_vec) const {return x_vec.unaryExpr([this](double x) { return ScaledValue(x); }).transpose();
}}  // namespace control
}  // namespace apollo
4.2 二维插值算法

interpolation_2d.h

#pragma once#include <map>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>namespace apollo {
namespace control {
/*** @class Interpolation2D** @brief linear interpolation from key (double, double) to one double value.*/
class Interpolation2D {public:typedef std::vector<std::tuple<double, double, double>> DataType;typedef std::pair<double, double> KeyType;Interpolation2D() = default;bool Init(const DataType &xyz);// 从二维关键点(两个double类型值,通常表示空间中的x和y坐标)到一个double值double Interpolate(const KeyType &xy) const;private:// 一个double到double的映射表,表示z值与y值之间的关系double InterpolateYz(const std::map<double, double> &yz_table,double y) const;// 处理两个值(value_before和value_after)和它们对应的分段距离,计算并返回在给定区间内的插值结果double InterpolateValue(const double value_before, const double dist_before,const double value_after,const double dist_after) const;std::map<double, std::map<double, double>> xyz_;
};}  // namespace control
}  // namespace apollo

interpolation_2d.cc

#include "modules/control/control_component/controller_task_base/common/interpolation_2d.h"#include <cmath>#include "cyber/common/log.h"namespace {const double kDoubleEpsilon = 1.0e-6;}  // namespacenamespace apollo {
namespace control {bool Interpolation2D::Init(const DataType &xyz) {if (xyz.empty()) {AERROR << "empty input.";return false;}// 对于每个元素t,提取其x、y和z坐标for (const auto &t : xyz) {xyz_[std::get<0>(t)][std::get<1>(t)] = std::get<2>(t);}return true;
}
// 函数接收一个键类型(KeyType)的xy参数,用于查找并进行二维空间中的插值计算。这里使用了双线性插值方法
double Interpolation2D::Interpolate(const KeyType &xy) const {// 获取数据集中最大和最小的x值,加上一个很小的容差kDoubleEpsilon防止比较时的精度问题double max_x = xyz_.rbegin()->first;double min_x = xyz_.begin()->first;// 如果输入的x值大于等于最大x减去一个小误差,表示接近最高点,进行y-z方向的插值if (xy.first >= max_x - kDoubleEpsilon) {return InterpolateYz(xyz_.rbegin()->second, xy.second);}// 同理,如果输入的x值小于等于最小x加上小误差,表示接近最低点,进行y-z方向的插值if (xy.first <= min_x + kDoubleEpsilon) {return InterpolateYz(xyz_.begin()->second, xy.second);}// 在数据集中找到输入x值对应的索引之后的元素(近似大于等于的元素)auto itr_after = xyz_.lower_bound(xy.first);// 同样找到索引之前的元素(近似小于的元素)auto itr_before = itr_after;// 如果不是第一个元素,则向前移动一位if (itr_before != xyz_.begin()) {--itr_before;}// 计算输入x值前后的z值double x_before = itr_before->first;double z_before = InterpolateYz(itr_before->second, xy.second);double x_after = itr_after->first;double z_after = InterpolateYz(itr_after->second, xy.second);// 计算输入x值与前两个x值之间的差double x_diff_before = std::fabs(xy.first - x_before);double x_diff_after = std::fabs(xy.first - x_after);// 最后使用差值和对应的z值执行双线性插值return InterpolateValue(z_before, x_diff_before, z_after, x_diff_after);
}double Interpolation2D::InterpolateYz(const std::map<double, double> &yz_table,double y) const {if (yz_table.empty()) {AERROR << "Unable to interpolateYz because yz_table is empty.";return y;}double max_y = yz_table.rbegin()->first;double min_y = yz_table.begin()->first;if (y >= max_y - kDoubleEpsilon) {return yz_table.rbegin()->second;}if (y <= min_y + kDoubleEpsilon) {return yz_table.begin()->second;}auto itr_after = yz_table.lower_bound(y);auto itr_before = itr_after;if (itr_before != yz_table.begin()) {--itr_before;}double y_before = itr_before->first;double z_before = itr_before->second;double y_after = itr_after->first;double z_after = itr_after->second;double y_diff_before = std::fabs(y - y_before);double y_diff_after = std::fabs(y - y_after);return InterpolateValue(z_before, y_diff_before, z_after, y_diff_after);
}
// 二维插值函数
double Interpolation2D::InterpolateValue(const double value_before,const double dist_before,const double value_after,const double dist_after) const {// 如果前一个点到目标点的距离小于一个很小的阈值,则直接返回前一个点的值,避免除零错误                                      if (dist_before < kDoubleEpsilon) {return value_before;}// 如果后一个点到目标点的距离小于一个很小的阈值,则直接返回后一个点的值,避免除零错误if (dist_after < kDoubleEpsilon) {return value_after;}// 计算前一个点与后一个点之间的值的差距double value_gap = value_after - value_before;// 根据距离权重计算插值的中间值double value_buff = value_gap * dist_before / (dist_before + dist_after);// 返回插值结果return value_before + value_buff;
}}  // namespace control
}  // namespace apollo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/27338.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

水表摄像直读抄表仪

1.技术性简述 水表摄像直读抄表仪&#xff0c;是一种前沿的智能计量机器设备&#xff0c;它利用超清摄像头部和图像识别算法&#xff0c;完成了远程控制、非接触的水表载入。这一技术的普及&#xff0c;颠覆了传统式人力抄表的形式&#xff0c;提高了效率&#xff0c;降低了不…

RPA-UiBot6.0控制与运行机器人 —工作任务智能调度自动运行

前言 来也产品文档中心 来也产品文档中心 (laiye.com)https://documents.laiye.com/ 友友们你们是否曾因为例行性工作的繁琐而苦恼&#xff1f;是否想要让机器人帮你自动执行这些任务&#xff1f;小北的这篇博客将为友友们揭示其中的奥秘&#xff0c;让我们一起学习如何通过RP…

计算机组成原理历年考研真题对应知识点(计算机系统层次结构)

目录 1.2计算机系统层次结构 1.2.2计算机硬件 【命题追踪——冯诺依曼计算机的特点(2019)】 【命题追踪——MAR 和 MDR 位数的概念和计算(2010、2011)】 1.2.3计算机软件 【命题追踪——三种机器语言的特点(2015)】 【命题追踪——各种翻译程序的概念(2016)】 1.2.5计算…

2024.6.14 刷题总结

2024.6.14 **每日一题** 2786.访问数组中的位置使分数最大&#xff0c;看到这题就想到动态规划的思路&#xff0c;遍历数组&#xff0c;每次选择移动该元素时能获得到的最大值&#xff0c;分别考虑最后一个的元素为奇数/偶数的最大值&#xff0c;用长度为2的数组来储存这两个值…

HTML解析之Beautiful Soup

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Beautiful Soup是一个用于从HTML和XML文件中提取数据的Python库。Beautiful Soup 提供一些简单的、函数用来处理导航、搜索、修改分析树等功能。Beau…

代码随想录:回溯20-21

51.N皇后 题目 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解…

【深度学习】Transformer分类器,CICIDS2017,入侵检测,随机森林、RFE、全连接神经网络

文章目录 1 前言2 随机森林训练3 递归特征消除 RFE Recursive feature elimination4 DNN5 Transformer5.1. 输入嵌入层&#xff08;Input Embedding Layer&#xff09;5.2. 位置编码层&#xff08;Positional Encoding Layer&#xff09;5.3. Transformer编码器层&#xff08;T…

堆的实现及其应用

堆的概念 堆是完全二叉树&#xff0c;分为大堆和小堆。大堆&#xff1a;任何一个父亲都大于等于孩子&#xff0c;小堆&#xff1a;任何一个父亲都小于等于孩子。 堆的实现 目录 typedef int HPDataType;typedef struct Heap { HPDataType* a;int size;int capacity; }HP;//交…

C语言之操作符

目录 一、二进制 原码、反码、补码 二、移位操作符 位操作符 三、 逗号表达式 四、下标访问[]、函数调用() 五. 操作符的属性 整型提升 算术转换 六、总结 一、二进制 其实2进制、8进制、10进制、16进制是数值的不同表示形式而已。 其实10进制是生活中经常使用的&am…

类别朴素贝叶斯CategoricalNB和西瓜数据集

CategoricalNB 1 CategoricalNB原理以及用法2 数据集2.1 西瓜数据集2.2 LabelEncoder2.3 OrdinalEncoder 3 代码实现 1 CategoricalNB原理以及用法 &#xff08;1&#xff09;具体原理 具体原理可看&#xff1a;贝叶斯分类器原理 sklearn之CategoricalNB对条件概率的原理如下&…

粉丝经济时代:微信订阅号如何助力中小企业增长

在数字化浪潮席卷全球的今天&#xff0c;微信订阅号凭借其独特的优势&#xff0c;成为了中小企业数字化出海的重要工具。作为NetFarmer&#xff0c;我们致力于帮助企业充分利用这一平台&#xff0c;推动业务发展和市场拓展。今天将深入探讨微信订阅号的概念、用途、使用方法、适…

mac安装高版本git(更新git)

问题 问题&#xff1a;新下载的idea&#xff0c;此idea的版本较高&#xff0c;但是在工作发现这个版本的git存在一定漏洞会导致一些信息泄露问题。 1.安装Homebrew 对于Mac更新git&#xff0c;最简单的就是使用brew命令。所以我们首先下载homebrew。已下载的同学忽略直接下一…

【数据结构陈越版笔记】进阶实验1-3.1:两个有序序列的中位数

我这答案做的可能不对&#xff0c;如果不对&#xff0c;欢迎大家指出错误&#xff0c;思路大部分直接写在注释中了。 进阶实验1-3.1&#xff1a;两个有序序列的中位数 已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列 A 0 , A 1 , . . . , A n −…

ES升级--05--快照生成 和备份

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 备份ES数据1.关闭集群自动均衡2.执行同步刷新3.停止集群节点的Elasticsearch服务4.修改Elasticsearch配置文件&#xff0c;开启快照功能&#xff0c;配置仓库目录为…

微信群发机器人.使用指南.

0.简介 1.介绍 微信群发机器人是用来群发微信消息的工具,通过控制电脑的键盘和鼠标操作微信app来实现群发.支持的消息类型有:文字,图片,视频,文件,小程序,位置等. 群发机器人也可以将微信联系人中的信息保存到电脑csv表格中,以供分析. 因其是通过模拟用户操作鼠标键盘来实现群…

the histogram of cross-entropy loss values 交叉熵损失值的直方图以及cross-entropy loss交叉熵损失

交叉熵损失值的直方图在机器学习和深度学习中有几个重要的作用和用途&#xff1a; 评估模型性能: 直方图可以帮助评估模型在训练数据和测试数据上的性能。通过观察损失值的分布&#xff0c;可以了解模型在不同数据集上的表现情况。例如&#xff0c;损失值分布的形状和范围可以反…

C++中extern “C“的用法

目的 extern "C"是经常用到的东西&#xff0c;面试题目也经常出现&#xff0c;然则&#xff0c;实际用时&#xff0c;还是经常遗忘&#xff0c;因此&#xff0c;深入的了解一下&#xff0c;以增强记忆。 extern "C"指令非常有用&#xff0c;因为C和C的近亲…

Android MediaMetadataRetriever获取视频宽高,Java

Android MediaMetadataRetriever获取视频宽高&#xff0c;Java public static int[] getVideoSize(Context ctx, Uri uri) {MediaMetadataRetriever retriever new MediaMetadataRetriever();int[] size {-1, -1}; //宽&#xff0c;高try {retriever.setDataSource(ctx, uri)…

双向转发检测BFD(学习笔记)

定义 双向转发检测BFD&#xff08;Bidirectional Forwarding Detection&#xff09;是一种全网统一的检测机制&#xff0c;用于快速检测、监控网络中链路或者IP路由的转发连通状况 BFD检测机制 BFD的检测机制是两个系统建立BFD会话&#xff0c;并沿它们之间的路径周期性发送B…

Java 开发实例:Spring Boot+AOP+注解+Redis防重复提交(防抖)

文章目录 1. 环境准备2. 引入依赖3. 配置Redis4. 创建防重复提交注解5. 实现AOP切面6. 创建示例Controller7. 测试8. 进一步优化8.1 自定义异常处理8.2 提升Redis的健壮性 9. 总结 &#x1f389;欢迎来到Java学习路线专栏~探索Java中的静态变量与实例变量 ☆* o(≧▽≦)o *☆嗨…