自动驾驶 Apollo 源码分析系列,感知篇:车道线检测基本流程

Posted frank909

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自动驾驶 Apollo 源码分析系列,感知篇:车道线检测基本流程相关的知识,希望对你有一定的参考价值。

前面的文章分析了 Apollo 6.0 代码中如何进行红绿灯检测,这篇文章介绍另外一个感知任务:车道线检测。
相关的文件路径整理如下:

1. 从 Component 出发

因为有了之前红绿灯检测代码分析的经验,我们自然能够知道感知任务先从它的 component 开始。
车道线检测的 component 是 lane_detection_component.cc
路径:

modules/perception/onboard/component

梳理代码可以得到基础的流程框架:

1.1 initConfig

initConfig 从本地目录中读取 proto 文件,然后配置变量。
那么,如何知道从哪里读取呢?
在 dag 目录下有配置。

我们只需找到 lane_detection_component.config 文件。

主要定义了输入输出 channel,frame 缓存数量,默认的相机离地物理高度。

1.2 initSensorInfo

起初我找不到有明显调用 initSensorInfo 的入口,后来发现了一段代码。

if (!EXEC_ALL_FUNS(LaneDetectionComponent, this,
                     LaneDetectionComponent::init_func_arry_)) 
    return false;
  

猜测应该是批量初始化数组列表中的函数,所以,我又去查看 init_func_arry_ 的定义。

果然如此,将函数指针全存储在数组当中,然后通过一个命令统一初始化。
回到 InitSensor 的逻辑,主要是检查相机数量是不是 2 个。
Apollo 只支持 2 个相机,多一个少一个都不行。
检测相机数量后,又初始化一个 sensormanger,然后再获取图像的宽高,frame id 信息就完成了。

1.3 initAlgorithmPlugin

代码非常简单,创建一个 LaneCameraPerception 对象,然后赋值给 camera_lane_pipeline_ 并初始化。
我们应该能够察觉到 LaneCameraPerception 是算法核心实现类,后面我们将重点关注它。

1.4 initCameraFrames

这个方法中,会初始化 CameraFrames 容量为 20,猜测是为了进行车道线的缓存及进行算法的跟踪。
然后,针对每个相机初始化 data provider。
并设置相机内参、外参、相机高度最终得到针孔模型。
针孔模型是为了进行坐标变换。

1.5 initProjectMatrix 和 initMotionService

这 2 个方法用来初始化投影矩阵和监听 motion service。
motion service 用来接收车辆运动信息。

1.6 initCameraListeners

给每一个相机绑定一个 onReceiveImage 回调函数,然后创建一个 CameraReader.

这部分代码和红绿灯检测没有什么不同,当通道中有图片,onReceiveImage 将被触发,之后进行相应的算法逻辑。

1.6 onReceiveImage

Apollo 中数据传输并不一定能保证有序传输,有时会收到一些过期的信息,在车道线检测中,这种数据将不会进行处理,apollo 代码注释说是要保证 monotoic。
onReceiveImage 核心代码是调用 InternalProc,所以我们还得看看这部分代码在做什么。
其实也很简单,获取世界到相机坐标转换矩阵和相机到世界转换矩阵。
在线标定,得到 Pitch 和 height 结果。
然后调用 camera_lane_pipeline_ 的 Perception 方法,之后发送结果就没有了。
我们注意力将转到 camera_lane_pipeline_ 中来,前面讲到,它是最核心的算法实现。

2. lane_camera_perception 算法逻辑


看名字都能获知,LaneCameraPerception 中最重要的 3 个成员变量是 lane_detector,lane_postprocessor_,calibration_service_。

猜测,车道线检测前在线标定,然后进行检测,检测后的结果进行后处理生成最终的车道线结果。

我们阅读它的 Perception 代码,可以得到这样的流程图:


其实整个过程非常的清晰,主要是 detector 和 postprocessor 进行车道线检测和后期处理,如果重点不是在算法实现而是算法集成上,关注到这一层面就可以了。

当然,我们想了解更多的细节,比如车道线如何表示,如何存储,所以,我们还需要更深入代码。

2.1 CameraFrame 及 LaneLine 表示

之前的文章分析红绿灯检测提到过 Apollo 将检测到的信息存放在 CameraFrame 这个数据结构当中,车道线检测也是一样的。


我们自然需要关注 base::LaneLine 这个数据结构,在 CameraFrame 中它是用 vector 存在的。


如何定义一条车道线可以很简单,也可以很复杂。

Apollo 中车道线相关的数据还是很多的。

LaneLineType,车道线类型。

Apollo 中只定义了 4 种车道线类型:

  • 白虚线
  • 白实线
  • 黄虚线
  • 黄实线
    实际驾驶过程中,车道线远远不止这 4 种,但大多数情况,这样是够了的。
    LaneLinePositionType,车道线位置关系

    相对于 Ego Lane,也就是当前车道,感知系统检测到的车道线可能的相对位置被定义到向左 5 条,向右 5 道。

并且,当车辆正在变道时,可能出现车道线在车辆中间位置,又或者是车道中间长长的箭头标志被定义为 EGO_CENTER。
LaneLineCubicCurve,车道线表达式。

车道可能是直的,也可能是弯曲的,用三次曲线表达式就可以拟合车道线。


EndPoints,端点
这个按照描述应该是物理位置,车道线的两端。

2.2 LaneDetector

在 LaneCameraPerception::Init 中有这么一段代码:

Apollo 支持不同的车道线检测模型,具体采用哪个模型通过配置文件提前指定,这个文件是 lane.pt。

我们可以看到采用的是 dark_scnn 模型。

SCNN 是 2018 年提出的模型,S 是 Spatial 的意思,最大的 ideal 在于提出了有效利用空间关系,毕竟车道线不同于其它目标,它在特定方向是细长且连续性较高的。

detector 的工作非常简单,就是进行图像推理,然后给出所有像素每个像素是车道线标志的概率值。

所以,可视化这一过程,可以用 opencv 将 net output 的结果生成概率图。

2.3 postprocessor

既然 detector 的工作量非常简单,那么,主要工作便自然是集成在 postprocessor 身上。
我们先来看 process2D 的过程。

主要的工作就是筛选合格的车道线标志,拟合,通过位置关系设置车道线类型,但代码非常复杂,在本篇文章不再讲述,后面会用一篇文章来做详细分析。

process2D 之后还有 process3D 函数,主要借助投影关系做车道线的物理世界坐标拟合。

总结

从红绿灯检测到车道线检测,可以看到算法并不是很难,但魔鬼是藏在细节当中,如何高效应对现实场景可不是单单复现论文上的算法就完事了的,本文简单过了一下车道线的基本流程,思路大致也理顺了,但篇幅原因,很多细节还没有弄明白,后面会专门写文章进行分析。

以上是关于自动驾驶 Apollo 源码分析系列,感知篇:车道线检测基本流程的主要内容,如果未能解决你的问题,请参考以下文章

自动驾驶 Apollo 源码分析系列,感知篇:车道线 Dark SCNN 算法简述及车道线后处理代码细节简述

自动驾驶 Apollo 源码分析系列,感知篇:感知融合代码的基本流程

自动驾驶 Apollo 源码分析系列,感知篇:感知融合代码的基本流程

自动驾驶 Apollo 源码分析系列,感知篇:感知融合代码的基本流程

自动驾驶 Apollo 源码分析系列,感知篇:感知融合代码的基本流程

自动驾驶 Apollo 源码分析系列,感知篇:Perception 如何启动?