激光雷达简介及物体检测(一)

一、激光雷达的技术特性

激光雷达简史
激光雷达技术已经存在了一段时间。 这个基本概念最初是由爱尔兰物理学家爱德华·h·辛格在20世纪30年代提出的。 在接下来的几十年里,出现了一些最初的应用,如1969年阿波罗11号上的月球测距实验,或2000年创建的第一个考古遗址数字高程模型。 2005年,斯坦福赛车队赢得了DARPA Grand Challengehttps://www.youtube.com/watch?v=saVZ_X9GfIM,这是一场在莫哈韦沙漠进行的自动驾驶汽车比赛,使用的是一组SICK线扫描仪。  

激光雷达简介及物体检测(一)_第1张图片

Source:CS221

两年后,也就是2007年,斯坦福大学的团队第一个完成了DARPA城市挑战赛(https://www.youtube.com/watch?v=p9XsldQjs_M,这是一场在美国西部乔治空军基地的一个模拟城市中进行的比赛,使用360°旋转Velodyne激光雷达传感器作为主要传感设备之一。

激光雷达简介及物体检测(一)_第2张图片

Source:http://robots.stanford.edu/papers/junior08.pdf

自那时起,随着自动驾驶技术的出现,激光雷达技术得到了显著的改进,使其成为自动驾驶汽车传感器套件的基石之一。 但在我们进一步了解细节之前,先了解一下激光雷达的基本工作原理。

激光雷达的工作原理
目前使用的最常见的激光雷达传感器被称为“脉冲激光雷达”。 它是一个由激光源和接收器组成的系统,光源向场景中发射短脉冲的紧密激光束。 当一束激光击中一个物体时,一部分激光折射回光雷达传感器,接收器可以检测到。 根据激光的飞行时间,可以计算出到目标的距离R:  

式中,c为真空中的光速,n为传播介质的折射率(对于空气,n可设为1.0)。  
在我们看一个例子之前,让我们首先讨论一个典型的激光雷达传感器的组件: 
激光雷达简介及物体检测(一)_第3张图片

在原理图中,可以看到主要部件“激光源”、“发射器”、“接收器”和“定时器”。 首先,激光源产生一个非常短的几皮秒或纳秒量级的脉冲。 然后激光脉冲被放大器放大,然后在光束扫描仪和光学发射器的帮助下进入大气层。 当它扫描感兴趣的区域时,每个脉冲都指向传感器视场内的特定位置。 到达接收透镜的后向散射脉冲能量的一部分通过接收光学收集,然后放大并转换为电压信号。  
为了精确地测量光束发射和探测之间的时间,需要一个非常精确的时钟。 从下图可以看出,对电压信号采用前沿阈值技术来检测激光脉冲返回的时刻。 


激光雷达简介及物体检测(一)_第4张图片

所能达到的范围分辨率与所述定时装置的分辨率成正比。 时间间隔测量的典型分辨率值可以假定在0.1 ns范围内,从而得到1.5 cm的范围分辨率。  
能探测到目标的最大范围主要是由激光束在穿越大气层时的能量损失决定的。 回波能量越低,环境噪声越高,接收机越难探测到清晰的侧翼。 信号能量和背景噪声之间的比率是由信噪比(SNR)描述的,它显示了从不同距离的目标返回的几个信号。 

激光雷达简介及物体检测(一)_第5张图片

Source:https://www.laserfocusworld.com/home/article/16556322/lasers-for-LiDAR-fmcw-LiDAR-an-alternative-for-selfdriving-cars

从图中还可以看出,信号峰值随着目标距离的增加而变平,这是由于波束相干性不足造成的。 这种效应被称为“光束发散”,它与激光的波长λ成正比。 例如,对于λ=1550nm的激光雷达,由于光束发散,横向上最小的可分辨特征尺寸在100m处≈4cm。 仅作为比较,77GHz雷达传感器的波长λ=0.3cm,相同距离下最小的可分辨特征尺寸为2m。  

同时,从图中可以看出,信号峰值信噪比随着距离的增加而减小,这是由于大气中的粒子(如水或灰尘)阻挡了激光路径造成的。 该文(https://arxiv.org/pdf/1906.07675.pdf对雾雨影响下激光雷达性能的下降进行了深入分析。  
提高信噪比有两种基本的解决方案,即(a)提高激光能量,(b)提高接收机在噪声存在时检测微弱信号的灵敏度。 虽然(a)受到眼睛安全法规的限制,但(b)方法增加了接收器电子的显著复杂性。  
关于最大距离需要考虑的另一个因素是信号模糊,即在每个时间点上,应该只有一个激光脉冲“在飞行中”,以便接收的脉冲可以明确地与先前发射的脉冲相关联。
尽管有上述种种限制,但飞行时间脉冲激光雷达系统是目前自动驾驶汽车中使用最频繁的类型,主要是因为其简单性,以及即使在具有挑战性的环境条件下也能表现良好的户外性能。  
其他测量飞行时间的方法还有雷达和超声波。 在这三种ToF技术中,激光雷达提供了最高的角分辨率,因为它的光束发散明显较小。 这样就可以更好地分离场景中相邻的物体,如下图所示:  

激光雷达简介及物体检测(一)_第6张图片

然而,就目前可用的激光雷达系统而言,可以探测到的物体的最大范围仍然低于雷达,这限制了激光雷达作为主要传感设备(例如在高速公路场景)的速度。  
激光雷达方程
在上一节中,我们研究了飞行时间脉冲激光雷达的基本工作原理。 现在大家知道,影响激光雷达系统检测质量的两个具有挑战性的因素是(a)波束一致性和(b)信噪比。 在本节中,将简要介绍所谓的“激光雷达方程”,这与激光束的能量回到接收器与一些因素,如发射器的传送功率、目标物反射率,以及光学和目标之间的大气状况。  
请注意,不是“一个”激光雷达方程,而是几个,取决于应用领域。 在汽车传感中,最常用的公式为: 

让我们快速看一下各个参数及其对自动驾驶的影响:  
P(R): power received ->可以看出,我们希望最大化这个表达式以获得高信噪比(SNR),从而稳定准确地检测反射激光脉冲。  
P0:输出峰值功率->放大器使用的功率越大,返回到接收机的光子就越多。 有两个限制因素控制这一参数,即眼睛安全法规和功耗。  
ρ:目标反射率->目标表面反射越强,返回到接收机的光子就越多。 下图显示了一个步行人体模特所穿的一系列棉质样品的反射率:  

激光雷达简介及物体检测(一)_第7张图片

Source:https://core.ac.uk/download/pdf/129148998.pdf

从图中可以看出,不同样本之间的反射率差异很大(每种棉花类型都用单独的颜色表示)。此外,反射率随发射激光的波长而变化:对于大多数材料,在1000nm左右的波长下,反射率在50 - 70%之间;在2500nm时,反射率下降到20-40%左右。一个没有功耗的完美反射显然是100%。

A0:接收器的光圈区域->与经典摄影一样,光圈的大小直接影响着入射到接收器上的光的数量。光圈越大,返回光子的数量就越高。
η0:接收器的光学传输系数->当光子通过非真空介质时,由于飞行路径上的障碍物,光子会被散射。传输系数表示散射的程度,即返回的光子在通过光学的路径上所经历的散射程度。光子损失越多,到达接收器的光子就越少,从而降低信号电压水平,从而降低信噪比(SNR)。

γ :大气消光系数->与光学传输系数类似,大气消光系数描述的是由于与大气中的空气粒子(如水分子或尘埃)碰撞而导致的光子损失量。空气中的粒子越多,返回接收器的光子就越少,这也降低了信噪比(SNR)。

基于这些参数,得以了解从激光束生成到光子探测的各种组件如何影响接收功率的数量,从而影响信噪比。

多个信号返回
注意到上一节中关于Waymo帧结构的ri_return2条目。该数据结构包含激光雷达光束的“第二次返回”,它在第一次(即初始)返回之后到达。由于激光束的直径是有限的,因此很有可能只有一部分被物体反射,特别是当激光照射在深度不连续处附近时,例如一辆汽车的边缘。在下面的图中,激光束首先击中前面车辆的左侧,产生第一次返回。然而,由于激光束只击中了目标的一部分,一小部分激光继续沿着它的路径前进,直到最终击中另一个目标,在这种情况下,是另一辆汽车。由于距离较远,返回信号通常比初始返回信号弱,在初始返回信号之后到达接收器,因此称为“第二次返回”。

激光雷达简介及物体检测(一)_第8张图片​在本文中,不会使用来自Waymo数据集的ri_return2结构,但原则上它可以被用来检测物体更精确的边界,类似于计算机视觉中使用的边缘检测技术。

二、激光雷达类型概述

下图是激光雷达传感器的典型分类:

激光雷达简介及物体检测(一)_第9张图片

扫描激光雷达-电驱动的光-机扫描
电动光学机械扫描仪是最常见的激光雷达扫描仪类型。2007年,激光雷达技术的先驱Velodyne公司发布了一款64波束旋转扫描器,这款扫描器显然在早期塑造并主导了自动驾驶汽车行业。这种扫描器最明显的优点是测距距离长,水平视场宽,扫描速度快。

大多数这种类型的传感器都有几个发射-接收通道,这些通道垂直堆叠,由电机旋转,形成360°视场。

激光雷达简介及物体检测(一)_第10张图片
尽管由此产生的点云能够对车辆周围的物体进行高质量的检测,但这种扫描器类型仍有几个显著的缺点:(a)高功耗,(b)易受振动和机械冲击,(c)庞大的包装,(d)在大多数情况下,价格非常高。此外,由于垂直堆叠激光LEDs的数量有限,(e)它们的垂直分辨率有限。然而,世界上有几家公司致力于减轻这些缺点,同时保持这类传感器能够产生的高质量点云。下图显示了Velodyne激光雷达的产品线,说明了包装尺寸的演变:

激光雷达简介及物体检测(一)_第11张图片

this source: https://commons.wikimedia.org/wiki/File:Velodyne_ProductFamily_BlueLens_32GreenLens.png

非扫描闪光激光雷达
这种类型的激光雷达不是通过重定向激光束来执行连续的场景重建,而是一次照亮所有东西,类似于使用闪光灯的相机。在这种闪光激光雷达中,一组光电探测器同时捕捉每一束激光的飞行时间,提供一系列深度图像,其中每个像素在同一时刻被捕获。由于没有活动部件,这种传感器类型也被称为“固态激光雷达”。

与机械激光扫描方法相比,这种方法在单个图像中捕获整个场景,因此数据捕获速度快得多。此外,由于每个图像都是在一次闪光中捕获的,这种扫描类型对振动具有更高的鲁棒性,否则可能扭曲扫描过程。下图显示了基本原理,并将其与前面已经讨论过的标准行扫描技术以及二维顺序光栅扫描技术进行了比较。

激光雷达简介及物体检测(一)_第12张图片

闪光激光雷达的一个缺点是,照亮场景所需的能量相对较高,因为整个场景是一次性照亮的。将功率降低到可接受的水平会导致返回信号的信噪比下降,这严重限制了可以检测到目标的范围。

这种方法的缺点是在现实环境中存在反向反射器,反向反射器反射大部分光线,反向散射很少,实际上使整个传感器失明,使其无用。这种方法的另一个缺点是需要非常高的峰值激光功率来照亮整个场景并看得足够远。

光学相控阵 (OPA)
另一种属于固态传感器的传感器类型是光学相控阵(OPA)激光雷达。除了闪光激光雷达,该传感器属于扫描激光雷达的类别,因为激光束在场景中的位置是主动定向的。在OPA系统中,光学相位调制器被用来控制通过透镜的光的速度。OPA系统能够通过不同数量的延迟单个光束来塑造波前(wave-front ),这有效地将激光束引导到不同的方向。

激光雷达简介及物体检测(一)_第13张图片
在原理图中,有多个发射器,从这些发射器发射出经过精心控制的相位差的光波。可以看到,由此产生的组合波前沿一个α方向传播,该方向取决于光的波长、发射器之间的相位差Φ以及它们的空间距离d。

尽管OPA技术非常有前景,但它属于最新开发的类型,还没有在汽车中获得任何有意义的规模应用。此外,在这一点上,大多数OPA系统的波束转向只在单个平面上工作,这有效地对应于线扫描器的功能。二维波束转向是可能的,但需要大量的研究和开发工作。

基于MEMS镜像的准固态激光雷达
最后,还有另一种类型的扫描激光雷达,它是固态闪光、OPA激光雷达和电驱动光-机扫描仪的混合体:微机电系统(MEMS)激光雷达系统使用微小的镜子,当施加电压时,其倾斜角度会发生变化。因此,他们使用硅上的机电等价物来替代机械扫描硬件。
激光雷达简介及物体检测(一)_第14张图片​MEMS镜像示例如下图所示:

激光雷达简介及物体检测(一)_第15张图片Source: https://commons.wikimedia.org/wiki/File:Translatmsm.jpg

MEMS反射镜能够控制和调节光源,甚至控制其相位。与电动扫描仪相比,MEMS扫描仪在尺寸、扫描速度和成本方面都优于电动扫描仪。MEMS设备不需要移动整个激光雷达单元,只需要旋转微小的镜像板(其直径在1-9毫米的范围内),而系统的其余部分保持静止。由于微型反射镜的转动惯量较低,可以在不到一秒的时间内完成对整个视场的二维扫描,这对于自动驾驶汽车的实时性要求来说是一个明显的优势。

与旋转激光雷达不同的是,MEMS激光雷达系统在水平和垂直方向的视野都有限,因此必须将多个单元组合起来才能生成360°扫描。此外,由于反射镜的尺寸小,MEMS激光雷达的光束发散度较高,这目前限制了它们的使用主要局限于近距离应用。与OPA激光雷达一样,有几家初创公司致力于改进MEMS激光雷达在汽车领域的应用。

调频连续波(FMCW)激光雷达

虽然目前列出的方法是基于使用窄光脉冲的飞行时间原理,FMCW激光雷达发出恒定的光流(即“连续波”),并以固定的间隔(即“调频”)改变光的频率。通过测量返回光的相位和频率,FMCW系统能够测量距离(高达300米)和速度(利用多普勒效应)。原理如下图所示:

激光雷达简介及物体检测(一)_第16张图片

假设t = 0时发射的波遇到一个距离为R的物体,运动速度为v_r。经过一段时间Δt=2 r/c,反射波到达发射-接收器,在那里它干扰了在那一刻发射的波。接收到的波的频率将不同于在那一刻发出的波的频率,这是由于两个因素:由物体的距离R决定的往返经历时间Δt,以及由相对速度运动的物体的反射波决定的多普勒频移ΔfD。根据两波的干涉,可以同时确定物体的距离和径向速度。

目前的飞行时间(time-of-fligh)激光雷达(ToF)系统的工作波长为850和905 nm,非常接近可见光光谱。为保护眼睛不受辐射伤害是一个主要问题,限制了激光的最大功率和系统的范围。此外,850 ~ 905 nm范围内有明显的太阳辐射,在白天会造成干扰。但另一方面, FMCW系统使用的波长为1550纳米,意味着对眼睛安全的担忧和来自太阳辐射的干扰要小得多。

尽管FMCW激光雷达传感器有着广阔的未来,但截至2020年,仍处于早期发展阶段。在达到成熟之前,标准飞行时间(time-of-fligh)系统仍将是自动驾驶汽车的最佳选择。

三、激光雷达的选择标准

眼睛安全

当激光雷达传感器向大气发射激光束时,必须确保它们不会对肉眼造成伤害。正如您在前一节中所看到的,影响眼睛安全的因素有三个,分别是(a)发射器功率,(b)脉冲持续时间和(c)光频率。眼睛安全标准根据激光雷达传感器的输出光功率水平对其进行分类。对于汽车应用,激光雷达传感器必须归类为1级或1M级激光安全级别。

视场(FOV)

自动驾驶汽车必须感知周围360°的环境。此外,它必须确保整个驾驶走廊与车辆的高度是观察。这两种观测结果都会产生两个参数,即传感器的水平视场(HFOV)和垂直视场(VFOV)。如果我们考虑经典的Velodyne HDL 64E激光雷达,HFOV是360°,HFOV是26.9°,从安装在屋顶的位置,足以从近到远的距离扫描环境。虽然不是使用单一传感器,但可以使用多个具有重叠视场的传感器来相互补充。例如,在Waymo的车辆中,前面的三个激光雷达传感器与一个相对狭窄但重叠的HFOV被用于近距离观察车辆的正面和侧面。

角度分辨率
控制点云密度的一个关键参数是激光雷达传感器的角度分辨率。它定义了传感器可以投射到视场的激光束数量。对于Velodyne HDL 64E,其64个独立光束,垂直分辨率约为0.4°,而水平分辨率约为0.8°,这是基于对镜面全旋转的扫描次数。为了跟踪特征,如路边或自行车车架,在水平和垂直方向上都需要非常好的角度分辨率。

点的数量
基于角度分辨率和视场,可以计算出传感器在整个扫描周期中返回的3d点的数量。

帧率
激光雷达传感器的帧率提供了对HFOV和VFOF的全扫描次数。在Velodyne HDL 64E的情况下,帧速率可以在5Hz和20Hz之间调整,这取决于应用的要求和各自的系统可以处理性能方面的点的数量。除了相机的帧率,激光雷达的测量可能不会同时执行。当传感器扫过场景时,移动的物体会改变位置,这将导致模糊和可能拉长的物体,在处理它们之前必须进行校正。然而,对于固态激光雷达传感器,情况并非如此。

最大范围
根据激光雷达传感器和目标之间的相对速度,需要决定物体仍然可以被检测到的最大范围,并且有足够高的概率。在激光雷达数据表中,有关于各自传感器的最大范围的信息。然而,不能指望正在寻找的目标类型会在最大范围内以稳定的方式被探测到。原因可以通过激光雷达制造商执行目标测量的方式来解释:当评估传感器的距离扫描能力时,将使用一组具有特定反射率参数的已知目标。通常情况下,当一个反射器的反射率低于10%时,它被描述为“差”;当反射率超过90%时,它被描述为“好”。在大多数情况下,用于定义最大范围的目标反射率将在数据表中指定。

距离分辨率
基于返回信号强度、接收机灵敏度和定时装置分辨率等因素,距离分辨率提供了最小的目标距离变化,仍然被激光雷达系统所感知。

其他常见参数如:“脉冲重复率(PRR)或频率(PRF)”、“峰值功率”和“平均功率”以及激光束的“波长”。

四、基于Waymo数据集中的激光雷达数据

Waymo激光雷达技术规格

在本节中,简要概述本文中使用的激光雷达传感器,介绍Waymo无人驾驶汽车,以及主360°激光雷达的一些技术规格。 本节的目的是对将在本课程的物体检测部分中处理的数据有一个第一印象。关于激光雷达作为传感器集的一部分的必要性,业界正在进行激烈的讨论。 激光雷达技术的主要倡导者之一是Waymo,该公司在其无人驾驶汽车上总共使用了5个激光雷达传感器。  
正如你从下面的图片中看到的,Waymo还使用了几个摄像头和雷达传感器来进行前后监视。 

 激光雷达简介及物体检测(一)_第17张图片

Source1:https://blog.waymo.com/2020/03/introducing-5th-generation-waymo-driver.html

Source2:https://waymo.com/lidar/

激光雷达传感器可以分为两大类:  
1. Perimeter LiDAR(周界激光雷达):这种传感器的垂直视野范围为-90°到+ 30°,范围限制在0-20m。 范围限制是由Waymo强加给数据的,并且只出现在Waymo开放数据集中。 可以假设,实际传感器的范围要高得多。 Perimeter LiDARs(周边激光雷达)位于Waymo无人驾驶汽车的前后,以及左右前角。 有趣的是,perimeter LiDAR被卖给了与Waymo没有直接竞争的公司,品牌名为Laser Bear Honeycomb(https://waymo.com/lidar/。 下图为前左激光雷达传感器生成的三维点云:

激光雷达简介及物体检测(一)_第18张图片

2.360 LiDAR:顶部LiDAR的垂直视场范围为-17.6°~ +2.4°,数据集中范围为75m。 该激光雷达围绕垂直轴旋转,并在车辆的360°周长范围内生成高分辨率的3d图像。 在本课程中,我们将使用来自这种传感器类型的数据来检测车辆。 下图展示了这个激光雷达传感器在鸟瞰图中生成的三维点云:

激光雷达简介及物体检测(一)_第19张图片​ 有两个方面值得注意:(1)相邻扫描线之间的距离随着距离的增加而增加;(b)车辆的直周长内不包含任何三维点。 这两种现象都可以通过观察传感器-车辆设置的几何形状来解释: 

 激光雷达简介及物体检测(一)_第20张图片

在车辆正前方,由于车辆对激光束的遮挡,存在较大的感知缺口(“盲点”)。 此外,可以看到,由于激光二极管垂直放置的角度固定,相邻光束之间的间隙随着距离的增加而扩大。  
本节的目的是对Waymo传感器套件的一般设置有一个顶层的了解。 在本文的下一部分更深入地研究激光雷达技术的技术属性。 现在看一看启动代码,使用它来加载、可视化和处理LiDAR数据。

五、练习:Waymo数据集中的激光雷达数据 

将 Github repository()GitHub - udacity/nd013-c2-fusion-exercises: Exercise Code for Course 2 of the Udacity Self-Driving Car Engineer Nanodegree Program,克隆到本地计算机上,完成第一个小练习。

工作文件:
basic_loop.py
lesson1-lidar-sensor/exercises/starter/l1_exercises.py

请尝试完成以下练习:

在basic_loop.py中,参数化代码以加载sequence 1,并从第1帧开始循环。
将当前帧传递给文件l1_exercising .py中的print_no_of_vehicles函数。
编写代码,找出有多少类型为“vehicle”的对象,作为这一帧的LiDAR传感器的真实标签,并打印结果。

参考代码:

def print_no_of_vehicles(frame):

    num_vehicles = 0
    for label in frame.laser_labels:
        if label.type == label.TYPE_VEHICLE:
            num_vehicles = num_vehicles + 1

    print("number of labeled vehicles in current frame = " + str(num_vehicles))

六、Waymo数据集中的帧结构

在本节中,了解Waymo Open Dataset中的帧的架构。 此外,熟悉从帧中提取数据元素(如特定相机图像或LiDAR数据)所需的代码。

如何访问帧

Waymo数据集将信息存储在.tfrecord文件中。 在本文中,使用三个具有不同内容的文件(车辆数量、杂物呈现、各种驾驶条件等)。 这些都是:  
Sequence 1 : training_segment-1005081002024129653_5313_150_5333_150_with_camera_labels.tfrecord
Sequence 2 : training_segment-10072231702153043603_5725_000_5745_000_with_camera_labels.tfrecord
Sequence 3 : training_segment-10963653239323173269_1924_000_1944_000_with_camera_labels.tfrecord 

每个序列都包含约200个独立的帧(Frame),其顶层结构如下:

|-- LaserName
|-- CameraName
|-- RollingShutterReadOutDirection
|-- Frame
|-- Label

虽然实际的传感器数据存储在帧(Frame)结构中,但可以使用LaserName和CameraName来选择要访问的特定传感器。对于每个激光器和相机,都有一个嵌套在Frame下的子分支,它包含以下项目:

-- Frame
   |-- images
   |-- Context
   |   |-- name
   |   |-- camera_calibrations
   |   |-- laser_calibrations
   |   |-- Stats
   |   |-- laser_object_counts
   |   |-- camera_object_counts
   |   |-- time_of_day
   |   |-- location
   |   |-- weather
   |-- timestamp_micros
   |-- pose
   |-- lasers
   |-- laser_labels
   |-- projected_lidar_labels (same as camera_labels)
   |-- camera_labels
   |-- no_label_zones

访问摄像头数据

为了访问特定的相机,可以使用以下代码:

camera_name = dataset_pb2.CameraName.FRONT
image = [obj for obj in frame.images if obj.name == camera_name][0]

image分支的结构如下:

|-- Frame
   |-- images ⇒ one branch for each entry in CameraName
      |-- name (CameraName)
      |-- image
      |-- pose
      |-- velocity (v_x, v_y, v_z, w_x, w_y, w_z)
      |-- pose_timestamp
      |-- shutter
      |-- camera_trigger_time
      |-- camera_readout_done_time

相机数据结构被命名为image可能会引起一些混淆,尽管实际的图像只是其中许多项中的一个(也称为image)。

如果想访问和显示相机图像,可以使用以下代码:

from PIL import Image
import io

# convert the image into rgb format
image = np.array(Image.open(io.BytesIO(camera.image)))
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# resize the image to better fit the screen
dim = (int(image.shape[1] * 0.5), int(image.shape[0] * 0.5))
resized = cv2.resize(image, dim)

# display the image 
cv2.imshow("Front-camera image", resized)
cv2.waitKey(0)

例C1-3-2:显示摄像头图像
通过调用basic_loop.py中的display_image函数来试验lesson-1-lidar-sensor/examples/l1_examples.py文件中的代码。以上这段代码产生以下输出:

激光雷达简介及物体检测(一)_第21张图片

 激光雷达数据的访问

 现在使用下面的代码以类似的方式访问LiDAR数据:

lidar_name = dataset_pb2.LaserName.TOP
lidar = [obj for obj in frame.lasers if obj.name == lidar_name][0]

在调试器中检查lidar子分支时,会发现它有以下结构:

-- lasers ⇒ one branch for each entry in LaserName
        |-- name (LaserName)
        |-- ri_return1 (RangeImage class)
            |-- range_image_compressed
            |-- camera_projection_compressed
            |-- range_image_pose_compressed
            |-- range_image
        |-- ri_return2 (same as ri_return1)

为什么不能像预期的那样直接访问点云?原因是在Waymo数据集中,激光雷达测量数据存储在一个称为范围图像(range image)的结构中。 关于这个概念以及如何从中生成一个点云,在更深入地研究这个之前,需要更好地理解激光雷达技术背后的物理原理,将是本文下一节的主要目标。首先,看一下框架结构(frame)中剩下的一些条目来结束本章: 

|-- Frame
        |-- Context
            |-- name
            |-- camera_calibrations ⇒ one branch for each entry in CameraName
            |-- laser_calibrations ⇒ ⇒ one branch for each entry in LaserName
            |-- Stats
            |-- laser_object_counts
            |-- camera_object_counts
            |-- time_of_day
            |-- location
            |-- weather
        |-- timestamp_micros
        |-- laser_labels ⇒ one branch for each entry in LaserName
        |-- camera_labels ⇒ one branch for each entry in CameraName

在Context子分支中,有大量有用的数据条目,其中一些将在文中使用。 例如,通过激光校准,您可以检索单个激光led的光束倾角以及外部校准。  
通过以下代码,您可以检索顶部激光雷达传感器的校准数据:  

lidar_name = dataset_pb2.LaserName.TOP
calib_lidar = [obj for obj in frame.context.laser_calibrations if obj.name == lidar_name][0]

使用调试器检查校准数据产生以下结果:

vfov_rad = calib_lidar.beam_inclination_max - calib_lidar.beam_inclination_min
print(vfov_rad*180/np.pi)
--> 20.360222720319797

例C1-3-3:打印垂直视场
在 workspace的basic_loop.py中调用函数print_vfov_lidar来实验文件' lecture -1-lidar-sensor/examples/l1_examples.py'中的代码。
从描述Waymo数据集的论文(https://arxiv.org/pdf/1912.04838.pdf中,可以收集到一个20度的垂直视场,这与上述输出是一致的。
同时,由外部校准的矩阵可知,顶部激光雷达位于距离车辆坐标系原点+1.43m的位置,高度为+2.184m。这些值可以在数据结构calib_lidar.extrinsic.transform中找到:

最后,子分支laser_labels包含当前框架中所有手工标记的ground-truth对象的列表,将在可视化激光雷达数据的部分中看到这些。本节是关于获取框架数据结构的概述,在本文后面的相关章节中重温许多单独的元素。在下一节中,将研究激光雷达传感器的物理特性,并涉及当今市场上的一些模型。

七、可视化激光雷达数据:范围图像

什么是范围图像?
比较一下点云和范围图像:通常,激光雷达扫描仪提供的传感器数据表示为3d点云,其中每个点对应于单个激光雷达光束的测量。每个点都用$(x, y, z)$的坐标和其他属性来描述,比如反射激光脉冲的强度,甚至是物体边界部分反射引起的二次返回。在下面的图中,点云显示了一个3d点的亮度编码了激光反射的强度。
激光雷达简介及物体检测(一)_第22张图片

 可以看到,在左侧鸟瞰图中,前面车辆的后段和右侧的墙壁清晰可见,且强度值较高,而中间车道的路面甚至车辆侧面几乎没有出现。
表示激光雷达扫描的另一种形式是范围图像。该数据结构将三维点保存为扫描环境的360度“照片”,行维度表示激光束的仰角,列维度表示方位角。随着绕z轴的每一次增量旋转,激光雷达传感器返回大量的范围和强度测量,然后将它们存储在范围图像的相应单元格中。

在下图中,一个点p在空间中被映射到一个范围图像单元,该单元由相应的方位角αp和传感器的倾角βp表示。在文献中,αp常被称为“yaw”,而βp被称为“pitch”。
在这个例子中,只有p的范围(即目标距离)存储在单元格中。然而,在Waymo数据集中,范围图像结构存储了测量创建时的距离、强度、伸长率和车辆姿态。

激光雷达简介及物体检测(一)_第23张图片
激光脉冲的延伸超过其公称宽度,结合强度,可用于分类大气条件,如雨,雾或灰尘。Waymo进行的实验表明,高延伸率和低强度的信号表明存在大气危害。
现在对范围图像的概念有了初步的了解,让我们将它们正确地形象化。
可视化范围图像
第一步,在Waymo开放数据集中定位范围图像数据结构:

-- lasers ⇒ one branch for each entry in LaserName
        |-- name (LaserName)
        |-- ri_return1 (RangeImage class)
            |-- range_image_compressed
            |-- camera_projection_compressed
            |-- range_image_pose_compressed
            |-- range_image
        |-- ri_return2 (same as ri_return1)

 示例C1-5-1:加载范围图像
通过调用basic_loop.py中的print_range_image_shape函数来试验文件lesson-1-lidar-sensor/examples/l1_examples.py中的代码。

对于每个激光雷达传感器,可以用以下代码提取其关联的范围图像:

lidar_name = dataset_pb2.LaserName.TOP
lidar = [obj for obj in frame.lasers if obj.name == lidar_name][0]
if len(lidar.ri_return1.range_image_compressed) > 0: # use first response
    ri = dataset_pb2.MatrixFloat()
    ri.ParseFromString(zlib.decompress(lidar.ri_return1.range_image_compressed))
    ri = np.array(ri.data).reshape(ri.shape.dims)
    print(ri.shape)

在执行代码之后,得到以下输出:

processing frame #1
(64, 2650, 4)

现在知道,一个范围图像有64行和2650列,顶部激光雷达覆盖360°水平角度。这意味着范围图像中的每一列覆盖的弧度为360°/2650=0.1358°,对应的水平分辨率约为8′角分。

为了计算垂直分辨率,需要利用最小和最大倾角(即俯仰pitch)。
八、练习:计算俯仰角分辨率
工作文件:
basic_loop.py
lesson1-lidar-sensor /exercises/starter/ l1_exercises.py

现在熟悉了范围图像的分辨率和基本结构,接下来将了解单元格中的数据。从 dataset.proto (lines 174-178) 的文档,我们知道范围图像单元格的内部尺寸为[range, intensity,elongation,is_in_no_label_zone]。在本文中,我们将专注于前两个条目,从实际的范围信息开始。

正如我们前面所讨论的,“范围”是指激光发射器和目标之间的直线距离。目标所处的空间方向由倾角和方位角共同决定。要访问范围数据,我们只需要访问范围图像的第一层,即ri[:,:,0]。
​参考代码:

def print_pitch_resolution(frame, lidar_name):

    # load range image
    lidar = [obj for obj in frame.lasers if obj.name == lidar_name][0] # get laser data structure from frame
    if len(lidar.ri_return1.range_image_compressed) > 0: # use first response
        ri = dataset_pb2.MatrixFloat()
        ri.ParseFromString(zlib.decompress(lidar.ri_return1.range_image_compressed))
        ri = np.array(ri.data).reshape(ri.shape.dims)

    # compute vertical field-of-view from lidar calibration 
    lidar_calib = [obj for obj in frame.context.laser_calibrations if obj.name == lidar_name][0] # get laser calibration
    min_pitch = lidar_calib.beam_inclination_min
    max_pitch = lidar_calib.beam_inclination_max
    vfov = max_pitch - min_pitch

    # compute pitch resolution and convert it to angular degrees
    pitch_res_rad = vfov / ri.shape[0]
    pitch_res_deg = pitch_res_rad * 180 / np.pi
    print("pitch angle resolution = " + '{0:.2f}'.format(pitch_res_deg) + "°")

九、更进一步的范围图像示例
例C1-5-3:检索最大和最小距离

通过调用basic_loop.py中的get_max_min_range函数来试验文件lesson-1-lidar-sensor/examples/l1_examples.py中的代码。

为了查找和打印最大和最小范围值,可以简单地使用以下命令:

print('max. range = ' + str(round(np.amax(ri[:,:,0]),2)) + 'm')
print('min. range = ' + str(round(np.amin(ri[:,:,0]),2)) + 'm')

从输出中,可以看到对目标的最大范围约为75m。该值与Waymo论文(https://arxiv.org/pdf/1912.04838.pdf中关于数据集细节的range cut-off值一致。

-1m -1m的最小范围在几何上没有意义,而是用来标记无效点而没有返回信号。为了不干扰范围图像可视化和其他操作,如物体检测,将所有元素的条目-1设置为0.0,而不是使用以下命令:

ri[ri<0]=0.0

现在已经从框架中正确地提取了范围图像,并且已经删除了无效的条目,可以继续下一步,即正确地缩放和可视化内容。


示例C1-5-4:可视化范围通道
通过在上面的工作区中调用basic_loop.py中的vis_range_channel函数,可以对文件lesson-1-lidar-sensor/examples/l1_examples.py中的代码进行实验。

首先,我们想要可视化实际的范围信息。为此,我们需要提取第一个通道,并将其按一定比例缩放,使从0.0m到75m的整个值正确映射到灰度图像的8位颜色深度。为此,我们可以使用以下代码:

ri_range = ri[:,:,0]
ri_range = ri_range * 255 / (np.amax(ri_range) - np.amin(ri_range))
img_range = ri_range.astype(np.uint8)

一旦缩放和转换了范围图像内容,就可以使用OpenCV将其可视化。此外,为了确保已经利用了整个8位分辨率,可以用上次练习中的代码再次简单地打印最大值和最小值:

print('max. val = ' + str(round(np.amax(img_range[:,:]),2)) )
print('min. val = ' + str(round(np.amin(img_range[:,:]),2)) )

cv2.imshow('range_image', img_range)
cv2.waitKey(0)

从输出中,可以看到范围图像的所有列都被正确地可视化了,范围值被映射拉伸了,以利用灰度图像的8位颜色深度:

8位灰度范围数据的范围图像:

max. val = 255
min. val = 0

为了专注于驾驶方向,可以缩小水平视场到±45°围绕向前的x轴。正如在Waymo数据集论文中所述,图像的中心对应于正x轴。而且,我们知道中心和左边的距离以及中心和右边的距离都是180°。因此,由于45°等于范围图像列数的1/8,可以使用如下代码提取中心90°:

deg45 = int(img_range.shape[1] / 8)
ri_center = int(img_range.shape[1]/2)
img_range = img_range[:,ri_center-deg45:ri_center+deg45]

但请注意,这种方法只适用于激光雷达传感器完全对准驾驶方向的情况。如果不是这样,则需要使用传感器校准矩阵来执行校正(见下一节)。不过,出于可视化的目的,这一步(还)不是必需的。

生成的图像如下所示:

裁剪范围图像范围数据

可以看到,无效像素被正确地设置为0,前面的车辆和右侧的墙显示了合理的深度值。

十、练习:可视化强度通道
工作文件:
basic_loop.py

lesson1-lidar-sensor/exercises/starter/l1_exercises.py

提示和技巧

如果你的结果看起来像下面的图像,那么可以放心地假设你已经使用了相同的方法将强度值映射到图像的8位范围,就像前面练习中的范围值一样。

范围图像强度数据(最小-最大归一化)

注意,在中心的左侧有一个非常亮的像素,而其余的强度映射几乎为零。缩放过程的这种行为的原因是,从最暗的区域到最亮的区域,值的范围扩展了十的几次方。这是激光雷达数据在汽车环境中的一个常见问题,因为在典型场景中存在逆向反射材料(例如一些交通标志,尾灯,一些车牌)。对于这种材料,反射激光束的强度明显高于其他材料。因此,如果我们使用文献中的标准方法(如z-归一化或类似方法)对数据进行归一化,我们将成功地减轻“强度异常值”的影响,但同时显著提高噪声水平。因此,解决这个激光雷达特定问题的一种启发式方法可能是简单地用最大强度值的一半乘以整个强度图像。在计算机视觉中,这种操作被称为“对比度调整”。在代码中,如下所示:

ri_intensity = np.amax(ri_intensity)/2 * ri_intensity * 255 / (np.amax(ri_intensity) - np.amin(ri_intensity))

当你再次运行该程序时,产生的强度图像将如下所示:

范围图像强度数据(最大自适应归一化)

正如你所看到的,前面的车辆现在清晰可见,车牌是反射性最强的材料。另外,图片右侧的灌木丛也显示得很清楚。
参考代码:

def vis_intensity_channel(frame, lidar_name):

    # extract range image from frame
    lidar = [obj for obj in frame.lasers if obj.name == lidar_name][0] # get laser data structure from frame
    if len(lidar.ri_return1.range_image_compressed) > 0: # use first response
        ri = dataset_pb2.MatrixFloat()
        ri.ParseFromString(zlib.decompress(lidar.ri_return1.range_image_compressed))
        ri = np.array(ri.data).reshape(ri.shape.dims)
    ri[ri<0]=0.0

    # map value range to 8bit
    ri_intensity = ri[:,:,1]
    ri_intensity = np.amax(ri_intensity)/2 * ri_intensity * 255 / (np.amax(ri_intensity) - np.amin(ri_intensity)) 
    img_intensity = ri_intensity.astype(np.uint8)

    # focus on +/- 45° around the image center
    deg45 = int(img_intensity.shape[1] / 8)
    ri_center = int(img_intensity.shape[1]/2)
    img_intensity = img_intensity[:,ri_center-deg45:ri_center+deg45]

    cv2.imshow('intensity image', img_intensity)
    cv2.waitKey(0)

十一、可视化激光雷达数据:点云
将范围图像转换为点云

现在您已经理解了范围图像的概念,我们将看看如何将它们转换为三维点云。为了执行这个空间重构,我们只需要反转本章开头讨论的映射过程:

激光雷达简介及物体检测(一)_第24张图片

                                                      三维点的方位角和倾角

示例C1-5-6:将范围图像转换为3D点云
您可以通过调用basic_loop.py中的range_image_to_point_cloud函数来试验文件class -1-lidar-sensor/examples/l1_examples.py中的代码。 

基于俯仰角和偏航角以及点p的实际范围,可以利用球坐标(https://en.wikipedia.org/wiki/Spherical_coordinate_system的概念,重构p的x、y和z分量​:

激光雷达简介及物体检测(一)_第25张图片

作为第一步,需要为范围图像的每个单元提取角度αP和βP。这可以通过检索来自激光雷达校准数据的最大和最小垂直光束倾角值,和创建一个在两者基于范围图像的高度上之间的线性空间值来完成:

height = ri.shape[0]
inclination_min = calibration.beam_inclination_min
inclination_max = calibration.beam_inclination_max
inclinations = np.linspace(inclination_min, inclination_max, height)
inclinations = np.flip(inclinations)

请注意,倾角必须顺序反转,以便第一个角度对应最上面的测量值。将光束倾斜角打印到终端,结果如下表:

[    2.47544479   2.15226665   1.82908851   1.50591037   1.18273223
  0.8595541    0.53637596   0.21319782  -0.10998032  -0.43315846 ...
...
    -15.29935282 -15.62253096 -15.9457091  -16.26888724 -16.59206538
    -16.91524352 -17.23842166 -17.56159979 -17.88477793    ]

下一步,需要更正方位角,使范围图像的中心对应于Waymo车辆向前的x轴方向。为此,需要提取顶层激光雷达传感器的外部标定矩阵(https://en.wikipedia.org/wiki/Camera_resectioning:

​在这种表示中,矩阵R3x3描述了传感器围绕其相对于上级坐标系(如车辆)的三个坐标轴的旋转,向量T3x1表示坐标系的相对中心。旋转矩阵由一系列绕坐标系轴的独立旋转组成,第一列如下所示:

当将第一个和第二个分量与球坐标变换方程进行比较时,注意到第一个分量对应于x,第二个分量对应于y。因此,为了得到坐标系绕z轴的旋转角度(即方位角校正),可以使用以下公式:

在代码中,方位角校正如下所示:

extrinsic = np.array(calibration.extrinsic.transform).reshape(4,4)
az_correction = math.atan2(extrinsic[1,0], extrinsic[0,0])
azimuth = np.linspace(np.pi,-np.pi,width) - az_correction

当打印第一帧的修正值时,得到以下输出:

az_correction = 148.50°

将校正后的方位角打印到终端,结果如下表:

[  31.4964375 ,   31.36053716,   31.22463682, ..., -328.23176182, -328.36766216, -328.5035625 ]

接下来,可以使用以下代码来计算每个范围图像条目的x, y和z坐标: 

# expand inclination and azimuth such that every range image cell has its own appropriate value pair
azimuth_tiled = np.broadcast_to(azimuth[np.newaxis,:], (height,width))
inclination_tiled = np.broadcast_to(inclinations[:,np.newaxis],(height,width))

# perform coordinate conversion
x = np.cos(azimuth) * np.cos(inclination) * ri_range
y = np.sin(azimuth) * np.cos(inclination) * ri_range
z = np.sin(inclination) * ri_range

最后,在能够正确地使用点云(例如,检测物体)之前,需要将当前在传感器坐标系中表示的所有点转换为车辆坐标。要做到这一点,首先需要添加第四个分量,将它们表示为齐次坐标,并将它们与外部变换矩阵相乘。在Python中,这可以使用einsum函数有效地表示,其中i,j表示外部矩阵的维数,j,k,l是每个单元格具有一组齐次坐标的范围图像的维数:

xyz_sensor = np.stack([x,y,z,np.ones_like(z)])
xyz_vehicle = np.einsum('ij,jkl->ikl', extrinsic, xyz_sensor)
xyz_vehicle = xyz_vehicle.transpose(1,2,0)

结果结构xyz_vehicle包含转换后的车辆坐标,第四个组件为1,因此可以忽略它。通过以下代码,所有范围大于零的3d点都被提取出来并使用open3d工具箱可视化:

pcl = xyz_vehicle[ri_range > 0,:3]
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(pcl)
o3d.visualization.draw_geometries([pcd])

得到的点云如下所示:

在右边,可以看到把街道和后面的住宅区隔开的墙。没有3d点的圆形片区是Waymo车辆的当前位置,并给出了顶部激光雷达传感器盲点的大小印象。在Waymo车辆前面,可以看到前面车辆的轮廓,它们都投下了一个相对于激光雷达传感器扫描方向的探测阴影。

在文件l1_examples.py中的range_image_to_point_cloud函数中,可以执行代码并使用可视化来了解点云的属性。

通过上述内容,应该熟悉激光雷达传感器技术,理解飞行时间(time-of-flight )原理,并了解其他技术,如FMCW或OPA系统,这标志着下一代激光雷达传感器。

此外,了解了如何从Waymo帧中提取范围图像,如何将范围和强度数据可视化,并将其转换为3d点云。在下一节中,将使用这样的激光雷达数据实际检测物体。

你可能感兴趣的:(人工智能,自动驾驶)