查看原文
其他

入门必看丨解析百度Apollo之Routing模块

强波 Apollo开发者社区 2022-07-29


《入门必看丨解析百度Apollo自动驾驶平台》的分享中,对百度Apollo项目做了整体的介绍,本章主要结合源码解析其中的Routing模块


下面是由社区开发者—强波提供的文章,对百度Apollo之Routing模块进行详细讲解(Apollo 3.5之前版本),希望这篇文章能给感兴趣的开发者带来更多帮助。



  ENJOY THE FOLLOWING  




Routing模块正如其名称所示,其主要作用就是根据请求生成路由信息。


模块输入:

  • 地图数据。

  • 请求,包括:开始和结束位置。


模块输出:

  • 路由导航信息。


Routing模块的实现文件结构如下图所示:





Apollo项目中使用了Google的gflags项目来定义常量。关于gflags可以访问下面两个链接:

  • 《Github: gflags》(链接见文末)。

  • 《How To Use gflags》(链接见文末)。


Routing中的相关代码如下。它们分别位于头文件和实现文件中。


routing_gflags.h文件内容:


1#pragma once
2
3#include "gflags/gflags.h"
4
5DECLARE_string(routing_conf_file);
6DECLARE_string(routing_node_name);
7
8DECLARE_double(min_length_for_lane_change);
9DECLARE_bool(enable_change_lane_in_result);
10DECLARE_uint32(routing_response_history_interval_ms);

<左右滑动以查看完整代码>


routing_gflags.cc文件内容:


1#include "modules/routing/common/routing_gflags.h"
2
3DEFINE_string(routing_conf_file,
4          "/apollo/modules/routing/conf/routing_config.pb.txt",
5          "default routing conf data file");
6
7DEFINE_string(routing_node_name, "routing""the name for this node");
8
9DEFINE_double(min_length_for_lane_change, 1.0,
10          "meters, which is 100 feet.  Minimum distance needs to travel on "
11          "a lane before making a lane change. Recommended by "
12          "https://www.oregonlaws.org/ors/811.375");
13
14DEFINE_bool(enable_change_lane_in_result, true,
15        "contain change lane operator in result");
16
17DEFINE_uint32(routing_response_history_interval_ms, 3000,
18          "ms, emit routing resposne for this time interval");

<左右滑动以查看完整代码>


即便没有接触过gflags,这段代码也应该很容易理解。代码定义了5个常量并指定了它们的值,同时还包含了每个常量的描述。这5个常量分别是:


  • routing_conf_file:Routing模块配置文件的路径。

  • routing_node_name:Routing模块的节点名称。

  • min_length_for_lane_change:变道前,在当前车道上行驶的最短距离。

  • enable_change_lane_in_result:导航结果是否允许变道。

  • routing_response_history_interval_ms:路由请求的响应时长。


将模块常用的几个常量定义在一起可以方便修改,也方便模块里面使用。


例如,Routing模块的代码中通过FLAGS_routing_conf_file便可以读取到值“/apollo/modules/routing/conf/routing_config.pb.txt”。


这是Routing模块的配置文件路径,其文件内容如下:


1base_speed: 4.167
2left_turn_penalty: 50.0
3right_turn_penalty: 20.0
4uturn_penalty: 100.0
5change_penalty: 500.0
6base_changing_length: 50.0

<左右滑动以查看完整代码>


后文中我们会提到这个配置文件的作用。




Apollo项目中的很多数据结构都是通过Protocol Buffers定义的。所以你看不到这些类的C++文件,因为C++需要的相关文件是在编译时通过Proto文件自动生成的。


Protocol Buffers是Google的开源项目。它具有无关语言、平台的特性,并且有很好的可扩展性。Protocol Buffers通常用于序列化结构化数据。


Apollo使用Protocol Buffers的一个很重要的作用是,用它来将结构化数据导出到物理文件中,并且也可以很方便的从物理文件中读取信息。例如,Routing模块需要的Topo地图就是Proto结构导出的。另外,如果导出的是文本形式的文件,也可以方便地进行人为的修改。例如,上面提到的routing_config.pb.txt。


