Apollo源码分析系列的第六部分planning

Posted slam庖丁解牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Apollo源码分析系列的第六部分planning相关的知识,希望对你有一定的参考价值。

planning模块 代码大概有1000行左右。 这个模块仍然处于待完善的状态。 

和其他模块一样,这里只有框架,除了 RTKReplayPlanner,没有实用的算法。

planning模块的输入:

  • Localization

  • Perception (future work)

  • Prediction (future work)

  • Decision (future work)

  • Recorded RTK trajectory

planning模块的输出:

  • trajectory point


planning/main.cc

照例先看看main.cc

 
   
   
 
  1. int main(int argc, char **argv) {

  2.  google::InitGoogleLogging(argv[0]);

  3.  google::ParseCommandLineFlags(&argc, &argv, true);

  4.  ros::init(argc, argv, "planning");

  5.  ::apollo::planning::PlanningNode planning_node;

  6.  planning_node.Run();

  7.  return 0;

  8. }

  9. //与#define APOLLO_MAIN(APP) 大同小异。可能后期会改为一致。

planning/common/planning_gflags.h

.h文件和.cc文件,声明和定义planning模块的变量。 
主要的变量是:

 
   
   
 
  1. #include "modules/planning/common/planning_gflags.h"

  2. //发布运动规划planning的频率,单位是HZ。

  3. DEFINE_int32(planning_loop_rate, 5, "Loop rate for planning node");

  4. //用RTK定位算法得到的最近一段时间的历史轨迹

  5. DEFINE_string(rtk_trajectory_filename, "modules/planning/data/garage.csv",

  6.              "Loop rate for planning node");

  7. //planning时,用于回退匹配的轨迹节点数量。

  8. DEFINE_uint64(rtk_trajectory_backward, 10,

  9.              "The number of points to be included in RTK trajectory "

  10.              "before the matched point");

  11. //前向匹配的轨迹节点数量

  12. DEFINE_uint64(rtk_trajectory_forward, 800,

  13.              "The number of points to be included in RTK trajectory "

  14.              "after the matched point");

  15. //重新规划planning的阈值,高于此值则重新planning

  16. DEFINE_double(replanning_threshold, 2.0,

  17.              "The threshold of position deviation "

  18.              "that triggers the planner replanning");

  19. //轨迹点之间的时间分辨率/间隔,0.01s

  20. DEFINE_double(trajectory_resolution, 0.01,

  21.              "The time resolution of "

  22.              "output trajectory.");

planning/planner/planner.h

Planner是纯虚基类。有一个公有的Plan(): 
纯虚函数。继承它的子类必须重写这个纯虚函数才能实例化。 
所有的路径规划的class都继承自这个类。 
目的:虚函数指针,运行时决断,父类指针指向子类对象,runtime时调用子类的成员函数。

具体到Planner 而言就是提供一个公有的接口 virtual bool Plan(). 
使得在运行时可以执行不同的planning算法。

 
   
   
 
  1. 参数1:planning起始点

  2. 参数2:路径规划结果集,由一系列离散的轨迹点组成。

  3. 目的:根据历史行驶的一系列轨迹节点,

  4. 并结合Perception模块+Prediction模块+Decision模块+

  5. Localization地图定位模块,来进行推算未来一段时间的行驶轨迹。

  6. 本质上是路径规划算法。

  7.  virtual bool Plan(

  8.      const apollo::common::TrajectoryPoint &start_point,

  9.      std::vector<apollo::common::TrajectoryPoint> *discretized_trajectory) = 0;

planning/planner/rtk_replay_planner.h

RTKReplayPlanner继承自虚基类Planner。 
这个类通过从历史轨迹的file文件中读取轨迹点, 
并且从这些历史轨迹点中选择连续的800个point组成历史轨迹的结果集合, 
输出到ptr_trajectory中。

