Apollo 2.0 软硬件框架初探(二)

本文原创作者:冀渊
地址:https://mp.weixin.qq.com/s/GccST3xJ1QRIVM7cFgsn3A


本篇开始将会与大家一起探讨 Apollo 的感知模块 (Perception) 。我会试着从整体框架开始,慢慢深入,与大家一同探讨。Perception 模块里代码较多、涉及面较广,我在理解代码的过程中有许多感到疑惑的地方。在叙述中,我会阐述我理解的部分,并列出一些我的疑惑点,希望可以与大家共同学习。

出于个人专业背景和内容重要性的考虑,我会把重点放在感知模块的障碍物感知部分。由于内容较多,所以本篇只会重点叙述 Lidar 的部分。Radar 和 fusion 及其他部分将会放在下一篇。

 Apollo 2.0 框架及源码分析(零) | 引言[1] 中,有介绍过一些笔者资料的参考来源,这次感知的部分也有参考 Apollo 开发者社区中分享的相关资料。对 Apollo 的感知及相关概念不是十分熟悉的同学,建议可以先看下这两份官方资料。同时,在本篇中也会用到其中的部分 PPT [2][3]。

Apollo 自动驾驶感知技术分享

PPT资料 | Apollo 自动驾驶感知技术


Apollo 感知模块 (Perception) 实现框架

无人驾驶系统车端的部分,大致可分为3块内容,感知,决策,控制。其中,感知是其余两者的基础,是无人驾驶中极为重要的一个部分。

无人驾驶中所用到的传感器各有长处和短板,单一的传感器难以满足复杂场景的需求,因此使用多种传感器进行相互融合(sensor funsion)就显得十分有必要了。

下图是 Apollo 公开课对无人驾驶中常见传感器的介绍,从图中看出,各个传感器擅长的情况不一,而依靠传感器的融合则可以应对大多数情况。

图片来源:PPT资料 | Apollo 自动驾驶感知技术

 

Apollo 的这张图整体表述不错,十分清晰。

但笔者的 mentor 认为图中对 Lidar 在 Lane Tracking 上的描述有点欠妥。他认为可以根据路面和车道线对 Lidar 射线反射强度的不同来追踪车道。因此 Lidar 在车道线追踪的能力应该可以算得上是 Fair 而不是和 Radar 一样的 Poor

另外,雷锋网的这篇文章中也提到了基于激光雷达回波信号检测车道线的可能性[4]。

专栏 | 如何利用激光雷达检测车道线?这里提供了4种方法

https://www.leiphone.com/news/201712/iG2xBYren1q9faI9.html?utm_source=debugrun&utm_medium=referral&viewType=weixin

Apollo 2.0 框架及源码分析(一) | 软硬件框架 中有介绍过,Apollo 2.0 感知的整体框架如下图所示,分为 3D障碍物感知 和 交通灯感知 两大部分 [5]。

Perception 框架,图片来源: Apollo 2.0 框架及源码分析,地址:https://zhuanlan.zhihu.com/p/33059132

 

其实这两者在 Apollo 官方文档和讲座中,已经有了比较清晰的讲解。笔者十分建议大家先阅读官方提供的材料。当然为了方便诸位阅读,本篇也会摘取其中部分重要内容,以期读者能够对感知部分的工作流程有个大体的了解[6][7]。

 


www.51apollo.com 无人驾驶,百度apollo,交流论坛


 

3D 障碍物感知

https://github.com/ApolloAuto/apollo/blob/master/docs/specs/3d_obstacle_perception_cn.md

交通灯感知

https://github.com/ApolloAuto/apollo/blob/master/docs/specs/traffic_light.md

Apollo 官方公开课中的对感知部分的功能是用下图描述的。

从图中可知,在 Apollo 中感知模块有以下几个职能:

  1. 探测物体 (是否有障碍物)
  2. 对物体分类 (障碍物是什么)
  3. 语义解析 (障碍物从背景中分割)
  4. 物体追踪 (障碍物追踪)

本篇接下来会叙述 Apollo 3D障碍物感知部分是如何实现上述职能的。

障碍物感知部分的框架如下图所示。障碍物感知主要依靠的是 Lidar 和 Radar 两者的感知结果的相互融合。该部分输入为传感器的原始数据,根据接收到的数据的来源的不同,进行不同的处理,最终输出融合后的结果。

Apollo 社区也提供了视频为其障碍物感知能力进行了演示,十分精彩。

观看地址:https://mp.weixin.qq.com/s/GccST3xJ1QRIVM7cFgsn3A


从代码上看,障碍物感知由3个主体部分组成: LidarRadar fusion。接下来,本篇将重点描述其中的 Lidar 部分的实现原理。

官方对 Lidar 部分的原理解释得极其清楚。大家结合上文列出的参考文档和PPT资料 ,应该就会对 Lidar 部分的整体工作原理会有个清晰的认识了。

如上图 PPT 所示,Lidar 部分简单来说是这样工作的:

  1. 输入为 Lidar 得到的点云数据,输出为检测到的障碍物
  2. 由 HDmap 来确定 ROI (region of interest),过滤 ROI 区域外的点
  3. 处理点云数据,探测并识别障碍物 (由 AI 完成)   AI => 深度学习
  4. 障碍物追踪

障碍物感知的各个部分的具体原理及细节,大家可以参考这篇官方文档 3D 障碍物感知[6]。

总得来说,Apollo 团队在 Lidar 部分投入了极大的精力。在Apollo 自己的演示视频中,Lidar 的感知能力被展现的淋漓尽致,十分精彩。但十分遗憾的是,Lidar 的最核心部分,障碍物探测和分类部分(Obstacle Detection & Classification),涉及到的 caffe 的源码是不开源的[8]。

caffe in docker is different from origin version and is not open source in Apollo 2.0. If you want to use outside docker please copy caffe’s *.h and *.so to your environment. Or wait for Apollo 2.5

由于笔者在深度学习上的积累有限,并且 Lidar 部分官方文档也已经十分清楚,因此本篇在此只再简单提炼下其中的一些要点,并加以一些细节以飨读者。

