Ego Planner规划算法(下)——Ego Planner程序框架

   本系列文章用于学习记录Ego-Planner规划算法的相关内容,本系列文章是在在Fast Planner规划算法系列文章的基础上,展开对Ego Planner的相关介绍,主要包括Ego Planner算法流程和程序框架两部分,主要学习资料是深蓝学院的移动机器人运动规划课程

   二、Ego Planner程序框架

   1、各功能包作用

   Ego Planner的开源代码中,uav_simulator文件夹中存放的是一些仿真相关的程序,planner文件夹中存放的是Ego Planner算法相关的程序。

   在planner文件夹中包含多个功能包,各功能包的作用如下:

   (1)、bspline_opt :均匀B样条,用于轨迹优化

   (2)、path_searching:dynamic A * 算法,但在Ego Planner的规划过程中并没有用到

   (3)、plan_env:用于地图生成

   (4)、plan_manage:主文件夹,主节点所在文件夹

   (5)、traj_utils:可视化及多项式轨迹类的相关内容


   2、Ego Planner程序框架/流程

   我们从主函数所在的ego_planner_node.cpp文件开始,该文件中初始化Ego Planner的节点以后,通过rebo_replan.init函数,来调用Ego Planner算法相关的程序,rebo_replan.init函数的具体程序在ego_replan_fsm.cpp文件中

rebo_replan.init(nh);
void EGOReplanFSM::init(ros::NodeHandle &nh)

   ego_replan_fsm.cpp文件中execFSMCallback函数是状态基,用于处理在机器人运行过程中遇到各种情况下,是选择急停、还是重新规划、还是继续执行等。

   我们前面介绍的Ego Planner算法的具体程序都在ego_replan_fsm.cpp 文件中 callReboundReplan函数中调用的reboundReplan函数中。

 bool EGOReplanFSM::callReboundReplan(bool flag_use_poly_init, bool flag_randomPolyTraj)
 bool plan_success =
        planner_manager_->reboundReplan(start_pt_, start_vel_, start_acc_, local_target_pt_, local_target_vel_, (have_new_target_ || flag_use_poly_init), flag_randomPolyTraj);

   reboundReplan函数的具体程序在planner_manager.cpp文件中

bool EGOPlannerManager::reboundReplan(Eigen::Vector3d start_pt, Eigen::Vector3d start_vel,
                                        Eigen::Vector3d start_acc, Eigen::Vector3d local_target_pt,
                                        Eigen::Vector3d local_target_vel, bool flag_polyInit, bool flag_randomPolyTraj)

   接下来,我们就顺着reboundReplan函数来看一下Ego Planner算法的流程

   首先是不考虑障碍物的情况下,生成从起点到目标点的光滑轨迹,如果之前已经有轨迹了,则初始轨迹就基于之前的轨迹来生成,这样保证在重规划时不会出现一会儿让无人机往左飞,一会儿让无人机往右飞,在几条轨迹间来回横跳的现象。

  double ts = (start_pt - local_target_pt).norm() > 0.1 ? pp_.ctrl_pt_dist / pp_.max_vel_ * 1.2 : pp_.ctrl_pt_dist / pp_.max_vel_ * 5; // pp_.ctrl_pt_dist / pp_.max_vel_ is too tense, and will surely exceed the acc/vel limits
    vector<Eigen::Vector3d> point_set, start_end_derivatives;
    static bool flag_first_call = true, flag_force_polynomial = false;
    bool flag_regenerate = false;
    do
    {
      point_set.clear();
      start_end_derivatives.clear();
      flag_regenerate = false;

      if (flag_first_call || flag_polyInit || flag_force_polynomial /*|| ( start_pt - local_target_pt ).norm() < 1.0*/) // Initial path generated from a min-snap traj by order.
      {

		 ......

       }
       else // Initial path generated from previous trajectory.
      {
      
		 ......

      }
     } while (flag_regenerate);

   上面生成的轨迹并不是B样条轨迹,接下来需要将其变成B样条轨迹(通过求解最小二乘问题获得B样条的控制带你来进行转换),该部分内容是通过调用parameterizeToBspline函数完成的,该函数的具体程序位于uniform_bspline.cpp文件中,该函数的输入参数中point_set是轨迹上的点,start_end_derivatives是起点和终点的高阶约束