Proto文件都位于名称为Proto的文件夹中,你可以通过下面这条命令在Apollo源码的根目录下找到所有的Proto文件夹:


1apollo$ find . -name proto

<左右滑动以查看完整代码>


这其中自然就包含了Routing模块的Proto文件夹:modules/routing/proto 。


这个目录中包含了4个Proto文件,每个文件中又包含了若干个结构,这些结构描述如下:


类型名称描述
Landmark地图上的一个点,包含了名称和位置信息。
POIPoint of interest的缩写,一个POI中可以包含多个Landmark。


类型名称描述
RoutingConfig描述了Routing模块的配置信息,上面提到的routing_config.pb.txt文件就是这个格式的。


类型名称描述
LaneWaypoint道路上的路径点,包含了id,长度和位置点信息。
LaneSegment道路的一段,包含了id和起止点信息。
RoutingRequest描述了路由请求的信息,一次路由请求可以包含多个路径点。详细结构见下文。
Measurement描述测量的距离。
ChangeLaneType道路的类型,有FORWARD,LEFT,RIGHT三种取值。
Passage一段通路,其中可以包含多个LaneSegment,以及ChangeLaneType。
RoadSegment道路的一段,拥有一个id,并可以包含多个Passage。
RoutingResponse路由请求的响应结果,可以包含多个RoadSegment,距离等信息。


类型名称描述
CurvePoint曲线上的一个点。
CurveRange曲线上的一段。
Node车道上的一个节点,包含了所属车道,道路,长度,曲线起止点,中心线等信息。
Edge连接车道之间的边,包含了起止车道id,代价和方向等信息。
Graph完整地图的Topo结构,这其中包含了多个Node和Edge。


Proto文件不是孤立存在的,每个Proto文件都可以通过import语法使用定义在其他文件中的结构。


例如,Routing模块以及其他模块都需要用的数据结构就定义在modules/common/proto/目录下。这其中包含的Proto文件如下:


1.
2├── drive_event.proto
3├── drive_state.proto
4├── error_code.proto
5├── geometry.proto
6├── header.proto
7├── pnc_point.proto
8└── vehicle_signal.proto

<左右滑动以查看完整代码>




为了计算路由路径,在Routing模块中包含一系列的类用来描述Topo地图的详细结构。


这些类的定义位于modules/routing/graph/目录下。它们的说明如下:


简单来说,Topo地图中最重要的就是节点和边,节点对应了道路,边对应了道路的连接关系。如下图所示:



从源码中可以看到,Routing模块需要的地图结构通过TopoGraph来描述,而TopoGraph的初始化需要一个地图文件。但该地图文件与其他模块需要的地图文件并不一样,这里的地图文件是Proto结构导出的数据。之所以这样做是因为:Routing模块不仅需要地图的Topo结构,还需要知道每条路线的行驶代价。在Proto结构中包含了这些信息。在下面的内容中,我们将看到这个行驶代价是从哪里来的。


很显然,两个地点的导航路径结果通常会有多个。而计算导航路径的时候需要有一定的倾向,这个倾向就是行驶的代价越小越好。我们很自然的想到,影响行驶代价最大的因素就是行驶的距离。


但实际上,影响行驶代价的因素远不止距离这一个因素。距离只是宏观上的考虑,而从微观的角度来看,行驶过程中,需要进行多少次转弯,多少次掉头,多少变道,这些都是影响行驶代价的因素。所以,在计算行驶代价的时候,需要综合考虑这些因素。


再从另外一个角度来看,(在路线已经确定的情况下)行驶的距离是一个物理世界客观存在的结果,这是我们无法改变的。不过,对于行驶过程中,有多在意转弯,掉头和变道,每个人或者每个场景下的偏好就不一样了。而这,就是上文中提到的配置文件“/apollo/modules/routing/conf/routing_config.pb.txt”存在的意义了。这里面配置了上面提到的这些动作的惩罚基数,而这些基数会影响路线的计算代价。


