无人驾驶软件分为车辆端和云端2个部分,其中车辆端运行操作系统和无人驾驶系统,云端提供无人驾驶所需要的各种服务。
操作系统主要负责无人车硬件资源(包括传感器、系统总线、网络等)的管理以及计算资源的调度。对无人驾驶来说,操作系统的实时性非常重要,操作系统要保证任务能够在规定的时间内得到响应。
无人驾驶系统实现了无人驾驶所需要的各种算法,包括定位、环境感知、路径规划和控制等,无人驾驶系统是无人驾驶软件的核心部分。最后云平台提供了无人驾驶所需要的各种基础服务,共同构成了整个无人驾驶软件。无人驾驶整体软件框图:
从整体上看,自动驾驶包括存储、计算和基础服务3个基本需求。
无人车的软件算法都运行在操作系统之上,对操作系统最主要的要求是稳定性和实时性。稳定性体现在操作系统占用的资源少,出现故障之后系统不会奔溃,能够长时间运行。
实时性要求系统能够及时响应控制指令,工业设备、汽车电子、航空航天等领域都要求采用实时操作系统,因为在这些领域操作系统如果不能及时响应控制指令,会产生很严重的后果。试想一下在驾驶过程中发出了紧急刹车指令,而操作系统没有及时响应,会导致严重的交通事故。
那么什么是实时操作系统呢?
实时操作系统是指能够在规定的时间范围内响应系统指令的操作系统,这在自动驾驶中非常关键。以Apollo 5.0为例,采用的操作系统是Linux操作系统,而Linux不是实时操作系统,需要打上系统补丁之后,才能成为实时操作系统。
那么如何保证系统任务的实时调度呢?接下来看Linux采取的调度策略。
操作系统最基本的功能就是管理进程,Linux的进程调度采用的是CFS(Completely Fair Scheduler)算法,我们先看下没有调度和有调度的情况下的差异。
如图3.2所示单个CPU核心的情况下,左边是没有进程调度的情况,任务1在执行完成之后,会读取IO(内存、硬盘等)数据,这时候CPU会进入等待状态,CPU在等待的过程中没有做任何事情。而右边采用了调度策略,在CPU等待的过程中,任务1主动让出CPU,下一个任务就可以在当前任务等待IO的过程中执行。综上所述任务的调度合理的利用了CPU资源,使得CPU的利用率更高,从而使任务执行的更快。
Linux内核分为抢占式内核和非抢占式内核。非抢占式内核禁止CPU被抢占,即在一个任务执行完成之前,除非它主动让出CPU,否则CPU会一直被这个任务占据,不能够被更高优先级的任务抢占。而抢占式内核则支持在一个任务执行的过程中,如果有更高优先级的任务请求,那么内核会暂停现在执行的任务,转而执行优先级更高的任务,显然抢占式内核的实时性更好。
CPU给任务划分时间片,通过时间片轮转,使CPU看起来在同一时间执行多个任务。就好像一个人同时交叉做几件事情,看起来多个事情像是一起完成的一样。每个进程会分配一段时间片,在进程的时间片用完之后,如果没有其它任务,那么会继续执行,如果有其它任务,那么当前任务会暂停,切换到其它任务执行。
内核把进程做了区分,分为交互型和脚本型。如果是交互型的进程,对实时性的要求比较高,但在大部分情况下又不会一直运行。典型的情况是,键盘大部分情况下可能没有输入,但是一旦用户输入了,又要求能够立刻响应,否则用户会觉得输入很卡顿。脚本型的进程一直在后台运行,对实时性的要求没那么高,所以不需要立刻响应。Linux通过抢占式的方式,对任务的优先级进行排序,交互型进程的优先级要比脚本型进程的优先级更高。从而在交互型进程到来之时能够抢占CPU,优先运行。还有一类进程是实时进程,这类进程的优先级最高,实时进程必须要保证立刻执行,因此会有限抢占其它进程。
如果单纯根据优先级,低优先级的任务可能很长一段时间都得不到执行,因此需要更加公平的算法,在一个进程等待时间过长的时候,会动态的提高它的优先级。当一个进程已经执行很长一段时间了,会动态降低它的优先级。这样带来的好处是,不会导致低优先级的进程长期得不到CPU,而高优先级的进程长期霸占CPU,Linux采用CFS算法来保证进程能够相对公平的占用CPU。
在多核场景下,由于每个核心的进程调度队列都是独立的,会导致一个问题:如果任务都集中在某一个CPU核心,而其它CPU核心的队列都是空闲状态,会导致CPU的整体性能下降。在这种情况下,Linux会把任务迁移到空闲的CPU核心,使得CPU核心之间的负载均衡。Linux进程迁移会带来开销,有些时候我们会绑定任务到某一个CPU核心,以减少进程迁移的开销。
参考Linux的进程调度,我们也思考下如何进行无人驾驶进程调度。
假设无人驾驶系统有以下几个进程:定位、感知、规划、控制、传感器、日志和地图,而CPU只有2个核心,那么应该如何规划这些任务的优先级呢?
首先我们假设定位、感知、规划、控制和传感器读取的优先级比日志和地图更高。这也很容易理解,打不打印日志和地图读取的慢对系统的影响不大,而定位、感知、规划,控制和传感器模块如果执行的慢,则会导致系统故障。
接下来我们再看优先级高的模块,因为目前只有2个CPU核心,所以不可能同时执行上述所有模块,只能通过时间片轮转来实现。如何分配时间片则成了一个问题,如果分配的时间片太长,会导致进程响应不及时,如果分配的时间片太短,又会导致进程切换开销,需要折中考虑。如果运行过程中感知和规划模块同时在执行,并且分配的时间片还没有用完,那么控制模块不会抢占CPU,直到运行中的模块时间片用完。
无人驾驶对模块的算法复杂度也有要求。如果感知模块采用了复杂度较高的算法来提高准确率,导致的结果是感知模块会占用更多的CPU时间,其它模块需要和感知模块竞争CPU,系统总的执行时间会变长。假设感知模块的执行时间是100ms,控制模块的时间是100ms,CPU的时间片是50ms,那么感知模块需要2个时间片,控制模块也需要2个时间片,总的执行时间是200ms,由于时间片轮转控制模块完成的时间为200ms。
如果感知模块为了提升效果,增加了算法的复杂度,运行时间变为200ms,感知模块照常能够完成任务,因为感知模块只要求在200ms内完成任务,系统总的执行时间是300ms。但由于竞争CPU,控制模块可能完成的时间会由200ms变为300ms。上述情况带来的问题是控制模块的时延达不到要求。解决的办法有2种,一种是升级硬件,增加CPU的核数;另外一种是降低系统算法复杂度,每个模块尽可能的高效,占用较少的系统时间。
系统的算法复杂度还要尽可能的稳定,不能一下子是50ms,一下子又是200ms,或者执行超时(这是最坏的情况)。如果各个模块的算法都不太稳定,当遇到极端情况,每个模块需要的时间都会变长,系统的负载会一下子突然变高,导致CPU的响应不及时,出现很致命的问题。
除此之外,无人驾驶车还需要考虑极端情况下,系统的进程会奔溃或者一直占用CPU的情况。
根据上述的思路,可以得到无人驾驶的进程调度策略:
把控制模块的优先级设置到最高,规划模块其次,感知和定位模块的优先级设置相对较低。因为控制和规划模块必须马上处理,感知和定位模块如果当前帧处理不过来,大不了就丢弃,接着处理下一帧。当然这些进程都需要设置为实时进程。而地图、日志等模块的优先级设置为最低,在其它高优先级的进程到来之时会被抢占。
前面介绍了实时操作系统,在实时操作系统之上运行的就是无人驾驶中最重要的算法实现。算法实现有2种不同的架构,一种是模块化的软件架构,这是目前无人驾驶系统的主流方案,世界上最大的2个无人驾驶开源社区Apollo和Autoware都是采用这种架构。另一种是端到端的软件架构,和模块化的思路不同,端到端的自动驾驶直接采用传感器(摄像头等)的数据作为输入,通过深度学习模型,直接输出控制信号(油门、刹车、方向转角)控制汽车的行驶。端到端的自动驾驶结构非常简单,但性能高效。由于深度学习模型不能安全硬编码,并且具有不可解释性,目前端到端的自动驾驶更多的只是作为研究手段。
根据无人车是否联网,可以将无人驾驶车分为单车智能和网联智能,单车智能强调车本身的智能,即使在没有网络的情况下,也具备完全自动驾驶能力。而网联车则强调车和车、车和环境的交互,通过整个车联网来实现更高级的智能,车本身可以具备自动驾驶能力,也可以只具备部分自动驾驶能力,通过网络获取更高级的智能。从目前的发展趋势来看,无人驾驶车要更快的落地,单车智能和网联智能二者需要互相融合,共同发展。
模块化的思想是将无人驾驶这个复杂问题划分为几个相对容易解决的子问题,这些子问题可以在机器人技术、计算机视觉和汽车动力学方面找到解决问题的思路,通过之前积累的经验来快速的解决问题。此外模块化的设计更加方便定界问题,修改一个模块的问题,不会影响到其它模块,这也是现代软件大量采用模块化设计的原因。
模块化设计的优点在于算法都是可控的,可以硬编码一些规则在系统中,确保算法出错的时候,无人车依然安全。模块化的设计方案是目前无人驾驶的主流方案,模块化的设计同时也存在一些问题。模块化的设计结构过于复杂,一个模块的错误会传导到其它模块,例如定位模块输出了错误的位置,会导致规划模块输出错误的行驶轨迹。
根据无人驾驶需要处理的任务类型,可以把无人驾驶分为:高精度地图、定位、感知、规划、控制、人机交互等6个模块。
(1)高精度地图比传统的地图包含的信息更多,传统的地图只有道路信息,而高精度地图除了包含道路信息之外,还包含车道信息、交通规则信息、红绿灯的位置信息等。同时高精度地图的精度需要达到厘米级,确保无人驾驶车通过高精度地图能够安全的行驶。
(2)定位模块主要解决无人车当前在哪里的问题,定位模块会实时更新无人车在地图上的精确位置,并提供给规划和控制模块使用。
(3)感知模块负责获取无人车周围的障碍物信息例如:汽车、行人、自行车等。同时还负责判断红绿灯状态、识别车道线、跟踪和预测障碍物的运动轨迹等,感知是无人驾驶中最难解决的问题之一。
(4)规划模块分为2块:长期规划和短期规划。长期规划任务是根据车当前在哪里,要去哪里,生成一条导航线路,和手机地图上的导航功能类似。而短期规划则是根据车当前的状态输出一条可行驶的轨迹,需要考虑如何避开障碍物,遵守交通规则等。短期规划只考虑未来200米左右的行驶距离,并且每隔一小段时间根据汽车的行驶状态实时修正行驶轨迹。长期规划是开车之前查询地图导航,而短期规划则是人开车在路上考虑如何驾驶汽车。
(5)控制模块根据规划模块的输出,在满足汽车运动学和动力学模型的前提下,控制汽车按照规划好的线路行驶。
(6)人机交互界面实现人和车的交互,比如让乘客设置线路的起点和终点,同时提供界面展示无人驾驶车当前的状态。
端到端的自动驾驶方法和人类的驾驶方式很相似,结构简单高效,并不依赖高精度地图,但用于生产实践还需要解决以下4个问题。
由于端到端自动驾驶的局限性,目前主要还在实验阶段。相信未来随着人工智能的发展,神经进化和深度强化学习等方法将推动端到端自动驾驶的发展。
云服务是自动驾驶不可或缺的一环,自动驾驶相关的高精度地图、数据存储、模型训练、自动驾驶仿真等都依赖于云服务。目前已经宣布能提供自动驾驶服务的云平台有百度Apollo、亚马逊AWS和华为Octopus,提供的主要功能包括:数据采集和存储、数据Pipeline、模型训练部署以及自动驾驶仿真。
一辆无人驾驶车配置了多种传感器包括摄像头、激光雷达、毫米波雷达、GPS、IMU等。每天使用到的数据量高达4000GB,这些数据需要收集并存储,用于高精度地图制作和模型训练。
数据存储首先需要的是一个分布式的文件系统,大数据时代已经被广泛证明了分布式文件系统的好处,最主要的好处是容量可以水平扩展,而且可靠性高。自动驾驶每天产生的大量数据都可以通过分布式文件系统保存下来。
接下来是数据库的选择,我们先分析下自动驾驶大数据应用场景和传统互联网的区别。互联网的数据生产方式是几亿用户,每人每天产生几条数据,合起来几个T的数据,而自动驾驶是一辆车每天产生几T数据,数据生产的方式差别很大。
互联网针对几亿用户,一般是选择key-value结构的数据库,例如HBase。但如果把HBase照搬到自动驾驶的场景就很别扭,因为HBase的单条数据最好是10M以内,否则会影响读写性能。一种办法是对数据做拆分,把几个T的数据,根据地理位置信息或时间做拆分,把地理位置信息或时间作为key,对应的数据作为value,就可以实现一条数据很小,拆分成很多key-value结构的小数据了。
再回过头去看互联网的应用场景,互联网场景是拿用户的ID作为key,如果同时频繁的命中相邻的ID,被称为单点问题,每次访问都到一台机器上去了,导致容量上不去。而按照地理位置或时间的方式刚好又导致了这个问题,因为无人驾驶中数据读取是按照地理位置顺序读取的,刚好每次都命中到一台机器,导致整个系统的容量上不去。如果我们把key做哈希散列,把地理位置信息打散,这样容量提高上去了,而这又恰恰和应用场景有冲突。所以自动驾驶需要的不是高并发读取,即同时几十万的并发,而是一个用户连续读取大量数据,单台机器能够对数据做预取,这样反而是单台读取的性能最高。
高精度地图也不应该直接以XML格式保存,占用的空间太大,应该把地图分块序列化之后再保存,压缩之后存储的效率会高很多。日志文件则采用时间序列型数据库保存,当通过日志文件获取无人驾驶车位置的时候,可以准确的反应出无人车从起点到终点的时间序列。
综上所述,不同的数据需要选择不同类型的存储和数据库。自动驾驶的一些大数据场景可能根本不需要数据库,只需要文件系统就可以了,如果需要管理结构化的数据,可以用数据库存储文件路径,而把文件本身放到文件系统中。例如存储图片文件,可以只保存路径到HBase数据库,把真实的图片文件压缩之后放在对象存储中。当需要查找图片时,先通过索引找到图片对应的路径,然后再从对象存储中解压出图片。导航路线、车和用户信息等需要多用户并发访问的数据可以采用HBase保存。
深度学习模型训练、高精度地图生成以及自动驾驶仿真等都需要进行数据处理。自动驾驶的数据处理流程包括:收集、清洗、标注、训练和部署。
用于自动驾驶模型训练的数据首先需要人工标注,然后再进行模型训练,最后才能得到能够识别车辆和行人的深度学习模型。数据的自动化标注是很大的挑战,通过工程的方法尽量减少人工标注,可以大幅度提高标注效率。实现自动标注通常有2种方法:一是通过机器自动标注,然后人工修正部分数据;二是通过仿真模拟生成大量标注好的数据。
数据处理的另一个挑战是大规模并行处理数据,由于数据量巨大,如何快速的处理数据是瓶颈。有很多优秀的分布式计算框架,其中Apache Spark可以构建大规模集群并发执行多个任务,在大规模数据处理中有非常好的实践。
还有一部分离线计算是利用空间换时间,比如planning模块reference line的生成;routing线路事先计算保存;感知的ROI区域;定位用到的点云数据等。
总之能够快速高效的处理数据,是自动驾驶数据处理的核心竞争力。
云端应该实时提供自动驾驶所需要的地图服务。地图包括道路信息和动态信息,道路信息不用再过多介绍,就是传统的道路信息,动态信息是地图上出现堵车或交通管制等需要实时动态更新的信息。
高精度地图的道路信息比传统地图的要求更加精细,不仅仅包括道路信息,还包括车道信息、红绿灯信息、交通标志信息等。同时高精度地图的精度也比传统地图要求更高,需要达到厘米级。
除了地图本身,一些动态信息可以通过地图服务的方式下发给无人车,在高精度地图中,这部分信息被称为动态图层。例如某条路突然发生交通事故,这个信息就会下发到动态图层,无人车接收到信息之后选择避开拥堵路段。动态图层包括:交通管制、交通拥堵状态、交通规则等,还包括周围的银行、医院、便利店等生活信息,为无人驾驶提供更多更有价值的服务。
高精度地图的维护是目前面临的最大问题,因为涉及到整个地图的采集、加工和标注,实时维护大体量的高精度地图目前来说成本高昂,一些高精度地图服务提供商提出采用众包的方式更新高精度地图。
自动驾驶仿真的目的是为了更早的发现问题,业界预测要确保安全,自动驾驶的安全性测试需要行驶至少2.5亿英里。如果全部采用真实环境测试,需要1000辆无人驾驶测试车每天测试100英里,不间断测试6.8年,短期内不可能实现。如果采用自动驾驶仿真,通过模拟真实场景的数据,让无人车大规模部署在虚拟环境中测试,然后再去真实场景路测,可以极大提高发现问题的效率。自动驾驶仿真测试流程如图3.15所示。目前Waymo宣称已经在现实世界中路测了1000万英里,在模拟世界中测试了100多亿英里。
除了要求能够大规模部署,仿真的另一个需求是问题快照,在测试出现问题的时候能够保存现场。在测试过程中,仿真系统通过判断车辆是否发生碰撞、是否开出路面、是否遵守交通规则以及是否安全距离过短等,对错误现场进行保存,记录在发生错误一小段时间内的数据快照,用来做数据回放解决问题。
除了自动驾驶功能测试,仿真还可以通过生成数据来帮助模型训练。数据的生成方式有2种,一种方式是生成标注好的数据,通过在仿真环境中模拟真实的车辆、行人、建筑物等,这些信息在仿真环境中都是已知的,可以直接生成标注好的数据用来进行模型训练。另一种方式是利用强化学习在仿真环境中模拟开车,进行端到端的自动驾驶模型训练,英伟达发布的端到端自动驾驶训练就是采用的此方法。