UniformBspline::parameterizeToBspline(ts, point_set, start_end_derivatives, ctrl_pts);
  void UniformBspline::parameterizeToBspline(const double &ts, const vector<Eigen::Vector3d> &point_set,
                                             const vector<Eigen::Vector3d> &start_end_derivative,
                                             Eigen::MatrixXd &ctrl_pts)

   转换完成后,我们就得到了后端优化的初值,然后执行轨迹优化,该部分内容是通过调用BsplineOptimizeTrajRebound函数来完成的,该函数的具体程序在bspline_optimizer.cpp中

 /*** STEP 2: OPTIMIZE ***/
    bool flag_step_1_success = bspline_optimizer_rebound_->BsplineOptimizeTrajRebound(ctrl_pts, ts);
  bool BsplineOptimizer::BsplineOptimizeTrajRebound(Eigen::MatrixXd &optimal_points, double ts)

   优化结束后,开始执行时间重分配,该部分内容通过调用refineTrajAlgo函数来完成,该函数的具体程序位于planner_manager.cpp文件中

flag_step_2_success = refineTrajAlgo(pos, start_end_derivatives, ratio, ts, optimal_control_points);
bool EGOPlannerManager::refineTrajAlgo(UniformBspline &traj, vector<Eigen::Vector3d> &start_end_derivative, double ratio, double &ts, Eigen::MatrixXd &optimal_control_points)

   时间重分配流程结束后,就得到了最终的轨迹,将其发布出去


   3、重要函数的展开介绍(BsplineOptimizeTrajRebound和refineTrajAlgo)

   我们先来看,完成轨迹优化的BsplineOptimizeTrajRebound函数

   该函数的具体程序在bspline_optimizer.cpp中,首先将时间间隔ts传给B样条曲线,然后调用rebound_optimize函数执行轨迹优化

  bool BsplineOptimizer::BsplineOptimizeTrajRebound(Eigen::MatrixXd &optimal_points, double ts)
  {
    setBsplineInterval(ts);

    bool flag_success = rebound_optimize();

    optimal_points = cps_.points;

    return flag_success;
  }

   在rebound_optimize函数中,调用了LBFGS优化器进行优化

 int result = lbfgs::lbfgs_optimize(variable_num_, q, &final_cost, BsplineOptimizer::costFunctionRebound, NULL, BsplineOptimizer::earlyExit, this, &lbfgs_params);

   其核心是目标函数costFunctionRebound如何编写

  double BsplineOptimizer::costFunctionRebound(void *func_data, const double *x, double *grad, const int n)
  {
    BsplineOptimizer *opt = reinterpret_cast<BsplineOptimizer *>(func_data);

    double cost;
    opt->combineCostRebound(x, grad, cost, n);

    opt->iter_num_ += 1;
    return cost;
  }

   目标函数的具体表达式在costFunctionRebound函数调用的combineCostRebound函数中,该函数中又调用了calcSmoothnessCost、calcDistanceCostRebound、calcFeasibilityCost这三个函数,分别对应着目标函数中的平滑项、避障约束项、动力学约束项,其中calcDistanceCostRebound在计算避障约束项之前会先检测是否碰撞了障碍物,若是则会停止优化

 void BsplineOptimizer::combineCostRebound(const double *x, double *grad, double &f_combine, const int n)
  {

    memcpy(cps_.points.data() + 3 * order_, x, n * sizeof(x[0]));

    /* ---------- evaluate cost and gradient ---------- */
    double f_smoothness, f_distance, f_feasibility;

    Eigen::MatrixXd g_smoothness = Eigen::MatrixXd::Zero(3, cps_.size);
    Eigen::MatrixXd g_distance = Eigen::MatrixXd::Zero(3, cps_.size);
    Eigen::MatrixXd g_feasibility = Eigen::MatrixXd::Zero(3, cps_.size);

    calcSmoothnessCost(cps_.points, f_smoothness, g_smoothness);
    calcDistanceCostRebound(cps_.points, f_distance, g_distance, iter_num_, f_smoothness);
    calcFeasibilityCost(cps_.points, f_feasibility, g_feasibility);

    f_combine = lambda1_ * f_smoothness + new_lambda2_ * f_distance + lambda3_ * f_feasibility;
    //printf("origin %f %f %f %f\n", f_smoothness, f_distance, f_feasibility, f_combine);

    Eigen::MatrixXd grad_3D = lambda1_ * g_smoothness + new_lambda2_ * g_distance + lambda3_ * g_feasibility;
    memcpy(grad, grad_3D.data() + 3 * order_, n * sizeof(grad[0]));
  }

   我们再来看一下,用于时间重分配的refineTrajAlgo函数

   首先要计算出新的合适的时间间隔,该部分内容在reparamBspline函数中完成,然后基于新的时间间隔计算新轨迹的初值,该部分内容主要在UniformBspline函数中完成,然后,再对新的轨迹进行优化,该部分内容在BsplineOptimizeTrajRefine中完成