通过将这种偏好以配置文件的形式存储在代码之外,可以在不用重新编译代码的情况下,直接调整导航搜索的结果。并且可以方便的为不同的场景进行策略的配置(例如:高速环境和城市道路,这些参数的值很可能就是不一样的)。


Topo地图本质上是一系列的Topo节点以及它们的连接关系。因此TopoNode就要能够描述这些信息。在这个类中,包含了许多的属性来存储这些连接关系,如下所示:


1// topo_node.cc
2
3std::vector left_out_sorted_range_;
4std::vector right_out_sorted_range_;
5
6std::unordered_set<const TopoEdge*> in_from_all_edge_set_;
7std::unordered_set<const TopoEdge*> in_from_left_edge_set_;
8std::unordered_set<const TopoEdge*> in_from_right_edge_set_;
9std::unordered_set<const TopoEdge*> in_from_left_or_right_edge_set_;
10std::unordered_set<const TopoEdge*> in_from_pre_edge_set_;
11std::unordered_set<const TopoEdge*> out_to_all_edge_set_;
12std::unordered_set<const TopoEdge*> out_to_left_edge_set_;
13std::unordered_set<const TopoEdge*> out_to_right_edge_set_;
14std::unordered_set<const TopoEdge*> out_to_left_or_right_edge_set_;
15std::unordered_set<const TopoEdge*> out_to_suc_edge_set_;
16
17std::unordered_map<const TopoNode*, const TopoEdge*> out_edge_map_;
18std::unordered_map<const TopoNode*, const TopoEdge*> in_edge_map_;

<左右滑动以查看完整代码>


有了这些信息之后,在进行路径搜索时,可以方便地查找线路。




与人类开车时所使用的导航系统不一样,自动驾驶需要包含更加细致信息的高精地图,高精地图描述了整个行驶过程中物理世界的详细信息,例如:道路的方向、宽度、曲率、红绿灯的位置等等。而物理世界的这些状态是很容易会发生改变的,例如,添加了一条新的道路,或者是新的红绿灯。这就要求高精地图也要频繁地更新。


那么Routing模块需要的地图文件也需要一起配套的跟着变化,这就很自然的需要有一个模块能够完成从原先的高精地图生成Routing模块的Proto格式地图这一转换工作。而完成这一工作的,就是TopoCreator模块。


TopoCreator的源码位于modules/routing/topo_creator/目录下,这是一个可执行程序。其main函数代码如下:


1int main(int argc, char **argv) {
2google::InitGoogleLogging(argv[0]);
3google::ParseCommandLineFlags(&argc, &argv, true);
4
5apollo::routing::RoutingConfig routing_conf;
6
7CHECK(apollo::common::util::GetProtoFromFile(FLAGS_routing_conf_file,
8                                           &routing_conf))
9  << "Unable to load routing conf file: " + FLAGS_routing_conf_file;
10
11AINFO << "Conf file: " << FLAGS_routing_conf_file << " is loaded.";
12
13const auto base_map = apollo::hdmap::BaseMapFile();
14const auto routing_map = apollo::hdmap::RoutingMapFile();
15
16apollo::routing::GraphCreator creator(base_map, routing_map, routing_conf);
17CHECK(creator.Create()) << "Create routing topo failed!";
18
19AINFO << "Create routing topo successfully from " << base_map << " to "
20    << routing_map;
21return 0;
22}

<左右滑动以查看完整代码>


这里的逻辑很简单,就是先读取配置文件中的信息到RoutingConfig中,然后通过GraphCreator根据高清地图文件生成Routing模块需要的Topo地图。


配置文件(routing_config.pb.txt)中的值的调整将影响这里生成的Topo地图的计算代价,而在Routing模块真正执行路线搜索的时候,又会考虑这些代价,于是就会影响最终的导航计算结果。整个流程如下图所示:





Routing模块通过Init方法来初始化。在初始化时,会创建Navigator对象以及加载地图,相关代码如下:


1apollo::common::Status Routing::Init() {
2const auto routing_map_file = apollo::hdmap::RoutingMapFile();
3AINFO << "Use routing topology graph path: " << routing_map_file;
4navigator_ptr_.reset(new Navigator(routing_map_file));
5CHECK(common::util::GetProtoFromFile(FLAGS_routing_conf_file, &routing_conf_))
6  << "Unable to load routing conf file: " + FLAGS_routing_conf_file;
7
8AINFO << "Conf file: " << FLAGS_routing_conf_file << " is loaded.";
9
10hdmap_ = apollo::hdmap::HDMapUtil::BaseMapPtr();
11CHECK(hdmap_) << "Failed to load map file:" << apollo::hdmap::BaseMapFile();
12
13return apollo::common::Status::OK();
14}
15Navigator的初始化

<左右滑动以查看完整代码>


Routing内部会通过Navigator来搜索路径。因为需要搜索路径,所以Navigator需要完整的Topo地图。在其构造函数中,会完成Topo地图的加载。


相关代码如下:


1Navigator::Navigator(const std::string& topo_file_path) {
2Graph graph;
3if (!common::util::GetProtoFromFile(topo_file_path, &graph)) {
4AERROR << "Failed to read topology graph from " << topo_file_path;
5return;
6}
7
8graph_.reset(new TopoGraph());
9if (!graph_->LoadGraph(graph)) {
10AINFO << "Failed to init navigator graph failed! File path: "
11      << topo_file_path;
12return;
13}
14black_list_generator_.reset(new BlackListRangeGenerator);
15result_generator_.reset(new ResultGenerator);
16is_ready_ = true;
17AINFO << "The navigator is ready.";
18}

<左右滑动以查看完整代码>


这里除了加载地图还初始化了下面两个类的对象:


  • BlackListRangeGenerator:隐藏地图生成器,下文会讲解。

  • ResultGenerator:当搜索完成之后,这个对象用来生成搜索结果。




处理路由请求的接口是下面这个:


1bool Routing::Process(const std::shared_ptr &routing_request,
2                  RoutingResponse* const  routing_response);

<左右滑动以查看完整代码>


这个接口只有很简洁的两个参数:一个是描述请求的输入参数routing_request,一个是包含结果的输出参数routing_response。它们都是在proto文件中定义的。


RoutingRequest的定义如下(RoutingResponse的结构我们在后文中会看到):


1message LaneWaypoint {
2optional string id = 1;
3optional double s = 2;
4optional apollo.common.PointENU pose = 3;
5}
6
7message LaneSegment {
8optional string id = 1;
9optional double start_s = 2;
10optional double end_s = 3;
11}
12
13message RoutingRequest {
14optional apollo.common.Header header = 1;
15repeated LaneWaypoint waypoint = 2;
16repeated LaneSegment blacklisted_lane = 3;
17repeated string blacklisted_road = 4;
18optional bool broadcast = 5 [default = true];
19optional apollo.hdmap.ParkingSpace parking_space = 6;
20}

<左右滑动以查看完整代码>


这里最关键的信息就是下面这个:


1repeated LaneWaypoint waypoint = 2;

<左右滑动以查看完整代码>


它描述了一次路由请求的路径点,repeated表示这个数据可以出现多次,因此是Routing模块是支持一次搜索多个途经点的。




在一些情况下,地图可能会有信息缺失。在这种情况下,Routing模块支持动态的添加一些信息。这个逻辑主要是通过BlackListRangeGenerator和TopoRangeManager两个类完成的。这其中,前者提供了添加数据的接口,而后者则负责存储这些数据。


BlackListRangeGenerator类的定义如下:


1class BlackListRangeGenerator {
2public:
3BlackListRangeGenerator() = default;
4~BlackListRangeGenerator() = default;
5
6void GenerateBlackMapFromRequest(const RoutingRequest& request,
7                               const TopoGraph* graph,
8                               TopoRangeManager* const range_manager) const
;
9
10void AddBlackMapFromTerminal(const TopoNode* src_node,
11                           const TopoNode* dest_node, double start_s,
12                           double end_s,
13                           TopoRangeManager* const range_manager) const
;
14};