笔者和 mentor 在阅读代码时遇到的第一个问题是如何寻找模块的函数入口。很遗憾,我们直到最后对 Lidar 这一部分的函数入口的选择也有所疑惑,最终只能凭经验选取下面的函数,作为 Lidar 的入口来阅读代码。

入口函数位置: [https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/onboard/lidar_process_subnode.cc]


void LidarProcessSubnode::OnPointCloud(
      const sensor_msgs::PointCloud2& message);

关于程序的结构和各部分的函数入口的问题,笔者会在下一篇中说到。

根据代码的注释,这里分成7步对 Lidar 的流程进行叙述。

1. 坐标及格式转换 (get velodyne2world transform)

Apollo 使用了开源库 Eigen 进行高效的矩阵计算,使用了 PCL 点云库对点云进行处理。

该部分中,Apollo 首先计算转换矩阵 velodyne_trans,用于将 Velodyne 坐标转化为世界坐标。之后将 Velodyne 点云转为 PCL 点云库格式,便于之后的计算。

2. 获取ROI区域 (call hdmap to get ROI)

核心函数位置:

https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/onboard/hdmap_input.cc


bool HDMapInput::GetROI(const PointD& pointd, const double& map_radius,
                        HdmapStructPtr* mapptr);

查询 HDmap, 根据 Velodyne 的世界坐标以及预设的半径 (FLAG_map_radius) 来获取 ROI 区域。

首先获取指定范围内的道路以及道路交叉点的边界,将两者进行融合后的结果存入 ROI 多边形中。该区域中所有的点都位于世界坐标系。

3. 调用ROI过滤器 (call roi_filter)

核心函数位置:

https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/roi_filter/hdmap_roi_filter/hdmap_roi_filter.cc


bool HdmapROIFilter::Filter(const pcl_util::PointCloudPtr& cloud,
                            const ROIFilterOptions& roi_filter_options,
                            pcl_util::PointIndices* roi_indices);

官方文档对该部分是这么描述的:

高精地图 ROI 过滤器(往下简称“过滤器”)处理在ROI之外的激光雷达点,去除背景对象,如路边建筑物和树木等,剩余的点云留待后续处理。

一般来说,Apollo 高精地图 ROI过滤器有以下三步:
1. 坐标转换
2. ROI LUT构造
3. ROI LUT点查询

蓝色线条标出了高精地图ROI的边界,包含路表与路口。红色加粗点表示对应于激光雷达传感器位置的地方坐标系原始位置。2D网格由8*8个绿色正方形组成,在ROI中的单元格,为蓝色填充的正方形,而之外的是黄色填充的正方形。

ROI 过滤器部分涉及到了 扫描线法 和 位图编码 两个技术。具体来看,该部分分以下几步:

a. 坐标转换

将地图 ROI 多边形和点云转换至激光雷达传感器位置的地方坐标系。

b. 确定地图多边形主方向

比较所有点的 x、y 方向的区间范围,取区间范围较小的方向为主方向。并将地图多边形 (map_polygons) 转换为待加工的多边形 (raw polygons)。

c. 建立位图

将 raw polygons 转化为位图 (bitmap) 中的格点,位图有以下特点:

  • 位图范围, 以 Lidar 为原点的一片区域 (-range, range)*(-range, range) 内,range 默认 70米
  • 位图用于以格点 (grid) 的方式存储 ROI 信息。若某格点值为真,代表此格点属于 ROI。
  • 默认的格点大小为 cell_size 0.25米。
  • 在列方向上,1bit 代表 1grid。为了加速操作,Apollo 使用 uint64_t 来一次操纵64个grids。

为了在位图中画出一个多边形,以下3个步骤需要被完成:

i. 获得主方向有效范围

ii. 将多边形转换为扫描线法所需的扫描间隔:将多变形在主方向上分解为线(多边形->片段->线),计算每条线的扫描间隔。

iii. 基于扫描间隔在位图中画格点

关于扫描线,这里推荐笔者参考的一篇解析 扫描线算法完全解析 [9]。

d. ROI 点查询

通过检查 grid 的值,确定在位图中得每一个 grid 是否属于 ROI。

4. 调用分割器 (segmentor)

入口函数所在文件:cnn_segmentation.cc


bool CNNSegmentation::Segment(const pcl_util::PointCloudPtr& pc_ptr,
                              const pcl_util::PointIndices& valid_indices,
                              const SegmentationOptions& options,
                              vector<ObjectPtr>* objects)

 

分割器采用了 caffe 框架的深度完全卷积神经网络(FCNN) 对障碍物进行分割,简单来说有以下四步:


www.51apollo.com 无人驾驶,百度apollo,交流论坛


 

a. 通道特征提取

计算以 Lidar 传感器某一范围内的各个单元格 (grid) 中与点有关的8个统计量,将其作为通道特征(channel feature)输入到 FCNN。

1. 单元格中点的最大高度
2. 单元格中最高点的强度
3. 单元格中点的平均高度
4. 单元格中点的平均强度
5. 单元格中的点数
6. 单元格中心相对于原点的角度
7. 单元格中心与原点之间的距离
8. 二进制值标示单元格是空还是被占用如

计算时默认只使用 ROI 区域内的点,也可使用整个 Lidar 范围内的点,使用标志位 use_full_cloud_ 作为开关。

b. 基于卷积神经网络的障碍物预测

  • 与 caffe 相关的 FCNN 源码貌似是不开源
  • Apllo 官方叙述了其工作原理,摘录如下

完全卷积神经网络由三层构成:下游编码层(特征编码器)、上游解码层(特征解码器)、障碍物属性预测层(预测器)

特征编码器将通道特征图像作为输入,并且随着特征抽取的增加而连续下采样其空间分辨率。 然后特征解码器逐渐对特征图像 上采样到输入2D网格的空间分辨率,可以恢复特征图像的空间细节,以促进单元格方向的障碍物位置、速度属性预测。 根据具有非线性激活(即ReLu)层的堆叠卷积/分散层来实现 下采样和 上采样操作。