bool EGOPlannerManager::refineTrajAlgo(UniformBspline &traj, vector<Eigen::Vector3d> &start_end_derivative, double ratio, double &ts, Eigen::MatrixXd &optimal_control_points)
  {
    double t_inc;

    Eigen::MatrixXd ctrl_pts; // = traj.getControlPoint()

    // std::cout << "ratio: " << ratio << std::endl;
    reparamBspline(traj, start_end_derivative, ratio, ctrl_pts, ts, t_inc);

    traj = UniformBspline(ctrl_pts, 3, ts);

    double t_step = traj.getTimeSum() / (ctrl_pts.cols() - 3);
    bspline_optimizer_rebound_->ref_pts_.clear();
    for (double t = 0; t < traj.getTimeSum() + 1e-4; t += t_step)
      bspline_optimizer_rebound_->ref_pts_.push_back(traj.evaluateDeBoorT(t));

    bool success = bspline_optimizer_rebound_->BsplineOptimizeTrajRefine(ctrl_pts, ts, optimal_control_points);

    return success;
  }

   在BsplineOptimizeTrajRefine函数中,调用了refine_optimize函数来执行具体的优化过程。

  bool BsplineOptimizer::BsplineOptimizeTrajRefine(const Eigen::MatrixXd &init_points, const double ts, Eigen::MatrixXd &optimal_points)
  {

    setControlPoints(init_points);
    setBsplineInterval(ts);

    bool flag_success = refine_optimize();

    optimal_points = cps_.points;

    return flag_success;
  }

   refine_optimize函数中同样是调用了LBFGS求解器来就行优化过程,其核心依然是目标函数costFunctionRefine的给定

  bool BsplineOptimizer::refine_optimize()