目的:从历史信息中提取与start_point相关联的轨迹point集合, 
为下一个具体的planner类进行路径规划做数据准备。

 
   
   
 
  1. // 构造函数直接读取历史轨迹节点文件file中的内容.并存储到complete_rtk_trajectory_数据成员中

  2. RTKReplayPlanner();

 
   
   
 
  1. /*

  2. 参数1:planning起始点

  3. 参数2:路径规划的结果集,800个连续的point存储在ptr_trajectory中,由一系列离散的轨迹点组成。

  4. ptr_trajectory存储的是历史轨迹节点而不是planning好的未来的轨迹节点。

  5. 故本class 命名为ReplayPlanner.

  6. 算法的大概步骤:

  7. 【1】找到与起点start_point最近的轨迹中的点的index。即在历史数据里找到一个点,然后把它看做start_point的近似。

  8. 【2】在match至轨迹的end之间找到一个end_index,使得end_index不越界,同时又尽可能的长。

  9. 【3】将区间[first,last)的元素赋值到在【ptr_discretized】vector容器中

  10. 【4】修改在ptr_discretized_trajectory轨迹集合中的相对时间。以第一个point为0起始点。

  11. 【5】如果point没有800个点,则直接用最后的一个点填充直到满足800个点。

  12. */

  13. bool Plan(const apollo::common::TrajectoryPoint &start_point,

  14. std::vector<apollo::common::TrajectoryPoint> *ptr_trajectory) override;

 
   
   
 
  1. ////打开轨迹文件,并将轨迹point存储到complete_rtk_trajectory_成员函数中。

  2. void ReadTrajectoryFile(const std::string &filename);

file内容举例:

csv格式每行的header信息: 
x,y,z,speed,acceleration,curvature,curvature_change_rate,time,theta,gear,s,throttle,brake,steering

具体的值: 
586385.858607, 4140674.7357, -28.3670201628, 0.216666668653, 0.887545286694, 0.0227670812611, -0.0177396744278, 1496957374.6140, 2.83470068837, 1, 0.00216666668653, 22.0157165527, 13.6934461594, 12.6382980347

 
   
   
 
  1. /*

  2. 参数1:planning起始点

  3. 参数2:历史轨迹

  4. 返回值:距离start_point的以L2距离度量的最近邻轨迹点。

  5. 步骤:

  6. 【1】求轨迹的点与start_point的距离的平方距离的最小值

  7. 【2】遍历轨迹点,找到相对于起点的最短距离点。

  8. 【3】返回最短距离点的index

  9. */

  10.  std::size_t QueryPositionMatchedPoint(

  11.      const apollo::common::TrajectoryPoint &start_point,

  12.      const std::vector<apollo::common::TrajectoryPoint> &trajectory) const;

测试代码:

 
   
   
 
  1. TEST_F(RTKReplayPlannerTest, ComputeTrajectory) {

  2.  FLAGS_rtk_trajectory_filename = "modules/planning/testdata/garage.csv";

  3.  RTKReplayPlanner planner;

  4.  TrajectoryPoint start_point;

  5.  start_point.set_x(586385.782842);

  6.  start_point.set_y(4140674.76063);

  7.  std::vector<TrajectoryPoint> trajectory;

  8.  bool planning_succeeded = planner.Plan(start_point, &trajectory);

  9.  EXPECT_TRUE(planning_succeeded);

  10.  EXPECT_TRUE(!trajectory.empty());

  11.  //800个点。不够时用最后一个点填充。

  12.  EXPECT_EQ(trajectory.size(), (std::size_t)FLAGS_rtk_trajectory_forward);

  13.  auto first_point = trajectory.front();

  14.  EXPECT_DOUBLE_EQ(first_point.x(), 586385.782841);//第一个匹配点,.csv第20个

  15.  EXPECT_DOUBLE_EQ(first_point.y(), 4140674.76065);

  16.  auto last_point = trajectory.back();

  17.  EXPECT_DOUBLE_EQ(last_point.x(), 586355.063786);//最后一个匹配点,.csv第819个。

  18.  EXPECT_DOUBLE_EQ(last_point.y(), 4140681.98605);

  19. }

 
   
   
 
  1. TEST_F(RTKReplayPlannerTest, ErrorTest) {

  2.  FLAGS_rtk_trajectory_filename =

  3.      "modules/planning/testdata/garage_no_file.csv";

  4.  RTKReplayPlanner planner;

  5.  FLAGS_rtk_trajectory_filename = "modules/planning/testdata/garage_error.csv";

  6.  RTKReplayPlanner planner_with_error_csv;//file不完整,只有1行数据时,直接返回false

  7.  TrajectoryPoint start_point;

  8.  start_point.set_x(586385.782842);

  9.  start_point.set_y(4140674.76063);

  10.  std::vector<TrajectoryPoint> trajectory;

  11.  EXPECT_TRUE(!planner_with_error_csv.Plan(start_point, &trajectory));

  12. }