c. 障碍物集群 (Cluster2D)

核心函数位置:

https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/segmentation/cnnseg/cluster2d.h

#void Cluster(const caffe::Blob<float>& category_pt_blob,
               const caffe::Blob<float>& instance_pt_blob,
               const apollo::perception::pcl_util::PointCloudPtr& pc_ptr,
               const apollo::perception::pcl_util::PointIndices& valid_indices,
               float objectness_thresh, bool use_all_grids_for_clustering);

Apollo基于单元格中心偏移预测构建有向图,采用压缩的联合查找算法(Union Find algorithm )基于对象性预测有效查找连接组件,构建障碍物集群。

https://github.com/ApolloAuto/apollo/blob/master/docs/specs/3d_obstacle_perception_cn.md(a)红色箭头表示每个单元格对象中心偏移预测;蓝色填充对应于物体概率不小于0.5的对象单元。(b)固体红色多边形内的单元格组成候选对象集群。

d. 后期处理

涉及的函数:

https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/segmentation/cnnseg/cluster2d.h

void Filter(const caffe::Blob<float>& confidence_pt_blob,
              const caffe::Blob<float>& height_pt_blob);void Classify(const caffe::Blob<float>& classify_pt_blob);void GetObjects(const float confidence_thresh, const float height_thresh,
                  const int min_pts_num, std::vector<ObjectPtr>* objects);

  • 聚类后,Apollo获得一组包括若干单元格的候选对象集,每个候选对象集包括若干单元格。根据每个候选群体的检测置信度分数物体高度,来确定最终输出的障碍物集/分段。
  • 从代码中可以看到 CNN分割器最终识别的物体类型有三种:小机动车、大机动车、非机动车和行人。

在obstacle/lidar/segmentation/cnnseg/cluster2d.h中

enum MetaType {
  META_UNKNOWN,
  META_SMALLMOT,
  META_BIGMOT,
  META_NONMOT,
  META_PEDESTRIAN,
  MAX_META_TYPE
};

5. 障碍物边框构建

入口函数位置:

https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/object_builder/min_box/min_box.cc

void BuildObject(ObjectBuilderOptions options, ObjectPtr object)

边界框的主要目的还是预估障碍物(例如,车辆)的方向。同样地,边框也用于可视化障碍物。

如图,Apollo确定了一个6边界边框,将选择具有最小面积的方案作为最终的边界框。

 

6. 障碍物追踪

入口函数位置:

https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/tracker/hm_tracker/hm_tracker.cc

// @brief track detected objects over consecutive frames
  // @params[IN] objects: recently detected objects
  // @params[IN] timestamp: timestamp of recently detected objects
  // @params[IN] options: tracker options with necessary information
  // @params[OUT] tracked_objects: tracked objects with tracking information
  // @return true if track successfully, otherwise return false
  bool Track(const std::vector<ObjectPtr>& objects, double timestamp,
             const TrackerOptions& options,
             std::vector<ObjectPtr>* tracked_objects); 

障碍物追踪可分两大部分,即 数据关联 和 跟踪动态预估。Apollo 使用了名为 HM tracker的对象跟踪器。实现原理:

在HM对象跟踪器中,匈牙利算法(Hungarian algorithm)用于检测到跟踪关联,并采用 鲁棒卡尔曼滤波器(Robust Kalman Filter) 进行运动估计。

数据关联

数据关联的过程是确定传感器接收到的量测信息和目标源对应关系的过程,是多传感多目标跟踪系统最核心且最重要的过程[10]。

Apollo 首先建立关联距离矩阵,用于计算每个对象 (object ) 和 每个轨迹 (track )之间的关联距离。之后使用 匈牙利算法 为 object和 track 进行最优分配。

计算关联距离时,Apollo 考虑了以下5个关联特征,来评估 object 和 track 的运动及外观一致性,并为其分配了不同的权重。


关联特征                             一致性评估         默认权重

location_distance                运动                    0.6

direction_distance              运动                     0.2

bbox_size_distance           外观                      0.1

point_num_distance           外观                      0.1

histogram_distance           外观                      0.5


由上表可以看出,Apollo 在计算关联距离时,重点考虑的还是几何距离和两者的形状相似度。计算得到类似下图的关联距离矩阵后,使用匈牙利算法将 Object 与 Track 做匹配。


Object             track1       track2      track3

Object A           2               3               4

Object B           3               4               5

Object C           2               4               5

关联距离矩阵实例


关于匈牙利算法的实现原理,这里有篇很有趣的解释 趣写算法系列之–匈牙利算法 – CSDN博客[11]。

跟踪动态预估 (Track Motion Estimation)

使用卡尔曼滤波来对 track 的状态进行估计,使用鲁棒统计技术来剔除异常数据带来的影响。

不了解卡尔曼滤波原理的同学请参考:卡尔曼滤波器的原理以及在matlab中的实现 [12]。这一部分的滤波整体看来是一个标准的卡尔曼滤波。在此基础上,Apollo 团队加入了一些修改,根据官方文档,Apollo 的跟踪动态预估有以下三个亮点 :

  • 观察冗余

在一系列重复观测中选择速度测量,即滤波算法的输入,包括锚点移位、边界框中心偏移、边界框角点移位等。冗余观测将为滤波测量带来额外的鲁棒性, 因为所有观察失败的概率远远小于单次观察失败的概率。

卡尔曼更新的观测值为速度。每次观测三个速度值 :

锚点移位速度、边界框中心偏移速度 和 边界框角点位移速度。

从三个速度中,根据运动的一致性,选出与之前观测速度偏差最小的速度为最终的观测值。

根据最近3次的速度观测值,计算出加速度的观测值。

  • 分解

高斯滤波算法 (Gaussian Filter algorithms)总是假设它们的高斯分布产生噪声。 然而,这种假设可能在运动预估问题中失败,因为其测量的噪声可能来自直方分布。 为了克服更新增益的过度估计,在过滤过程中使用故障阈值。

这里的故障阈值应该对应着程序中的 breakdown_threshold_。