<左右滑动以查看完整代码>


从这个定义中可以看到,它提供了两个接口来添加数据:


  • GenerateBlackMapFromRequest:是从RoutingRequest包含的数据中添加。

  • AddBlackMapFromTerminal:是从终端添加数据。


这两个接口最后都会通过TopoRangeManager::Add接口来添加数据。该方法代码如下:


1void TopoRangeManager::Add(const TopoNode* node, double start_s, double end_s) {
2NodeSRange range(start_s, end_s);
3range_map_[node].push_back(range);
4}

<左右滑动以查看完整代码>


TopoRangeManager中的数据最终会被ResultGenerator在组装搜索结果的时候用到。




前面我们提到了Navigator。如果你浏览了这个类的代码就会发现。Navigator本身并没有实现路径搜索的算法。它仅仅是借助其他类来完成路由路径的搜索过程。


相关逻辑在Navigator::SearchRoute方法中。该方法代码如下:


1bool Navigator::SearchRoute(const RoutingRequest& request,
2                        RoutingResponse* const response) {
3if (!ShowRequestInfo(request, graph_.get())) { ①
4SetErrorCode(ErrorCode::ROUTING_ERROR_REQUEST,
5             "Error encountered when reading request point!",
6             response->mutable_status());
7return false;
8}
9
10if (!IsReady()) { ②
11SetErrorCode(ErrorCode::ROUTING_ERROR_NOT_READY, "Navigator is not ready!",
12             response->mutable_status());
13return false;
14}
15std::vector<const TopoNode*> way_nodes;
16std::vector way_s;
17if (!Init(request, graph_.get(), &way_nodes, &way_s)) { ③
18SetErrorCode(ErrorCode::ROUTING_ERROR_NOT_READY,
19             "Failed to initialize navigator!", response->mutable_status());
20return false;
21}
22
23std::vector result_nodes;
24if (!SearchRouteByStrategy(graph_.get(), way_nodes, way_s, &result_nodes)) { ④
25SetErrorCode(ErrorCode::ROUTING_ERROR_RESPONSE,
26             "Failed to find route with request!",
27             response->mutable_status());
28return false;
29}
30if (result_nodes.empty()) {
31SetErrorCode(ErrorCode::ROUTING_ERROR_RESPONSE, "Failed to result nodes!",
32             response->mutable_status());
33return false;
34}
35result_nodes.front().SetStartS(request.waypoint().begin()->s());
36result_nodes.back().SetEndS(request.waypoint().rbegin()->s());
37
38if (!result_generator_->GeneratePassageRegion( ⑤
39      graph_->MapVersion(), request, result_nodes, topo_range_manager_,
40      response)) {
41SetErrorCode(ErrorCode::ROUTING_ERROR_RESPONSE,
42             "Failed to generate passage regions based on result lanes",
43             response->mutable_status());
44return false;
45}
46SetErrorCode(ErrorCode::OK, "Success!", response->mutable_status());
47
48PrintDebugData(result_nodes);
49return true;
50}

<左右滑动以查看完整代码>


这段代码虽长,但其实主体逻辑是很清晰的,主要包含了这么几个步骤:


1、对请求参数进行检查;

2、判断自身是否处于就绪状态;

3、初始化请求需要的参数;

4、执行搜索算法;

5、组装搜索结果。


搜索结果的组装就是通过ResultGenerator借助搜索的结果std::vector以及TopoRangeManager来进行组装的。


前面我们提到,搜索的结果RoutingResponse类型也是在Proto文件中的定义的,其内容如下:


1message RoutingResponse {
2optional apollo.common.Header header = 1;
3repeated RoadSegment road = 2;
4optional Measurement measurement = 3;
5optional RoutingRequest routing_request = 4;
6optional bytes map_version = 5;
7optional apollo.common.StatusPb status = 6;
8}

