机器人规划与控制研究所 ——机器人/自动驾驶规划与控制方向综合、全面、专业的平台。4万人订阅的微信大号。点击标题下蓝字“机器人规划与控制研究所”关注,我们将为您提供有价值、有深度的延伸阅读。
今天是周六,现在是北京时间23:19:00,经过7个小时编码和调试,刚将代码上传到我的Github仓库,链接:
上周,我写了第一个深入理解轨迹优化器TEB算法:深入解析轨迹优化(TEB) 算法中非线性求解器的 C++ 框架的实现。这篇文章,主要是介绍了TEB算法的g2o框架是如何编写的,如果没有读过的朋友,可以先去读这篇文章。写这篇文章的原因是,我觉得上一篇文章的内容不够透彻,读完后依然觉得理解不深刻。
考虑于此,于是我今天在上一篇文章的基础上,又写了一个demo,这个demo主要是实现跟踪轨迹点和绕障碍物的曲线生成,即写了 EdgeViaPointConstraint约束和EdgeObstacleConstraint这两个约束,熟悉TEB调参的朋友应该知道,EdgeViaPointConstraint约束是保证生成的轨迹尽可能逼近参考轨迹,而障碍物EdgeObstacleConstraint约束是尽快可能让生成的轨迹绕开障碍物。这两个权重在调参中比较重要,有些场景需要严格跟线,而有些场景需要绕开障碍物,这就需要调整这两个权重了。
我想,如果结合本文以及我github仓库开源TrajectoryOptimizeMethod的代码,读懂了以后,你应该对TEB算法有一个深刻的理解,后续可以在这个TEB框架上进行改进优化,抑或参考TEB算法自己再写一个轨迹优化器算法,或者数学水平不错的话,还可以做些算法创新。
好了,说了这么多废话,大半夜码字,总是性情中人!
喝杯酒,再点一根烟,抖音循环播放着好听的背景音乐,便是我的此时此刻!
对了,我写的这个demo有个缺陷,目前生成的轨迹没有考虑平滑约束,也没有考虑转弯半径,所以出来的曲线不会平滑,大家如若有兴趣可以在我的开源代码基础上加入时间间隔,把TEB的里面其它约束写进来。
下面是仿真结果图:
上图,是考虑EdgeViaPointConstraint和EdgeObstacleConstraint这两个约束的生成的曲线,其中ref_traj模拟的是全局规划器发过来的轨迹,planTraj是写的轨迹优化器TrajectoryOptimizeMethod优化后的结果,那两个黑点是模拟的障碍物信息,障碍物最小安全距离设置为1米。
另外,如果你想传入多边形障碍物抑或是线型的障碍物,这个接口没写,其实就是算个距离,可以参考TEB里的处理方式。
本文主要讲解https://github.com/JackJu-HIT/TrajectoryOptimizeMethod的代码思路。
下面我们开始正文吧。
因个人水平有限,如有错误,欢迎指出。
2025年9月14日 01:00:00
Jack Ju 于沈阳
代码的目录层级如下:
其中:
(1)edge_obstacle_edge.h:这个文件主要是实现EdgeObstacleConstraint障碍物约束,保证优化后的轨迹,可以绕开障碍物。
(2)edge_via_point.h:这个文件主要是实现EdgeViaPointConstraint跟随参考轨迹点的约束,保证优化后的轨迹逼近参考轨迹。
(3)planner_manager.h:这个文件是轨迹优化器的管理类。
(4)main.cpp:这个文件是模拟外部调用。
1.主函数
* @Function:using Trajectory Optimize Method * @Create by:juchunyu@qq.com * @Date:2025-09-13 16:10:01 */#include "planner_manager.h"#include "matplotlib-cpp/matplotlibcpp.h"
namespace plt = matplotlibcpp;
int main(){ std::vector<double> glob_x; std::vector<double> glob_y;
std::vector<double> plan_x; std::vector<double> plan_y;
std::vector<double> obs_x; std::vector<double> obs_y;
std::vector<double> color; teb_local_planner::TebConfig cfg; cfg.no_inner_iterations = 5; cfg.no_outer_iterations = 4; cfg.min_obstacle_dist = 1.0; cfg.penalty_epsilon = 0.05; cfg.obstacle_weight = 10; cfg.optimization_verbose = true; cfg.weight_viapoint = 1;
std::shared_ptr<:plannermanager> planner = std::make_shared<:plannermanager>(cfg);
std::vector<:pathinfo> gloablPlan; std::vector<:obstacleinfo> obstacles; std::cout << std::endl; for(int
i = 0;i 10;i++) { tools::pathInfo temp; temp.x = i; temp.y = i; gloablPlan.push_back(temp); std::cout << temp.x glob_x.push_back(temp.x); glob_y.push_back(temp.y); } std::cout << std::endl;
tools::obstacleInfo obstemp; obstemp.x = 3; obstemp.y = 3.5; obs_x.push_back(obstemp.x); obs_y.push_back(obstemp.y); color.push_back(1.0); obstacles.push_back(obstemp);
obstemp.x = 7; obstemp.y = 7.6; obs_x.push_back(obstemp.x); obs_y.push_back(obstemp.y);
color.push_back(1.0);
obstacles.push_back(obstemp);
planner->setpathInfo(gloablPlan); planner->setObstacleInfo(obstacles);
planner->runOptimization();
std::vector<:pathinfo> planResult;
planner->getPlannerResults(planResult);
for(int i = 0;i < planResult.size();i++) { plan_x.push_back(planResult[i].x); plan_y.push_back(planResult[i].y); }
std::map<:string std::string> keywords1; keywords1.insert(std::pair<:string std::string>("label", "ref_traj") ); keywords1.insert(std::pair<:string std::string>("linewidth", "3.5") ); plt::plot(glob_x,glob_y,keywords1);
std::map<:string std::string> keywords4; keywords4.insert(std::pair<:string std::string>("label", "planTraj") ); keywords4.insert(std::pair<:string std::string>("linewidth", "3.5") ); plt::plot(plan_x,plan_y,keywords4);
double point_size = 100.0; plt::scatter_colored(obs_x, obs_y,color,point_size,{{"cmap", "viridis"}});
plt::legend(); plt::title("Trajectory Optimize Method "); plt::show();
return 0;}
主函数主要做了如下几个事情:
(1)配置的初始化
teb_local_planner::TebConfig cfg; cfg.no_inner_iterations = 5; cfg.no_outer_iterations = 4; cfg.min_obstacle_dist = 1.0; cfg.penalty_epsilon = 0.05; cfg.obstacle_weight = 10; cfg.optimization_verbose = true; cfg.weight_viapoint = 1;
设置优化器的迭代次数no_inner_iterations 和no_outer_iterations ,最小障碍
物距离min_obstacle_dist,障碍物权重obstacle_weight,跟线的权重weight_viapoint 。
(2) 创建规划器并运行优化
std::shared_ptr<:plannermanager> planner = std::make_shared<:plannermanager>(cfg); std::vector<:pathinfo> gloablPlan; std::vector<:obstacleinfo> obstacles; std::cout << std::endl; for(int i = 0;i 10;i++) { tools::pathInfo temp; temp.x = i; temp.y = i; gloablPlan.push_back(temp); std::cout << temp.x glob_x.push_back(temp.x); glob_y.push_back(temp.y); } std::cout << std::endl; tools::obstacleInfo obstemp; obstemp.x = 3; obstemp.y = 3.5; obs_x.push_back(obstemp.x); obs_y.push_back(obstemp.y); color.push_back(1.0); obstacles.push_back(obstemp); obstemp.x = 7; obstemp.y = 7.6; obs_x.push_back(obstemp.x); obs_y.push_back(obstemp.y); color.push_back(1.0); obstacles.push_back(obstemp); planner->setpathInfo(gloablPlan); planner->setObstacleInfo(obstacles); planner->runOptimization();
首先实例化plannerManager类,然后模拟产生参考轨迹。
for(int i = 0;i 10;i++) { tools::pathInfo temp; temp.x = i; temp.y = i; gloablPlan.push_back(temp); std::cout << temp.x glob_x.push_back(temp.x); glob_y.push_back(temp.y); }
模拟产生障碍物点云:产生两个障碍物(3,3.5)和(7,7.6)。
tools::obstacleInfo obstemp; obstemp.x = 3; obstemp.y = 3.5; obs_x.push_back(obstemp.x); obs_y.push_back(obstemp.y); color.push_back(1.0); obstacles.push_back(obstemp);
obstemp.x = 7; obstemp.y = 7.6; obs_x.push_back(obstemp.x); obs_y.push_back(obstemp.y); color.push_back(1.0); obstacles.push_back(obstemp);
最后调用plannerManager类成员将参考轨迹和障碍物信息传递到轨迹优化器,并开始启动优化。
planner->setpathInfo(gloablPlan);planner->setObstacleInfo(obstacles);planner->runOptimization();
(3) 获取轨迹规划器结果
planner->getPlannerResults(planResult);
(4)可视化结果
std::map<:string std::string> keywords1; keywords1.insert(std::pair<:string std::string>("label", "ref_traj") ); keywords1.insert(std::pair<:string std::string>("linewidth", "3.5") ); plt::plot(glob_x,glob_y,keywords1); std::map<:string std::string> keywords4; keywords4.insert(std::pair<:string std::string>("label", "planTraj") ); keywords4.insert(std::pair<:string std::string>("linewidth", "3.5") ); plt::plot(plan_x,plan_y,keywords4); double point_size = 100.0; plt::scatter_colored(obs_x, obs_y,color,point_size,{{"cmap", "viridis"}}); plt::legend(); plt::title("Trajectory Optimize Method "); plt::show();
2.plannerManager优化器管理类
* @Function:Trajectory Optimize Method Manager * @Create by:juchunyu@qq.com * @Date:2025-09-13 16:10:01 */#pragma once #include "base_teb_edges.h"#include "vertexPoint.h"#include "edge_obstacle_edge.h"#include "edge_via_point.h"
#include "tools.h"namespace teb_local_planner{class plannerManager{public: plannerManager(const TebConfig& cfg) : cfg_(cfg) { optimizer_ = initOptimizer(); pose_vertices_.clear(); } ~plannerManager() { clearGraph(); } void setpathInfo(std::vector<:pathinfo>& path); void setObstacleInfo(std::vector<:obstacleinfo>& obs); void getPlannerResults(std::vector<:pathinfo>& path); void runOptimization();private: boost::shared_ptr<:sparseoptimizer> initOptimizer(); static void registerG2OTypes(); void AddVertices(); void AddObstacleEdges(); void AddViaPointEdges(); int findClosestTrajectoryPose(tools::pathInfo &ref_point,int& idx); bool optimizeGraph(); void clearGraph() { if (optimizer_) { optimizer_->edges().clear(); optimizer_->clear(); } } void printResult();private: const TebConfig& cfg_; boost::shared_ptr<:sparseoptimizer> optimizer_; std::vector pose_vertices_; std::vector<:pathinfo> pathPointArr_; std::vector<:obstacleinfo> obstaclePointInfo_;};}
这个C++类主要做下面几个事
(1)G2O的初始化
boost::shared_ptr<: class="code-snippet__title">SparseOptimizer> plannerManager::initOptimizer() { // 线程安全注册自定义类型 static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(®isterG2OTypes, flag); // 配置求解器(二维顶点,动态残差维度) typedef g2o::BlockSolver<:blocksolvertraits>2, -1>> BlockSolverType; typedef g2o::LinearSolverDense<:posematrixtype> LinearSolverType; // 创建求解器 auto linear_solver = std::make_unique<LinearSolverType>(); auto block_solver = std::make_unique<BlockSolverType>(std::move(linear_solver)); auto solver = new g2o::OptimizationAlgorithmLevenberg(std::move(block_solver)); // 初始化优化器 auto optimizer = boost::make_shared<: class="code-snippet__title">SparseOptimizer>(); optimizer->setAlgorithm(solver); optimizer->setVerbose(cfg_.optimization_verbose); // 从配置读取verbose return optimizer;}
其中registerG2OTypes需要将我们创建的EdgeViaPointConstraint和EdgeObstacleConstraint这两个约束进行注册,还有待优化的顶点进行注册。
void plannerManager::registerG2OTypes() { g2o::Factory* factory = g2o::Factory::instance(); factory->registerType("VERTEX_POINT2D", new g2o::HyperGraphElementCreator); factory->registerType("EDGE_obstacle_CONSTRAINT", new g2o::HyperGraphElementCreator); factory->registerType("EDGE_via_point_CONSTRAINT", new g2o::HyperGraphElementCreator);}
(2)开始执行优化
void plannerManager::runOptimization(){ std::cout << std::endl; AddVertices(); AddObstacleEdges(); AddViaPointEdges(); for(int i = 0; i < cfg_.no_outer_iterations;i++) { optimizeGraph(); } clearGraph();}
添加顶点
AddVertices(),添加障碍约束AddObstacleEdges(),添加AddViaPointEdges() 添加逼近参考路线的约束。
1.障碍约束AddObstacleEdges
首先看下,这个障碍物约束如何实现。
* @Function: Obstacle Contraint Edge Class * @Create by:juchunyu@qq.com * @Date:2025-09-13 16:10:01 */
#pragma once #include "base_teb_edges.h"#include "vertexPoint.h"#include "tools.h"
namespace teb_local_planner{class EdgeObstacleConstraint : public BaseTebUnaryEdge<1, tools::obstacleInfo, VertexPoint2D>{public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW
virtual void computeError() override { if (!cfg_) { std::cerr << std::endl; _error[0] = 0.0; return; }
const VertexPoint2D* v1 = static_cast<const VertexPoint2D*>(_vertices[0]); Eigen::Vector2d point = v1->estimate();
double dist = sqrt(pow(_measurement.x - point[0],2) + pow(_measurement.y - point[1],2)); _error[0] = tools::penaltyBoundFromBelow(dist, cfg_->min_obstacle_dist, cfg_->penalty_epsilon); }
void setObstcele(tools::obstacleInfo &obs,const TebConfig* cfg) { _measurement = obs;
cfg_ = cfg; }};}
这个使用的是一元边,其中外部障碍物点云是_measurement ,待优化的顶点_vertices,是我们要计算待优化的顶点(_vertices)与障碍物的距离,将这个距离与最小障碍物距离进行比较,并设置成error误差(cost)。
那么plannerManager如何调用EdgeObstacleConstraint 类的?
void plannerManager::AddObstacleEdges(){ Eigen::Matrix<double,1,1> information; information.fill(cfg_.obstacle_weight); std::cout << information << std::endl; int edge_id = 0; for (int i = 0; i < pose_vertices_.size(); ++i) { for(int j = 0; j < obstaclePointInfo_.size();j++) { EdgeObstacleConstraint* e_soft = new EdgeObstacleConstraint(); e_soft->setId(edge_id++); std::cout << obstaclePointInfo_[j].x endl; e_soft->setVertex(0, pose_vertices_[i]); e_soft->setTebConfig(cfg_); e_soft->setObstcele(obstaclePointInfo_[j],&cfg_); e_soft->setInformation(Eigen::Matrix<double,1,1>::Identity());
optimizer_->addEdge(e_soft); } }}
上述需要注意的是, e_soft->setId(edge_id++)这个的id一定要唯一。pose_vertices_是待优化的顶点,它最终赋值给_vertices
,obstaclePointInfo_是外部障碍物点云,它最终赋值给_measurement 。information是信息矩阵,cfg_.obstacle_weight的权重设置。
2.AddViaPointEdges逼近参考路线的约束
首先看下,这个约束如何实现的,
* @Function: Follw Path Contraint Edge Class * @Create by:juchunyu@qq.com * @Date:2025-09-13 16:10:01 */
#pragma once #include "base_teb_edges.h"#include "vertexPoint.h"#include "tools.h"
namespace teb_local_planner{class EdgeViaPointConstraint : public BaseTebUnaryEdge<1, tools::pathInfo, VertexPoint2D>{public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW
virtual void computeError() override { if (!cfg_) { std::cerr << std::endl; _error[0] = 0.0; return; } const VertexPoint2D* v1 = static_cast<const VertexPoint2D*>(_vertices[0]); Eigen::Vector2d point = v1->estimate(); _error[0] = sqrt(pow(_measurement.x - point[0],2) + pow(_measurement.y - point[1],2)); }
void setPathPoint(tools::pathInfo &path,const TebConfig* cfg) { _measurement = path; cfg_ = cfg; }};}
上述也是一元边,其中外部的参考轨迹点是_measurement,_vertices[0]代表待优化的顶点,需要指出的是,这个待优化的顶点是
_measurement在待优化的顶点容器上的投影,投影过程如下:
int plannerManager::findClosestTrajectoryPose(tools::pathInfo &ref_point,int& idx) { int n = pose_vertices_.size(); if (idx 0 || idx >= n) return -1;
double min_dist_sq = std::numeric_limits<double>::max(); int min_idx = -1;
for (int i = idx; i < n; i++) { VertexPoint2D* v = static_cast(pose_vertices_[i]); Eigen::Vector2d point = v->estimate();
double dist_sq = sqrt(pow(ref_point.x - point[0],2) + pow(ref_point.y - point[1],2)); if (dist_sq < min_dist_sq) { min_dist_sq = dist_sq; min_idx = i; } }
idx = min_idx;
return min_idx;}
现在分析,plannerManager类如何调用EdgeViaPointConstraint类?
void plannerManager::AddViaPointEdges() { int start_index = 0; int edge_id = 0; for(int i = 0; i < pathPointArr_.size();i++) { int index = findClosestTrajectoryPose(pathPointArr_[i],start_index);
Eigen::Matrix<double,1,1> information; information.fill(cfg_.weight_viapoint);
std::cout << index << std::endl;
EdgeViaPointConstraint* edge_viapoint = new EdgeViaPointConstraint; edge_viapoint->setId(edge_id++); edge_viapoint->setVertex(0,pose_vertices_[index]); edge_viapoint->setInformation(information); edge_viapoint->setPathPoint(pathPointArr_[i],&cfg_); optimizer_->addEdge(edge_viapoint); } }
这个也是,跟上述差不多。
如果不太明白,请阅读这篇文章:深入解析轨迹优化(TEB) 算法中非线性求解器的 C++ 框架的实现。
1.执行方法:
g2o版本建议使用2020年4月份版本,你去官方仓库找源码,并切换到2020年4月份的递交commit,然后对g2o进行源码编译安装。
mkdir buildcd buildcmake ..make./main
2.参数调整:
(1)如果你想让EdgeObstacleConstraint障碍物约束起的权重大,那就使obstacle_weight更大些。
设置如下:
teb_local_planner::TebConfig cfg; cfg.no_inner_iterations = 5; cfg.no_outer_iterations = 4; cfg.min_obstacle_dist = 1.0; cfg.penalty_epsilon = 0.05; cfg.obstacle_weight = 10; cfg.optimization_verbose = true; cfg.weight_viapoint = 1;
曲线图:
会注意到,轨迹优化器输出的planTraj轨迹确实躲闪障碍物了。
(2)如果你想让EdgeViaPointConstraint逼近参考轨迹约束起的权重大,那就使weight_viapoint 更大些。
设置如下:
teb_local_planner::TebConfig cfg; cfg.no_inner_iterations = 5; cfg.no_outer_iterations = 4; cfg.min_obstacle_dist = 1.0; cfg.penalty_epsilon = 0.05; cfg.obstacle_weight = 1; cfg.optimization_verbose = true; cfg.weight_viapoint = 10;
会注意到,轨迹优化器输出的planTraj轨迹确实跟随参考轨迹ref_traj了。
电脑好卡...
公众号平台编辑界面老崩溃...
可算写完了...
我建了一个轨迹优化与运动控制方向技术交流群,欢迎各位同行专家加入交流讨论!扫描下方二维码,添加作者微信,然后拉进群。