该参数被用于以下两个函数中,当更新的增益过大时,它被用来克服增益的过度估计:

  • KalmanFilter::UpdateVelocity
  • KalmanFilter::UpdateAcceleration

两者的区别在于:

速度的故障阈值是动态计算的,与速度误差协方差矩阵有关

velocity_gain *= breakdown_threshold_; 

加速度的故障阈值是定值,默认为2

acceleration_gain *= breakdown_threshold;  
  • 更新关联质量 (UpdateQuality)

原始卡尔曼滤波器更新其状态不区分其测量的质量。 然而,质量是滤波噪声的有益提示,可以估计。 例如,在关联步骤中计算的距离可以是一个合理的测量质量估计。 根据关联质量更新过滤算法的状态,增强了运动估计问题的鲁棒性和平滑度。

更新关联质量 update_quality 默认为 1.0,当开启适应功能时 (s_use_adaptive ==true)Apollo 使用以下两种策略来计算更新关联质量:

  1. 根据 object 自身的属性 — 关联分数 (association_score) 来计算
  2. 根据新旧两个 object 点云数量的变化

首先根据这两种策略分别计算更新关联质量,之后取得分小的结果来控制滤波器噪声。

7. 障碍物类型融合 (call type fuser)

入口函数位置:

https://github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/type_fuser/sequence_type_fuser/sequence_type_fuser.cc

/**
   * @brief Fuse type over the sequence for each object
   * @param options Some algorithm options declared in BaseTypeFuser
   * @param objects The objects with initial object type
   * @return True if fuse type successfully, false otherwise
   */
  bool FuseType(const TypeFuserOptions& options,
                std::vector<ObjectPtr>* objects) override;

该部分负责对 object 序列 (object sequence) 进行类型 (type) 的融合。

object 的type 如下代码所示:

enum ObjectType {
  UNKNOWN = 0,
  UNKNOWN_MOVABLE = 1,
  UNKNOWN_UNMOVABLE = 2,
  PEDESTRIAN = 3,
  BICYCLE = 4,
  VEHICLE = 5,
  MAX_OBJECT_TYPE = 6,
};

Apollo 将被追踪的objects 视为序列。

当 object 为 background 时,其类型为 “UNKNOW_UNMOVABLE”。

当 object 为 foreground 时,使用线性链条件随机场(Linear chain Conditional Random Fields) 和 维特比(Viterbi)算法对 object sequence 进行 object 的类型的融合。

由于笔者对统计学习方法上的积累不足,为避免误导,这部分就不过多叙述了。由于该部分没有任何文档,大家如需更多的细节,请自行浏览代码,查看注释。

 

相关链接:

Apollo 2.0 框架及源码分析(零) | 引言

【http://link.zhihu.com/?target=https%3A//github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/type_fuser/sequence_type_fuser/sequence_type_fuser.cc】

参考来源:

[1] Apollo 2.0 框架及源码分析(零) | 引言

【http://link.zhihu.com/?target=https%3A//github.com/ApolloAuto/apollo/blob/master/modules/perception/obstacle/lidar/type_fuser/sequence_type_fuser/sequence_type_fuser.cc】

[2] Apollo 自动驾驶感知技术分享

[3] PPT资料 | Apollo 自动驾驶感知技术

[4] 专栏 | 如何利用激光雷达检测车道线?这里提供了4种方法

【https://www.leiphone.com/news/201712/iG2xBYren1q9faI9.html?utm_source=debugrun&utm_medium=referral&viewType=weixin】

[5] Apollo 2.0 框架及源码分析(一) | 软硬件框架

【http://link.zhihu.com/?target=https%3A//www.leiphone.com/news/201712/iG2xBYren1q9faI9.html%3Futm_source%3Ddebugrun%26utm_medium%3Dreferral】

[6] 3D 障碍物感知

【https://github.com/ApolloAuto/apollo/blob/master/docs/specs/3d_obstacle_perception_cn.md】

[7] 交通灯感知

【https://github.com/ApolloAuto/apollo/blob/master/docs/specs/traffic_light.md】

[8] 交通信号灯识别模块caffe net的输入与输出层问题 · Issue #2547 · ApolloAuto/apollo

【https://github.com/ApolloAuto/apollo/issues/2547】

[9] 扫描线算法完全解析

【https://www.jianshu.com/p/d9be99077c2b】

[10][多源信息融合(第二版)]韩崇昭等著 清华大学出版社

[11] 趣写算法系列之–匈牙利算法 – CSDN博客

【http://blog.csdn.net/dark_scope/article/details/8880547】

[12] 卡尔曼滤波器的原理以及在matlab中的实现

【https://v.qq.com/x/page/o03766f94ru.html】

Open Software Platform开放软件平台[开源模块]专业介绍

Open Software Platform 开放软件平台:为 Apollo 的核心部分。

Apollo 的 Open Software Platform 分三个部分 RTOS,Runtime Framework 及各个功能模块 Modules。


RTOS

Apollo RTOS的主体部分是为Ubuntu 14.04+ApolloAuto/apollo-kernel 【https://github.com/ApolloAuto/apollo-kernel】的组合。 Apollo 安装指南上推荐的在工控机上安装的软件及版本如下表所示。

无人驾驶|自动驾驶论坛(图片点击可以放大) 【百度无人驾驶QQ群,519034368 】

 

Apollo-kernel 的存在是因为 Ubuntu 并不是一个实时系统。

实时系统的定义如下:实时操作系统(RTOS)是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统做出快速响应,调度一切可利用的资源完成实时任务,并控制所有实时任务协调一致运行的操作系统。提供及时响应和高可靠性是其主要特点。[实时操作系统_百度百科]【https://baike.baidu.com/item/%E5%AE%9E%E6%97%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/357530?fr=aladdin】

简单说,实时系统保证了某任务在 deadline 之前被完成。系统的实时性在汽车上的重要性是不言而喻的,我用一个视频来说明下其重要程度 (最早看到这段视频是在学校实时系统课的官网上,十分震撼,二话没说直接就去上课了…)。