int result = lbfgs::lbfgs_optimize(variable_num_, q, &final_cost, BsplineOptimizer::costFunctionRefine, NULL, NULL, this, &lbfgs_params);

   costFunctionRefine函数中调用了combineCostRefine函数来计算目标函数值

  double BsplineOptimizer::costFunctionRefine(void *func_data, const double *x, double *grad, const int n)
  {
    BsplineOptimizer *opt = reinterpret_cast<BsplineOptimizer *>(func_data);

    double cost;
    opt->combineCostRefine(x, grad, cost, n);

    opt->iter_num_ += 1;
    return cost;
  }

   combineCostRefine函数中又调用了calcSmoothnessCost函数、calcFitnessCost函数、calcFeasibilityCost函数来分别计算平滑项、曲线拟合项、动力学约束项

  void BsplineOptimizer::combineCostRefine(const double *x, double *grad, double &f_combine, const int n)
  {

    memcpy(cps_.points.data() + 3 * order_, x, n * sizeof(x[0]));

    /* ---------- evaluate cost and gradient ---------- */
    double f_smoothness, f_fitness, f_feasibility;

    Eigen::MatrixXd g_smoothness = Eigen::MatrixXd::Zero(3, cps_.points.cols());
    Eigen::MatrixXd g_fitness = Eigen::MatrixXd::Zero(3, cps_.points.cols());
    Eigen::MatrixXd g_feasibility = Eigen::MatrixXd::Zero(3, cps_.points.cols());

    //time_satrt = ros::Time::now();

    calcSmoothnessCost(cps_.points, f_smoothness, g_smoothness);
    calcFitnessCost(cps_.points, f_fitness, g_fitness);
    calcFeasibilityCost(cps_.points, f_feasibility, g_feasibility);

    /* ---------- convert to solver format...---------- */
    f_combine = lambda1_ * f_smoothness + lambda4_ * f_fitness + lambda3_ * f_feasibility;
    // printf("origin %f %f %f %f\n", f_smoothness, f_fitness, f_feasibility, f_combine);

    Eigen::MatrixXd grad_3D = lambda1_ * g_smoothness + lambda4_ * g_fitness + lambda3_ * g_feasibility;
    memcpy(grad, grad_3D.data() + 3 * order_, n * sizeof(grad[0]));
  }

   参考资料:

   1、深蓝学院-移动机器人运动规划


  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Ego-Planner是一个基于ROS路径规划器,它可以在给定的地图和起点、终点信息下,生成一条可行的路径。以下是Ego-Planner的代码框架: 1. 首先需要定义一个EgoPlanner类,其中包含了一些必要的成员变量和函数。 ```c++ class EgoPlanner { private: ros::NodeHandle nh_; ros::Subscriber sub_map_; ros::Subscriber sub_pose_; ros::Subscriber sub_goal_; ros::Publisher pub_path_; nav_msgs::OccupancyGrid map_; geometry_msgs::PoseStamped start_; geometry_msgs::PoseStamped goal_; public: EgoPlanner(); // 构造函数 ~EgoPlanner(); // 析构函数 void mapCallback(const nav_msgs::OccupancyGrid::ConstPtr& msg); // 地图回调函数 void poseCallback(const geometry_msgs::PoseStamped::ConstPtr& msg); // 当前位置回调函数 void goalCallback(const geometry_msgs::PoseStamped::ConstPtr& msg); // 目标位置回调函数 void plan(); // 路径规划函数 }; ``` 2. 在构造函数中,需要完成ROS节点的初始化、订阅和发布话题的设置。 ```c++ EgoPlanner::EgoPlanner() { nh_ = ros::NodeHandle("~"); sub_map_ = nh_.subscribe("map", 1, &EgoPlanner::mapCallback, this); sub_pose_ = nh_.subscribe("pose", 1, &EgoPlanner::poseCallback, this); sub_goal_ = nh_.subscribe("goal", 1, &EgoPlanner::goalCallback, this); pub_path_ = nh_.advertise<nav_msgs::Path>("path", 1); } ``` 3. 在地图、当前位置和目标位置的回调函数中,需要将接收到的信息保存到对应的成员变量中。 ```c++ void EgoPlanner::mapCallback(const nav_msgs::OccupancyGrid::ConstPtr& msg) { map_ = *msg; } void EgoPlanner::poseCallback(const geometry_msgs::PoseStamped::ConstPtr& msg) { start_ = *msg; } void EgoPlanner::goalCallback(const geometry_msgs::PoseStamped::ConstPtr& msg) { goal_ = *msg; } ``` 4. 在路径规划函数中,需要调用路径规划算法,生成一条可行的路径,并将路径发布出去。 ```c++ void EgoPlanner::plan() { // 调用路径规划算法,生成一条可行的路径 std::vector<geometry_msgs::PoseStamped> path = pathPlanning(map_, start_, goal_); // 将路径发布出去 nav_msgs::Path path_msg; path_msg.header.frame_id = "map"; path_msg.header.stamp = ros::Time::now(); path_msg.poses = path; pub_path_.publish(path_msg); } ``` 5. 在主函数中,创建EgoPlanner对象,并进入ROS循环。 ```c++ int main(int argc, char** argv) { ros::init(argc, argv, "ego_planner"); EgoPlanner planner; ros::Rate rate(10); while (ros::ok()) { planner.plan(); ros::spinOnce(); rate.sleep(); } return 0; } ``` 以上就是Ego-Planner的代码框架,其中路径规划算法需要根据具体情况进行选择和实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

慕羽★

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

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

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

打赏作者

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

抵扣说明:

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

余额充值