自动驾驶感知-3D目标跟踪及防碰撞系统搭建

来源:公众号“汽车电子与软件”
2020-06-30
1722

导读:


在我之前的专辑中我已经介绍了相机关键点提取和匹配方法,激光雷达的物体检测原理,3D点云投影以及图像对象检测技术。我们已经了解了很多传感器融合领域的基础知识,现在让我们来看看究竟怎么融合多传感器的特征?多传感器融合是否真的能带来性能与稳定的提升?
在本文中我会基于YOLO v3 深度学习对象检测框架开发一种使用关键点对应关系持续匹配3D对象的方法。然后分别使用激光雷达和相机计算与前车的实时碰撞时间TTC。


1. Collision detection model

碰撞检测是自动驾驶安全的基石,是最后一道保险杠,需要大量的工程实践和测试才能确保碰撞检测功能的绝对安全和可靠。因此碰撞检测即使在整车厂,也是汽车工程师的一个重要的研究方向。在本文中,我会带大家了解经典基于摄像头的碰撞检测原理,以及使用激光雷达为碰撞检测功能提供冗余。现在让我们来了解下什么是碰撞检测。
防碰撞系统(CAS) 是在行驶路径上和物体即将发生碰撞时,警告司机甚至触发紧急制动的主动安全系统。它允许汽车通过单一传感器或多传感器组合感知其他车辆,行人和障碍物。
防碰撞系统市场,按类型:
•自适应巡航控制(ACC)
•自主紧急制动(AEB)
•车道偏离警告系统(LDWS)
•停车辅助系统
•其他(盲点检测和夜视)
而按照技术分类,分为激光雷达,毫米波雷达,超声波雷达以及相机。
人类的反应时间大约是1 s,因此一个好的CAS应该至少在触发紧急刹车前2s向我们发出警告。如果存在前车,则CAS会持续估算碰撞时间TTC 。当TTC降至较低阈值以下时,CAS可以决定警告驾驶员迫在眉睫的危险,或者根据系统情况自动使用车辆制动器。
让我们看下下面的场景:


在这个场景中,绿色前车在t0时刻,相距d0的位置突然减速,经过Δt ,绿车位于与黄车仅仅相距d1的位置了,我们的目标是计算剩余碰撞时间Time to collision(TTC),以便系统可以警告黄色车辆的驾驶员,甚至自动触发刹车。
在汽车领域凡是涉及到汽车随时间的运动,就必须考虑车辆动态学中的相对运动数学模型。

恒定速度模型与恒定加速度模型

车辆动态学中的相对运动数学模型最常用的有两种,恒定速度模型与恒定加速度模型。在本项目中,我会使用恒定速度模型分别计算相机和激光雷达的TTC。
要计算TTC,我们需要对自身车辆与先前车辆的物理行为进行假设。恒定速度模型(constant velocity model CVM)假设上图中黄色和绿色车辆之间的相对速度是恒定的:
从上述公式看出,我们需要一种能够在恒定时间dt内精确地测量到前车距离的传感器。由于激光雷达的空间分辨率很高,通过激光雷达传感器可以很好地实现测距。
但恒定速度模型在实际动态交通情况下很少用到,因为实际情况下车辆可能会急刹车,导致在不同时刻的测量下,两车之间的相对速度呈现急速动态变化。
为了解决这一问题,我们可以采用恒定加速度模型(constant acceleration model CAM),将速度看成时间的函数。大多数市售的碰撞检测系统都是恒定加速度模型:
2004年Mobileye在“Forward Collision Warning with a Single Camera ”论文中基于CAM模型单独使用相机计算TTC。下面我简单介绍下Mobileye的基于CAM碰撞检测推导。基于CAM碰撞检测推导需要恒速模型的理论做支持。
对于恒速模型,此处Tm是碰撞时间。
我们通过针孔投射模型可以推导:
S表示Δt连续两帧时间间隔内同一物体连续两张图片的尺度比例:
从Z0运动到Z1:
由此可以推导出:
现在考虑恒加速度模型:

另Z=0,求解一元二次方程:
由于对于摄像头来说,速度和加速度都不是可以直观观察到的。由于恒定速度模型下Tm是已知的。
对上述等式两边求导,Tm‘可以理解为前一时刻的Tm值减掉后一时刻Tm的值,然后再除以时间Δt:
由于Ż = V and V̇ = a,继续简化:
我们定义辅助参数C:
可以求得α:

带入一元二次方程式,得:
最后将Tm计算公式带入,得:
在知道前后两帧主目标的宽度(或高度)变化比例后,通过求得Tm和C,我们就能求得基于CAM模型的碰撞时间。我在我的代码库中实现了这一算法,但是由于恒定加速度模型需要恒速模型的理论支持,而恒速模型求出的摄像头碰撞时间Tm数值可能会存在误差,并且时间间隔Δt很短,导致C的数值存在很大波动。也就是说恒加速度模型对于前后两帧S尺度比的计算精度要求很高,因此对于在本项目场景下,恒加速度模型并没有很大的使用必要。
但是在本项目中,我们将使用CVM而不是CAM,是因为我们假定CVM模型足够准确,能为我们提供TTC的合理的估算,而且CVM在编程上会很相对简单很多。但是,请记住如果你要构建此类系统的商业版本,一定要使用恒加速度模型。接下来我们来讨论基于恒速模型的摄像头TTC计算。

2. Estimating TTC with a Camera

单眼相机无法测量公制距离。它们是无源传感器,其依赖于环境光,该环境光将物体反射回相机镜头。因此,不可能像激光雷达技术那样测量光的运行时间。
一般为了测量距离,将需要第二个摄像机。给定两个经过仔细对准的相机同时拍摄的图像(也称为多目设置),在两个图像中定位共同的兴趣点(例如,前车的尾灯),然后使用相机几何学和透视投影对它们的距离进行三角测量 。目前汽车研究人员已经开发出了用于 ADAS 产品的立体相机,其中一些已经投入市场。尤其是梅赛德斯-奔驰是这项技术的先驱。然而,随着更先进的 ADAS 技术和自动驾驶汽车的出现,由于双目相机的包装尺寸、高昂的价格和寻找相应特征所需的高计算量,导致双目相机已经逐渐从市场上消失。

恒速模型TTC计算

尽管单目相机无法测量距离,但是我们可以使用一种无需测量距离即可计算TTC的方法。如将公制距离d用在像面上的像素距离来代替的方法。在下图中,您可以看到高度H可以使用透视投影将先前车辆的“位置”映射到图像位置。我们可以看到相同的车辆高度H,取决于车辆距离 d_0 和 d_1, 映射到图像平面上不同的高度 h_0 和 h_1 。显然, h, H, d和 针孔相机焦距 f 之间存在几何关系。我们看下图:


经过一系列公式推导,最终结果为:

边界框检测问题

在本项目中,我们会使用YOLO V3在单目相机的连续图像中定位车辆。对于每辆车,网络都会返回一个边界框,该边界框的宽度或高度原则上可以用于计算我们在上一节中得出的TTC方程中的高度比。
但是,当仔细观察时,可以看到边界框并不总是反映真实的车辆尺寸,并且图像之间的纵横比有所不同。因此,使用边界框高度或宽度进行TTC计算会导致明显的估计误差。


在大多数工程任务中,仅依靠单个测量或属性是不够可靠的。对于与安全相关的产品尤其如此。因此,我们要考虑的是,在图像中还能观察到车辆和物体的其他属性。

改用纹理关键点

现在,我们不再依赖整个车辆的检测,而是希望小范围分析其结构。如果有可能找到可以从一帧追踪到下一帧的唯一可识别关键点,则我们可以使用车辆上所有关键点之间的相对距离来计算出TTC方程中高度比的可靠估计。下图说明了该概念。