针对于此,百度在 Ubuntu 的基础上提供了 Apollo-kernel 来保证系统的实时性。Apollo-kernel主要使用了一个名为 PREEMPT_RT 【https://rt.wiki.kernel.org/index.php/Main_Page】的 Linux 实时补丁包,并在此基础上做了部分改动。这个实时内核在非生产环境下不是必须的,在生产环境中 Apollo 官方是建议安装这个内核的[5]。


Runtime Framework
Runtime Framework 是 Apollo 软件的运行环境,即 ApolloAuto/apollo-platform 【https://github.com/ApolloAuto/apollo-platform】的部分。Apollo 的 Runtime Framework 是一个定制版的 Robot Operating System (ROS) 。针对 ROS 系统的不足百度对 ROS 做了以下三方面的改动 [6] 。

ROS Decentralization Feature 去中心化
High Efficient Communication based on Shared Memory Transport Feature 共享内存以提升节点间的传输效率
Native Support with Protobuf Feature 原生支持 Protobuf

这里有篇文章很详细的阐述了 Apollo 对 ROS 的做的改动,我在这里只把这三点再简单叙述一下。

 

《干货曝光(二)|资深架构师首次解密Apollo ROS有何不同!》

【https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/nYVuqYvUBdBJ84EdNaF1dQ】

 

去中心化

ROS 在安全性上的一个不足是 ROS 需要有一个节点作为主服务器,用于建立各节点之间的通信连接。这一机制使得ROS节点的容错性增强,各模块的隔离程度增高,但也带来了单点失效(single-point failure)的风险。由于 ROS 本身缺乏针对这种状况的异常恢复机制,当服务器宕机时,整个系统会崩溃。这种情况如果发生在自动驾驶行驶过程中,无疑会造成车毁人忙的后果。针对于此,Apollo 采用了FAST RTPS (real-time Publish/Subscribe)【https://github.com/eProsima/Fast-RTPS】来实现去中心化。

共享内存

ROS节点之间的通信是通过 socket 完成的,在进行数据广播的时候,底层使用的是多个点对点的传送。这种方式速度比较缓慢,且使用了较多的资源。Apollo 使用共享内存的方式对其进行改进,加快了通信速率,减少了CPU损耗。

支持Protobuf

Apollo 将 Google 的 Protobuf 【https://github.com/google/protobuf】与 ROS 深度集成,用于提高数据的版本兼容性。其优势在于当模块接口升级以后,通讯的数据也可以相互兼容。另一个好处是宝贵的自动驾驶的历史数据在模块升级后也可以一直被使用。

其实关于 ROS 的优劣和在自动驾驶中的修改解决方案在不少文章中都有被提到。我比较喜欢的是刘少山团队的介绍。

 

《【无人驾驶系列】基于ROS的无人驾驶系统》
【http://geek.csdn.net/news/detail/78099】

用下面这个表格来对比下百度和刘少山对 ROS 的改进。

无人驾驶|自动驾驶论坛ROS 的不足及改进

(图片点击可以放大) 百度无人驾驶QQ群,519034368 

 

由表格可以看出,

使用共享内存的方式来提升 ROS 节点之间的通讯是一个常用的方式。
刘少山团队使用了 Zookeeper 的机制来应对 ROS 单点失效的问题。也就是说,当旧的主节点失效之后,其余节点会选举出新的主节点,使得系统可以正常运行。这个问题上 Apollo 使用了 RTPS。
刘少山团队使用了 Linux Container 对 ROS 的节点进行了隔离,确保当某一节点被劫持时,此节点不会影响整个系统。在 Apollo 的文档里,我没找到相对应的信息。

此外再想提一下 ROS 实时性的问题,ROS 本身不是一个实时系统。而在汽车领域,对实时的要求却很高。虽然百度使用了 PREEMPT_RT 【https://rt.wiki.kernel.org/index.php/Main_Page】内核来增强了 ROS 所在的 linux 环境的实时性,但我不认为这会彻底解决 ROS 本身弱实时性的问题。如果这一问题能这么轻易的被一个补丁包解决的话,ROS 2.0 也不会进展如此缓慢了。想必百度也深知这一点,所以才会寻求和黑莓的嵌入式实时系统 QNX 的合作吧。

或许 Apollo 使用 ROS 也只是为了快速开发和推广宣传,在之后的量产过程中百度改用其他的框架也不是没有可能。

 


Modules

各个功能模块的实现是 Apollo 代码的一个重点,如下表所示 :无人驾驶|自动驾驶论坛

各个模块的简单描述许多文章都有介绍,这里就不再赘述了,具体可参考以下两篇文章。

《百度Apollo1.5 自动驾驶开源模块分析》

【https://zhuanlan.zhihu.com/p/29723447】

《百度无人驾驶系统Apollo 2.0小解析》

【https://zhuanlan.zhihu.com/p/32511462】

 

Apollo 工作的整体流程也可参考上面提到的这篇文章 xinhe sun:百度无人驾驶系统Apollo 2.0小解析 。为方便阅读,这里把它摘过来。

首先,用户输入目的地,routing模块就可以根据终点位置计算出具体的导航信息。激光雷达、毫米波雷达和摄像头拍摄到的数据配合高精度地图由perception模块计算出3D的障碍物信息并识别交通标志及交通信号,这些数据进入perdiction模块,计算出障碍物的可能轨迹,如此就可以结合以上信息并根据车辆定位模块localization提供的车辆位置由planning模块得到车辆应该走的具体车道。

得到轨迹后车辆control模块结合车辆的当前状态计算加速、刹车和方向的操作信号,此信号进入CAN卡后输出到车内,如此实现了车辆的自动驾驶。

在整个流程中,monitor模块会及时监测硬件及系统的健康状况,出现问题肯定就会中止驾驶过程。对于驾驶中的信息,用户可以通过web应用dreamview来查看。

对于各个模块的源码实现,可以参考这篇文章。这篇文章侧重分析各模块中函数的含义及代码技巧。虽然 Apollo 1.0 稍微旧了一点,但代码量相对小,阅读一下有助于理解系统的代码框架。