planning/planner_factory.h

 
   
   
 
  1. // PlannerType指定可选的路径规划class类型。

  2. // 目前Apollo只支持由RTK组成的plan路径规划方法,其他方法还没有实现。

  3. enum class PlannerType { RTK_PLANNER, OTHER };

 
   
   
 
  1. // 工厂方法模式,没有数据成员,只有一个静态成员函数CreateInstance()

  2. class PlannerFactory {

  3. public:

  4.  PlannerFactory() = delete;

  5. // 参数:路径规划方法。目前只支持PlannerType::RTK_PLANNER类型。

  6.  static std::unique_ptr<Planner> CreateInstance(

  7.      const PlannerType &planner_type);

  8. };

CreateInstance()实现代码:

 
   
   
 
  1. //  参数:planning路径规划的算法类型

  2. //  返回值:planning路径规划实例对象。

  3. std::unique_ptr<Planner> PlannerFactory::CreateInstance(

  4.    const PlannerType &planner_type) {

  5.  switch (planner_type) {

  6.    case PlannerType::RTK_PLANNER:

  7.    // new 一个实例对象。

  8.      return std::unique_ptr<Planner>(new RTKReplayPlanner());

  9.    //这里可见其他类型的规划方法为空指针实现。

  10.    default:

  11.      return nullptr;

  12.  }

  13. }

planning/planning.h

class Planning 是上层的路径规划方法封装。 
数据成员:

 
   
   
 
  1. std::unique_ptr<Planner> ptr_planner_;//由工厂方法模式创建的路径规划实例。

  2. std::vector<common::TrajectoryPoint> last_trajectory_; //最近一段时间planning的轨迹point集合

  3. double last_header_time_ = 0.0;//最近planning时间

私有成员函数:

 
   
   
 
  1. /*

  2. 计算从上次【预测的】轨迹记录历史得到的起始点的车辆自身point位置

  3. 参数:当前时间

  4. 返回值:轨迹point+索引

  5. 算法步骤:

  6. 【1】二分查找,找到【start_time】小于【轨迹point的time+last_header】的第一个值。

  7. 【2】返回point+index

  8. */

  9. std::pair<common::TrajectoryPoint, std::size_t>

  10.  ComputeStartingPointFromLastTrajectory(const double curr_time) const;

 
   
   
 
  1. /*计算车辆启动时的地理位置所在的轨迹point。

  2. 参数1:当前车辆状态(车辆速度,加速度,行驶方向等)

  3. 参数2:planning的预测时间段。

  4. 算法步骤:

  5. 【1】获取当前车辆的位置x,y,z

  6. 【2】获取当前车辆的速度,加速度信息

  7. 【3】设置point曲率

  8. 【4】设置转弯半径

  9. 【5】返回当前轨迹point

  10. */

  11. common::TrajectoryPoint ComputeStartingPointFromVehicleState(

  12.      const common::vehicle_state::VehicleState &vehicle_state,

  13.      const double forward_time) const;

 
   
   
 
  1. /*

  2. 参数1:与起始点match的轨迹点

  3. 参数2:需要回退的size,正常情况下回退10个轨迹point

  4. 返回值:在match之前的10个轨迹point

  5.  */

  6. std::vector<common::TrajectoryPoint> GetOverheadTrajectory(

  7.      const std::size_t matched_index, const std::size_t buffer_size);