在(a)中,已检测到一组关键点,并且已计算出关键点1-7之间的相对距离。在(b)中,已使用称为关键点描述符在连续图像之间匹配了4个关键点。彼此之间的所有相对距离之比可以用来代替高度比,从而计算出可靠的TTC估算值h1/h0。备注:对所有距离比率的求平均值或中位数 dk/dk′可以减少异常数值的干扰。
下图为关键点相对距离的示意图:


3. Estimating TTC with Lidar

由于激光雷达能直接测量与前车的距离,因此我们不再需要获得针孔成像的高宽比了。让我们来看下如何使用激光传感器测量碰撞时间。
假设配备CAS的车辆正在使用激光雷达传感器对先前车辆进行距离测量。在这种情况下,传感器将为我们提供到行驶路径中最接近的3D点的距离。在下图中,最接近的点由CAS车辆顶部的激光雷达传感器发出的红线表示。


在前文中,我们基于恒速模型讨论了速度 v0可以通过两次连续的激光雷达测量来计算,如下所示:
一旦知道相对速度v0后,碰撞时间可以很容易地通过将两辆车之间的剩余距离除以 v0获得。因此,只要给定一个能够进行精确距离测量的激光雷达传感器,就可以基于CVM和上述方程组开发用于TTC估算的系统。
下图显示了激光雷达点云,它是在高速公路场景中拍摄的摄像机图像的叠加,前车直接在行驶路径中。到传感器的距离用颜色编码(绿色很远,红色很近)。在左侧,还显示了激光雷达点的鸟瞰图。


由于测量精度与从物体反射的光量相关,因此有必要考虑除x,y和z坐标之外每个激光雷达点的反射率r。下图以绿色突出显示高反射率,而具有低反射率的区域显示为红色。对点云的相关反射率的分析表明,此类偏差通常发生在反射率降低的区域中。
为了从给定的点云中获得稳定的TTC测量,必须执行两个主要步骤:
1、删除路面的尺寸
2、去除反射率低的测量
我在本项目的代码库中,将Lidar点打包到一个名为LidarPoints的数据结构中。如下所示,该结构由公制坐标中的点坐标x(正向),y(左侧)和z(向上)以及点反射率r(介于0和1之间(高反射率))组成。
struct LidarPoint { // single lidar point in space
    double x, y, z; // point position in m
    double r; // point reflectivity in the range 0-1
};
为了计算TTC,我们需要找到到行驶路径中最接近的激光雷达点的距离。后挡板一般为激光雷达反射最佳的位置。
这是由于车辆后挡板处反射面积大,较为平整,对于激光点云的反射效果相对更佳。因此我们将点云裁剪到车辆后挡板处,以便检测后车到前车的最短距离。
在下图中,我们可以获得前车后挡板上的激光雷达测量值t_0 (绿色)和 t_1(红色)。可以看出,在两个时刻之间到车辆的距离略有减小。


以下代码在与点关联的点云中搜索最接近的点 t0(lidarPointsPrev)以及与t_1(lidarPointsCurr)。在分别找到最接近点的距离后,根据我们在本节开始时得出的公式来计算TTC。
void computeTTCLidar(std::vector &lidarPointsPrev,
                     std::vector &lidarPointsCurr, double &TTC)
{
    // auxiliary variables
    double dT = 0.1; // time between two measurements in seconds

    // find closest distance to Lidar points
    double minXPrev = 1e9, minXCurr = 1e9;
    for(auto it=lidarPointsPrev.begin(); it!=lidarPointsPrev.end(); ++it) {
        minXPrev = minXPrev>it->x ? it->x : minXPrev;
    }

    for(auto it=lidarPointsCurr.begin(); it!=lidarPointsCurr.end(); ++it) {
        minXCurr = minXCurr>it->x ? it->x : minXCurr;
    }

    // compute TTC from both measurements
    TTC = minXCurr * dT / (minXPrev-minXCurr);
}