《百度 自动驾驶框架 Apollo 1.0 -源码分析》

【https://zhuanlan.zhihu.com/p/28708170】

无人驾驶|自动驾驶论坛

从此图可以看出定位高精度地图是 Apollo 整个软件体系的基石,在代码中也会看到高精度地图起到极其重要的作用。比如在感知环节,摄像头会通过高精度地图知道交通灯所在的位置,确定 ROI 区域,从而减少计算量。这个构架也佐证了,百度一定会试图在高精度地图上分一杯羹[8]。

为了后面方便叙述各模块的工作,这里简单介绍两个名词。在实时系统中一个任务的调度方式通常可大致分为两类:时间驱动(timer-triggered) 和 事件驱动(event-triggered)。

所谓时间驱动是指该任务的触发条件是时间,通常该任务为周期性的任务。比如某任务每 10ms 执行一次,那么该任务为时间驱动的任务。顺便安利嵌入式的一本书 《时间触发嵌入式系统设计模式》,思路清晰,相当实用。在 Apollo 的代码里,以时间为触发条件的模块都会提供 OnTimer 这样的一个接口。

事件驱动是指任务的触发条件为某一事件。例如驾驶员看到红灯会踩刹车,在这一操作中,踩刹车举动是由于红灯亮这一事件触发的。

了解这两个概念更有助于进一步了解每个模块是如何被调用的。接下来选几个重要的模块简单分析一下。

Localization

无人驾驶|自动驾驶论坛

 

在这个模块里,Apollo提供了两种定位模式:

1 RTK-based,即 GNSS+IMU的传统定位方式。 该模式属于 timer-triggered, 该模式下的定位应该会被周期性的调用。

2 多传感器融合 (Multi-sensor Fusion Localization),即 GNSS, IMU 和 Lidar 三者配合使用,完成定位。如下图所示,大体原理应该是这样的:首先比对 Lidar 采集的点云和事先建好的地图,得到 Lidar 的定位结果。之后 Lidar 定位 ,GNSS 定位,IMU 三者用卡尔曼滤波做融合。实现的具体原理参见这篇论文 [1711.05805] Robust and Precise Vehicle Localization based on Multi-sensor Fusion in Diverse City Scenes 【https://arxiv.org/abs/1711.05805】。无人驾驶|自动驾驶论坛

Robust and Precise Vehicle Localization based on Multi-sensor Fusionin Diverse City Scenes

 

Perception无人驾驶|自动驾驶论坛

Perception 模块有两个重要的内容,即3D障碍物感知和交通灯感知。

需要交通灯感知是因为 Apollo 2.0 是简单城市路况下的自动驾驶。在高速公路之类的限定场景的自动驾驶中,交通灯的检测就不是必须了。在这个部分中,摄像头只负责感知交通灯的状态,而不负责其他障碍物的检测。

3D 障碍物感知部分使用卡尔曼滤波,融合了 Lidar 和 Radar 检测的结果。正常情况下,视觉检测障碍物应该也是一个很重要的部分。Apollo 2.0 没有做视觉的融合,可能是因为时间有限,该功能要在下一个版本才出现?

有趣的是当我们观察 Apollo 官方库的时候,会发现有个分支叫 “mobileye_radar”, 该分支4个月前就停止更新了,但该版本中也未看到 mobileye 或者视觉检测障碍物的影子。这个名称说明百度曾经也考虑过使用 mobileye 的方案?不知道基于什么样的考虑,百度没有继续采用这个方案。这些背后的事情,想想很有趣啊。

 

个人倾向于百度藏拙了。因为在百度其自定位模块 elo (见下图)已经使用了摄像头来提取环境特征。那么用摄像头来完善感知部分,技术上肯定可以实现的。或者百度觉得现有的感知方案已经足够,即使再融合视觉对整体性能提高也有限。无人驾驶|自动驾驶论坛

 

Planning

Planning 模块是 timer-triggered的, 以固定的频率被调用,该模块提供了两种方案:

1. RTK replay planner (since Apollo 1.0)

2. EM planner (since Apollo 1.5)

这里引用下 @xinhe sun 的回答 [9]:

规划车道有两种方式,一种是提前把轨迹存入程序,然后根据车辆状态和位置提取轨迹,另外一种是实时计算。

Apollo 最新在开发的是 lattice planner 决策算法[10] (应该不属于Apollo 2.0 的范畴了)。

Lattice Planner是基于斯坦福大学在DARPA挑战赛中使用过的一种路径规划算法,优势在于更好地理解交通情况,降低规划的复杂性,高速情况表现优异。感兴趣的同学可以参考Moritz Werling等人所著论文:<Optimal Trajectory Generation for Dynamic Street Scenarios in a Frene´t Frame>
【https://pdfs.semanticscholar.org/0e4c/282471fda509e8ec3edd555e32759fedf4d7.pdf】

 

Control & Canbus

无人驾驶|自动驾驶论坛

如图所示,Control 控制模块提供了三个主要的数据接口: OnPad,OnMonitor 和 OnTimer。 其中 OnPad 和 OnMonitor 用于仿真和人机交互,OnTimer 用于周期性的指令传递。

控制模块以决策模块生成的轨迹为输入,计算出底层所需的具体的指令,比如加速,减速,转向等等,并通过OnTimer 接口,周期性的向 Canbus 传递控制指令。Canbus 一旦接收到具体的指令之后就会调用对应的函数执行此操作。与此同时,Canbus 也会周期性的向 Control 模块发送汽车当前的信息。

我比较好奇的一点是,Apollo 会怎么使用这些汽车底盘信息。光从图中的信息的流动上看,Control 端并没有继续向 Planning 或者其他上层模块发送底盘信息。但根据技术文档,在Planning 模块中这些信息有被使用 [7]。

Finally, the planning module needs to know the location (Localization: where I am) as well as the current autonomous vehicle information (Chassis: what is my status).

我的一个猜测是这些信息目前只被用于监控汽车的当前状况。从这一点看,Apollo 也许可以考虑在将来结合更多的底盘信息来进一步优化整个系统。譬如结合汽车的里程数、轮速等等来提高汽车定位的精度。

 


