相关文章:

《解析百度Apollo之参考线与轨迹》

《apollo介绍之参考线(二十七)》

《Apollo 6.0 参考线平滑算法解析》

《Apollo 6.0 pnc_map解析》

《pnc_map模块(规划与控制地图)》

简介

参考线在apollo中起到了关键的承上启下的作用。

它收集了上游路由,高精地图,定位等模块的信息,整合后生成一段基于车道中心线的平滑的路径,输送给下游决策规划控制模块使用。

在阅读了几遍源码和网上的介绍后,我想要整理出一版参考线生成的简化版本。

主要参考的还是下面这个链接。

pnc_map模块(规划与控制地图)_lzw0107的博客-CSDN博客​blog.csdn.net/lzw0107/article/details/107814610#t1正在上传…重新上传取消​https://link.zhihu.com/?target=https%3A//blog.csdn.net/lzw0107/article/details/107814610%23t1

我补充说明了一些上面的链接中部分未解释到的细节。

为了让思路更清晰,本文章未对解释如何实现某功能,而更注重于该模块实现了什么,以及每个模块的输入和输出,对实现细节感兴趣的同学可以去自行查阅源码。

参考线生成流程

从Apollo中Planning模块的入口说起,调用流程大致为:

其中,箭头代表函数间的调用关系。例如,Planning模块入口为PlanningComponent::Init(...),它调用了OnLanePlanning::Init(...),而后调用ReferenceLineProvider中的函数开启新的线程来生成ReferenceLine,其刷新的频率为20Hz。

本文章主要为了说明生成参考线的这一过程,即ReferenceLineProvider::CreateReferenceLine(...)函数的说明。该函数根据调用顺序可以分为以下三部分,也是本文章的大体结构。

第一部分:路由响应的处理

  • RoutingResponse数据结构

首先,我们需要了解路由输出的结果的数据结构,在apollo中,它们被定义在modules/routing/proto/routing.proto文件中,我挑选了比较重要的数据展示在下面。

其中我们最为关注的为RoutingResponse,它为参考线接收到的数据。

// 为路由模块的输入,车辆在全局地图中必须经过的点
// PointENU为东北天坐标系,apollo中被简化为二维横轴墨卡托坐标系
message LaneWaypoint {optional string id = 1;optional double s = 2;optional apollo.common.PointENU pose = 3;// When the developer selects a point on the dreamview route editing// the direction can be specified by dragging the mouse// dreamview calculates the heading based on this to support construct lane way point with headingoptional double heading = 4;
}// 其中id存储的为全局唯一的车道id,即定义在map_lane.proto中的Lane的id
message LaneSegment {optional string id = 1;optional double start_s = 2;optional double end_s = 3;
}message RoutingRequest {optional apollo.common.Header header = 1;// at least two points. The first is start point, the end is final point.// The routing must go through each point in waypoint.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 [deprecated = true];optional ParkingInfo parking_info = 7;optional DeadEndInfo dead_end_info = 8;
}enum ChangeLaneType {FORWARD = 0;LEFT = 1;RIGHT = 2;
};message Passage {repeated LaneSegment segment = 1;optional bool can_exit = 2;optional ChangeLaneType change_lane_type = 3 [default = FORWARD];
}message RoadSegment {optional string id = 1;repeated Passage passage = 2;
}message RoutingResponse {optional apollo.common.Header header = 1;repeated RoadSegment road = 2;optional Measurement measurement = 3;optional RoutingRequest routing_request = 4;// the map version which is used to build road graphoptional bytes map_version = 5;optional apollo.common.StatusPb status = 6;
}

而其中最重要的为RoadSegment,路由模块将道路结构分解为多个RoadSegment,每个RoadSegment又包括多个通道:Passage,每个Passage又由多个LaneSegment组成。

如图所示,为双车道中的一个例子。红色点为Routing起点,橙色为终点。

则RoutingResponse中给我们的结果就是RoadSegment1, 2, 3。

RoadSegment1中包括两个Passage(即每个车道为一个通道),每个Passage又包括三个LaneSegment。

这样做有什么好处呢?

当我们为RoadSegment,Passage,LaneSegment编号时,就可以将Routing模块输出的地图道路信息中每个车道中的每个LaneSegment快速的索引出来。如图中(1, 1, 1)代表左下角红圈所在的LaneSegment。

另外需要注意的是Passage中的两个属性:

can_exit : 代表能否从当前通道去下一个通道。用于判断将来是否需要变道。例如Passage1中can_exit为false, Passage2中为true。

change_lane_type : 代表当前通道是直行(FORWARD),左转(LEFT)还是右转(RIGHT)。 图中Passage1为RIGHT, 因为需要右转才能到终点。Passage2为FORWARD。

RoadSegment,Passage,LaneSegment图解:

  • 更新路由信息

主要函数为:

PncMap::UpdateRoutingResponse(const routing::RoutingResponse &routing)

可以看到,其输入为RoutingResponse成员,即routing模块的输出。

其主要功能为初始化下面加粗的三个pnc_map类成员

  1. 在得到RoutingResponse后,该函数为所有的routing::LaneSegment构建了索引列表route_indices_ ,其类型为vector<RouteIndex>。并将索引相对应的LaneSegment类型转换为hdmap::LaneSegment存进RouteIndex。
  struct RouteIndex {//此处类型为hdmap::LaneSegmentLaneSegment segment;//{road_index, passage_index, lane_index}std::array<int, 3> index;};

2. 将LaneSegment中的车道的lane_id取出,存入all_lane_ids_,和route_indices_ 中的LaneSegment一一对应。

3. 查询起始点和终点在哪段LaneSegment中,存入routing_waypoint_index_

第二部分:根据车辆当前位置生成短期通道

这一部分主要涉及到的函数为ReferenceLineProvider::CreateRouteSegments(...)调用的PncMap::GetRouteSegments(...)

输入为车辆当前位置,前向查找距离,后向查找距离

输出为std::list<RouteSegments>,list中每一个RouteSegments代表一条参考线经过的RouteSegments。在apollo中,该list的元素被限制为了最多为两个,原因后面会说明。

PncMap::GetRouteSegments(const VehicleState &vehicle_state,const double backward_length,const double forward_length,std::list<RouteSegments> *const route_segments)

RouteSegments类型为公有继承的std::vector<LaneSegment>,我们来看apollo中对它的说明,简单来说,该类从Passage扩展而来,保留了Passage的某些特征。该类包含了生成hdmap::Path类的原始数据,而Path类又可以用于初始化参考线。

也就是说我们在这一步主要是生成连接起来的可通行的Passage,从而为生成参考线做准备。

/*** @brief class RouteSegments** This class is a representation of the Passage type in routing.proto.* It is extended from a passage region, but keeps some properties of the* passage, such as the last end LaneWaypoint of the original passage region* (route_end_waypoint), whether the passage can lead to another passage in* routing (can_exit_).* This class contains the original data that can be used to generate* hdmap::Path.**/
class RouteSegments : public std::vector<LaneSegment>

该函数大致分为三步:

  • 更新pnc map中无人车状态

主要函数为PncMap::UpdateVehicleState(const VehicleState &vehicle_state) ,输入为VehicleState类对象,该类被定义在modules/common/vehicle_state/proto/vehicle_state.proto文件中,主要存储自车当前的位置信息,状态信息等。

message VehicleState {optional double x = 1 [default = 0.0];optional double y = 2 [default = 0.0];optional double z = 3 [default = 0.0];optional double timestamp = 4 [default = 0.0];optional double roll = 5 [default = 0.0];optional double pitch = 6 [default = 0.0];optional double yaw = 7 [default = 0.0];optional double heading = 8 [default = 0.0];optional double kappa = 9 [default = 0.0];optional double linear_velocity = 10 [default = 0.0];optional double angular_velocity = 11 [default = 0.0];optional double linear_acceleration = 12 [default = 0.0];optional apollo.canbus.Chassis.GearPosition gear = 13;optional apollo.canbus.Chassis.DrivingMode driving_mode = 14;optional apollo.localization.Pose pose = 15;optional double steering_percentage = 16;
}

VehicleState在这里主要用到了其全局坐标x,y,以及自车航向角heading。

这一部分就是根据自车的全局坐标系以及航向角,找到自车在哪一个LaneSegment中,或者说投影后距离那个LaneSegment最近,输出自车所在LaneSegment对应的index,并计算离自车最近的下一个必经点LaneWaypoint所在的LaneSegment对应的index

  • 计算临近通道

主要函数为GetNeighborPassages。

输入为自车所在的RoadSegment和通道Passage的id,如果当前Passage的change_lane_type为RIGHT,则输出为当前RoadSegment中自车右手边全部通道Passage的id,包括自车所在通道。若为LEFT,则输出左边。

举例来说,蓝色车为自车,则该函数输出Passage1, 2。

若不为双车道,例如右边有Passage3,Passage4,则输出为Passage1,2,3,4。

  • 生成list<RouteSegments>

遍历第二步中输出的全部通道,

若为自车所在通道,根据前向查找距离,后向查找距离生成一条短期通道RouteSegments,存入list<RouteSegments>。

若不为自车所在通道,调用CanDriveFrom查看能否从自车位置驶入该通道,若能则将自车投影到该通道并根据前向查找距离,后向查找距离生成一条短期通道RouteSegments,存入list<RouteSegments>。

CanDriveFrom函数判定依据为:

自车能投影到该Passage上,自车距离该Passage横向距离不能超过一条车道的宽度,自车heading应该和该Passage所在车道方向一致等。

上面标黑的条件导致了最终生成的list最多只有两个RouteSegments!

因此,对于上例来说,最终生成的list<RouteSegments>可能长这样,图中灰色区域:

至于为什么RouteSegments1会延长超出当前RoadSegment2, 感兴趣的同学可以去查看ExtendSegments中的算法。

至此,ReferenceLineProvider::CreateRouteSegments函数运行完毕。

第三部分:初始化Path

这一部分涉及到ReferenceLineProvider::SmoothRouteSegment函数,该函数输入为RouteSegments(上图中灰色区域),输出为ReferenceLine。

  • 首先,会根据输入的RouteSegments初始化一条Path。

Path的构造函数重载了很多版本,而当输入只有vector<LaneSegment>(RouteSegments为其子类)时,构造函数会调用MapPathPoint::GetPointsFromLane(...)来从LaneSegment中取出车道中心线的构成点来初始化Path。

Path::Path(const std::vector<LaneSegment>& segments): lane_segments_(segments) {for (const auto& segment : lane_segments_) {const auto points = MapPathPoint::GetPointsFromLane(segment.lane, segment.start_s, segment.end_s);path_points_.insert(path_points_.end(), points.begin(), points.end());}MapPathPoint::RemoveDuplicates(&path_points_);CHECK_GE(path_points_.size(), 2U);Init();
}

MapPathPoint::GetPointsFromLane(...)的输入为当前LaneSegment所在的车道,起点s和终点s。这里segment.lane为指向LaneInfo类的指针。

apollo中车道的数据结构主要写在modules/map/proto/map_lane.proto中,然后又用LaneInfo对proto数据做了一层封装。感兴趣的同学可以去查阅。

根据输入,该函数取出车道数据中的中线构成点的世界坐标和Frenet坐标,然后根据这些信息初始化MapPathPoint点,也就是构成Path的基本元素,输出vector<MapPathPoint>

  • 调用Init()为Path中的点赋予属性
void Path::Init() {InitPoints();InitLaneSegments();InitPointIndex();InitWidth();InitOverlaps();
}

初始化函数为Path点初始化一些Path类易维护的属性以及行车过程中所需要的必要信息。

例如InitWidth为初始化该路径点距离两边的车道边缘距离,距离两边道路边缘距离。

InitOverlaps为初始化该路径经过的一些和交通元素等的一些重叠,细节部分可以重开一帖再写。

  • 平滑参考线

在得到最终Path后,用它来构造一条粗糙的ReferenceLine,然后通过函数ReferenceLineProvider::SmoothReferenceLine调用smoother平滑该参考线得到最终的参考线。

平滑参考线见《Apollo6.0_ReferenceLine_Smoother解析与子方法对比

Apollo 6.0 参考线 ReferenceLine 生成相关推荐

  1. 无人驾驶算法——Baidu Apollo代码解析之ReferenceLine Smoother参考线平滑

    无人驾驶算法--Baidu Apollo代码解析之ReferenceLine Smoother参考线平滑 Apollo 参考线平滑类 reference_line_provider.cc 代价函数 c ...

  2. Apollo 2.0 传感器标定方法 使用指南(官方)

    点击我:官方地址 Apollo 2.0 传感器标定方法使用指南 欢迎使用Apollo传感器标定服务.本文档提供在Apollo 2.0中新增的3项传感器标定程序的使用流程说明,分别为:相机到相机的标定, ...

  3. 自主泊车、车载OS...Apollo 3.0的发布意味着什么?

     整理 |  just 出品 | 人工智能头条(公众号ID:AI_Thinker) 人工智能头条按:7 月 4 日,在百度 AI 开发者大会上 Apollo3.0 正式发布,百度智能驾驶事业群总经理 ...

  4. Apollo 2.0 传感器标定方法

    欢迎使用Apollo传感器标定服务.本文档提供在Apollo 2.0中新增的3项传感器标定程序的使用流程说明,分别为:相机到相机的标定,相机到多线激光雷达的标定,以及毫米波雷达到相机的标定. 文档概览 ...

  5. Apollo 6.0前景匹配逻辑分析(hm_data_association文件夹)

    相关链接 上篇链接 Apollo6.0融合帧分析(FuseFrame函数) 下篇链接 Apollo 6.0更新成功匹配的航迹_前景(UpdateAssignedTracks) KM匹配算法的C++实现 ...

  6. Apollo 2.0 框架及源码分析(一) | 软硬件框架

    原文地址:https://zhuanlan.zhihu.com/p/33059132 前言 如引言中介绍的,这篇软硬件框架多为现有消息的整合加一些个人的想法.关于 Apollo 介绍的文章已经有许多, ...

  7. apollo 8.0 系统安装与实践

    材料总结源于apollo 开发者社区 apollo 8.0 版本特殊性 apollo 8.0 增加了软件包管理,不需要全量代码下载,支持二进制安装, 大大方便了安装,以及某一个模块源码方式的安装开发调 ...

  8. 百度自动驾驶新突破:获首批T4牌照,升级Apollo 5.0,将进行复杂城市场景路测...

    作者 | KYLE WIGGERS等 编译 | 夕颜 出品 | AI科技大本营(ID:rgznai100) 最近,百度自动驾驶项目终于有了进展.先是上周悄然发布了 Apollo 的最新版本 Apoll ...

  9. Apollo 5.0,GitHub热榜第四

    雷刚 发自 凹非寺  量子位 报道 | 公众号 QbitAI Apollo 5.0有多火? 看GitHub最新热榜就行了,在全球最大的开发者社区,目前迭代新版本的Apollo,已经冲至第4. 目前总获 ...

最新文章

  1. 工作流编程循序渐进(9:使用本地服务在宿主和工作流之间通信)
  2. 【java】【mybatis】在使用mybatis进行批量插入,批量更新等批量操作时,切割In集合List进行分批批量操作的java中的切割代码...
  3. 解析 http 请求 header 错误_详解http报文(2)-web容器是如何解析http报文的
  4. DeepLab v2的摘要部分(翻译加理解)
  5. AI理论知识整理(17)-子式,非奇异,可逆
  6. 16、分布式文档系统--document的_source元数据以及定制返回结果解析(来自学习资料+自己整理)
  7. 微软CRM 2011 Beta 新功能之二:不同组织不同基本语言
  8. 近年来NLP在法律领域的相关研究工作
  9. day10作用域与闭包
  10. html中的a标签、img标签、iframe标签、列表标签
  11. LeetCode#67--Add Binary(用Java实现二进制相加)
  12. python3档案管理程序源码_php人事档案管理系统,源码免费分享
  13. ubuntu 18.04 安装 LNMP后,nginx 和 apachae 冲突
  14. eclipse 如何查看Java源码
  15. pandoc -crossref插件实现markdwon文档转word后公式编号自定义
  16. 2021年12月全国程序员薪资出炉,你的排哪里……
  17. YOLOv1: You Only Look Once: Unified, Real-Time Object Detection
  18. android流量卡信息,Android 双卡获取当前使用流量在线卡的信息
  19. 考研-英语经验贴2.0
  20. 你为什么总是做出错误的选择?

热门文章

  1. vue发起微信或支付宝支付
  2. 已知矩形的宽和高以及与x轴夹角,求对边的两条边的中心坐标
  3. 简易的ATM银行系统(Java)
  4. PIC单片机通用定时器使用说明
  5. editplus配置java快捷键,editplus快捷键大全
  6. UE4 在游戏运行时显示或隐藏鼠标
  7. Django之ContentType理解应用
  8. 【FFMpeg】视频播放时前几秒只有声音没有视频
  9. Oracle提高(访问其他用户和视图、同义词、序列、索引学习)
  10. shopeeman本土多店铺管理系统商品活动如何设置