公有成员函数:

 
   
   
 
  1. /*

  2.   从历史信息+目前的车辆状态(车辆速度,加速度,行驶方向等)进行路径规划,

  3.   结果存储在discretized_trajectory中。

  4.   参数1:目前车辆状态信息

  5.   参数2:是否自动驾驶模式

  6.   参数3:路径规划发布时间

  7.   参数4:路径规划的轨迹point结果集合

  8. */

  9.  bool Plan(const common::vehicle_state::VehicleState &vehicle_state,

  10.            const bool is_on_auto_mode, const double publish_time,

  11.            std::vector<common::TrajectoryPoint> *discretized_trajectory);

  12. /*

  13. 算法步骤:

  14. 如果是自动驾驶模式,并且【预测】历史轨迹不为空(已有历史预测信息)

  15. 那么:

  16. 【1】从【预测的】历史轨迹中寻找与当前时间匹配的point

  17. 【2】计算匹配点与当前点的距离的平方差

  18. 【3】若小于阈值(2.0m),则不需要重新进行路径规划,直接从匹配点开始路径规划.

  19. 【4】将last_traj修改为*planning_traj以用于下一次路径规划

  20. 若非自动驾驶模式/没有历史轨迹点/匹配点距离太远:

  21. 【6】从当前车辆状态获取point位置,然后再从当前位置进行路径规划。

  22. 【7】保存预测轨迹,然后用于下一次预测

  23.  */

 
   
   
 
  1. // 重置为初始状态。清空轨迹point的信息。

  2. void Reset();

看到这里读者肯定以为planning模块的路径规划算法已经完成了。有这种看法非常不错,但是我认为他读懂了这个代码的50%。

实际情况是在 Planning类的Plan()成员函数中有这么一句话(而且是2次出现):

 
   
   
 
  1.    bool planning_succeeded =

  2.          ptr_planner_->Plan(matched_point, planning_trajectory);

  3.      if (!planning_succeeded) {

  4.        last_trajectory_.clear();

  5.        return false;

  6.      }

  7. 以上代码的含义对不熟悉c++的人来说是有一点困难的。

  8. 理解这段代码需要了解3个知识:

  9. 1】面向对象设计

  10. 2】继承,封装,多态

  11. 3】设计模式

planning/planning_node.h

 
   
   
 
  1. PlanningNodeApollo planning模块与ROS通信的Node封装。

  2. class PlanningNode {

  3. public:

  4.  /*  构造函数,初始化配置文件*/

  5.  PlanningNode();

  6.  /*  析构函数,什么也不做*/

  7.  virtual ~PlanningNode();

  8.  /* 在ROS中按照设置的频率(默认5hz),定时发布路径规划结果。多次调用RunOnce() 。  */

  9.  void Run();

  10.  /*复位,清空,调用Planning类的Reset()成员函数*/

  11.  void Reset();

  12. private:

  13. //进行一次planning路径规划算法

  14.  void RunOnce();

  15. //序列化轨迹轨迹point到文件

  16.  ADCTrajectory ToTrajectoryPb(

  17.      const double header_time,

  18.      const std::vector<common::TrajectoryPoint> &discretized_trajectory);

  19. //进行路径规划的实例对象      

  20.  Planning planning_;


总结

planning模块分析就到这里了。废话不多说了。 
回顾一下:

planning模块的输入:

  • Localization

  • Perception (future work)

  • Prediction (future work)

  • Decision (future work)

  • Recorded RTK trajectory

planning模块的输出:

  • trajectory point


一句话:

目前的planning模块除了能处理由 RTK记录的历史轨迹point 数据,啥也没干不了。【只能回顾过去】,【不能规划未来】。

而基于【过去和现在】并【规划未来】才是planning模块的应有之意。


另,如果你能为知识付费,我会很高兴 ^-^.


以上是关于Apollo源码分析系列的第六部分planning的主要内容,如果未能解决你的问题,请参考以下文章

自动驾驶 Apollo 源码分析系列,系统监控篇:Monitor模块如何监控硬件

自动驾驶 Apollo 源码分析系列,系统监控篇:Monitor模块如何监控硬件

自动驾驶 Apollo 源码分析系列,系统监控篇:Monitor模块如何监控硬件

开发者说| Apollo 源码分析系列感知篇:红绿灯检测和识别

自动驾驶 Apollo 源码分析系列,感知篇:红绿灯检测和识别

自动驾驶 Apollo 源码分析系列,感知篇:将红绿灯检测和识别代码细致走读一遍