阿波罗项目(https://github.com/ApolloAuto/apollo)规划(Planning)模块位于命名空间:apollo::planning,其作用在于构建无人车从起点到终的局部行驶路径,具体而言,就是给定导航地图、导航路径、当前定位点、车辆状态(包括:位置、速度、加速度、底盘)、 周边目标的感知及预测信息(如交通标志和障碍物等),规划模块计算出可供控制模块(Controller)执行的一条安全且舒适的行驶路径。注意,规划模块输出的路径是局部路径而非全局路径。举个简单示例加以说明,假如无人车需从长沙智能驾驶研究院行驶至长沙高铁南站,首先需借助Routing(路由寻径)模块输出全局导航路径,接下来才是规划模块基于全局导航路径进行一小段、一小段具体行驶路径的规划。规划模块内部结构及其与其他模块的交互示意如下图所示。
规划模块内部的主要类成员如下图所示:
该模块的主要执行流程如下图所示:
该模块的主入口为:modules/planning/main.cc
:
APOLLO_MAIN(apollo::planning::Planning)
该宏展开后为:
int main(int argc, char **argv) {
google::InitGoogleLogging(argv[0]);
google::ParseCommandLineFlags(&argc, &argv, true);
signal(SIGINT, apollo::common::apollo_app_sigint_handler);
apollo::planning::Planning apollo_app_;
ros::init(argc, argv, apollo_app_.Name());
apollo_app_.Spin();
return 0;
}
Main函数完成以下工作:始化Google日志工具,使用Google命令行解析工具解析相关参数,注册接收中止信号“SIGINT”的处理函数:apollo::common::apollo_app_sigint_handler
(该函数的功能十分简单,就是收到中止信号“SIGINT”后,调用ros::shutdown()
关闭ROS),创建apollo::planning::Planning对象:apollo_app_
,初始化ROS环境,调用apollo_app_.Spin()
函数开始消息处理循环。
int ApolloApp::Spin() {
ros::AsyncSpinner spinner(callback_thread_num_);
auto status = Init();
if (!status.ok()) {
AERROR << Name() << " Init failed: " << status;
ReportModuleStatus(apollo::hmi::ModuleStatus::UNINITIALIZED);
return -1;
}
ReportModuleStatus(apollo::hmi::ModuleStatus::INITIALIZED);
status = Start();
if (!status.ok()) {
AERROR << Name() << " Start failed: " << status;
ReportModuleStatus(apollo::hmi::ModuleStatus::STOPPED);
return -2;
}
ExportFlags();
ReportModuleStatus(apollo::hmi::ModuleStatus::STARTED);
spinner.start();
ros::waitForShutdown();
Stop();
ReportModuleStatus(apollo::hmi::ModuleStatus::STOPPED);
AINFO << Name() << " exited.";
return 0;
}
apollo::planning::Planning类的继承关系见下图:
基本时序如下图所示:
现在来看ApolloApp::Spin()函数内部,首先创建ros::AsyncSpinner对象spinner,监控用户操作。之后调用虚函数:Init()(实际调用apollo::planning::Planning::Init()
)执行初始化。
Status Planning::Init() {
hdmap_ = apollo::hdmap::HDMapUtil::BaseMapPtr();
CHECK(hdmap_) << "Failed to load map file:" << apollo::hdmap::BaseMapFile();
CHECK(apollo::common::util::GetProtoFromFile(FLAGS_planning_config_file,
&config_))
<< "failed to load planning config file " << FLAGS_planning_config_file;
if (!AdapterManager::Initialized()) {
AdapterManager::Init(FLAGS_planning_adapter_config_filename);
}
if (AdapterManager::GetLocalization() == nullptr) {
std::string error_msg("Localization is not registered");
AERROR << error_msg;
return Status(ErrorCode::PLANNING_ERROR, error_msg);
}
if (AdapterManager::GetChassis() == nullptr) {
std::string error_msg("Chassis is not registered");
AERROR << error_msg;
return Status(ErrorCode::PLANNING_ERROR, error_msg);
}
if (AdapterManager::GetRoutingResponse() == nullptr) {
std::string error_msg("RoutingResponse is not registered");
AERROR << error_msg;
return Status(ErrorCode::PLANNING_ERROR, error_msg);
}
if (AdapterManager::GetRoutingRequest() == nullptr) {
std::string error_msg("RoutingRequest is not registered");
AERROR << error_msg;
return Status(ErrorCode::PLANNING_ERROR, error_msg);
}
if (FLAGS_enable_prediction && AdapterManager::GetPrediction() == nullptr) {
std::string error_msg("Enabled prediction, but no prediction not enabled");
AERROR << error_msg;
return Status(ErrorCode::PLANNING_ERROR, error_msg);
}
if (FLAGS_enable_traffic_light &&
AdapterManager::GetTrafficLightDetection() == nullptr) {
std::string error_msg("Traffic Light Detection is not registered");
AERROR << error_msg;
return Status(ErrorCode::PLANNING_ERROR, error_msg);
}
ReferenceLineProvider::instance()->Init(
hdmap_, config_.qp_spline_reference_line_smoother_config());
RegisterPlanners();
planner_ = planner_factory_.CreateObject(config_.planner_type());
if (!planner_) {
return Status(
ErrorCode::PLANNING_ERROR,
"planning is not initialized with config : " + config_.DebugString());
}
return planner_->Init(config_);
}
在apollo::planning::Planning::Init()
函数内部,首先获取高精地图指针,之后执行适配器管理者(AdapterManager)对象的初始化状态,接着检查AdapterManager里定位适配器(AdapterManager::GetLocalization()
)、底盘适配器(AdapterManager::GetChassis()
)、路由寻径响应适配器(AdapterManager::GetRoutingResponse()
)、路由寻径请求适配器(AdapterManager::GetRoutingRequest()
)的初始化状态,若启用预测(FLAGS_enable_prediction)则继续检查预测适配器(AdapterManager::GetPrediction()
)的初始化状态,若启用交通信号灯(FLAGS_enable_traffic_light)则继续检查交通信号灯适配器(AdapterManager::GetTrafficLightDetection()
)的初始化状态。接下来,执行参考线提供者(所谓参考线就是一条候选路径)的初始化。此后调用Planning::RegisterPlanners()
将RTKReplayPlanner、EMPlanner对象的创建函数注册到计划者工厂类对象planner_factory_
中。另外通过读取配置文件中给定的计划者类型,使用planner_factory_.CreateObject(config_.planner_type())
动态生成所需的计划者对象(查询配置文件得知,实际生成EMPlanner对象)。最后执行计划者对象的初始化:planner_->Init(config_)
。apollo::planning::Planning::Init()
函数内,适配器管理者的初始化过程值得深入探讨:
AdapterManager::Init(FLAGS_planning_adapter_config_filename);
首先研究初始化传入的参数:FLAGS_planning_adapter_config_filename
实际上是利用Google开源库gflags宏:
DEFINE_string(planning_adapter_config_filename,
"modules/planning/conf/adapter.conf",
"The adapter configuration file");
定义出的一个字符串变量,该变量的缺省值为:“modules/planning/conf/adapter.conf”,关于该变量的描述信息为: “The adapter configuration file”。配置文件:modules/planning/conf/adapter.conf
的内容如下:
config {
type: LOCALIZATION
mode: RECEIVE_ONLY
message_history_limit: 1
}
config {
type: CHASSIS
mode: RECEIVE_ONLY
message_history_limit: 1
}
config {
type: ROUTING_RESPONSE
mode: RECEIVE_ONLY
message_history_limit: 1
}
config {
type: ROUTING_REQUEST
mode: PUBLISH_ONLY
message_history_limit: 1
}
config {
type: PREDICTION
mode: RECEIVE_ONLY
message_history_limit: 10
}
config {
type: PLANNING_TRAJECTORY
mode: PUBLISH_ONLY
message_history_limit: 1
}
config {
type: TRAFFIC_LIGHT_DETECTION
mode: RECEIVE_ONLY
message_history_limit: 1
}
is_ros: true
该文件表明,Planning模块配置以下几种类型的适配器:LOCALIZATION(仅接收)、CHASSIS(仅接收)、ROUTING_RESPONSE(仅接收)、ROUTING_REQUEST(仅发布)、PREDICTION(仅接收)、PLANNING_TRAJECTORY(仅发布)、TRAFFIC_LIGHT_DETECTION(仅接收),且使用ROS环境。
现在进入AdapterManager::Init(const std::string &adapter_config_filename)
内部一探究竟:
void AdapterManager::Init(const std::string &adapter_config_filename) {
// Parse config file
AdapterManagerConfig configs;
CHECK(util::GetProtoFromFile(adapter_config_filename, &configs))
<< "Unable to parse adapter config file " << adapter_config_filename;
AINFO << "Init AdapterManger config:" << configs.DebugString();
Init(configs);
}
顺藤摸瓜,继续深入AdapterManager::Init(const AdapterManagerConfig &configs)
内部,前面我们通过查看配置文件adapter.conf得知,此次配置类别为:LOCALIZATION、CHASSIS、ROUTING_RESPONSE、ROUTING_REQUEST、PREDICTION、PLANNING_TRAJECTORY、TRAFFIC_LIGHT_DETECTION。
void AdapterManager::Init(const AdapterManagerConfig &configs) {
if (Initialized()) {
return;
}
instance()->initialized_ = true;
if (configs.is_ros()) {
instance()->node_handle_.reset(new ros::NodeHandle());
}
for (const auto &config : configs.config()) {
switch (config.type()) {
// 省略不相关的配置类别
...
case AdapterConfig::CHASSIS:
EnableChassis(FLAGS_chassis_topic, config);
break;
case AdapterConfig::LOCALIZATION:
EnableLocalization(FLAGS_localization_topic, config);
break;
// 省略不相关的配置类别
...
case AdapterConfig::TRAFFIC_LIGHT_DETECTION:
EnableTrafficLightDetection(FLAGS_traffic_light_detection_topic,
config);
// 省略不相关的配置类别
...
case AdapterConfig::ROUTING_REQUEST:
EnableRoutingRequest(FLAGS_routing_request_topic, config);
break;
case AdapterConfig::ROUTING_RESPONSE:
EnableRoutingResponse(FLAGS_routing_response_topic, config);
break;
case AdapterConfig::PLANNING_TRAJECTORY:
EnablePlanning(FLAGS_planning_trajectory_topic, config);
break;
case AdapterConfig::PREDICTION:
EnablePrediction(FLAGS_prediction_topic, config);
break;
// 省略不相关的配置类别
...
default:
AERROR << "Unknown adapter config type!";
break;
}
上述代码中的FLAGS_chassis_topic、FLAGS_localization_topic、FLAGS_traffic_light_detection_topic、FLAGS_routing_request_topic、FLAGS_routing_response_topic、FLAGS_planning_trajectory_topic、FLAGS_prediction_topic是利用DEFINE_string宏定义出来的几个字符串变量,其缺省值分别为:
“/apollo/canbus/chassis”
“/apollo/localization/pose”
“/apollo/perception/traffic_light”
“/apollo/routing_request”
“/apollo/routing_response”
“/apollo/prediction”
代码中出现了几个函数:
EnableChassis、
EnableLocalization、
EnableTrafficLightDetection、
EnableRoutingRequest、
EnableRoutingResponse、
EnablePlanning、
EnablePrediction,
但我们找遍AdapterManager类内部,也无法发现它们的定义,这是从哪里冒出来的呢?其实这是宏
REGISTER_ADAPTER(Chassis)、
REGISTER_ADAPTER(Localization)、
REGISTER_ADAPTER (TrafficLightDetection)、
REGISTER_ADAPTER(RoutingRequest)、
REGISTER_ADAPTER (RoutingResponse)、
REGISTER_ADAPTER(Planning)、
REGISTER_ADAPTER(Prediction)
的杰作。
下面以REGISTER_ADAPTER(Planning)
为例进行分析:命名空间Apollo::common::adapter内的适配器管理者类AdapterManager使用宏REGISTER_ADAPTER (Planning)
注册了一个PlanningAdapter类对象及与其相关的变量、函数。这个PlanningAdapter类是何方神圣?请看文件:modules/common/adapters/message_adapters.h
内的这条语句:
using PlanningAdapter = Adapter<planning::ADCTrajectory>;
原来PlanningAdapter类是模板类Adapter
的一个别名。
再来看宏REGISTER_ADAPTER(Planning)展开后的实际代码,是不是找到了我们关注的函数:EnablePlanning(const std::string &topic_name, const AdapterConfig &config)
?分析该函数内部可知其功能有两个:一是订阅接收消息FLAGS_planning_trajectory_topic
,并绑定该消息的处理函数为PlanningAdapter::OnReceive
;二是将匿名函数(或称Lambda表达式,不熟悉的同学可查阅C++11标准)[this]() { Planning_->Observe(); }
作为规划适配器PlanningAdapter的观察函数存储到函数对象数组:std::vector
内部,提供给相关代码调用。
public:
static void EnablePlanning(const std::string &topic_name,
const AdapterConfig &config) {
CHECK(config.message_history_limit() > 0)
<< "Message history limit must be greater than 0";
instance()->InternalEnablePlanning(topic_name, config);
}
static PlanningAdapter *GetPlanning() {
return instance()->InternalGetPlanning();
}
static AdapterConfig &GetPlanningConfig() {
return instance()->Planningconfig_;
}
static bool FeedPlanningFile(const std::string &proto_file) {
if (!instance()->Planning_) {
AERROR << "Initialize adapter before feeding protobuf";
return false;
}
return GetPlanning()->FeedFile(proto_file);
}
static void PublishPlanning(const PlanningAdapter::DataType &data) {
instance()->InternalPublishPlanning(data);
}
template <typename T>
static void FillPlanningHeader(const std::string &module_name, T *data) {
static_assert(std::is_same<PlanningAdapter::DataType, T>::value,
"Data type must be the same with adapter's type!");
instance()->Planning_->FillHeader(module_name, data);
}
static void AddPlanningCallback(PlanningAdapter::Callback callback) {
CHECK(instance()->Planning_)
<< "Initialize adapter before setting callback";
instance()->Planning_->AddCallback(callback);
}
template <class T>
static void AddPlanningCallback(
void (T::*fp)(const PlanningAdapter::Dssage_history_limit()));
if (config.mode() != AdapterConfig::PUBLISH_ONLY && IsRos()) {
Planningsubscriber_ =
node_handle_->subscribe(topic_name, config.message_history_limit(),
&PlanningAdapter::OnReceive, Planning_.get());
}
if (config.mode() != AdapterConfig::RECEIVE_ONLY && IsRos()) {
Planningpublisher_ = node_handle_->advertise<PlanningAdapter::DataType>(
topic_name, config.message_history_limit(), config.latch());
}
observers_.push_back([this]() { Planning_->Observe(); });
Planningconfig_ = config;
}
PlanningAdapter *InternalGetPlanning() { return Planning_.get(); }
void InternalPublishPlanning(const PlanningAdapter::DataType &data) {
/* Only publish ROS msg if node handle is initialized. */
if (IsRos()) {
if (!Planningpublisher_.getTopic().empty()) {
Planningpublisher_.publish(data);
} else {
AERROR << "Planning" << " is not valid.";
}
} else {
/* For non-ROS mode, just triggers the callback. */
if (Planning_) {
Planning_->OnReceive(data);
} else {
AERROR << "Planning" << " is null.";
}
}
Planning_->SetLatestPublished(data);
}ataType &data), T *obj) {
AddPlanningCallback(std::bind(fp, obj, std::placeholders::_1));
}
template <class T>
static void AddPlanningCallback(
void (T::*fp)(const PlanningAdapter::DataType &data)) {
AddPlanningCallback(fp);
}
/* Returns false if there's no callback to pop out, true otherwise. */
static bool PopPlanningCallback() {
return instance()->Planning_->PopCallback();
}
private:
std::unique_ptr<PlanningAdapter> Planning_;
ros::Publisher Planningpublisher_;
ros::Subscriber Planningsubscriber_;
AdapterConfig Planningconfig_;
void InternalEnablePlanning(const std::string &topic_name,
const AdapterConfig &config) {
Planning_.reset(
new PlanningAdapter("Planning", topic_name, config.mePublishssage_history_limit()));
if (config.mode() != AdapterConfig::PUBLISH_ONLY && IsRos()) {
Planningsubscriber_ =
node_handle_->subscribe(topic_name, config.message_history_limit(),
&PlanningAdapter::OnReceive, Planning_.get());
}
if (config.mode() != AdapterConfig::RECEIVE_ONLY && IsRos()) {
Planningpublisher_ = node_handle_->advertise<PlanningAdapter::DataType>(
topic_name, config.message_history_limit(), config.latch());
}
observers_.push_back([this]() { Planning_->Observe(); });
Planningconfig_ = config;
}
PlanningAdapter *InternalGetPlanning() { return Planning_.get(); }
void InternalPublishPlanning(const PlanningAdapter::DataType &data) {
/* Only publish ROS msg if node handle is initialized. */
if (IsRos()) {
if (!Planningpublisher_.getTopic().empty()) {
Planningpublisher_.publish(data);
} else {
AERROR << "Planning" << " is not valid.";
}
} else {
/* For non-ROS mode, just triggers the callback. */
if (Planning_) {
Planning_->OnReceive(data);
} else {
AERROR << "Planning" << " is null.";
}
}
Planning_->SetLatestPublished(data);
}
现在重新回到ApolloApp::Spin()
函数的分析。初始化完成后继续调用虚函数:Start()(
实际调用apollo::planning::Planning::Start())
开始运行;之后使用spinner来监听各种信号,并调用相关处理函数;最后,在收到ros::waitForShutdown()
信号后,调用Stop()
(实际调用apollo::planning::Planning::Stop()
)完成资源清理退出。调试信息的输出贯穿于整个函数过程。
AdapterManager类不属于规划模块,但它是所有消息适配器的管理者,这里对其作一初步分析。顾名思义,AdapterManager是所有适配器的管理容器。什么是适配器,Apollo项目为什么要使用适配器?第一个问题很好理解,借用网上经常提到的一个例子,中国电压标准是220V,美国电压标准是110V,中国电器要在美国使用,必须借助一个变压器,这个变压器就是适配器。另外一个例子是,中国插座标准是扁孔,欧洲插座标准是圆孔,中国的电源插头到欧洲使用,必须借助一个插孔转接器,这个转接器也是适配器。以上只是对适配器的形象描述,C++中用到的适配器,自然是来自于鼎鼎有名的GOF名著《Design Patterns: Elements of Reusable Object-Oriented Software》中提到的Adapter设计模式。关于该模式的描述,网上有一堆文章,此处不再赘述。关于第二个问题,Apollo项目各模块内部的消息传递使用Google Protocol Buffer格式,各模块之间的底层消息传递目前使用ROS机制,今后可能会替换为百度自研消息机制。不管Apollo项目各模块之间的底层消息传递机制如何改变,我们必须确保各模块内部的消息格式不变,即使用Google Protocol Buffer格式,否则会因为底层消息传递机制的修改,导致各模块内部涉及消息处理的代码被迫修改,这既严重破坏各模块代码的独立性和正确性,也严重影响项目开发效率。这就是Apollo创建众多Adapter及其管理者AdapterManager的根本原因。
Apollo 2.0版本目前定义了以下适配器:
using ChassisAdapter = Adapter<::apollo::canbus::Chassis>;
using ChassisDetailAdapter = Adapter<::apollo::canbus::ChassisDetail>;
using ControlCommandAdapter = Adapter<control::ControlCommand>;
using GpsAdapter = Adapter<apollo::localization::Gps>;
using ImuAdapter = Adapter<localization::Imu>;
using RawImuAdapter = Adapter<apollo::drivers::gnss::Imu>;
using LocalizationAdapter = Adapter<apollo::localization::LocalizationEstimate>;
using MonitorAdapter = Adapter<apollo::common::monitor::MonitorMessage>;
using PadAdapter = Adapter<control::PadMessage>;
using PerceptionObstaclesAdapter = Adapter<perception::PerceptionObstacles>;
using PlanningAdapter = Adapter<planning::ADCTrajectory>;
using PointCloudAdapter = Adapter<::sensor_msgs::PointCloud2>;
using ImageShortAdapter = Adapter<::sensor_msgs::Image>;
using ImageLongAdapter = Adapter<::sensor_msgs::Image>;
using PredictionAdapter = Adapter<prediction::PredictionObstacles>;
using DriveEventAdapter = Adapter<DriveEvent>;
using TrafficLightDetectionAdapter = Adapter<perception::TrafficLightDetection>;
using RoutingRequestAdapter = Adapter<routing::RoutingRequest>;
using RoutingResponseAdapter = Adapter<routing::RoutingResponse>;
using RelativeOdometryAdapter =
Adapter<calibration::republish_msg::RelativeOdometry>;
using InsStatAdapter = Adapter<drivers::gnss::InsStat>;
using InsStatusAdapter = Adapter<gnss_status::InsStatus>;
using GnssStatusAdapter = Adapter<gnss_status::GnssStatus>;
using SystemStatusAdapter = Adapter<apollo::monitor::SystemStatus>;
using MobileyeAdapter = Adapter<drivers::Mobileye>;
using DelphiESRAdapter = Adapter<drivers::DelphiESR>;
using ContiRadarAdapter = Adapter<drivers::ContiRadar>;
using CompressedImageAdapter = Adapter<sensor_msgs::CompressedImage>;
using GnssRtkObsAdapter = Adapter<apollo::drivers::gnss::EpochObservation>;
using GnssRtkEphAdapter = Adapter<apollo::drivers::gnss::GnssEphemeris>;
using GnssBestPoseAdapter = Adapter<apollo::drivers::gnss::GnssBestPose>;
using LocalizationMsfGnssAdapter =
Adapter<apollo::localization::LocalizationEstimate>;
using LocalizationMsfLidarAdapter =
Adapter<apollo::localization::LocalizationEstimate>;
using LocalizationMsfSinsPvaAdapter =
Adapter<apollo::localization::IntegSinsPva>;
注意,AdapterManager类是使用宏DECLARE_SINGLETON(AdapterManager)
定义的单实例类,获取该类对象请使用AdapterManager::instance()
。
AdapterManager类的数据成员除了包含上述各个适配器对象及其关联的消息发布、订阅对象外,还有如下重要成员:
std::unique_ptr node_handle_
)std::vector> observers_
)bool initialized_
)Status Planning::Start() {
timer_ = AdapterManager::CreateTimer(
ros::Duration(1.0 / FLAGS_planning_loop_rate), &Planning::OnTimer, this);
ReferenceLineProvider::instance()->Start();
AINFO << "Planning started";
return Status::OK();
}
Planning类是规划模块的主要类,它将GPS和IMU提供的信息作为输入,处理后生成规划信息(包括路径和速度信息),提供给控制模块使用。
该类数据成员包括:规划者工厂类对象(planner_factory_)、规划配置类对象(config_)、高精地图类对象指针(hdmap_)、帧类对象指针(frame_)、规划者类对象指针(planner_)、可发布轨迹类对象指针(last_publishable_trajectory_)、计时器对象(timer_)。注意frame_、planner_和last_publishable_trajectory_使用C++标准库的唯一性智能指针unique_ptr包装,其优点在于:一是不用人工管理内存;二是上述指针由Planning类对象专享,确保数据的唯一性。
apollo::common::util::Factory planner_factory_
)PlanningConfig config_
)hdmap::HDMap* hdmap_
)std::unique_ptr frame_
)std::unique_ptr planner_
)std::unique_ptr last_publishable_trajectory_
)ros::Timer timer_
)该类主要包含三个重载函数:Init、Start、Stop(均为基类ApolloApp的虚函数)和几个功能函数:InitFrame、RunOnce、Plan,其他函数不太重要,如感兴趣可直接查看源代码。
apollo::common::adapterAdapterManager
,Apollo项目内的所有模块如定位、底盘、预测、控制、规划等全部归其管理,类似于一个家庭管家)对象获取规划模块所需的定位、底盘、路径、交通信号灯探测、周边障碍物预测等信息;获取参考线(基于导航路径和当前位置计算得到,提供给Planning类决策的候选轨迹线);注册具体的规划者类(目前注册RTKReplayPlanner及EMPlanner);使用工厂创建模式生成具体的规划者类对象(通过查询配置文件获知,实际生成了EMPlanner类对象);最后完成规划者类对象的初始化。planner_->Plan
,当前实现就是调用EMPlanner::Plan
函数)决策得到可用参考线信息集(可能不止一条);接着调用Frame::FindDriveReferenceLineInfo
函数从可用参考线信息集获取最优的参考线信息:best_reference_line;最后调用ReferenceLineInfo::ExportDecision
函数,将best_reference_line转化可供控制模块使用的驾驶决策,包括:路径、速度等。整个函数存在很多输出调试信息的语句,注意不要被他们所干扰,影响对核心思路的理解。EMPlanner类是具体的规划实施类,它基于高精地图、导航路径及障碍物信息作出实际的驾驶决策,包括路径、速度等方面。首先使用DP(动态规划)方法确定初始的路径和速度,再利用QP(二次规划)方法进一步优化路径和速度,以得到一条更平滑的轨迹,既满足舒适性,又方便车辆操纵。EMPlanner类的继承关系如下图所示:
该类数据成员包括:任务工厂类对象(task_factory_)、任务类对象数组(tasks_)。
apollo::common::util::Factory task_factory_
)std::vector> tasks_
)该类主要包含两个重载函数:Init、Plan(基类Planner的虚函数)和两个功能函数:GenerateInitSpeedProfile、GenerateSpeedHotStart,其他函数是辅助函数,如感兴趣可直接查看源代码。
Frame类用于存储规划模块用到的各类信息,包括起始点、车辆状态、参考线信息列表(即多条候选路径)、驾驶参考线信息(即实际执行的驾驶路径)、障碍物预测等。所有决策都基于当前帧进行。
uint32_t sequence_num_
)hdmap::HDMap* hdmap_
)common::TrajectoryPoint planning_start_point_
)common::VehicleState vehicle_state_
)std::list reference_line_info_
)cconst ReferenceLineInfo *drive_reference_line_info_
)prediction::PredictionObstacles prediction_
)prediction::PredictionObstacles prediction_
)该类主要包含如下成员函数:Init、FindDriveReferenceLineInfo,其他函数是辅助函数,如感兴趣可直接查看源代码。
ReferenceLineProvider类是规划模块中参考线(即候选路径)的提供和管理者,它以Routing模块输出的高精度路径(从起点到终点的Lane片段集)为输入数据,经优化处理后生成平滑的参考线集给后续的动作规划子模块使用。ReferenceLineProvider类是采用C++ 11标准实现的多线程类,每次调用ReferenceLineProvider::Start()函数后,该类就会在内部开启一个新线程,执行参考线的优化。ReferenceLineProvider类是使用宏DECLARE_SINGLETON(ReferenceLineProvider)定义的单实例类,获取该类对象请使用ReferenceLineProvider::instance()。
该类数据成员包括:是否初始化(is_initialized_)、是否停止(is_stop_)、线程类对象智能指针(thread_)、参考线平滑类对象智能指针(smoother_)、二维样条求解类对象智能指针(spline_solver_)、二次规划样条参考线平滑配置类对象指针(smoother_config_)、规划控制地图互斥体类对象(pnc_map_mutex_)、规划控制地图类对象智能指针(pnc_map_)、车辆状态互斥体类对象(vehicle_state_mutex_)、车辆状态类对象(vehicle_state_)、路由寻径互斥体类对象(routing_mutex_)、路由寻径响应类对象(routing_)、是否具备路由(has_routing_)、历史片段互斥体类对象(segment_history_mutex_)、历史片段无序映射表类对象(segment_history_)、历史片段ID列表类对象(segment_history_id_)、参考线互斥体类对象(reference_lines_mutex_)、参考线列表类对象(reference_lines_)、路由片段列表类对象(route_segments_)、上次计算时间(last_calculation_time_)。注意该类的指针使用C++标准库的唯一性智能指针unique_ptr包装,其优点在于:一是不用人工管理内存;二是上述指针由ReferenceLineProvider类对象专享,确保数据的唯一性。
bool is_initialized_
)bool is_stop_
)std::unique_ptr thread
_)std::unique_ptr smoother_
)std::unique_ptr spline_solver_
)QpSplineReferenceLineSmootherConfig smoother_config_
)std::mutex pnc_map_mutex_
)std::unique_ptr pnc_map_
)std::mutex vehicle_state_mutex_
)common::VehicleState vehicle_state_
)std::mutex routing_mutex_
)routing::RoutingResponse routing_
)bool has_routing_
)std::mutex segment_history_mutex_
)std::unordered_map segment_history_
)std::list segment_history_id_
)std::list reference_lines_
)std::list route_segments_
)double last_calculation_time_
)该类的重要成员函数包括:Init、Start、Stop、GenerateThread、CreateReferenceLine、UpdateReferenceLine,其他函数都是辅助性的功能函数,如感兴趣可直接查看源代码。
ReferenceLineProvider:: GenerateThread
为入口函数创建一个新线程并返回true,否则直接返回true,不做任何实际工作。void ReferenceLineProvider::GenerateThread() {
constexpr int32_t kSleepTime = 50; // milliseconds
while (!is_stop_) {
std::this_thread::yield();
std::this_thread::sleep_for(
std::chrono::duration<double, std::milli>(kSleepTime));
double start_time = Clock::NowInSeconds();
if (!has_routing_) {
AERROR << "Routing is not ready.";
continue;
}
std::list<ReferenceLine> reference_lines;
std::list<hdmap::RouteSegments> segments;
if (!CreateReferenceLine(&reference_lines, &segments)) {
AERROR << "Fail to get reference line";
continue;
}
UpdateReferenceLine(reference_lines, segments);
double end_time = Clock::NowInSeconds();
std::lock_guard<std::mutex> lock(reference_lines_mutex_);
last_calculation_time_ = end_time - start_time;
}
}
pnc_map_->IsNewRouting(routing)
判断当前路由是否为新路由,若是则调用pnc_map_->UpdateRoutingResponse(routing)
更新路由。之后,调用CreateRouteSegments
函数来创建路由片段。若不需粘合参考线(!FLAGS_enable_reference_line_stitching)
,则调用SmoothRouteSegment
函数来平滑各路由片段列表segments,并将平滑后的路由片段存储到参考线列表reference_lines中,同时将不能平滑的路由片段从segments中删除;若需粘合参考线(FLAGS_enable_reference_line_stitching)
,则调用ExtendReferenceLine
函数来合并不同参考线片段的重合部分,并将粘合后的路由片段保存到参考线列表reference_lines中,同时将不能粘合的路由片段从列表segments中删除。ReferenceLineInfo类用于存储参考线信息。所谓参考线实际就是一条驾驶路径,因此参考线信息实际上就是一条驾驶路径所需的各种信息,包括车辆状态、当前轨迹点、路径数据、速度数据等。
该类的数据成员较多,包括:车辆状态类对象(vehicle_state_)、规划点类对象(adc_planning_point_)、参考线类对象(reference_line_)、代价(cost_)、能否驾驶(is_drivable_)、路径决策类对象(path_decision_)、路径数据类对象(path_data_)、速度数据类对象(speed_data_)、离散化轨迹类对象(discretized_trajectory_)、SL边界类对象(adc_sl_boundary_)、隐藏状态类对象(latency_stats_)、车道路径片段类对象(lanes_)。
该类主要包含以下重要成员函数:AddObstacles、IsStartFrom、IsChangeLanePath、CombinePathAndSpeedProfile、ExportTurnSignal、ExportDecision、ReachedDestination,其他函数是辅助函数,如感兴趣可直接查看源代码。
TrafficDecider类继承自Task类,其作用是完成与交通相关的决策。Apollo 2.0版本中的道路交通包括:后方车辆、信号灯、人行横道、安全区、重新路由寻径、参考线终点、目的地、停止信号、改变车道等。Task类提供两个虚函数Init、Execute,其中Init函数用于任务对象的初始化,Task类目前未做任何实际工作;Execute用于执行实际的优化,Task类也未实现任何功能。
该类包含两个数据成员:规则工厂类对象(rule_factory_),规则配置类对象(rule_configs_)。
apollo::common::util::Factory rule_factory_
)该类重载了Task类的Init、Execute函数。
rule_factory_.CreateObject
函数动态创建配置文件中指定的规则对象,Apollo 2.0版创建了如下规则对象:BacksideVehicle、SignalLight、ReferenceLineEnd、Destination、ChangeLane。最后,对上述规则对象依次调用rule->ApplyRule(frame, reference_line_info)
来获得交通决策下的参考线信息:reference_line_info。PathDecider类继承自Task类,其作用是完成与路径相关的决策,Apollo 2.0版本中,该决策器根据当前路线中静态障碍物与车辆的位置,作出忽略(Ignore)、停车(Stop)、左绕行(Left nudge)和右绕行(Right nudge)等决策。Task类提供两个虚函数Init、Execute,其中Init函数用于任务对象的初始化,Task类目前未做任何实际工作;Execute用于执行实际的优化,Task类也未实现任何功能。
该类没有数据成员。
该类重载了Task类的Execute函数,并实现Process、MakeObjectDecision、MakeStaticObstacleDecision几个辅助的功能函数。
SpeedDecider类继承自Task类,其作用是完成与速度相关的决策。Apollo 2.0版本中,该决策器根据当前路线中障碍物与车辆的位置,作出跟随(Follow)、避让(Yield)、超车(Overtake)、停车(Stop)等决策。Task类提供两个虚函数Init、Execute,其中Init函数用于任务对象的初始化,Task类目前未做任何实际工作;Execute用于执行实际的优化,Task类也未实现任何功能。
该类包含以下数据成员:
该类重载了Task类的Init、Execute函数,并提供重要的功能函数:MakeObjectDecision。
DpPolyPathOptimizer类继承自PathOptimizer,后者又继承自Task,其作用是对当前路径进行动态规划多项式优化处理。Task类提供两个虚函数Init、Execute,其中Init函数用于任务对象的初始化,Task类目前未做任何实际工作;Execute用于执行实际的优化,Task类也未实现任何功能。PathOptimizer类重载了Task类的虚函数Execute,同时提供一个保护的纯虚函数Process(由派生类具体实现),在Execute内部调用Process函数完成实际的优化。注意:PathOptimizer是虚基类,不可直接使用它创建类对象,必须从该类派生出具体类后方可实例化。
该类包含一个数据成员:动态规划多项式路径配置类对象(config_)。
6. 动态规划多项式路径配置类对象(DpPolyPathConfig config_)
从配置文件中读取相关信息,包括:每步采样点数(sample_points_num_each_level)、步长最大值(step_length_max)、步长最小值(step_length_min)、横向采样偏移(lateral_sample_offset)、横向调整系数(lateral_adjust_coeff)、评估时间间隔(eval_time_interval)、路径分辨率(path_resolution)、障碍忽略距离(obstacle_ignore_distance)、障碍碰撞距离(obstacle_collision_distance)、障碍危险距离(obstacle_risk_distance)、障碍碰撞代价(obstacle_collision_cost)等。
该类重载了Task类的Init函数,PathOptimizer类的Process函数。
4. Init函数
该函数很简单,就是从配置文件中读取相关信息,获取算法所需的各项参数初始值。
5. Process函数
该函数创建一个DPRoadGraph类对象dp_road_graph,然后调用DPRoadGraph::FindPathTunnel函数寻找最优路径。
DpStSpeedOptimizer类继承自SpeedOptimizer,后者又继承自Task,其作用是对当前速度进行动态规划ST坐标优化处理。Task类提供两个虚函数Init、Execute,其中Init函数用于任务对象的初始化,Task类目前未做任何实际工作;Execute用于执行实际的优化,Task类也未实现任何功能。SpeedOptimizer类重载了Task类的虚函数Execute,同时提供一个保护的纯虚函数Process(由派生类具体实现),在Execute内部调用Process函数完成实际的优化,如果优化失败,则调用GenerateStopProfile函数生成停车速度信息。注意:SpeedOptimizer是虚基类,不可直接使用它创建类对象,必须从该类派生出具体类后方可实例化。
该类数据成员包括:动态规划ST坐标速度配置类对象(dp_st_speed_config_)、ST坐标边界配置类对象(dp_st_speed_config_)。
该类重载了Task类的Init函数,PathOptimizer类的Process函数。
QpSplinePathOptimizer类继承自PathOptimizer,后者又继承自Task,其作用是对当前路径进行二次规划样条优化处理。Task类提供两个虚函数Init、Execute,其中Init函数用于任务对象的初始化,Task类目前未做任何实际工作;Execute用于执行实际的优化,Task类也未实现任何功能。PathOptimizer类重载了Task类的虚函数Execute,同时提供一个保护的纯虚函数Process(由派生类具体实现),在Execute内部调用Process函数完成实际的优化。注意:PathOptimizer是虚基类,不可直接使用它创建类对象,必须从该类派生出具体类后方可实例化。
该类数据成员包括:二次规划样条路径配置类对象(qp_spline_path_config_)、样条生成器类对象指针(spline_generator_)。
std::unique_ptr spline_generator_
)该类重载了Task类的Init函数,PathOptimizer类的Process函数。
QpSplineStSpeedOptimizer类继承自SpeedOptimizer,后者又继承自Task,其作用是对当前速度进行二次规划样条ST坐标优化处理。Task类提供两个虚函数Init、Execute,其中Init函数用于任务对象的初始化,Task类目前未做任何实际工作;Execute用于执行实际的优化,Task类也未实现任何功能。SpeedOptimizer类重载了Task类的虚函数Execute,同时提供一个保护的纯虚函数Process(由派生类具体实现),在Execute内部调用Process函数完成实际的优化,如果优化失败,则调用GenerateStopProfile函数生成停车速度信息。注意:SpeedOptimizer是虚基类,不可直接使用它创建类对象,必须从该类派生出具体类后方可实例化。
该类数据成员包括:二次规划样条ST坐标速度配置类对象(qp_st_speed_config_)、ST坐标边界配置类对象(st_boundary_config_)、样条生成器类对象指针(spline_generator_)。
该类重载了Task类的Init函数,PathOptimizer类的Process函数。