参考来源:

[1] 无人驾驶技术入门(二)——不会写代码也能做无人驾驶工程师
【https://zhuanlan.zhihu.com/p/32308457】
[2] 无人驾驶技术入门(一)| 百度无人驾驶的引路人
【https://zhuanlan.zhihu.com/p/31853845】
[3] apollo_2_0_hardware_system_installation_guide_v1
【https://github.com/ApolloAuto/apollo/blob/master/docs/quickstart/apollo_2_0_hardware_system_installation_guide_v1.md】
[4] 新年伊始,Velodyne带来利好消息:16线激光雷达价格减半 | 雷锋网
【https://www.leiphone.com/news/201801/UmBzORwaTe3xyqqq.html?viewType=weixin】
[5]【Apollo直答号】Apollo的实时内核必须安装吗?安装和不安装有什么区别?
【https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/pvGFyQEURrALN3-mnJMYag】
[6] ApolloAuto/apollo-platform
【https://github.com/ApolloAuto/apollo-platform】
[7] Apollo_2.0_Software_Architecture
【https://github.com/ApolloAuto/apollo/blob/master/docs/specs/Apollo_2.0_Software_Architecture.md】
[8] 大家都在关注百度Apollo,但你们的重点选错了 【图】- 车云网
【http://www.cheyun.com/content/17078】
[9] 百度无人驾驶系统Apollo 2.0小解析
【https://zhuanlan.zhihu.com/p/32511462】
[10] 【Apollo直答号】Apollo2.0的定位模块中LiDAR定位使用的预建地图是LiDAR-SLAM地图吗?
【https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/p4FRpJb6dj6Wdf8EzQpfig】


* 原文来自知乎,作者冀渊
https://zhuanlan.zhihu.com/p/33059132

Apollo高精地图技术与应用

文章摘自:Apollo开发者社区

百度是国内唯一一家既拥有高精地图领先技术,又能提供自动驾驶完整解决方案的全面布局的公司。百度 Apollo 拥有国内最领先的自动驾驶技术,以及国内最大的自动驾驶车队,因此 Apollo 高精地图堪称“最懂自动驾驶”。

公开课里,百度高精地图编译团队技术负责人王健老师为大家分享了“高精地图技术与应用”。

主要包含三个方面的内容:

1 高精地图在自动驾驶系统中如何发挥最大的作用;
2 百度高精地图如何制作、制作流程以及制作数据量的大小;
3 百度制作出的高精地图,百度开放了 Apollo 平台,愿意把地图一起开放,供做自动驾驶的公司和学术单位不需要重复造轮。

 

1,高精地图在自动驾驶中如何发挥作用?

Apollo 开放框架中,高精地图排在云服务的第一位,高精引擎排在开放软件平台的第一位。这是一个巧合,但也是重要性的显示。

从云端平台来看,仿真系统无法离开高精地图,仿真系统构建真实道路、真实路况,没有高精地图无法模拟真实路况,车无法从中训练和模拟。高精引擎,通过和云端高精地图联网,它可以为其他模块提供丰富的数据,主要从四方面对自动驾驶提供支持:一是定位,二是感知,三是决策,四是规划。

关于感知——很多地方离开高精地图就没有信号灯,没有参考的“老司机”。高精地图给感知带来的,除了“老司机”,还有探测范围。传感器可以探测 500 米还是 1 公里,只要高精地图有的都可以给你。比如汇入汇出时,你错过一个高速路出口需要多绕几十公里。有了高精地图,你可以提前 5-10 公里感知距离。

高精地图不仅包含数据,如果把地图理解为数据,有一定的局限性。高精地图是语义性的数据,这是图标,但这个图标代表什么意思,高精地图在制作过程中赋予语义信息。路灯、交通信号灯,交通信号灯到底有几个灯,带箭头还是不带箭头的灯,提前预知感知模块,减少运算量,提高感知算法的准确率,降低算法设计难度。如果传感器突然坏了,无法感知,如果有高精地图,它可以根据高精地图这个“老司机”继续行驶一段,直到安全的地方。这是高精地图的作用。

关于定位——高精地图如何定位?如果一辆车没有地图,只有相机、雷达,很难知道自己在哪个精确的位置。高精地图提供标准的位置,现在有一种低成本的设计方案,采用单目相机拍摄虚线和实线,把采集的图像和高精地图做比对,通过算法可以知道我当前在道路面的第几个车道。算出车道后解决定位问题,这是横向定位。纵向定位可以借助交通信号灯、路灯、灯杆等实现定位。

关于规划——如果车在路上发现前面有事故或者施工路面,这时候需要变道,高精地图提供了有利的支持。

关于决策——车到了十字路口,高精地图会采集安全岛的信息,复杂十字路口有安全岛,车在决策过程中需要参考安全岛等重要要素,否则这辆车冲上安全岛,导致行人发生交通事故。

在自动驾驶的几个重要模块,包括定位、决策、规划和感知。这几个模块离不开高精地图的支持,有了高精地图可以节省很多传感器的成本,这是加速量产的方法。在自动驾驶行业,暂时离不开高精地图的支持,如果在座各位提出低成本量产方案,可以离开高精地图,基本可以引领自动驾驶行业。

 

2,百度高精地图制作流程:

这些年百度和宝马、奔驰、大众和福特等 OEM 进行联合研发,侧面证明车厂对百度高精地图的认可。经过多年的研究和车厂联合开发,百度简化为以下流程:

一是外业采集,传送到内业借助 AI 算法,后来会谈到 AI 算法如何处理要素;

二是根据采集车采集回来的数据进行分类,校验准确性等;

三是人工验证阶段,算法只能保证 90%,为了保证百分百,必须加入人工校验步骤,人工校验结束后可以发布。现在有三级别产品,包括高精地图,三维地图可以满足自动驾驶需要;