<左右滑动以查看完整代码>




Navigator::SearchRoute方法的第四步调用了类自身的SearchRouteByStrategy方法。在这个方法中,会借助AStarStrategy来完成路径的搜索。


AStarStrategy类是抽象类Strategy子类,这两个类的结构如下图所示:



很显然,这里是Strategy设计模式的应用。定义了Strategy基类的作用是:今后可以很容易的实现另外一种算法将原先的A*算法替换掉。


从AStarStrategy这个类的名称我们就可以看出,这个类的实现是通过A*算法来搜索路径的。关于A*算法我们已经在另外一篇文章中详细讲解过了(《路径规划之 A* 算法》(链接见文末)),因此这里不再赘述。


对于不了解A*算法的读者可以先阅读那篇文章,这里仅仅对Apollo中A*实现的关键概念做一点说明。


AStarStrategy由a_star_strategy.cc实现,对应的头文件为a_star_strategy.h。其类定义如下:


1class AStarStrategy : public Strategy {
2public:
3explicit AStarStrategy(bool enable_change);
4~AStarStrategy() = default;
5
6virtual bool Search(const TopoGraph* graph, const SubTopoGraph* sub_graph,
7                  const TopoNode* src_node, const TopoNode* dest_node,
8                  std::vector* const result_nodes)
;
9
10private:
11void Clear();
12double HeuristicCost(const TopoNode* src_node, const TopoNode* dest_node);
13double GetResidualS(const TopoNode* node);
14double GetResidualS(const TopoEdge* edge, const TopoNode* to_node);
15
16private:
17bool change_lane_enabled_;
18std::unordered_set<const TopoNode*> open_set_;
19std::unordered_set<const TopoNode*> closed_set_;
20std::unordered_map<const TopoNode*, const TopoNode*> came_from_;
21std::unordered_map<const TopoNode*, double> g_score_;
22std::unordered_map<const TopoNode*, double> enter_s_;
23};

<左右滑动以查看完整代码>


A*算法实现最关键的就是计算Cost,因为Cost会影响最终的搜索结果。而影响Cost的一个关键因素就是启发函数的选取。下面我们就看看Apollo中是如何实现的。


  • 关于Cost:前面已经提到,Proto格式的Topo地图中存有节点之间的Cost(Topo地图由TopoCreator生成,在生成的时候会根据配置文件设置Cost值),因此在这里直接读取即可。下面这个函数就是读取了节点之间的Cost:


1double GetCostToNeighbor(const TopoEdge* edge) {
2return (edge->Cost() + edge->ToNode()->Cost());
3}

<左右滑动以查看完整代码>


每个节点的Cost都可以由相邻节点的Cost加上连接至自身的边的Cost之和来计算。


  • 关于启发函数:Routing模块中的A*算法使用TopoNode锚点的坐标差值作为启发函数,相关代码如下:

1double AStarStrategy::HeuristicCost(const TopoNode* src_node,
2                                const TopoNode* dest_node) {
3const auto& src_point = src_node->AnchorPoint();
4const auto& dest_point = dest_node->AnchorPoint();
5double distance = fabs(src_point.x() - dest_point.x()) +
6                fabs(src_point.y() - dest_point.y());
7return distance;
8}

<左右滑动以查看完整代码>




Routing的搜索结果由RoutingResponse描述。RoutingResponse及相关结构定义如下:


1message RoutingResponse {
2optional apollo.common.Header header = 1;
3repeated RoadSegment road = 2;
4optional Measurement measurement = 3;
5optional RoutingRequest routing_request = 4;
6
7optional bytes map_version = 5;
8optional apollo.common.StatusPb status = 6;
9}
10
11message RoadSegment {
12optional string id = 1;
13repeated Passage passage = 2;
14}
15
16message Passage {
17repeated LaneSegment segment = 1;
18optional bool can_exit = 2;
19optional ChangeLaneType change_lane_type = 3 [default = FORWARD];
20}
21
22message LaneSegment {
23optional string id = 1;
24optional double start_s = 2;
25optional double end_s = 3;
26}
27
28enum ChangeLaneType {
29FORWARD = 0;
30LEFT = 1;
31RIGHT = 2;
32};
33
34message Measurement {
35optional double distance = 1;
36}

