自动驾驶之心推出的《国内首个BVE感知全栈系列学习教程》,链接。记录下个人学习笔记,仅供自己参考
本次课程我们来学习下课程第四章——基于环视Camera的BEV感知算法,一起去学习下 PETR 感知算法
课程大纲可以看下面的思维导图
本次课程我们再一起学习一篇非常好的多视角 3D 目标检测工作叫 PETR,大家可以想一下,P 是什么,E 是什么,TR 我们知道是 Transformer,P 是 Position,E 是 Embedding,PE 合在一起是 Position Embedding 位置编码,所以说我们从题目中也能看出核心思路在哪呢,在位置编码的设计上
和之前一样我们依然从四个方面展开,也就是算法动机&开创性工作、主体结构、损失函数以及性能对比
我们在讲解 PETR 之前先带大家回顾一下 DETR3D 这篇工作,因为 PETR 是针对 DETR3D 做改进的工作,大家没有印象的可以先去复习一下之前 4.4 小节的内容
DETR3D 是一篇关于多视角图像的 3D 目标检测工作,输入是多视角的图像,输出是 3D 检测结果,对输入的多视角图像用什么处理呢,图像处理网络提取多视角的图像特征,那 DETR3D 用的是什么呢,是 ResNet 和 FPN,那 ResNet 作为 Backbone 提取图像特征,FPN 是特征金字塔网络用来处理多尺度特征,所以我们最后得到的图像特征是什么呢,我们叫多尺度的图像融合特征,有了图像特征之后我们再怎么做呢,初始化一系列的 Object Query,以 Object Query 为基础从图像中拿特征,通过这种方式 2D 特征其实被采样成一系列的 Object Query Feature,物体特征有了,我们后续自然可以拿来做检测
所以我们这里再详细拆解一下 2D 到 3D 的特征变化过程,对应图中的 2D-to-3D Feature Transformation 模块,我们说它名字叫这个,其实它的过程是一个反方向的过程,是一个从 3D 到 2D 的过程,有了图像特征之后第一步初始化 Object Query,Query 的作用是随机初始化一系列查询向量,这是第一步,用于查询空间当中哪里有物体,那我们具体怎么查呢,通过网络来预测,对应图中的第二步也就是图中蓝色箭头所示的步骤
我们具体来讲一下,Object Query 通过 Transformer 模块来预测生成 3D reference points,我们叫参考点对应图中的 c l i c_{li} cli?,有了参考点之后就可以通过相机内外参数将参考点逆投影回图像空间找到参考点在图像上对应的位置也就是 c l m i c_{lmi} clmi?,那这个过程对于图中的第三步,也就是绿色箭头所示的步骤,有了原始图像中的像素位置之后我们可以找到该像素位置对应的特征或者说不同尺度的 2D 特征
那图中给了几种不同大小的尺度特征,有比较小的,比较中等的,比较大的,通过三种尺度特征的融合我们得到最终的也就是图中步骤 5 所示的图像特征,有了图像特征之后我们就可以做预测了,那这个过程其实是迭代预测的过程,是多次 refining 的过程,我们可以不断地得到新的 Object Query,可以不断的更新预测,DETR3D 是通过这样一个过程去做 3D 目标检测任务的,回顾完 DETR3D 之后我们再来看看 PETR 的动机
我们先简单总结下上图中的三个网络框架:
DETR 设计流程:
DETR3D 设计流程:
PETR 设计流程:
图 (a) 是 DETR 的框架,它有什么输入呢,有 Feature,有 2D 的 Position Embedding,2D 的位置编码,还有什么呢,有 Query,每一个 Query 都代表一个目标,通过 Object Query 找到 2D 特征图对应的特征,再结合 2D 位置编码信息去迭代更新 Object Query,我们最后会得到一个检测结果,可以看到整体流程非常简洁
图 (b) 是 DETR3D 的框架,有什么呢,有特征,还有什么呢,有 Query,Query 生成一系列的 reference point 参考点去 2D 图像采样特征,采样完事之后去更新 Object Query 然后用来做预测
PETR 作者认为 DETR3D 的流程不是特别优雅存在一些缺陷,首先第一个问题利用参考点索引图像特征是不合理的,也就是说参考点索引出来的图像特征可能不是特别准确,如果参考点位置出错了,投影回图像位置就会产生一定的偏差,比如投影到目标区域以外的区域,从而导致采样到的图像特征是无效的
其次还有一个问题,作者认为 DETR3D 如果只采用参考点投影位置得到图像特征来更新 Query 是不够充分的,可能导致模型对于全局特征学习得不够充分,我们也能想到它属于一个比较 Local 的比较局部的特征,因为只有一个参考点的特征,所以说对于全局特征的学习其实不是特别充分,另外一个问题是 PETR 的作者认为像 DETR3D 的方法采样的过程比较复杂很难落地
那所以作者在 PETR 原文中也进行了表述,PETR 通过 3D 位置编码将多视角的 2D 图像特征转换为 3D 感知特征,2D 图像特征和 3D 位置编码融合后生成的特征我们叫 3D 感知特征。本身的 2D 特征和 3D 位置编码合在一起我们就有了 3D 感知信息的特征表达,那使得 Query 可以在 3D 语义环境下进行更新,从而省略了这个来回反投影的过程以及特征重复采样的过程,所以大大简化了 DETR3D 的完整流程
我们再来看看 PETR 的模型结构是什么,其主体结构如下所示:
和之前一样,分析一个模型从输入输出看起,输入是多视角图像,以 nuScenes 数据集为例,输入有 6 个不同视角的图像,输出对于 3D 目标检测任务而言是 3D 检测框,由目标的类别和边界框组成,所以 PETR 功能性表述就是输入是多视角的图像输出是 3D 检测结果
那具体的框架流程是什么样的呢,从图中可以清晰的看出 PETR 主要包括什么呢,图像处理模块、图像特征提取,图像是怎么得到特征的,3D 坐标生成模块,3D 位置编码,还有解码五个部分,那对于输入的多视角图像经过一个 Backbone 可以得到图像特征,同时 3D 坐标生成器可以经过一系列的坐标转换将图像坐标转化为 3D 空间中的坐标,然后 2D 图像特征和 3D 位置坐标同时送到 3D 位置编码器当中我们得到产生 3D 位置感知的特征,那也就是我们把 3D 位置融到了 2D 特征里面生成一系列的 Object Query 与我们得到的特征一起去得到预测结果
那上面是 PETR 整个流程模块,我们再仔细看一下 PETR 各个模块是怎么工作的,首先是 PETR 的 Backbone,那也就是我们之前一直说的 Image-view Encoder 图像编码器,作用是什么呢,是提取多视角图像特征,输出是啥,是图像特征,输入的维度是 3 × H I × W I 3\times H_I\times W_I 3×HI?×WI?,其中 3 是通道数, H I × W I H_I \times W_I HI?×WI? 是图像尺寸, I I I 表示什么呢,表示不同视角的编号,通过一个 Backbone 之后可以得到多尺度的图像特征,维度是什么呢,是 C × H F × W F C\times H_F \times W_F C×HF?×WF?,我们说过 C C C 是通道数, H F × W F H_F\times W_F HF?×WF? 是下采样后的图像尺寸也就是特征图的尺寸
那在实现过程当中我们的 Backbone 可以用什么呢,也有很多选择包括 ResNet 和 Swin Transformer 等等成熟的框架可以作为 Backbone 提取图像特征,然后利用 FPN 网络也就是我们说的 Neck 网络进一步的得到多尺度的图像特征,那我们前面提到过 PETR 动机是什么呢,是通过引入 3D 位置编码从而将多视角图像的 2D 特征转换为 3D 位置感知特征
那么如何生成这个 3D 信息,那所以作者在这里引入了一个叫 3D 坐标生成器的方法,通过这个 3D 坐标的 Generator 可以将图像坐标转化为 3D 空间坐标,从而完成 2D 到 3D 的转换,那 3D Generation 怎么做呢,如下图所示:
3D Coordinates Generator 设计流程如下:
首先相机的视锥空间可以离散为一系列的网格,维度是 W F × H F × D W_F\times H_F\times D WF?×HF?×D 的三维网格,如下图所示所示,网格中的每个点用什么表示呢,用 p j m p_j^m pjm? 表示, u j u_j uj? 和 v j v_j vj? 是像素坐标, d j d_j dj? 是深度值,每一个 j j j 表示当前离散化网格内的一个点,前面我们提到过 PETR 输入是什么,是多视角图像,那以 nuScenes 为例包含 6 个视角的相机图像,我们相机 i i i 在视锥空间中的第 j j j 个点在 3D 空间应该表示成 p i , j 3 d p_{i,j}^{3d} pi,j3d?, i i i 是第 i i i 个相机,后面的 j j j 是相机空间中的第 j j j 个点, p i , j 3 d p_{i,j}^{3d} pi,j3d? 就表示第 i i i 个相机中的第 j j j 个点的 3D 坐标,用 ( x i , j , y i , j , z i , j ) (x_{i,j},y_{i,j},z_{i,j}) (xi,j?,yi,j?,zi,j?) 来表示
那怎么得到 p i , j 3 d p_{i,j}^{3d} pi,j3d? 呢,分析它是我们的主要目的,我们怎么能得到它呢,从 p j m p_{j}^{m} pjm? 能不能得到它呢,是可以的,通过相机坐标系到世界坐标系的转换公式,也就是通过相机内外参矩阵可以将 p j m p_{j}^{m} pjm? 变换到 p i , j 3 d p_{i,j}^{3d} pi,j3d? 上,那也就是我们上面提到的 3D 坐标生成的第三步, K i K_i Ki? 是相机 i i i 的转换矩阵,通过这样一个变换,我们就可以把 p j m p_{j}^{m} pjm? 变换到 p i , j 3 d p_{i,j}^{3d} pi,j3d?,那这个变换其实很简单乘上转换矩阵就好了,每个视角的图像都经过相应的转换后可以得到 6 个视角下的不同坐标转换结果
上面这个图给得也很清楚,它表示 6 个视角的相机在 3D 空间中所覆盖的区域范围,为什么会有 6 种颜色呢,因为有 6 个视角下不同的转换结果,有没有重叠区域呢,有一定的重叠区域,6 个视角下不同的转换结果组成了完整的 3D 空间,那最后我们得到 3D 坐标之后然后再进行归一化,PETR 作者就得到 3D 坐标生成了。那所以到这里 3D 坐标 Generator 任务就完成了,将每个视角相机空间的像素坐标转换到对应的 3D 世界坐标中从而为后续的 3D Position Encoder 做准备
那这里可能稍微比较难理解一些,所以说得稍微多一点,希望大家可以理解这个 3D 坐标生成器是怎么工作的,我们得到 3D 坐标之后我们现在有了什么东西呢,一个是图像的 2D Feature, F i 2 d F_{i}^{2d} Fi2d? 表示的是第 i i i 相机下的 2D 图像特征,一个是 P i 3 d P_{i}^{3d} Pi3d? 表示的是第 i i i 个相机下生成的坐标网格,有了 F i 2 d F_{i}^{2d} Fi2d? 有了 P i 3 d P_{i}^{3d} Pi3d?,我们要做什么,PETR 作者引入了 3D Position Encoder 将坐标信息编码到 2D 图像当中,如上图所示,也就是说通过一个变换网络可以得到我们认为融合了坐标位置的 3D 感知特征,那有一个表述词叫 Position-aware Feature 也就是能感知 3D 位置的特征
所以总的来说 3D Position Encoder 完成的是哪个功能呢,其实就是位置编码的功能,我们怎么样把位置信息可以编码到特征当中呢,我们接着看
3D Position Encoder 设计流程:
我们这里的网络就是刚提到的位置编码网络,也就是 PETR 里面的 Position Encoder 模块,模块的输入有两个,一个是 2D Feature 是我们得到的图像特征,一个是 3D 世界坐标,那作者用两个支路分别处理这两个输入,那对于 2D 特征图作者直接用一个 1x1 的卷积去做的,那对于 3D 坐标我们发现一个事情,如果想和图像特征直接相加的话,能不能直接相加呢,不行,维度不一样
对于 3D 坐标而言,每一张图像每一个视角的图像是 C C C 维通道的, N N N 表示的是相机的数量, H F × W F H_F\times W_F HF?×WF? 是图像尺寸,那后面 D × 4 D\times 4 D×4 是什么呢,是每一个相机视角下 3D 空间位置的坐标维度,那这个 D D D 是深度值,我们知道是离散化的深度网格的深度量,4 其实是一个 ( x , y , z , 1 ) (x,y,z,1) (x,y,z,1) 的坐标,那这个 D × 4 D\times 4 D×4 维的向量要怎么和通道做对齐呢
那所以作者引入了一个 3D PE 的模块,这个模块的作用其实就是把通道维度变成和图像的通道维度一样,这样它们俩就可以相加了,那相加完事之后就得到了后续的特征,我们叫 F 3 d F^{3d} F3d, F 3 d F^{3d} F3d 的维度跟实际的我们 2D Feature 的维度是不一样的,维度不一样的点在于得到的 F 3 d F^{3d} F3d 我们认为它是融合了 P 3 d P^{3d} P3d 的特征也就是最终的 Position-aware Features 其实是融合了位置信息的特征的
那这个特征由于编码了位置信息 PETR 作者认为它更适合用来做任务,那这个 ? 的意思是叫 Flatten 一个展平,那把原本的 N 个相机特征进行展平可以得到 6 个,那这个展平后的特征是作为 Decoder 的输入的,和 Object Query 一起用来预测最后的检测结果
那所以后续是利用这个 Query Generator 去生成一系列的可学习的 Query,那初始化其实是一个比较复杂的过程,所以 PETR 作者为了降低初始化场景的收敛难度,在 3D 空间当中以均匀分布的方式先初始化一些可学习的 3D anchor,这些 anchor 通过 MLP 去生成所谓的 Object Query,然后将 3D Position-aware Feature 和 Object Query 一起喂给 Decoder,可以迭代更新 Object Query 然后可以去做检测
后续这个流程其实与常见的 DETR、DETR3D 都是一样的,后续的损失函数其实都是一样的,有分类损失,还有回归损失等等,所以到这里 PETR 模型的主体结构我们已经说清楚了
我们再把完整流程过一遍,PETR 的框图如下所示:
PETR 输入是多视角图像,输出是 3D 目标检测结果,包括目标的类别和位置信息,那具体而言对于输入的多视角 RGB 图像首先经过一个共享的 Backbone 提取多视角图像特征,同时利用 3D 坐标生成器,我们叫 3D Coordinates Generator 将相机视锥空间转化为 3D World space 世界坐标空间,将 2D 特征和 3D 世界坐标一起输入到 3D Position Encoder 一个 3D 坐标编码的网络生成 3D Position-aware Feature,3D 位置感知的特征,和得到的 Object Query 一起喂给 Decoder 最后用来生成 3D 目标检测结果,这就是一个完整流程
我们来看看性能,如下表所示:
从表 1 来讲是 nuScenes 验证集的结果,作者也实验了不同的 Backbone 包括 DLA、ResNet50、ResNet101、Swin-Transformer 等等,在不同的分辨率和不同的预训练模型之间来看性能的影响,那图像 size 也实验了很多,比如 1600x900、1056x384 等等,从实验结果看分辨率越高的图像性能是要明显好于低分辨率图像的,那更强大的 Backbone 性能也是更好的,那与我们常见的预期是一样的
另外作者还给了一个什么呢,还给了 PETR 收敛性和速度的分析,与 DETR3D 相比 PETR 在初始阶段收敛速度比较慢并且需要比较长的训练时间才能收敛,那这其实也很好理解,因为我们说 PETR 是一种隐式位置编码的关系,也就是说没有显示的告诉 PETR 这个网络哪里是我们对应的特征,所以说仅仅依靠 Position Encoder 的操作,3D 位置编码融合到 2D 特征当中是一个比较漫长的过程,同时作者还给了一个什么呢,还比较不同 Embedding 方式对性能的影响,也就是表 3,从表中能看到通过 3D 的 Position Embedding 性能提升还是比较明显的
另外一个是表 5,表 5 是一个消融实验部分,那实验中这个详细的说明大家可以去看一下文章是怎么表述的,我们这里提一个点希望大家注意一下也就是表中红色框标注的部分,从 3D 世界坐标得到 3D Position Embedding 需要什么呢,MLP,通过实验作者发现如果这个 MLP 使用 3x3 的卷积模型将无法收敛,mAP 是等于 0 的,那作者认为这是由于什么呢,由于 3x3 的卷积会破坏 2D 特征和 3D 位置间的对应关系,那这个其实是一个比较有意思的发现
另外是一些可视化的结果,大家可以看一下,这里就不详细的展开了,上面的图是一个相似度的比较,可以看到我们的位置编码和特征位置其实对应的相似度是很明显的,比如说图中框出的红色的部分,我们在相对应的图像上其实也能找得到它的相似度比较明显的部分,比如我们左视图上偏右侧的部分,比如出现在中间也能很好的响应到,比如出现在右侧也可以很好的响应到,那这里其实就说明了 Position Embedding 的一个作用,就 Position Embedding 可以很好的感知到图像和图像位置之间的关系
另外还有一些可视化的结果,那 PETR 作者还是比较实在的,没有过分吹嘘自己算法怎么怎么好,还给出了一些 failure case 也就是失败的案例,图中红色和绿色部分其实都是失败的案例,红色部分是什么失败呢,是漏检,我们看到上面就是 GT 的结果,如果 GT 中有框实际检出没有框的话,我们称为漏检。那漏检的对象主要是远处的车辆和一些小目标,比如交通锥一些比较难测的一些目标。另外一个是绿色部分,绿色部分其实是误检,属于类别的误检,就像检测目标本来是车,结果算法识别成卡车了,那就属于类别不分
另外这里给大家补充一下就是说我们 PETR 和 PETRv2 的版本,PETR 之后旷世很快就发布了 PETRv2,那 v2 整体框架还是建立在 PETR 基础上的,它主要区别其实在于一个是时域的建模,我们可以看到下面 PETRv2 的框图中引入了 t-1 的时域,也就是时序的信息,另外一个是引入了一个多任务学习,那这个多任务也就不仅仅包括我们的 detection head 也就是检测头了还包括分割头还包括车道线
那所以这里包括两个关键词,一个是时序建模另一个是多任务学习,时域建模的一个关键问题是什么,是要如何在 3D 空间中对齐不同帧的位置关系,像我们上节课讲过的 BEVDet4D 通过姿态变换的方式显式的对齐帧与帧之间的 BEV 特征,我们要知道显式对齐,那 PETR 一直在强调的一个概念是什么,叫隐式对齐
它觉得我并不用显式的告诉网络那个变换矩阵是什么,网络是可以自己学到变换矩阵的,我们不用很明白的告诉对齐要怎么做,网络可以自己学明白,那所以作者发现一个问题,是只要对齐相邻帧的 3D 目标其性能就可以表现得很好,而 3D 目标是什么东西,是 PETR 前面 Generator 做的事情
所以说时域建模中不同帧的对齐问题就已经解决了,通过生成的 3D 坐标点和历史的 3D 坐标点,它们两个做对齐可以得到一个新的位置表示,拿这个位置表示和得到的特征送入到 Feature Position Encoder 这个网络当中它就可以得到一个我们认为叫 Position-aware Feature,融合了时序信息,融合了位置信息的 Feature,拿这个 Feature 去做检测那自然可以得到更好的结果
另外一个是 Object Query 发生了变化,由于 PETRv2 是多任务处理的,所以针对不同任务设计了不同的 Query,那说了这么多我们一起来看一看这个网络
首先看模型还是先看输入输出,和 PETR 一样 PETRv2 的输入也是多视角输入,由于是多任务的,输出也比较多,包括它的一些检测框、分割、车道线等等,所以输出是一个多任务的输出。那对于多视角图像输入和 PETR 一样,PETRv2 是通过 ResNet、Swin Transformer 等等提取特征,那得到图中浅蓝色的 2D Feature,通过 3D 坐标生成可以得到图中橙色的 3D 坐标,那当前帧是图中的 t frame,历史帧是 t-1 frame,那在 PETR 中 2D Feature 和 3D 坐标怎么做的,通过我们说的 Position Encoder 将 3D 位置坐标信息编码到 2D 特征当中,我们得到一个 Position-aware Feature
在 PETRv2 中怎么做呢,红色框 1 的部分是第一个区别,PETRv2 因为要考虑时序信息,所以说首先要通过姿态变换,将前一帧的 3D 坐标变换到当前帧的坐标系当中,然后将当前帧和前一帧的 3D 坐标信息和 2D 特征一起送到后续的 Encoder 当中,关联 2D 特征和 3D 坐标之间的关系。另外一个我们得到了融合时序的 BEV 特征之后怎么做检测呢,那就是图中 3 这个模块所圈出的内容,原本的单路学习变成多路学习,换句话说就是 PETR 只能做 3D 目标检测,而 PETRv2 不仅可以做 3D 目标检测还可以做车道线检测,BEV 分割等等,而且性能都是不错的
所以 PETRv2 的名字才敢说它是一个 unified framework perception,它是一个 3D 感知的联合框架,那这个联合统一的一致的框架是什么呢,其实是对于多任务而言的,把不同任务统一到了 PETRv2 中,在实验性能方面同时也做了很多的实验比较,毕竟是大厂所以说显卡资源还是挺丰富的,大家感兴趣的可以自己去看一下
那我们有关 PETR 和 PETRv2 一系列的课程讲解到这里就结束了
这节课程我们学习了一个非常经典的纯视觉 BEV 感知算法 PETR,PETR 的作者认为像 DETR3D 这种通过参考点来采样图像特征更新 Query 的方式可能会导致采样的特征不是特别准确,存在一定的偏差,此外参考点比较 Local 对于全局特征的学习不够充分,而且采样过程比较复杂难落地。所以 PETR 的作者考虑的是引入 3D 位置编码将多视角的 2D 图像特征转换为 3D 位置感知特征,其中的 3D 信息通过 3D 坐标器生成,具体是将相机视锥空间离散成网格,通过相机内外参将网格中的每个点转换到 3D 世界坐标下,融合了位置信息的 3D 感知特征会和 Object Query 一起送入到 Decoder 来生成 3D 目标检测结果,这就是 PETR 完整的流程。此外,我们还简单介绍了一下 PETRv2,它与 PTER 的主要区别在于时序建模和多任务学习,引入时序信息可以生成更好的特征,不过需要保证不同帧的对齐,多任务意味着不仅仅有检测任务还包含分割、车道线等等
OK,以上就是 PETR 的全部内容了,下节我们学习另外一篇非常经典的 BEV 感知算法 BEVDepth,敬请期待😄