四是 ADAS 地图应用广泛在卡车行业和乘用车行业。卡车拉着四五十吨货物,如果它能提前预知前方 1 公里外有一个大坡或者小坡,它可以灵活的控制发动机,进行节能技术的开发,我们跟 OEM 合作中有一定的项目经验。结合 ADAS 地图的卡车能耗节省 30%。地图需要持续的更新。

 

3,百度开放高精地图服务:

百度从 2013 年开始高精地图的研究,是国内唯一具备完整的自主知识产权,拥有从采集设备到数据加工全流程自主技术研发能力的高精地图数据提供商。百度 Apollo 平台的高精地图具备“精细化程度最高、生产效率最高以及覆盖最广”三大优势。

2017 年 9 月,百度对外发布了 Apollo1.5,对外开放了包括高精地图服务在内的五大能力。通过开放高精地图服务,Apollo 将帮助合作伙伴实现精准的自主定位,并为决策规划提供精确、充分的数据支持,让自动驾驶车辆掌握“全局视角”。

Apollo自动驾驶感知技术分享

文章摘自: Apollo开发者社区

1,感知技术是什么?

感知属于自动驾驶核心技术,本期沙龙,百度无人车感知核心算法技术负责人陈世佳首次对外公开宣讲了这部分内容。

为了让大家更好地理解,陈世佳老师将汽车上的感知与人类感官进行了类比:人有感知,通过感官器官获取外界信息,传达感知功能区,把形象化的东西抽象成概念性或者更高层的语义,供我们思维记忆、学习、思考或者决策,让我们运动控制功能区,让我们身体对外界进行反馈。无人车类似这样的结构,这是强相关的东西,我们无人车也是一样。

下图所示这辆车是 2016 年 12 月乌镇演示车队的其中一台,它有传感器、雷达、摄像头,这是覆盖比较全面的设置,包括视觉、触觉、嗅觉等信息。它需要大脑处理,大脑是无人车里的感知功能模块。

无人驾驶|自动驾驶论坛|51apollo.com

由于感知范围是广泛的,它依赖于人工驾驶或者自动驾驶需要的环境匹配,工况复杂度越高,感知复杂度越高。自动驾驶不同级别里,感知的复杂度也不同。Apollo 目前开放的定位是 Level3 或者 Level4,感知、决策、控制是三位一体的过程。

感知与传感器系统紧密结合,获取外部环境信息,比如有没有障碍物,障碍物的距离、速度等,把数据交给感知处理模块,我们会收集信息,构成人开车时理解的环境。这些信息会被我们决策模块进行分析和提取,在周围环境车辆行驶状况下,下一步怎么走才是安全的。控制模块会让车向前行,感知模块获得新的信息,不停循环,应对更新的环境状态,实现整体良性的循环。

无人驾驶|自动驾驶论坛|51apollo.com

2,核心:感知用来做什么?

感知的输入跟环境相关。只要符合条件,都可以被列为感知。在 Level3 和 Level4 里定义的细分任务,把输入输出具体化。

无人驾驶|自动驾驶论坛|51apollo.com

障碍物检测,包括人、车、石头、树木等。上图是点云输出,下图是图像感知示例。Level3 检测结果障碍物,对于 Level4 来说,不仅知道这是车,而且可以将其按大车、小车分类,因为大车和小车的开车方式不一样。不同的车,做出的决策规划不一样。你可以超小车,但无法超大车。

我们需要一个很细的障碍物分类,这根据输入的不同划分,有点云分类和障碍物中的分类。著名例子是红绿灯的识别,你需要判断交通灯的颜色。障碍物检测分类,我们得出障碍物信息,这样有利于我们做后续决策。我们要知道每个障碍物可能运行的轨迹,它会不会超车、插入车道或者无故变线,这需要障碍物跟踪。障碍物跟踪是很重要的模块。我们要运用障碍物,也有对场景的分析,我们点云也用到这个。

我们在图像级别会做类似的分割,目的是我们做场景建模和语义化的描述。我们有很多任务,每个任务输入是多源的,包括激光雷达、图像等。如果要用 Apollo 搭建感知系统,如何选择传感器、传感器配置?希望它做什么任务。

无人驾驶|自动驾驶论坛|51apollo.com

这是三种基本传感器的效果对比,LiDAR 是激光雷达,Radar 是汽车通用毫米波雷达,Camera 是摄像头。绿色代表做得好,黄色代表做得普通,红色代表做得差。最后,说明了三种传感器融合效果是最好的。

那么 ,感知系统开放模块怎么做?

无人驾驶|自动驾驶论坛|51apollo.com

点云感知。开放了 LiDAR 点云检测,可以判断点云里的每个点是否为障碍物,障碍物的类型是什么。

感知框架。用的是深度学习,它可以做到精准检测和识别。而深度学习非常耗费计算量。需要依靠搭建的车载智能系统,来支撑深度学习模型,以达到毫秒级感知。

高精地图。先以当前的激光雷达作为坐标系核心,把地图中的点投到坐标系里。然后建立快速的表格,根据感知的距离扩大坐标区域。之后对俯视图进行网格化,网格化参数可以在 Apollo 进行配置。最后输送给障碍物检测。

障碍物检测。分为特征抽取、点云检测、点云聚类、后处理、闭包提取。特征抽取,就是建立一个网格,每一个网格提取的信息对应一个值,每一个网格都有一个特征,拼接形成一张图;点云聚类,是用可信的网格做结果预测;后处理,是由于预测不准,对障碍物的判断会存在误差,所以要通过后处理来精确障碍物。闭包提取,是据朝向补全障碍物的形状。

障碍物跟踪。与障碍物检测相结合,检测结果和历史障碍物进行信息匹配,得出新障碍物列表。并且输出下一帧以什么速度怎样行驶,得出列表。

视觉感知。Apollo 之前版本的视觉感知数据,主要是红绿灯的数据。已发布的 Apollo2.0 同时开放红绿灯检测和识别算法,可以作为视觉感知的典型代表。

红绿灯识别。是根据当前车的位置查找高精地图,判断前方是否有红绿灯。如果有,高精地图会返回红绿灯的物理位置,同时采集视频图像。如果并排很多灯,需要准确判断影响决策的灯。