1. abstract
本文主要讲解routing和planning模块中的reference line,我之前一直搞不明白这个reference line是如何生成的,有什么作用,和routing以及planning的关系。现在有了一些心得打算梳理一下:
决策规划模块负责生成车辆的行驶轨迹。要做到这一点,决策规划模块需要从宏观到局部经过三个层次来进行决策。
- 第一个层次是Routing的搜索结果。Routing模块的输入是若干个按顺序需要达到的途径点(也可能只有一个起点和终点)。Routing模块根据地图的拓扑结构搜索出可达的完整路线来,这个路线的长度可能是几公里甚至几百公里。因此这个是最为宏观的数据。另外,Routing的搜索结果是相对固定的。在没有障碍物的情况下,车辆会一直沿着原先获取到的Routing路线行驶。只有当车辆驶出了原先规划的路线之外(例如:为了避障),才会重新发送请求给Routing模块,以重新计算路线。
- 第二个层次就是reference line。决策规划模块会实时的根据车辆的具体位置来计算reference line。reference line的计算会以Routing的路线为基础。但同时,reference line会考虑车辆周边的动态信息,例如:障碍物,交通规则等。reference line是包含车辆所在位置周边一定的范围,通常是几百米的长度。相较于Routing结果,它是较为局部的数据。
- 第三个层次是Trajectory。Trajectory是决策规划模块的最终输出结果。它的依据是reference line。在同一时刻,reference line可能会有多条,例如:在变道的时候,自车所在车道和目标车道都会有一条reference line。而Trajectory,是在所有可能的结果中,综合决策和优化的结果,最终的唯一结果。因此它是更为具体和局部的数据。轨迹不仅仅包含了车辆的路线,还包含了车辆行驶这条路线时的详细状态,例如:车辆的方向,速度,加速度等等。
参考线是整个决策规划算法的基础。在Planning模块的每个计算循环中,都会先生成参考线,然后在这个基础上进行后面的处理,例如:交通规则逻辑,障碍物投影,路径优化,速度决策等等。可以说,参考线贯穿了整个Planning模块的实现。下面就看下reference line如何在routing和plannin中承前启后的:
从这幅图中可以看出,这里涉及到三个模块,下面会挨个梳理:
- routing模块,这部分内容追在Routing模块一文中讲解,本文不再赘述。
- **pnc_map模块:负责读取和处理Routing搜索结果。**是planning的子模块,因为重要就把他单独拎出来了。
- planning模块:根据Routing结果和车辆的实时状态(包括周边环境)生成reference line和trajectory。
2. routing
Routing模块正如其名称所示,其主要作用就是根据请求生成路由信息。
模块输入:
- 地图数据
- 请求,包括:
- 开始和结束位置
模块输出:
- 路由导航信息
2.1 Topo地图
为了计算路由路径,在Routing模块中包含一系列的类用来描述Topo地图的详细结构。这些类的定义位于modules/routing/graph/
目录下。它们的说明如下:
简单来说,Topo地图中最重要的就是节点和边,本质上是一系列的Topo节点以及它们的连接关系,节点对应了道路,边对应了道路的连接关系,这个在Apollo开发者社区的技术文章中着重讲过。如下图所示:
Routing模块需要的地图结构通过TopoGraph来描述,而TopoGraph的初始化需要一个地图文件。但该地图文件与其他模块需要的地图文件并不一样,这里的地图文件是Proto结构导出的数据。之所以这样做是因为:Routing模块不仅需要地图的Topo结构,还需要知道每条路线的行驶代价。在Proto结构中包含了这些信息。在下面的内容中,我们将看到这个行驶代价是从哪里来的。
很显然,两个地点的导航路径结果通常会有多个。而计算导航路径的时候需要有一定的倾向,这个倾向就是行驶的代价越小越好。我们很自然的想到,影响行驶代价最大的因素就是行驶的距离。
但实际上,影响行驶代价的因素远不止距离这一个因素。距离只是宏观上的考虑,而从微观的角度来看,行驶过程中,需要进行多少次转弯,多少次掉头,多少变道,这些都是影响行驶代价的因素。所以,在计算行驶代价的时候,需要综合考虑这些因素。
再从另外一个角度来看,(在路线已经确定的情况下)行驶的距离是一个物理世界客观存在的结果,这是我们无法改变的。不过,对于行驶过程中,有多在意转弯,掉头和变道,每个人或者每个场景下的偏好就不一样了。而这,就是配置文件“/apollo/modules/routing/conf/routing/config.pb.txt“存在的意义了。这里面配置了上面提到的这些动作的惩罚基数,而这些基数会影响路线时的计算代价。
routing的请求接口是下面这个:
bool Routing::Process(const std::shared_ptr<RoutingRequest> &routing_request, RoutingResponse* const routing_response);
这个接口只有很简洁的两个参数:一个是描述请求的输入参数routing_request
,一个是包含结果的输出参数routing_response
。它们都是在proto文件中定义的。
2.2 RoutingRequest
message LaneWaypoint {
optional string id = 1;
optional double s = 2;
optional apollo.common.PointENU pose = 3;
}
message LaneSegment {
optional string id = 1;
optional double start_s = 2;
optional double end_s = 3;
}
message RoutingRequest {
optional apollo.common.Header header = 1;
repeated LaneWaypoint waypoint = 2;
repeated LaneSegment blacklisted_lane = 3;
repeated string blacklisted_road = 4;
optional bool broadcast = 5 [default = true];
optional apollo.hdmap.ParkingSpace parking_space = 6;
}
2.3 RoutingResponse:
message RoutingResponse {
optional apollo.common.Header header = 1;
repeated RoadSegment road = 2;
optional Measurement measurement = 3;
optional RoutingRequest routing_request = 4;
optional bytes map_version = 5;
optional apollo.common.StatusPb status = 6;
}
message RoadSegment {
optional string id = 1;
repeated Passage passage = 2;
}
message Passage {
repeated LaneSegment segment = 1;
optional bool can_exit = 2;
optional ChangeLaneType change_lane_type = 3 [default = FORWARD];
}
message LaneSegment {
optional string id = 1;
optional double start_s = 2;
optional double end_s = 3;
}
enum ChangeLaneType {
FORWARD = 0;
LEFT = 1;
RIGHT = 2;
};
message Measurement {
optional double distance = 1;
}
RoutingResponse中的属性说明如下:
这里的RoadSegment road
是最重要的数据。这个数据其实是一个三层的结构体嵌套,它们的说明如下:
RoadSegment road
:描述道路,一条道路可能包含了并行的几条通路(Passage)。Passage
:描述通路,通路是直连不含变道的可行驶区域。一个通路可能包含了前后连接的多个车道。(对应了pac_map中的RouteSegments
)LaneSegment
:描述车道,车道是道路中的一段,自动驾驶车辆会尽可能沿着车道的中心线行驶。
3. pnc_map
pnc全称是Planning And Control,是Planning用来对接Routing搜索结果的子模块,但他比较重要,这里我把它单独拎出来梳理下。
PncMap
类负责对接Routing搜索结果的更新:会根据车辆当前位置,提供车辆周边的RouteSegments
信息供ReferenceLineProvider
生成ReferenceLine
。而这里的RouteSegments
对应了routing模块里RoutingResponse
里的Passage
结构,它其中会包含若干个车道信息。这个RouteSegments类继承自std::vector。RouteSegments中有如下一些方法值得关注:
- NextAction():车辆接下来要采取的动作。可能是直行,左变道,或者右变道。
- CanExit():当前通路是否可以接续到Routing结果的另外一个通路上。
- GetProjection():将一个点投影到当前通路上。返回SLPoint和LaneWaypoint。
- Stitch():缝合另外一个RouteSegments。即:去除两个RouteSegments间重合的多余部分,然后连接起来。
- Shrink():缩短到指定范围。
- IsOnSegment():车辆是否在当前RouteSegments上。
- IsNeighborSegment():当前RouteSegments是否是车辆的临近RouteSegments。
4. planning
在Planning模块中有以下三个数据结构将是本文关注的重点:
ReferenceLine
:原始参考线,源码位于planning/reference_line/
目录下。根据Routing的搜索结果生成。ReferenceLineInfo
:源码位于planning/reference_line/
目录下。Planning实现中,逻辑计算的基础数据结构,很多操作都会在这个数据结构上进行(例如:交通规则逻辑,障碍物投影,路径优化,速度决策等)。本文中的“参考线”一词将不区分ReferenceLine
和ReferenceLineInfo
两个结构。Trajectory
:下文中我们将看到,有好几个结构用来描述轨迹。它们在不同的场合下使用。这其中,ADCTrajectory
是Planning模块的输出。它是Planning模块一次计算循环中,处理了所有逻辑的最终结果,包含了车辆行驶需要的所有信息。因此,这个数据将直接影响到自动驾驶车辆的行车行为。
5. reference
解析百度Apollo之Routing模块
解析百度Apollo之参考线与轨迹
开发者说丨离散点曲线平滑原理
直播回顾丨Apollo自动驾驶论坛①规划模块算法解析
Apollo Planning Frame
Apollo ReferenceLineProvider
Apollo EMPlanner(v 3.0)
Apollo 6.0 规划算法解析
Apollo 6.0 规划模块算法解析2