注意即使激光雷达是可靠的传感器,也可能会发生错误的测量。如上图所示,少量点位于后挡板的后面,似乎没有与车辆连接。当搜索最接近的点时,由于估计的距离太小,因此此类测量会带来问题。有一些方法可以通过对点云进行后处理来避免此类错误,但不能保证此类问题在实践中永远不会发生。因此,在本项目中为了对minXCurr和minXPrev进行更可靠的计算,我使用了所有距离的中值,来处理一定数量的离群值,其次通过融合另一个能够计算TTC的传感器 如相机,来过滤离群值。
尽管我们使用了相机及激光雷达计算TTC,但是实际上毫米波雷达传感器仍然是TTC计算的最佳解决方案,因为它可以利用多普勒效应在返回的电磁波中利用频移来对速度进行直接测量,而对于激光雷达传感器,我们需要通过两个距离之间的差值测量速度。对于实际解决方案来说,通过摄像头或激光雷达测量TTC更多的是为了提供足够的安全冗余。

4. Camera based 2D feature detection

现在我们已经了解了如何使用恒速运动模型来设计碰撞检测系统,该系统将计算碰撞检测时间。我们在基于摄像头的TTC计算章节提过,我们可以使用车辆上所有关键点之间的相对距离来计算出TTC方程中高度比的可靠估计。那么问题来了,我们如何实现相邻两帧图片间的关键点提取与匹配。在我之前的文章自动驾驶汽车视觉- 图像特征提取与匹配技术中,我详细介绍了关键点检测,描述以及匹配的背后原理,不同的组合方式之间的优劣,并提出几组根据实践结果得出的最佳组合。因此在本文中我只简单讲述特征匹配的流程。目前存在通过多种检测器,描述符以及匹配子组合来执行图像关键点检测,我所使用的是FAST+Brisik+MAT_BF,但是你们也可以在我的代码库中尝试其它组合。

以下是关键点提取和匹配的顺序:
1、从File中加载图像。在实际项目中,您将使用相机并拍摄实时图像,为了减少对于实时存储的消耗,我们将从File中加载图像并将其放入环形缓冲区。现在,此缓冲区为我们提供了每个时间点的图像。
2、检测图像关键点。我们可以通过多种方法实现关键点检测,所有方法返回的结果都是一个关键点集合。
3、提取关键点描述符。此模块需要输入关键点及原图。通过在每个关键点位置周围,计算出一个关键点描述符,以反映关键点附近的像素强度结构或其周围的其他属性,从而在图像局部邻域中唯一标识此关键点。
4、描述符匹配。匹配是基于关键点及关键点的描述符。由于是不同帧图之间的关键点匹配,因此会输入前后帧两组关键点和描述符,基于这两个输入,输出是两个连续的图像的一组关键点匹配。我们会在两个图像中找到多组匹配的关键点集合,用来计算碰撞时间。
现在,我们以及解决了关键点匹配的问题,但是同样面临一个严重的问题,大量的匹配关键点并非在目标车辆上。因此我们必须以某种方式隔离位于前车上的关键点,并排除路面上以及其他静态对象上的所有其他关键点。对象检测算法能够输出对象的边界框,从而有效的帮我们隔离前车上的关键点,接下来我们来了解对象检测算法。

5、Object Detection with YOLO

