摘要
提出了一种几何引导的核变换器(Geometry-guided Kernel Transformer:GKT),这是一种新颖的2D到BEV表示学习机制。GKT利用几何先验来引导变换器聚焦于有区别的区域,展开核特征生成BEV表示。为了快速推断,我们进一步引入了查找表(LUT)索引方法,以在运行时消除相机的校准参数。 GKT可以在3090 GPU上以72.3 FPS运行,在2080ti GPU上以45.6 FPS运行,并且对相机偏差和预定义的BEV高度具有鲁棒性。GKT实现了最先进的实时分割结果,即在nuScenes值集上达到38.0 mIoU(100米×100米感知范围,0.5米分辨率)。鉴于效率、有效性和鲁棒性,GKT在自动驾驶场景中具有很大的实用价值,尤其是对于实时运行系统。
论文地址:https://arxiv.org/abs/2206.04584
源码地址:https://github.com/hustvl/GKT
介绍
基于鸟瞰图(BEV)表示的环绕视图感知是自动驾驶领域的一个前沿范式。对于多视图相机系统,如何将2D图像表示转换为BEV表示是一个具有挑战性的问题。根据几何信息是否明确用于特征变换,先前的方法可分为两类,即 geometry-based pointwise transformation 基于几何的逐点变换 and geometry-
free global transformation 无几何的全局变换。
geometry-based pointwise transformation
基于几何的逐点变换,例如:逐点变换法,利用相机的校准参数(内参和外参)来确定2D位置和BEV网格之间的对应关系(一对一或一对多)。利用可用的对应关系,2D特征被投影到3D空间并形成BEV表示。
然而,逐点变换过于依赖于校准参数。对于正在运行的自动驾驶系统,由于复杂的外部环境,摄像机可能在运行时偏离校准位置,这使得2D到BEV的对应关系不稳定,并给BEV表示带来系统误差。此外,逐点变换通常需要复杂且耗时的2D-3D映射操作,例如,预测像素上的深度概率分布、沿射线向BEV空间广播像素特征以及关于相机参数的高精度计算。这些操作效率低且难以优化,限制了实时应用程序。
geometry-free global transformation
全局变换方法考虑了图像和BEV之间的完全相关性。如图1(b)所示,多视图图像特征被展平,每个BEV网格与所有图像像素交互。全局变换不依赖于2D到BEV投影中的几何先验。因此,它对相机偏差不敏感。
(基于transformer:使用global attention的方式用设定好的bev query在图像特征上做attention从而得到bev特征,以CVT为代表,其收敛性和运算效率上存在瓶颈)
但这也带来了问题。1) 全局变换的计算预算与图像像素的数量成比例。分辨率与效率之间存在着尖锐的矛盾。2) 在没有几何先验作为指导的情况下,模型必须从所有视图中全局地挖掘出有区别的信息,这使得收敛更加困难。
这项工作
在这项工作中,针对高效和鲁棒的BEV表示学习,我们提出了一种新的2D到BEV转换机制,称为几何引导核变换器(简称GKT)。利用粗略的相机参数,我们粗略地投影BEV位置来获得先前的2D位置,在多视图和多尺度特征地图中。然后,我们在先前位置周围展开 Kh × Kw 的内核特征,并使 BEV查询 和相应的展开特征交互,以生成BEV表示。此外,在运行时,我们引入LUT索引来消除相机的参数。
优势:
- 鲁棒性:相机的参数只作为指导, GKT可以始终聚焦于目标,对相机的偏差不敏感
- 效率高:与全局变换相比,GKT只关注几何引导的核区域,避免了全局交互
GKT在运行时具有很高的鲁棒性。与逐点变换相比,GKT只将相机的参数作为指导,而不太依赖它们。当相机偏离时,相应地,内核区域会移动,但仍然可以覆盖目标。变换器是置换不变的, attention weig是根据偏差动态生成的。因此,GKT可以始终聚焦于目标,对相机的偏差不敏感。
GKT效率高。利用所提出的LUT索引,我们在运行时消除了逐点变换所需的2D-3D映射操作,使正向过程紧凑而快速。与全局变换相比,GKT只关注几何引导的核区域,避免了全局交互。GKT需要更少的计算,收敛更快。
因此,GKT很好地平衡了逐点和全局变换,从而实现了高效和稳健的2D到BEV表示学习。我们在nuScenes地图视图分割上验证了GKT。GKT非常高效,在3090 GPU上以72.3 FPS运行,在2080ti GPU上以45.6 FPS运行,比所有现有方法都快得多。GKT达到38.0 mIoU,是所有实时方法中的SOTA。我们将在不久的将来将GKT扩展到其他基于BEV的任务。
本文提出了一种几何引导的核Transformer(GKT),这是一种新的2D到BEV表示学习机制。GKT利用几何先验来引导Transformer聚焦于局部区域,并展开内核特征以生成BEV表示。为了快速推断,进一步引入了查找表(LUT)索引方法,并在运行时消除相机的校准参数。
本文提出了一种基于几何先验在图像特征中寻找reference points,同时在该reference points处通过预先设置窗口抠取图像特征,并在此基础上使用attention操作实现特征优化,从而获取bev特征的方法。上述通过几何先验寻找reference points的方法可以通过look up table实现加速,这样整体网络的计算耗时就相对较小,可以跑到很高的帧率。对于车辆行驶过程中存在抖动的情况,文中提出了一种扰动机制添加到训练过程中,使得网络对扰动具有一定鲁棒性。
方法
从上图中可以将该pipline划分为如下几个步骤:
1)CNN骨干网络(efficientnet-b4)从环视图像 I 中提取多尺度多视图特征 Fimg,完成对输入图像数据特征抽取。 然后通过几何先验确定bev grid与图像特征上reference point的对应关系,相当于是粗定位。BEV空间均匀划分为网格,每个BEV网格对应一个3D坐标 Pi(xi, yi, z) 和一个可学习查询的 qi 。 z 是所有查询的共享的BEV平面预定义高度。利用几何先验知识来引导 transformer 聚焦于可鉴别区域。通过摄像头参数,粗略地将每个BEV网格Pi投影到一组浮点2D像素坐标{Qsv}(对于不同的视图和尺度),然后将它们四舍五入到整数坐标。
在先验位置{Q̄sv}附近展开Kh×Kw核区域。值得注意的是,如果核区域超出图像边界,则超出部分将设置为零。每个BEV查询qi与相应的未展开核特征F(F ∈ RNview ×Nscale ×C×Kh×Kw)交互,并生成BEV表征。 完成各种任务(如检测、分割、运动规划)的头部可以在BEV表征上形成。
efficientnet-b4:一个输入图像产生多个尺度(2个尺度)的特征表达。
2)对于正在运行的自动驾驶系统,外部环境是复杂的。摄像头将偏离其标定位置。GKT对摄像机偏差具有鲁棒性。为了验证这一点,在真实场景中模拟摄像机的偏差。具体而言,将偏差分解为旋转偏差Rdevi和平移偏差Tdevi,并将随机噪声添加到所有x、y、z维度。
所有的偏差均为高斯噪声。取整函数也可以保证一定的抗噪能力。
由于自身运动或者周围场景中物体的运动相比标准环境带来的扰动,这里对每个bev grid对应的reference point处使用窗口进行特征抠取,抠取之后的特征展平之后使用attention确定元素权重,从而得到在该bev grid下的特征表达。
当摄影机偏离时,先验区域会移动,但仍然可以覆盖目标。舍入操作是抗噪的。当摄像头的参数略有变化时,四舍五入后坐标保持不变。此外,transformer是排列组合不变,核区域的注意权值随偏差动态变化。
3)position encoding
CNN特征基础上添加position encoding(依赖于各自视图标定得到的内外参数矩阵)作为refine从而得到attention中的key。
4)交叉视角的 cross-attention机制
这部分主要是实现了感知相机的位置编码,利用相机独立的校准矩阵(内外参等)对特征进行位置编码。
数据为多视图数据
(分别代表图像、旋转矩阵、内参矩阵、平移向量)
5)我们进一步引入BEV-to-2D LUT索引以加快GKT。每个BEV网格的内核区域是固定的,可以离线预计算。在运行前,我们构建了一个LUT(查找表),它缓存了BEV查询索引和图像像素索引之间的对应关系。
在运行时,我们从LUT中获得每个BEV查询的对应像素索引,并通过索引有效地获取内核特征。
通过LUT索引,GKT摆脱了对相机参数的高精度计算,实现了更高的FPS。
6)Configuration of Kernel
对于GKT,核的配置是灵活的。可以调整核大小来平衡感受野和计算开销。 核模式是可行的(十字形核、扩张核等)。由于采用LUT索引提取核特征,GKT的效率不受核模式的影响。
class GeometryKernelEncoder(nn.Module):
def __init__(
self,
backbone,
cross_view: dict,
bev_embedding: dict,
dim: int = 128,
middle: List[int] = [2, 2],
scale: float = 1.0,
):
super().__init__()
self.norm = Normalize()
self.backbone = backbone
if scale < 1.0:
self.down = lambda x: F.interpolate(
x, scale_factor=scale, recompute_scale_factor=False)
else:
self.down = lambda x: x
assert len(self.backbone.output_shapes) == len(middle)
cross_views = list()
layers = list()
for feat_shape, num_layers in zip(self.backbone.output_shapes, middle):
_, feat_dim, feat_height, feat_width = self.down(
torch.zeros(feat_shape)).shape
cva = GeometryKernelAttention(
feat_height, feat_width, feat_dim, dim, **cross_view)
cross_views.append(cva)
layer = nn.Sequential(*[ResNetBottleNeck(dim)
for _ in range(num_layers)])
layers.append(layer)
self.bev_embedding = BEVEmbedding(dim, **bev_embedding)
self.cross_views = nn.ModuleList(cross_views)
self.layers = nn.ModuleList(layers)
def forward(self, batch):
b, n, _, _, _ = batch['image'].shape
# b n c h w
image = batch['image'].flatten(0, 1)
# b n 3 3
I_inv = batch['intrinsics'].inverse()
# b n 4 4
E_inv = batch['extrinsics'].inverse()
features = [self.down(y) for y in self.backbone(self.norm(image))]
# d H W
x = self.bev_embedding.get_prior()
# b d H W
x = repeat(x, '... -> b ...', b=b)
for cross_view, feature, layer in zip(self.cross_views, features, self.layers):
feature = rearrange(feature, '(b n) ... -> b n ...', b=b, n=n)
x = cross_view(x, self.bev_embedding, feature, I_inv,
E_inv, batch['intrinsics'], batch['extrinsics'])
x = layer(x)
return x
参考:
https://blog.csdn.net/weixin_46779338/article/details/129017303
https://it.cha138.com/javascript/show-84155.html
https://zhuanlan.zhihu.com/p/527308492