赞
踩
整个apollo perception传感器架构图如下:
整个感知模块的硬件方面包括了多个相机、毫米波雷达(前后)、激光雷达;在算法方面使用了单传感器、多传感器融合后的检测、识别,能够对障碍物进行分类、跟踪和运动轨迹的预测,除此之外还能给出障碍物的运动信息(前进方向、加速度等),给出车道相与本车的相对位置。
各传感器数据流细节如下图:
3D障碍物模块主要由三个模块组成:
下面的步骤展示了Apollo框架中从激光雷达获取点云数据后的3D障碍物感知处理流程:
ROI的作用是确定从高精地图中检索获取的可行驶区域,可行驶区域包括路面和路口。高精地图ROI过滤点云中ROI之外的数据,移除背景对象,例如道路周围的建筑物、数目,只留下之后处理步骤中需要的数据。给定一个高精地图后,点云之间的联系可以确定该点云是否属于ROI内部。每个激光点云点都能通过一张汽车周围2D量化的查询表(lookup table, LUT)查询到。
高精地图ROI过滤输入数据为由激光雷达捕捉到的点云数据,输出由高精地图定义的ROI范围内的点云切片,其中,高精地图的定义为一组多边形,每个多边形都是一个有序的点的集合。
Apollo高精地图ROI过滤通常由三个连续的模块组成:
坐标转换
在高精地图ROI过滤中,数据交互由一组多边形定义,每个多边形实际上是一组在世界坐标系下有序的点。在点云上执行一次询问要求点云和多边形在同一个坐标系中呈现,因此,Apollo将来自点云的数据和高精地图中的多边形均转换到激光雷达的相对坐标系下。
ROI LUT
为了判断一个输入的点是否处于ROI范围内,Apollo采用了一个网格式的LUT,该LUT将ROI量化成一个鸟瞰2D网格。LUT覆盖了一个矩形区域,该矩形区域与高精地图边界上方的一般视图周围的预定义空间绑定。那么,LUT代表了与ROI关于每个网格之间的关系。为了提高计算效率,Apollo使用扫描线算法和位图编码来构建ROI LUT。
带ROI LUT的点询问
基于ROI LUT,每个输入点之间的联系通过一个两步确认的方式进行询问。对于点询问过程:
最后,高精地图ROI过滤使用到的参数如下(参数名,用法,默认值):
通过高精地图ROI过滤确定周围环境后,Apollo获取了过滤后的点云,该点云只在ROI范围内,然后将这些点云数据投入分割模块中,该模块将前景障碍物从点云中检测和分割出来,包括汽车,卡车,自行车,行人等。
该模块输入数据是一组点云数据,输出一组与ROI中障碍物对应的一组对象。
Apollo使用一个深度卷积神经网络来检测和分割障碍物,整个CNN分割由一下连续部分构成:
通道特征提取
给定一个点云处理框架,apollo构建了一个相对坐标系下的2D方格鸟瞰视图,在相对坐标系下,每个预定义范围内的点被量化为一个基于网格坐标的点。经过量化后,Apollo计算了每个方格中点的8个统计测量值,这些值将会作为输入通道特征被投入CNN中。8个统计测量值如下:
基于CNN的障碍物预测
经过通道特征提取后,Apollo使用一个深度全卷积神经网络(FCNN)来预测方格各自方格障碍物属性,包括相对于潜在对象中心(称为中心偏移)的偏移位移,客观程度,真实程度以及障碍物高等。
模型输入数据为一个W×H×C方格的点云数据,其中W代表方格列,H代表方格的行,C代表通道特征的数量
FCNN由三层组成:
特征编码器将通道特征图像(点云)作为输入且对其空间像素进行连续向下取样,不断抽象特征。之后,特征解码器对编码的特征图像向上取样至2D方格输入的空间像素,这一过程能恢复特征图像空间细节,方便各方格障碍物属性预测。
向下和向上取样是通过带非线性激活函数的堆叠卷积/传递层实现的。经过解码器后输出的属性内容包括:中心偏移,客观程度,真实程度,障碍物高,类概率。
障碍物聚类
经过上面步骤后,Apollo获取了带五个属性的各方格的预测信息,即解码器输出的属性内容。为了产生障碍物对象,Apollo构建了一个有向图,基于方格中心偏移的预测结果,搜寻连接的组件作为候选对象簇。
其中每个方格都是有向图中的一个节点,有向边界基于该方格的中心偏移预测结果构建,每个方格根据另一个方格指向它的父节点。
在生成所有方格的有向图后,Apollo采用了一个压缩联合查找算法来高效查找构成障碍物的组件,每一个组件都是障碍物簇中的候选者。客观性表示一个方格是一个有效对象的概率。因此Apollo定义非对象方格的概率在0.5以下,最终过滤掉每个候选簇中的空的方格和非对象方格,余下构成障碍物的边界框。
一个对象簇可由多个相邻连接的方格组成,且这些方格的根节点可能是相邻的。
类概率是一个对象簇中所有障碍物(交通工具、行人、自行车人等)的概率总和。根据最大平均概率,障碍物的类型由对象簇最终分类结果决定。
后处理
经过聚类后,Apollo获得了一组对象簇,每一个对象簇都包含几个方格。在后处理步骤中,Apollo首先计算每个候选簇检测结果的置信分和对象高,计算由每个簇中方格的真实程度和对象高的平均值构成。之后,Apollo移除每个对象簇中相对于预测对象高过高的点,收集有效方格中的点。最后,Apollo移除低置信率或只有少量点的候选簇,输出最终的障碍物分割。
cnn分割模块的参数路径于:modules/perception/production/data/perception/lidar/models/cnnseg/velodyne128/cnnseg_param.conf
,其中包括的参数和用法如下(参数名,用法,默认值):
该模块为检测到的障碍物构建边界框。由于遮挡或距离对激光雷达的影响,点云形成障碍物可能存在稀疏或只覆盖了障碍物表面的一部分。因此,边界框构建器的工作就是恢复完整的边界框并给出边界框点。边界框的主要目的是估计障碍物的名称(例如,交通工具),即便点云数据是稀疏的。同样地,边界框也被用于可视化障碍物。
MinBox算法的思想是在给定多边形点的边界后寻找全部区域。例如,当给定两个点(形成一条边界后),Apollo映射其他多边形点到AB上,建立拥有最大距离的交点对。形成的边界是边界框中的一条。然后同样的方法获取边界框的其他边界。最后通过迭代多边形的所有边界,Apollo选择出一个带6条边界的边界框,选择拥有最小面积的解作为最终边界框。
该模块是对分割步骤分割出的障碍物进行跟踪。通常,该功能通过综合当前检测结果和存在的跟踪表,形成并更新跟踪表。如果障碍物不再存在,则删除旧的跟踪表;如果存在新检测的障碍物而跟踪表上没有,则产生新的跟踪表。经过关联后,将会对新的跟踪表上障碍物的运动状态进行更新。
HM障碍物跟踪使用匈牙利算法进行检测和检测的关联,使用鲁棒卡尔曼滤波(RKF)对运动进行估计。
检测和跟踪关联
当把检测结果与现有的跟踪表进行关联时,Apollo构建了一个二分图,且使用匈牙利算法,通过衡量最短距离来查找最优的检测和跟踪匹配。
计算距离关联矩阵
第一步是先建立距离的关联矩阵。给定检测对象和跟踪对象之间的距离会根据一系列关联特征进行计算,部分HM跟踪器的距离计算特征有:位置距离(运动),方向距离(运动),边界框大小距离(外观),点数量距离(外观),直方图距离(外观)。除此之外,还有一些重要的距离权重参数也被使用,这些参数结合了上面提到的关联特征。最终将所有特征和参数都纳入最终的距离测量。
通过匈牙利算法进行二分图匹配
给定关联距离矩阵后,Apollo构建了一个二分图,并使用匈牙利算法计算最小距离查找出最佳检测和跟踪匹配。它解决了O(n^3)时间复杂度内的分配问题。为了提高计算性能,匈牙利算法是在将原始二分图切割成子图后,通过删除距离大于合理最大距离阈值的顶点来实现的。
跟踪运动估计
经过检测和跟踪关联后,HM对象跟踪使用鲁棒卡尔曼滤波(RKF)和很俗运动模型对当前跟踪表上的对象进行运动状态估计。运动状态包括信念锚点和信念速度,与3D位置和3D速度对应。为了克服可能存在的由不完美检测导致的干扰,鲁棒统计技术被加入到跟踪滤波算法中。
观察冗余
作为滤波算法输入的速度测量是从一系列冗余观察中挑选出来的,这些观测值包括锚点漂移,边界框中心漂移,边界框角点漂移。冗余观察增强了滤波测量的稳定性,因为所有观察结果同时失效出现的概率远远低于单个观察失效。
突破
高斯滤波算法假定噪声来自高斯分布。然而,这一假设在一个运动估计问题中却可能不成立,因为测量噪声可能来自一个厚尾分布。Apollo在过滤过程中使用一个突破阈值来中和更新增益的高估。
根据关联质量更新
原始卡尔曼滤波在更新状态时没有区分测量质量的好坏,然而测量质量是过滤过程中一个有益的指标且能够被估计。例如,关联步骤中的计算距离就可以是一个测量结果的合理评估。根据关联质量更新滤波算法的状态能够增加运动估计问题中的鲁棒性和平滑性。
一个HM对象跟踪器的高抽象工作流包括:启动和预处理,预测和匹配,更新和收集。
HM对象跟踪器工作流的主要点包括:
为平滑障碍物类型、减少整个轨迹中的类型转换,Apollo采用了一个基于线性链条件随机场的时序类型融合算法(a sequential type fusion algorithm based on a linear-chain Conditional Random Field (CRF)),表述如下:
其中一元项对单个节点操作,二元节点对每条边界操作。
一元项中的概率是基于CNN预测的类概率输出。二元项中的状态转移概率由t-1时刻至t时刻障碍物类型转移建模,建模过程是从大量障碍物轨迹中进行统计学习。特别地,Apollo也使用学习后的混淆矩阵来表示从预测类型到实际类型的概率变化,以此优化原始基于CNN的类概率。
利用维特比算法(the Viterbi algorithm),连续的障碍物类型可以通过下面的公式优化:
文件顺序下的实现流程:
bool SegmentationComponent::Init()
读取配置文件,做一些变量初始化,创建writer结点,再初始化一些算法插件。
bool SegmentationComponent::InitAlgorithmPlugin()
初始化分割组件segmentor_,计算lidar到novatel的坐标变换矩阵
bool SegmentationComponent::Proc()
新建一个writer结点写到channel里的out_message,调用InitAlgorithmPlugin()函数,将该函数处理的结果写入channel
bool SegmentationComponent::InternalProc()
该函数在前半段主要是做一些变量的更新(时间戳,out_message更新等)
然后计算了传感器(激光雷达)到障碍物的坐标变换矩阵、传感器(激光雷达)到novatel的坐标变换矩阵,所有矩阵存储在变量lidar2world_trans_
中
最后调用了segmentor_的Process方法,进入激光雷达分割(lidar_obstacle_segmentation.cc)
bool LidarObstacleSegmentation::Init()
除了配置文件读取、赋值,还对cloud_preprocessor_, map_manager_, segmentor_, builder_, filter_bank_进行了初始化
LidarProcessResult LidarObstacleSegmentation::Process()
该函数是一个重载函数,另一个请查阅文件,这里只说被调用的一个
该函数首先通过cloud_preprocessor_调用了Preprocess()函数对点云进行预处理,紧接着调用ProcessCommon()函数,执行ROI地图过滤、分割、边界框构建、过滤等工作。
LidarProcessResult LidarObstacleSegmentation::ProcessCommon()
函数主要做了以下工作:
bool PointCloudPreprocessor::Init()
读取配置文件,变量赋值
bool PointCloudPreprocessor::Preprocess()
首先对要处理的数据做了一些指针非空判断;然后开始对点云进行过滤,包括点云点中x, y, z任一维度是否为Nan,是否超过最大值范围(1e3/1000);接着将汽车周围一圈(范围可修改)的零散的点云去掉;最后还可以限制点云高度,将高度超过某一范围的点云过滤掉。
经过滤后,调用TransformCloud()函数,计算了点云在世界坐标系下的每个点云的坐标。
bool PointCloudPreprocessor::TransformCloud()
将点云从激光雷达坐标系转换到世界坐标系
bool MapManager::Init()
读取配置文件,变量赋值和更新;初始化hdmap_input_变量
bool MapManager::QueryPose()
该函数没写,但应该更新汽车pose,重新计算变换矩阵,如果时间差很小可以不更新。
bool MapManager::Update()
该函数首先对将要处理的数据进行指针非空判断、变量重置等工作。
紧接着调用QueryPose()函数对汽车pose进行更新。
最后调用hdmap_input_对象的GetRoiHDMapStruct()方法,细节见2.1.2.5;执行完该函数后 ,ROI高精地图过滤部分算是完成了。
(部分函数)
bool HDMapInput::GetRoiHDMapStruct()
首先判断判断指针是否非空;接着调用hdmap_对象的GetRoadBoundaries()方法
int HDMap::GetRoadBoundaries()
直接跳转到impl_对象的GetRoadBoundaries()方法
int HDMapImpl::GetRoadBoundaries()
(该方法是一个重载函数)
首先判断要处理的数据指针是否非空,重置了存储路面边界的road_boundaries和存储路口的junctions变量指针
紧接着调用函数GetRoads()获取,该函数首先获取了道路信息,然后对每条道都进行id判断,最后通过id来获取最终所需的道路(我不知道,我乱讲的,反正大概这个意思,我没看,我什么都不知道);在获取到道路信息后,对每条道路都判断是否有路口信息,如果有,则将路口信息取出来。总之,经过该函数后,最终获取到了路面信息和路口信息。
在获取到路面信息和路口信息后,由于这两类信息分别存储在两个变量中,因此紧接着调用MergeBoundaryJunction()函数,将存储路面和路口信息的变量整合到hdmap_struct_ptr指向的变量中;最后调用 GetRoadBoundaryFilteredByJunctions()利用路口信息过滤掉多余的路面信息。
bool CNNSegmentation::Init()
读取cnn分割、推理模型的配置文件,变量赋值,初始化特征生成器feature_generator_、推理inference_、点云映射方格point2grid_
最后调用了函数InitClusterAndBackgroundSegmentation()对聚类和背景分割做初始化
bool CNNSegmentation::InitClusterAndBackgroundSegmentation()
初始化了背景检测器ground_detector_、ROI过滤器roi_filter_、spp网络spp_engine_、线程优化器worker_
在这里进行了奇怪的一个功能:背景过滤。按理说不应该放在初始化模块,毕竟初始化模块还没接收点云数据,所以很奇怪。虽然不知道目的是什么,但我们先放个**?**在这里
void CNNSegmentation::MapPointToGrid()
首先计算2D方格中每米有多少个cell;然后对每个点云点进行过滤,超过范围(+5 -5)则舍弃。
然后调用函数GroupPc2Pixel(),将点云映射到2D网格中。这个函数一开始没看明白,后来发现它并不是将点云的三维数据x, y, z变成二维数据,而是用x, y这二维数据计算出了一个pos_x, pos_y(类似于对数据编码,后面会解码),并且后面会用这两个值做一些过滤工作,最终投入网络进行推理分割。在计算出pos_x和pos_y后,如果pos_x和pos_y的值分别超出范围(0, 5),则被过滤掉;没被过滤掉点使用公式pos_y * width_ + pos_x
计算出一个值,存储到向量point2grid_中,其中point2grid_是一个长度等于所有点云长度的一个向量。
映射到2D网格后,开始调用feature_generator_的Generate()方法,利用原始点云original_cloud_和上一步计算的point2grid_生成特征。
特征生成后,再调用inference_对象的推理方法Infer(),进行推理
最后调用GetObjectsFromSppEngine()函数,对分割后的对象进行MinBox聚类处理,处理完毕后该函数执行完毕,返回布尔真。
void CNNSegmentation::GetObjectsFromSppEngine()
bool CNNSegmentation::GetConfigs()
bool CNNSegmentation::Segment()
首先判断传入的数据指针是否非空,接着判断激光雷达坐标系下的点云和世界坐标系下的点云指针是否非空,指针非空,再判断激光点云数量是否为0,激光点云和世界点云的点数量是否相同(应该相同)
然后调用函数MapPointToGrid()将点云映射到方格中
最后进行spp推理,获取障碍物对象
(部分函数)
void FeatureGenerator::GenerateCPU()
在接收到original_cloud_和point2grid_后,首先创建了一个864864的向量max_height_data_,并初始化,用于记录每个cell(一共864864个cell,每个cell包含多个点云点)中最高的点云点的值;然后做了一些内存分配。
接着建立了一个for循环,循环次数为original_cloud_中包含的点云数量,循环内部对original_cloud_和point2grid_同时进行处理,其中original_cloud_和point2grid_中的点是一一对应的。接着开始遍历每个点云点,舍掉original_cloud_对应的point2grid_中值为-1的点,记录每个cell中最高点云的高度于max_height_data_中,并计算了每个cell的平均高度于mean_height_data_中。
考虑来自毫米被雷达的数据,可以使用下面基础的处理步骤。
首先,跟踪ID应该被扩展,因为Apollo需要全局ID进行障碍物关联。初始毫米波雷达提供只有8位的ID,因此这很难判断两幅相连帧中带相同ID的两个对象在跟踪历史中是否指的是同一个对象,尤其是在经历掉帧后。Apollo使用毫米波雷达提供的测量状态来处理这个问题。同时,Apollo为一个远离的对象分配一个新的跟踪ID,该远离的对象在之前帧中拥有和另一个对象相同的ID。
其次,使用假正过滤器来移除噪声。Apollo利用毫米波雷达数据设置了一些阈值来对可能是噪声的结果进行过滤。然后,将毫米波雷达数据作为统一的对象格式构建对象。Apollo通过标定将对象转换成世界坐标系下的对象。原始毫米波雷达提供对象的相对加速度,因此Apollo采用来自定位的主车速度。Apollo使用这两个指标定义被检测对象的绝对速度。
最后,高精地图ROI过滤器被用于获取感兴趣的对象。只有在ROI范围内的对象将被用于融合算法中。
传感器融合模组的作用是融合激光雷达跟踪结果和毫米波雷达检测结果。Apollo首先根据障碍物跟踪ID将传感器识别结果和融合结果进行匹配。然后计算未被匹配的识别结果和融合结果之间的关联矩阵以寻求最佳匹配结果。
对于被匹配的传感器结果,使用自适应卡尔曼滤波(AKF)器更新相应的融合对象。对于未被匹配的传感器结果,创建一个新的融合对象。移除所有旧的不匹配的融合对象。
Apollo有发布传感器(publish-sensor)的概念。给定的毫米波雷达结果是缓存的。给定的激光雷达结果触发融合操作。传感器融合输出的频率和发布传感器的频率是相同的。Apollo的发布传感器是激光雷达。传感器结果根据传感器时间戳投入到融合处理管道。Apollo保管所有传感器的结果。对象存活时间是为Apollo中不同传感器对象设定的。一个对象如果在至少一个传感器中存在,那么该对象的状态将保持活跃。Apollo感知模组提供汽车小范围内的激光雷达和毫米波雷达融合结果和长距离范围内的单毫米波雷达结果。
当关联传感器结果到融合列表时,Apollo首先匹配相同传感器下的相同跟踪ID,然后构建一个二分图,使用最小距离损失的匈牙利算法,查找未被匹配的传感器结果和融合列表之间的最佳结果-融合匹配。这里使用的匈牙利算法和HM对象跟踪使用的匈牙利算法相同。距离损失是由传感器结果和融合结果之间对应锚点的欧氏距离计算得出。
Apollo使用带恒定加速运动模型的自适应卡尔曼滤波(AKF)来估计当前对象的运动。运动状态包括信念锚点,信念速度,信念加速度,分别和3D位置,3D速度,3D加速度对应。Apollo只使用来自传感器的位置和速度作为基础。在运动融合中,Apollo捕捉所有传感器结果的状态,通过卡尔曼滤波计算加速度。Apollo提供激光雷达跟踪器和毫米波雷达检测器数据中的位置和速度的不确定性。Apollo将所有状态和不确定性投入自适应卡尔曼滤波器(AKF)中,获取融合结果。Apollo使用过滤过程中的突破阈值来中和更新增益中的过度估计。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。