在本节中,我们将使用深度学习方法来检测图像中的车辆。我们已经在整个图像上识别出了关键点。为了识别单个车辆并计算TTC ,我们需要将前后两帧图像上两两关键点归类并与场景中的车辆关联起来。
通过对象检测算法,我们可以检测图像中的车辆,以此隔离匹配的关键点以及投影的激光雷达点,并将它们与特定对象相关联。也就是说为了计算特定车辆的碰撞时间,我们需要隔离该车辆上的关键点,以使TTC不会因包含例如路面,静止物体或其他车辆中的匹配项而失真。
对象检测算法的输出将是场景中所有对象周围的一组2D边界框。基于这些边界框,我们可以轻松地将关键点匹配与对象相关联,并获得稳定的TTC估算值。
对于激光雷达测量,我们也可以用对相机图像进行对象检测生成的2D边界框,将点云聚类到单个对象中。在我之前的一篇文章自动驾驶激光雷达物体检测技术中我提到过可以通过PCL点云库中的算法对点云进行直接聚类,但是激光雷达对象检测的能力是远弱于摄像头的,因此在本文中我们使用基于相机图像的物体识别算法YOLOv3给定一组对象的边界框,将激光雷达投影到图像平面的点进行聚类并与特定对象相关联。关于如何将激光雷达点投影到图像平面,请参考我的另外一篇文章: 自动驾驶视觉融合-相机校准与激光点云投影。
我在上一篇文章自动驾驶目标检测- YOLO v3 深入解析中详解介绍了YOLOv3的原理及实现,因此在本文中我只讲述YOLOv3在C++环境下基于OPENCV快捷实现。


YOLOv3工作流程

主要算法步骤:
首先,将图像划分为13x13的单元格网格。根据输入图像的大小,这些像元的大小(以像素为单位)会有所不同。在下面的代码中,使用了416 x 416像素的大小,因此单元格大小为32 x 32像素。
如上图所示,每个单元然后用于预测一组边界框。对于每个边界框,网络还预测边界框包围特定对象的置信度以及该对象属于特定类的概率(取自COCO数据集)。
最后,使用非最大抑制来消除具有低置信度的边界框以及包围同一对象的冗余边界框。
主要代码步骤:
始化参数
  • “confThreshold”用于删除分数值较低的所有边界框

  • “ nmsThreshold”将非最大抑制NMS应用于剩余的边界框。

  • “ inpWidth”和“ inpHeight”,YOLO作者所建议,将其设置为416。其他值例如可以是320(更快)或608(更准确)。

准备模型
  • 文件“ yolov3.weights”包含预先训练的网络的权重,并且已由YOLO的作者在此处提供。

  • 包含网络配置的文件“ yolov3.cfg”在此处可用。

  • 而包含COCO数据集中使用的80个不同类名称的coco.names文件可在此处下载。


以下代码显示了如何加载模型权重以及相关的模型配置:


// load image from file
cv::Mat img = cv::imread("./images/img1.png");

// load class names from file
string yoloBasePath = "./dat/yolo/";
string yoloClassesFile = yoloBasePath + "coco.names";
string yoloModelConfiguration = yoloBasePath + "yolov3.cfg";
string yoloModelWeights = yoloBasePath + "yolov3.weights";

vector classes;
ifstream ifs(yoloClassesFile.c_str());
string line;
while (getline(ifs, line)) classes.push_back(line);

// load neural network
cv::dnn::Net net = cv::dnn::readNetFromDarknet(yoloModelConfiguration, yoloModelWeights);
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

从输入图像生成4D Blob
  • 从文件加载的图像通过blobFromImage函数传递,然后转换为神经网络的输入块。像素值以1/255的缩放因子缩放到0到1的目标范围。它还可以将图像的大小调整为指定的大小(416、416、416),而不会进行裁切。


 // generate 4D blob from input image
    cv::Mat blob;
    double scalefactor = 1/255.0;
    cv::Size size = cv::Size(416, 416);
    cv::Scalar mean = cv::Scalar(0,0,0);
    bool swapRB = false;
    bool crop = false;
    cv::dnn::blobFromImage(img, blob, scalefactor, size, mean, swapRB, crop); 



    输出Blob将作为输入传递到网络。然后,将执行前向传递以获得从网络输出的预测边界框的列表。这些框经过一个后处理步骤,以过滤掉具有低置信度值的那些框.


    通过网络运行正向传递

    我们必须将刚创建的Blob作为输入传递给网络。然后,我们运行OpenCV的转发功能,以执行通过网络的单个转发。为此,我们需要确定网络的最后一层,并为该功能提供相关的内部名称。这可以通过使用OpenCV函数“ getUnconnectedOutLayers”来完成,该函数给出所有未连接输出层的名称,这些输出层实际上是网络的最后一层。


 // Get names of output layers
    vector names;
    vector outLayers = net.getUnconnectedOutLayers(); // get indices of output layers, i.e. layers with unconnected outputs
    vector layersNames = net.getLayerNames(); // get names of all layers in the network

    names.resize(outLayers.size());
    for (size_t i = 0; i < outLayers.size(); ++i) // Get the names of the output layers in names
    {
        names[i] = layersNames[outLayers[i] - 1];
    }

    // invoke forward propagation through network
    vector netOutput;
    net.setInput(blob);
    net.forward(netOutput, names);


    前向传递的结果以及网络的输出是大小为C的向量,每个类中的前四个元素表示中心x,y,相关边界框的宽度和高度。第五个元素表示各个边界框实际上包围对象的信任度或置信度。矩阵的其余元素是与coco.cfg文件中包含的每个类相关联的置信度。


