查看原文
其他

开发者说|Apollo 源码分析感知篇(五):车道线检测基本流程

Frank909 Apollo开发者社区 2022-07-29


下面是由社区开发者—Frank909提供的文章,之前讲到了Apollo 6.0代码中如何进行红绿灯检测,这篇文章介绍另外一个感知任务:车道线检测。

相关的文件路径整理如下:



因为有了之前红绿灯检测代码分析的经验,我们自然能够知道感知任务是先从它的 Component开始。

车道线检测的Component是lane_detection_component.cc路径:


modules/perception/onboard/component

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



1、initConfig

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



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



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


2、initSensorInfo


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

猜测应该是批量初始化数组列表中的函数,同时又查看了init_func_arry_的定义。



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


3、initAlgorithmPlugin

代码非常简单,首先创建一个LaneCameraPerception对象;


然后赋值给camera_lane_pipeline_并初始化。


此时我们应该能够察觉到LaneCameraPerception是算法核心实现类,后面我们将重点关注它。

4、initCameraFrames

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


5、initProjectMatrix 和 initMotionService

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


6、initCameraListeners

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



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


7、onReceiveImage

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




看名字方可获知,LaneCameraPerception中最重要的3个成员变量是 

lane_detector

lane_postprocessor_

calibration_service_



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


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



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


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


1、CameraFrame 及 LaneLine 表示

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



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



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

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


LaneLineType,车道线类型。



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

  • 白虚线

  • 白实线

  • 黄虚线

  • 黄实线


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


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


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

LaneLineCubicCurve,车道线表达式。

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



EndPoints,端点



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


2、LaneDetector

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



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



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


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


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


所以,可视化这一过程,可以用Opencv将Net Output的结果生成概率图。


3、postprocessor

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



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


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




从红绿灯检测到车道线检测,可以看到算法并不是很难,但需要注意细节,如何高效应对现实场景并不是仅仅复现论文上的算法就可以。本文简单讲解了车道线的基本流程,理顺了大致思路,但由于篇幅问题,很多细节还没有讲明白,后面将专门写文章进行分析。


*Frank909 微信公众号

https://mp.weixin.qq.com/s/t9tpwI8fHLuQE084AjRIXA


以上是"Apollo 源码分析感知篇(五):车道线检测基本流程"的全部内容,更多话题讨论、技术交流可以扫描下方二维码添加『Apollo小哥哥』为好友,进开发者交流群。

 


©️著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。




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

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