<左右滑动以查看完整代码>


RoutingResponse中的属性说明如下:


名称说明
header消息头
road具体的路径信息,最重要的数据
measurement距离
routing_request原始请求
map_version地图版本
status状态位

很显然,这里的RoadSegment road是最重要的数据。这个数据其实是一个三层的结构体嵌套,它们的说明如下:


  • RoadSegment:描述道路,一条道路可能包含了并行的几条通路(Passage)。

  • Passage:描述通路,通路是直连不含变道的可行驶区域。一个通路可能包含了前后连接的多个车道。

  • LaneSegment:描述车道,车道是道路中的一段,自动驾驶车辆会尽可能沿着车道的中心线行驶。




好奇的读者可能会发现,讲到这里,我们都没有提到Routing模块是如何启动的。


如果你查看Routing模块根目录下的BUILD文件。你会发现该模块的编译产物其实是一个动态库(so文件),而非一个可执行文件。


那么这个模块到底是如何启动的呢?答案就是Cyber RT。


Apollo 3.5彻底摒弃了ROS,改用自研的Cyber作为底层通讯与调度平台。Apollo Cyber RT 系统是Apollo开源软件平台层的一部分,作为运行时计算框架,处于实时操作系统 (RTOS)和应用模块之间。Apollo Cyber RT作为基础平台,支持流畅高效的运行所有应用模块。


Cyber RT的工作流如下图所示:



简单来说,在Apollo 3.5中,各个模块(这也包括:Localization、Perception、Prediction、Planning、Control)的启动都是由Cyber RT这个运行时来处理的。


如果你浏览Routing模块的源码,你会发现一个dag文件,其内容如下:


1Define all coms in DAG streaming.
2module_config {
3module_library "/apollo/bazel-bin/modules/routing/librouting_component.so"
4components {
5    class_name : "RoutingComponent"
6    config {
7        name : "routing"
8        flag_file_path: "/apollo/modules/routing/conf/routing.conf"
9        readers: [
10            {
11                channel: "/apollo/routing_request"
12                qos_profile: {
13                    depth : 10
14                }
15            }
16        ]
17    }
18}
19}

<左右滑动以查看完整代码>


Apollo Cyber RT框架核心理念是基于组件的,组件有预先设定的输入输出。实际上,每个组件就代表一个专用的算法模块。框架可以根据所有预定义的组件生成有向无环图(DAG)。


在运行时刻,框架把融合好的传感器数据和预定义的组件打包在一起形成用户级轻量任务,之后,框架的调度器可以根据资源可用性和任务优先级来派发这些任务。


关于这部分内容不再继续深入,有兴趣的读者可以看下面两个链接:


  • 《Apollo 3.5 各功能模块的启动过程解析》(链接见文末)。

  • 《Apollo Cyber RT framework》(链接见文末)。




文末,我们通过一幅图来描述Routing模块中的主要组件以及它们的交互关系。





*《Github: gflags》

https://github.com/gflags/gflags

*《How To Use gflags》

https://gflags.github.io/gflags/

*《路径规划之 A* 算法

https://paul.pub/a-star-algorithm/

*《Apollo 3.5 各功能模块的启动过程解析

https://blog.csdn.net/davidhopper/article/details/85248799

*《Apollo Cyber RT framework

http://apollo.auto/cyber.html


以上是"百度Apollo之Routing模块"的全部内容,更多话题讨论、技术交流可以扫描下方二维码添加『Apollo小哥哥』为好友,进开发者交流群。






* 以上内容为开发者原创,不代表百度官方言论。

内容来自开发者网站—保罗的酒吧:

https://paul.pub/apollo-routing/,欢迎大家收藏点赞。已获开发者授权,原文地址请戳阅读原文。





您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存