Apollo Planning(一)

规划(planning)模块的作用是根据感知预测的结果,当前的车辆信息和路况规划出一条车辆能够行驶的轨迹,这个轨迹会交给控制(control)模块,控制模块通过油门,刹车和方向盘使得车辆按照规划的轨迹运行。

规划模块的轨迹是短期轨迹,即车辆短期内行驶的轨迹,长期的轨迹是routing模块规划出的导航轨迹,即起点到目的地的轨迹,规划模块会先生成导航轨迹,然后根据导航轨迹和路况的情况,沿着短期轨迹行驶,直到目的地。这点也很好理解,我们开车之前先打开导航,然后根据导航行驶,如果前面有车就会减速或者变道,超车,避让行人等,这就是短期轨迹,结合上述方式直到行驶到目的地。

一、Planning输入输出

我们先看下Apollo的数据流向:

Apollo Planning(一)_第1张图片

可以看到规划(planning)模块的上游是Localization, Prediction, Routing模块,而下游是Control模块。Routing模块先规划出一条导航线路,然后Planning模块根据这条线路做局部优化,如果Planning模块发现短期规划的线路行不通(比如前面修路,或者错过了路口),会触发Routing模块重新规划线路,因此这两个模块的数据流是双向的。

Planning模块的输入在"planning_component.h"中,接口如下:

输入参数为:

  1. 预测的障碍物信息(prediction_obstacles)
  2. 车辆底盘(chassis)信息(车辆的速度,加速度,航向角等信息)
  3. 车辆当前位置(localization_estimate)

实际上还有高精度地图信息,不在参数中传入,而是在函数中直接读取的。

Planning模块的输出结果在"PlanningComponent::Proc()"中,为规划好的线路,发布到Control模块订阅的Topic中。

输出结果为:规划好的路径。

planning_writer_->Write(std::make_shared(adc_trajectory_pb));

二、Planning整个流程

下图是整个Planning模块的执行过程:

Apollo Planning(一)_第2张图片

  1. 模块的入口是PlanningComponent,在Cyber中注册模块,订阅和发布消息,并且注册对应的Planning类(NaviPlanning / OnLanePlanning)。
  2. Planning的过程为事件触发,即只要收集完成对应TOPIC的消息,就会触发执行,保证实时性。
  3. Planning类主要实现了2个功能,一个是启动ReferenceLineProvider来提供参考线,后面生成的轨迹都是在参考线的基础上做优化。Planning类另外的一个功能是执行Planning主流程。
  4. Planning主流程先是选择对应的Planner,此处用到工厂模式planner_factory_.CreateObject根据配置信息创建Planner(LatticePlanner、NaviPlanner、PublicRoadPlanner、RTKReplayPlanner),这里主要分析PublicRoadPlanner。在配置文件中定义了Planner支持的场景(Scenario),把规划分为具体的几个场景来执行,每个场景又分为几个阶段(Stage),每个阶段会执行多个任务(Task),任务执行完成后,对应的场景就完成了。不同场景间的切换是由一个状态机(ScenarioDispatch)来控制的。规划控制器根据ReferenceLineProvider提供的参考线,在不同的场景下做切换,生成一条车辆可以行驶的轨迹,并且不断重复上述过程直到到达目的地。

接下来我们逐步分析整个planning模块的代码结构。

三、Planning模块入口

3.1 模块注册

Planning模块的入口为"planning_component.h"和"planning_component.cc"两个文件,实现的功能如下:

  // 订阅和发布消息
  std::shared_ptr> traffic_light_reader_;
  std::shared_ptr> routing_reader_;
  std::shared_ptr> pad_msg_reader_;
  std::shared_ptr> relative_map_reader_;
  std::shared_ptr> story_telling_reader_;

  std::shared_ptr> planning_writer_;
  std::shared_ptr> rerouting_writer_;
  std::shared_ptr> planning_learning_data_writer_; 

  // 在Cyber中注册模块
  CYBER_REGISTER_COMPONENT(PlanningComponent)

3.2 模块初始化

除了注册模块,订阅和发布消息之外,planning_component实现了2个主要函数"init"和"proc"。

Init中实现了模块的初始化:

if (FLAGS_use_navigation_mode) {
    planning_base_ = std::make_unique(injector_);
} else {
    planning_base_ = std::make_unique(injector_);
}

上面实现了2种Planning的注册,planning模块根据配置选择不同的Planning实现方式, "FLAGS_use_navigation_mode"在Planning模块的conf目录中。在global_flagfile.txt中use_navigation_mode=false,Planning默认情况下的实现是"OnLanePlanning"。下面介绍下这2种Planning的区别。

NaviPlanning - 相对地图规划器;

OnLanePlanning - 主要的应用场景是开放道路的自动驾驶。

"NaviPlanning"和"OnLanePlanning"都继承自同一个基类,并且在PlanningComponent中通过配置选择一个具体的实现进行注册。

PlanningComponent::Init()接下来实现了具体的消息发布和消息订阅,我们只看具体的一个例子:

// 读取routing模块的消息
routing_reader_ = node_->CreateReader(
      config_.topic_config().routing_response_topic(),
      [this](const std::shared_ptr& routing) {
        AINFO << "Received routing data: run routing callback."
              << routing->header().DebugString();
        std::lock_guard lock(mutex_);
        routing_.CopyFrom(*routing);
      });
// 读取红绿灯
  traffic_light_reader_ = ...
// 读取驾驶行为
  pad_msg_reader_ = ...
// 
	story_telling_reader_ =
// 是否使用导航模式
if (FLAGS_use_navigation_mode) {
//  读取相对地图
    relative_map_reader_ = node_->CreateReader(
        config_.topic_config().relative_map_topic(),
        [this](const std::shared_ptr& map_message) {
          ADEBUG << "Received relative map data: run relative map callback.";
          std::lock_guard lock(mutex_);
          relative_map_.CopyFrom(*map_message);
        });
  }// 发布规划好的线路
  planning_writer_ =   node_->CreateWriter(FLAGS_planning_trajectory_topic);
// 发布重新规划请求
  rerouting_writer_ =      node_->CreateWriter(FLAGS_routing_request_topic);
planning_learning_data_writer_ = node_->CreateWriter(config_.topic_config().planning_learning_data_topic());

至此,Planning模块的初始化就完成了。

3.3 模块运行

Proc的主要是检查数据,并且执行注册好的Planning,生成路线并且发布。

bool PlanningComponent::Proc(...) {
  // 1. 检查是否需要重新规划线路。
  CheckRerouting();

  // 2. 数据放入local_view_中,并且检查输入数据。
local_view_.prediction_obstacles = prediction_obstacles;
  ...
  // 3. 执行注册好的Planning,生成线路。
  planning_base_->RunOnce(local_view_, &adc_trajectory_pb);

  // 4. 发布消息
planning_writer_->Write(adc_trajectory_pb);
}

整个"PlanningComponent"的分析就完成了,可以看到"PlanningComponent"是Planning模块的入口,在Apollo3.5引入了Cyber之后,实现了Planning模块在Cyber中的注册,订阅和发布topic消息。同时实现了3种不同的Planning,根据配置选择其中的一种并且运行。

由于默认的Planning是开放道路的OnLanePlanning,我们接下来主要分析这个Planning。

参考文献

apollo介绍之planning模块(四) - 知乎 王方浩,apollo介绍之planning模块(四)

Baidu Apollo代码解析之Open Space Planner中的Hybrid A* - 知乎 Baidu Apollo代码解析之Open Space Planner中的Hybrid A*

你可能感兴趣的:(Apollo,自动驾驶,人工智能,机器学习)