以下代码显示了如何扫描网络结果以及如何将具有足够高置信度得分的边界框组装到向量中。该函数cv::minMaxLoc使用在整个数组中搜索的极值来找到最小和最大元素值及其位置。
// Scan through all bounding boxes and keep only the ones with high confidence
    float confThreshold = 0.20;
    vector classIds;
    vector confidences;
    vector boxes;
    for (size_t i = 0; i < netOutput.size(); ++i)
    {
        float* data = (float*)netOutput[i].data;
        for (int j = 0; j < netOutput[i].rows; ++j, data += netOutput[i].cols)
        {
            cv::Mat scores = netOutput[i].row(j).colRange(5, netOutput[i].cols);
            cv::Point classId;
            double confidence;

            // Get the value and location of the maximum score
            cv::minMaxLoc(scores, 0, &confidence, 0, &classId);
            if (confidence > confThreshold)
            {
                cv::Rect box; int cx, cy;
                cx = (int)(data[0] * img.cols);
                cy = (int)(data[1] * img.rows);
                box.width = (int)(data[2] * img.cols);
                box.height = (int)(data[3] * img.rows);
                box.x = cx - box.width/2; // left
                box.y = cy - box.height/2; // top

                boxes.push_back(box);
                classIds.push_back(classId.x);
                confidences.push_back((float)confidence);
            }
        }
    }
  • 网络输出的后处理
    • OpenCV库提供了一种现成的函数,用于抑制重叠的边界框。此功能称为NMSBoxes,可以按以下简短代码示例所示使用它:

// perform non-maxima suppression
    float nmsThreshold = 0.4;  // Non-maximum suppression threshold
    vector indices;
    cv::dnn::NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, indices);

    • 应用非最大抑制后,多余的边界框将被成功删除。下图显示了结果,其中绿色表示保留的边界框,而红色的边界框在NMS期间已删除。




至此我们已经使用对象检测成功地自动识别了场景中的车辆并输出场景中所有对象周围的一组2D边界框。
让我来看看整体的代码框架。

6. Flow schematic



本项目的目的是同时使用摄像头和激光雷达测量TTC,在这过程中既有传感器各自的处理部分,也有相互融合和补充的部分。
在上文中我们介绍了基于摄像机和激光雷达的TTC计算方法,关键点匹配,Yolov3对象识别,激光雷达3D点云投影到2D图像平面,以及如何将相机图像中的区域与自由空间中的点相关联。现在让我们看一下程序原理图,看看我们已经完成了什么,以及仍然缺少什么。
  • 在第一部分(黄色框)中,我们已经完成了编写有关关键点检测和关键点匹配的代码。黄色矩形是其程序流程。首先我们加载图像并使用环形缓冲区,然后检测图像关键点,提取关键点描述符,最后匹配相邻两帧图像的关键点描述符。虽然关键点匹配存在于每个图像中,但是它们没有聚类并绑定到任何对象。

  • 在第二部分中(绿色框),我们使用Yolov3完成了对对象的检测和分类,这为我们提供了一组感兴趣对象边界框。对于相机图像,我们可以将对象上的关键点关联到对应的边界框。而对于激光雷达,我们也可以将激光雷达3D点云投影到2D图像平面,使用对象检测的边界框对点云进行聚类,到第四步时该边界框变量既包含有关对象位置的信息,还包括车辆在图像中的关键点信息,以及对应的3D激光雷达点云。

  • 接下来我们需要时刻跟踪3D对象边界框。因此,我们需要通过查看边界框内的关键点对应关系来关联相邻帧之间的边界框。

  • 在完成第八步之后,我们可以计算相机及激光雷达传感器正前方物体的TTC。值得注意的是,我们将只关注本车道线正前方最近的对象。


