在自动驾驶技术中,软件系统是最具有技术壁垒的领域之一。近年来,国内外成立了不少自动驾驶创业公司,花费了大量人力物力投入到自动驾驶软件系统的开发中,每一行代码、每一个专利,都是它们未来竞争的底气和资本。可以说,自动驾驶软件系统是上述公司最核心的资产之一。
百度是国内最早投入自动驾驶技术研发的公司之一。Apollo是百度发布的向汽车行业及自动驾驶领域合作伙伴提供的软件平台,不仅在全球各种权威自动驾驶榜单中成绩斐然,也在商业化推进上有着惊人的速度。下图是Apollo开源项目的系统架构。
图1. Apollo 6.0 Architecture
图片来源:Apollo项目GitHub地址https://github.com/ApolloAuto/apollo
其中,Open Software Platform指的是Apollo开源软件平台,它是图中位于右侧的各个子模块的总称。RTOS(real-time operating system)是实时操作系统,实时性是它的最大特征,它为上层功能模块的高效执行提供底层环境。
Map Engine是地图引擎,在软件中负责获取各类地图数据,并提供相应的地图数据功能接口。Localization和Perception分别是定位和感知模块,是处理汽车周围环境信息的功能模块,负责将各类传感器收集到的数据进行加工和处理,用结构化的结果来描述汽车周围的场景。Planning是规划模块,负责对结构化的场景信息进行下一步的处理,通过计算得到一条安全可通行的路径。Control是控制模块,负责把规划的结果转换成对电子油门、电子刹车和电子转向的控制信号,最终实现对车辆运动的控制。
这其中,感知模块需要对大量的传感器数据进行实时处理,需要准确且高效地识别场景信息,因此是最有工程挑战的子模块之一。
下图是Apollo开源项目的感知框架图,可以看到整个感知部分的结构是比较复杂的,多条数据处理路线并行展开,每一个节点子模块都会涉及到很多算法处理,同时,这些路线之间还会有相互的数据交换,最后,综合处理多条路线的结果得到感知模块的输出。
图片来源:Apollo项目GitHub地址https://github.com/ApolloAuto/apollo
下文将简单地剖析一下感知模块的框架,从数据流动的维度讲述感知模块是如何运行的。
我们先从简单的模型入手,把感知模块拆分成数据输入、数据处理、数据输出三个部分,再分别对每个部分进行深入探讨。
数据输入过程
首先是数据输入端。数据输入主要包括激光雷达、车载相机、毫米波雷达(以下分别简称Lidar、Camera、Radar)等传感器的场景数据采集,以及从车辆底盘获取自车信号(速度、加速度、转向等)。此外,感知还会依赖到高精地图等信息。
下面主要探讨Lidar和Camera的数据输入过程。
Lidar数据输入
Lidar的工作原理,是通过激光照射到物体表面接收到反射光,从而计算反射点相对发射器的空间坐标。不论是哪种类型、哪个厂家的Lidar,最终输出的数据本质上都是大量反射点的信息集合。这些空间中大量的反射点集合在一起就是我们经常称呼的点云(point cloud)。
图片来源:Velodyne官网 https://velodynelidar.com/
上图是将Lidar点云可视化以后的结果,为了直观,图中用了颜色梯度来表示点的距离。Lidar设备端口发出来的原始数据并不是我们在图中看到的这样,感知模块需要对原始数据进行处理才能得到合适的数据形式。
这里先介绍一下点的描述参数。如下图所示,单个反射点的信息主要包括空间信息、时间戳和反射强度。其中,由于Lidar的工作原理不同,机械旋转式Lidar通常会使用径向距离(radius)、俯仰角(elevation)、方位角(azimuth)来表示点的空间位置,而半固态/固态Lidar,通常使用笛卡尔坐标系下xyz来表示点的空间位置。
虽然不同的Lidar设备厂家描述点云的数据形式不同,发送出来的格式不同,但究其本质都是对点云的数据描述。因此,各大厂商的Lidar发送出来的数据结构都是类似的。在此,我们以Velodyne 16线Lidar为例讲述。
下图所示是Velodyne 16线Lidar在单回波模式下发送的数据结构。每一个区域里都存放着固定字节大小的数据,整个区域组成了数据包(Data Block)。每个 Data Block里面包括了Head(数据头部区域),Data(点云数据存放区)和Tail(数据尾部区域,有些Lidar硬件不发送尾部数据域)。Head和Tail里面通常存储一些用来通信校验的数据以及Lidar的自身工作状态参数等,Data域里包含很多Channel域,每一个Channel里存放着一个点的信息。固定数量的点云数据整齐而紧密地排列在一起,组成了中间的Data域。最后打包成的Data Block是一块Bytes大小固定的数据区。
图6. Velodyne 16线Lidar发送的数据包结构示意图
Lidar的工作模式有单回波和双回波之分。简单来讲,Lidar发射出来的激光束是有面积的,打在物体表面的是一小片区域,如果激光束刚好打在了物体边界,那么就会有一部分留在近处的物体上,另一部分触碰到更远的物体,这时一个发射信号就会有两个回波信号,这就是双回波原理。在单回波模式下,可以选择光强高或低,距离近或远的回波。而通常情况下,双回波模式的数据大多都是重复的,所以,一般都会选择让Lidar在单回波模式下工作,并选择光强高&距离近的回波作为原数据。
在数据发送方式上,有些Lidar厂商选用网线连接工控机,基于UDP传输协议(User Datagram Protocol)发送Lidar硬件打包好的Data Block。
UDP是一个无连接协议,传输数据之前的源端和终端不建立连接,不维护连接状态以及收发状态,也就是说,在UDP方式下Lidar设备只需要单方面发送数据,并不需要考虑通信是否成功。选择这样的发送方式可以保证Lidar向下游的发送不间断、无延迟,在提高发送效率的同时,也保证了Lidar硬件和下游系统的独立性。
通过这种方式,Lidar设备将打包好的点云数据发送出去,下游系统通过预先设置好的通信协议接收数据并进入点云数据的处理流程。
Camera数据输入
以RGB图像为例,一张图像是由红绿蓝三种颜色通道叠加而成的(见下图)。在一个通道内使用数字来表示颜色,比如在红通道中用8位bit来表示红色,也就是用十进制下的0~255(2=256)来表示红色。如果一张照片的像素是1280*960,那么这张照片的红色通道(R通道)就是1280*960大小的数字矩阵,矩阵中每个位置上都填写着0~255的数字。再结合绿色和蓝色通道,这张RGB图像就可以用1280*960*3大小的数字矩阵来表示。
图片来源:https://www.geeksforgeeks.org/matlab-rgb-image-representation/
车载相机主要由镜头、感光传感器和图像信号处理器(ISP)组成。其中感光传感器主要是用CMOS,它的作用是将场景的光信号(模拟信号)转换成对应的电信号。接下来,电信号再通过ADC(模数转换器)转化成数字信号输送给ISP,在ISP上进行图像处理(自动曝光、自动白平衡、自动对焦、暗角修复等),处理完成后通过标准的SCCB总线(I2C总线)接口和外部进行通信,输出RGB格式(或YUV格式)的图像。
一般情况下,需要驱动(driver)来控制CMOS模组工作,相机厂商一般会把开发好的驱动提供给使用者。将driver部署在自动驾驶平台的软件端,就可以控制外接CMOS模组的工作状态了。通过调用相机厂商提供的API(application programming interface,应用程序接口),感知模块可以访问这些原始图像数据,这样,Camera数据就传入了感知模块中。整个过程可以用下图简单表示。
Lidar数据拥有丰富的空间信息,而图像数据拥有着物体几何、色彩纹理的特征。
下图十分清楚地展示了三类传感器之间的优劣势。Camera在物体分类和车道线检测中表现较好,但是在恶劣天气、弱光条件下表现不佳;Lidar在物体检测和弱光条件下的性能较好,但是在车道检测上表现较差;Radar在恶劣天气和弱光条件下表现较好,但是在物体分类和车道线检测上表现不行。使用多种传感器数据,根据它们各自的工作特点,优势互补,可以使整体数据在多个衡量纬度上表现优异,提高对场景的数据表示性和数据鲁棒性。
接下来,传感器将大量的原始数据传输给下游,由感知模块依靠丰富的算法手段进行进一步处理。
数据处理过程
从上游获取到感知模块需要的数据以后,感知模块就需要对这些数据进行处理。参考Apollo开源项目的感知框架图,我们可以看到Lidar、Camera、Radar的数据是先分成三条线路处理的,然后再把各条路线的数据进行融合,得到最终的输出。
简单来讲,可以分为传感器数据预处理、检测、跟踪和融合。
预处理模块
预处理模块主要是将从上游接收到点云和图像数据进一步转化成算法需要的形式。
首先是点云的预先处理。把上游发送来的Data Block去除Head和Tail以后,按照定长字节读取,就可以获得每一个点的元数据,通过空间变换可以计算出点相对Lidar设备的空间坐标xyz,之后再把坐标、时间戳、强度等信息存储在特定的数据域或结构体中。最后,把Lidar完整走完一周的数据放入集合中作为一帧点云数据。使用可视化工具或开源库(例如PCL,https://pointclouds.org)就会呈现出类似图3的点云图。
对图像的预处理主要包括对图像的剪裁、图片灰度处理、缩放、数据增强等操作,这一步的目的是适配神经网络输入数据的尺寸和形式,同时也减少了输入神经网络的数据量,从而减少计算资源占用。
预处理模块都是对于各类传感器数据的简单处理,占用计算资源较少,实现过程也比较容易。
物体检测模块
点云数据带有明确的空间位置信息,工程师们通常使用它来检测车辆、行人、路侧栏杆等物体。点云数据的检测可以依赖传统算法和深度学习网络。传统的障碍物点云检测算法,依靠点云的几何特征,通过分割聚类等方法输出障碍物;深度学习算法则是通过对标注的障碍物点云进行模型训练,再使用模型检测实际场景点云数据。实际工程中可以仅使用深度学习算法,也可以将传统算法和深度学习算法结合运用。
对于图像数据,工程师们主要依靠深度学习的方法来处理。自动驾驶公司大都会选择自主开发检测模型,通过训练标注好数据的真值图像,调整神经网络的结构和参数,以达到最优的检测结果。
物体检测是最具工程挑战的部分之一。在车辆高速行驶的过程中,如果错误地检测了某个障碍物体,会导致错误的车辆控制,最终可能带来非常严重的后果。如何快速且准确地识别障碍物是一项极具挑战的任务。
传统的计算机视觉算法(Harris角点检测、SIFT算法、SURF算法、ORB算法等)相比于深度学习算法,检测速度慢、准确率低,很难满足大多数场景下自动驾驶的需求。此外,这些传统的视觉算法都需要对图像提取特征,换句话说就是,对数据在更高、更多的维度上寻找特点,提取特征的过程通常比较复杂。相比于深度学习这种从端到端的算法,传统的视觉算法难以开发,不具优势。深度学习算法的应用,极大地加速了物体检测领域的发展,同时,这也对深度学习算法广泛应用于自动驾驶技术,起到了至关重要的作用。
深度学习算法确实有很多优点,但同时其输出结果是概率分布式的,不能保证检测的完全准确性。在今天,这种端到端算法的中间过程仍然是不可解释的,大家无法解释为什么输入一张照片,就会在它的某个区域内检测到某个物体。而传统的视觉算法却具有成熟的理论支撑、透明的中间过程和稳定的性能。
笔者比较看好传统计算机视觉算法和深度学习算法两者结合,可以充分利用两种方法的优势。
自动驾驶公司一般都会花费大量力气投入到检测模型的开发中。同样,网上也有很多开源的图像检测模型和点云检测模型,为学者、工程师们提供一些开发支持。
Lidar和Camera的物体检测,虽然数据输入端不同,但是通过神经网络后,输出的检测物体都具有同样的数据结构。其中主要包括被检测物体的类型、ID、包围框(bounding box,框顶点集合,2D或3D形式)、横纵向距离、速度、加速度、置信度等等。当然,不同类型的被检测物体也会存在不一样的描述特征,比如红绿灯类型的障碍物就需要颜色信息,车道线类型的物体不是输出包围框,而是输出若干个车道线上的关键点(通过连接这些点构成完整的车道线)。
跟踪模块
在单帧数据中检测到我们关注的物体后,我们仍然需要知道障碍物(车辆、行人)是如何运动的,场景中障碍物的运动信息(轨迹、速度变化等)对于后面预测这些障碍物运动轨迹和控制自车运动至关重要。想要得到障碍物的历史运动状态,就需要一些历史帧信息,从连贯的历史帧信息(例如,研究从历史第10帧到当前帧这一段连续时序过程)中挖掘出物体的运动状态。
通常使用的方法是,分析前后帧内检测到的同一障碍物。如果上一帧出现的物体在下一帧也出现了,那么前后帧内出现的同一个物体可以用相同的ID来表示,并把检测结果按照时间序列存储,这样就得到了某物体在两帧时间内的运动状态,包括速度变化、位置变化、方向变化等等。在连续帧内不断使用上述过程,就能得到物体的历史运动状态。以上就是一个跟踪过程。
那么工程师们是如何确认前后帧中的同一个物体呢?通常情况下,是使用ROI(前后帧内物体包围框的交并比)这一指标来匹配。以图14为例,在T时刻检测到了A和B两个障碍物,在T+1时刻检测到了C、D和E三个障碍物。其中包围框(bounding box)可以表示被检测物体在图中的位置。根据bounding box的位置,很容易发现A与C的重叠度很大,B与E的重叠度也很大,那么就有理由相信,在这连续的两个时刻内,A与C,B与E都分别代表了同一个物体。
当然,这种方法成立有一个前提,一般在10帧率的检测过程中,也就是0.1s内,被检测物体的运动位置变化不是足够大,这样才能有前后帧bounding box的重叠度,才能用来衡量匹配过程。
在跟踪模块,通常使用的方法有卡尔曼滤波和匈牙利匹配算法。第一步,使用卡尔曼滤波阶段一对上一帧物体进行运动估计,估计出其当前时刻的位置和速度。接下来,使用匈牙利算法对上一步中运动估计出的物体和当前帧检测到的物体进行匹配,匈牙利算法中使用的匹配权重就可以结合上面所讲的IOU方法来设计,成功匹配代表跟踪成功。第三步,通过卡尔曼滤波阶段二更新被跟踪物体当前的最优位置和最优速度。
此外,还有使用神经网络直接检测障碍物并输出跟踪结果的方法,也就是说,跟踪过程也是在神经网络中完成的。这种方法需要使用连续帧数据训练网络,在使用该类神经网络时,输入端就是连续帧的图像或点云,输出端仍然是一系列被检测到的物体,额外的信息是这些物体已经具有了被跟踪的属性,在时间序列上,同一个物体具有同一个跟踪识别ID。在网上,很容易能找到一些开源的跟踪网络模型。
融合模块
当前,自动驾驶技术需要使用多种传感器,并对这些不同传感器数据进行处理,最终输出单一结果,这就涉及到数据融合。融合又可以分为前融合和后融合,它们的区别在于,融合过程是在检测和跟踪过程之前还是之后。在Apollo感知软件框架中,融合过程位于不同传感器数据跟踪处理之后,是一个典型的后融合过程。整个过程就是将Lidar、Camera、Radar三条处理路线输出的追踪结果进行融合,修正被追踪物体的各项参数数据。
举个简单的例子,假设Lidar、Camera、Radar三种传感器的时间已经同步,对于同一帧内的被检测物体A来说,使用Lidar数据检测出了结果a1,使用Camera数据检测出了结果a2,使用Radar数据检测出了结果a3,融合过程就是在这三个结果中,通过一定的策略得到最优的检测结果。
这里的融合策略会综合考虑各类传感器的优劣势,比如Lidar在测距方面的表现性能更好,那么对于距离或者空间位置的参数,就会更倾向于选择通过Lidar数据得到的结果。对于交通标志、红绿灯、车道线这类物体,后融合的过程会更倾向于选择通过Camera数据得到的结果。基于这样的考量,就可以将多类传感器数据的感知结果进行融合,从而得到最优解。
另一种前融合的方式是,在数据输入端就将Lidar、Camera等数据组合起来,形成一种新的数据体,这个数据里既有图像纹理、颜色这样的信息,同时也包含了3D空间中的距离信息,这种数据结构加强了不同传感器数据之间的数据关联性。
上图简单介绍了Lidar和Radar数据融合的逻辑。在首次获取数据时,先初始化Kalman滤波器的各个矩阵参数,接下来对两类数据进行预测和更新,最后输出融合后障碍物的位置、速度。[1][2]
上图简单介绍了智行者科技在传感器数据前融合过程中的步骤,包括了采集及预处理、坐标转换和信息融合。其中最有难度的部分就是信息融合,它包括了数据关联、时间同步、数据融合、目标生命周期管理四个步骤。首先是在数据关联阶段,建立评价指标并选择全局最近邻匹配方法;第二步对多传感器进行时间同步,选用了固定时漂和随机时漂的方法; 第三步,使用粒子滤波算法融合数据;最后,对目标进行生命周期管理,提高融合后数据的鲁棒性。
同样地,通过标注前融合数据并训练模型,可以得到适用于前融合数据的神经网络,这种模型的输出结果也是对同一物体的检测结果,然后再进行物体跟踪,不同的是,检测跟踪之后,不需要再次融合。
目前,很多主流公司都在朝着前融合的方式研究,这样做的好处之一就是提前耦合了多种传感器的数据,因此数据维度更加丰富,更能真实地反映场景。
数据输出
在Apollo的感知框架中,最终输出到下游的结果可以分为两部分,一部分是车辆、行人等障碍物的数据,另一部分是交通信号灯的结果。场景中障碍物的数据可以通过上述的数据处理过程得到,而交通信号灯的结果则是通过一种多灯投票机制来得到的。简单来讲,就是在一个路口处,一般会有多个红绿灯被检测到,这其中既有红灯又有绿灯,也有其他不同类型的灯(直行、左转、掉头等等),需要设计一种投票机制来决策当前直行、左转或右转的单一灯状态。
原始的传感器数据经过感知模块的处理,输出的结果会传递到下游预测模块。预测模块主要是针对车辆、行人这类障碍物,预测障碍物在未来一段时间内的运动轨迹。然后便进入了规划模块,结合场景信息和障碍物预测轨迹,进行自车的路线规划。
小结
从各类传感器数据采集开始,到输入到感知模块,再经过检测、跟踪、融合这些过程,最终向下游输出场景内的必要信息。本文简要地介绍了一下感知模块的数据流动。
当然,感知模块还会涉及很多其他功能,比如利用地图数据增强感知结果、自动录入采集数据、功能监控和失效诊断等等。这些功能之间也存在着复杂的数据交换,在感知模块运行的过程中共同作用。
[1] 《开发者说丨手把手教你实现多传感器融合技术》
https://mp.weixin.qq.com/s/th-Wq5wA9CChIFEioBfpKw
[2] 《开发者说|Apollo 6.0 Perception 模块 Fusion 组件(二):主体算法流程分析》
https://mp.weixin.qq.com/s/aik25sYdNebYbMcR-6SKNA