Code Implementation


载入图像Buffer,在每次循环中只需要前后两个图像数据
if (dataBuffer.size() > dataBufferSize) {
	dataBuffer.erase(dataBuffer.begin());
	}
dataBuffer.push_back(frame);

使用YOLOv3检测和分类对象,获得汽车对象的边界框
float confThreshold = 0.2;
float nmsThreshold = 0.4;
detectObjects((dataBuffer.end() - 1)->cameraImg, (dataBuffer.end() - 1)->boundingBoxes, confThreshold,nmsThreshold,yoloBasePath, yoloClassesFile, yoloModelConfiguration, yoloModelWeights, bVis);

载入激光雷达点云,根据距离属性裁剪点云
// remove Lidar points based on distance properties
float minZ = -1.5, maxZ = -0.9, minX = 2.0, maxX = 20.0, maxY = 2.0, minR = 0.1; // focus on ego lane
cropLidarPoints(lidarPoints, minX, maxX, maxY, minZ, maxZ, minR);

(dataBuffer.end() - 1)->lidarPoints = lidarPoints;

将3D点云投射到2D图像平面,并使用边界框对点云进行过滤和聚类
// associate Lidar points with camera-based ROI
float shrinkFactor = 0.20; // shrinks each bounding box by the given percentage to avoid 3D object merging at the edges of an ROI
clusterLidarWithROI((dataBuffer.end()-1)->boundingBoxes, (dataBuffer.end() - 1)->lidarPoints, shrinkFactor, P_rect_00, R_rect_00, RT);

检测图像关键点,选择描述符,匹配相邻两帧图像的关键点
// choose keypoint detector
detKeypointsModern(keypoints, imgGray, detectorType, false);
// choose keypoint descriptor
descKeypoints((dataBuffer.end() - 1)->keypoints, (dataBuffer.end() - 1)->cameraImg, descriptors,descriptorType);
// choose keypoint matcher and match corresponding kepoints
matchDescriptors((dataBuffer.end() - 2)->keypoints, (dataBuffer.end() - 1)->keypoints,
(dataBuffer.end() - 2)->descriptors, (dataBuffer.end() - 1)->descriptors,matches, descriptorclass, matcherType, selectorType);
// store matches in current data frame
(dataBuffer.end() - 1)->kptMatches = matches;

追踪3D对象边界框
map bbBestMatches;
matchBoundingBoxes(matches, bbBestMatches, *(dataBuffer.end() - 2), *(dataBuffer.end() -1)); // associate bounding boxes between current and previous frame using keypoint matches
// store matches in current data frame
(dataBuffer.end() - 1)->bbMatches = bbBestMatches;

分别计算基于恒速模型摄像头以及激光雷达测量的碰撞时间
for (auto it1 = (dataBuffer.end() - 1)->bbMatches.begin();it1 != (dataBuffer.end() - 1)->bbMatches.end(); ++it1) {

    // find bounding boxes associates with current match
    BoundingBox *prevBB, *currBB;
    for (auto it2 = (dataBuffer.end() - 1)->boundingBoxes.begin();it2 != (dataBuffer.end() - 1)->boundingBoxes.end(); ++it2) {
        if (it1->second == it2->boxID) // check wether current match partner corresponds to this BB
            {
            currBB = &(*it2);
            }
        }

    for (auto it2 = (dataBuffer.end() - 2)->boundingBoxes.begin();it2 != (dataBuffer.end() - 2)->boundingBoxes.end(); ++it2) {
        if (it1->first == it2->boxID) // check wether current match partner corresponds to this BB
            {
            prevBB = &(*it2);
            }
    	}

    // compute TTC for current match
    if (currBB->lidarPoints.size() > 0 &&prevBB->lidarPoints.size() > 0)
    // only compute TTC if we have Lidar points
        {
        // compute time-to-collision based on Lidar data (implement -> computeTTCLidar)
        double ttcLidar;
        computeTTCLidar(prevBB->lidarPoints, currBB->lidarPoints, sensorFrameRate, ttcLidar);

        // assign enclosed keypoint matches to bounding box (implement -> clusterKptMatchesWithROI)
        // compute time-to-collision based on camera (implement -> computeTTCCamera)
        double ttcCamera;
        clusterKptMatchesWithROI(*currBB, (dataBuffer.end() - 2)->keypoints,(dataBuffer.end() - 1)->keypoints, (dataBuffer.end() - 1)->kptMatches);
        computeTTCCamera((dataBuffer.end() - 2)->keypoints, (dataBuffer.end() - 1)->keypoints,currBB->kptMatches, sensorFrameRate, ttcCamera);
        currBB->kptMatches, sensorFrameRate, ttcCameraCAM,TmPrev);
        }
    }

至此我们完成了整个3D对象跟踪及防碰撞系统搭建项目。

8. Conclusion



本文的目的是教会初学者如何利用关键点提取和匹配技术,对象检测技术,3D点云投影技术,激光雷达对象检测技术来完成3D对象追踪,并且计算与前车的碰撞时间TTC。
我们可以看到随着时间变化,前后车的相对距离和速度改变,激光雷达和摄像机计算出的碰撞时间也随之变化。
值得注意的是,在我们的使用场景中前后两个时刻之间的Δt很短,恒速模型能够满足我们的要求。但在市售的防碰撞系统CAS中,几乎都是使用恒定加速度模型,这一模型所需要的参数更多,对于相机前后两帧图像尺度比的精度要求很高。而恒速模型跳过了对于速度的测量,但是如果我们能获得精确的速度,我们可以大大减少工作量,并且提高碰撞时间的计算精度。对于单目摄像头来说,我们也以使用光流法,检测图像像素点的强度随时间的变化进而推断出物体移动速度及方向。
在实际汽车供应商的市售产品中,往往使用毫米波雷达搭建防碰撞系统,这是由于多普勒效应能直接获得前车的精确速度。对于毫米波雷达的工作原理,大家可以参考我之前的文章: 自动驾驶毫米波雷达物体检测技术-硬件自动驾驶毫米波雷达物体检测技术-算法。其次对于防碰撞系统来说,系统的绝对安全可靠和及时是非常重要的,因此除了算法开发,大量研究人员的重心都放在测试与调优上了。如果你们有机会在你们后续的工作中接触到CAS系统的设计和测试,很希望能听到你们的技术分享。
最后在实际项目的后续处理中,我们还会用到卡尔曼滤波器,对来自不同传感器的特征级数据如速度,距离,位置等进行融合,从而获得远比单个传感器更可靠的测量结果。在我之前的文章: 自动驾驶感知融合-无迹卡尔曼滤波(Lidar&Radar)技术文章:自动驾驶感知融合-卡尔曼及扩展卡尔曼滤波(Lidar&Radar)中,我详细讲述了卡尔曼,扩展卡尔曼及无迹卡尔曼滤波器的原理以及实践,可以供大家参考。通过多传感器融合技术,感知的性能与稳定将会得到很大的提升。
END


收藏
点赞
2000