本文经自动驾驶之心公众号授权转载,转载请联系出处。
目前,在自动驾驶的车辆中已经配备了多种信息采集传感器,如激光雷达、毫米波雷达以及相机传感器。目前来看,多种传感器融合在自动驾驶的感知任务中显示出了巨大的发展前景。比如:相机采集到的2D图像信息捕获了丰富的语义特征,激光雷达采集到的点云数据可以为感知模型提供物体的准确位置信息和几何信息。通过将不同种传感器获取到的信息充分利用起来可以减少自动驾驶感知过程中的不确定性因素的发生,同时使感知模型具有更好的检测鲁棒性。
今天介绍的是一篇来自旷视的自动驾驶感知论文,并且中稿了今年的ICCV2023 视觉顶会,该文章的主要特点是类似PETR这类End-to-End的BEV感知算法(不再需要利用NMS后处理操作过滤感知结果中的冗余框),同时又额外使用了激光雷达的点云信息来提高模型的感知性能,是一篇非常不错的自动驾驶感知方向的论文,文章的链接和官方开源仓库链接如下:
接下来先整体介绍一下CMT感知模型的网络结构,如下图所示:
通过整个算法框图可以看出,整个算法模型主要包括三个部分
在介绍完网络整体结构之后,接下来就详细介绍一下上述提到的三个子部分
输入:2D Backbone输出的降采样16倍和32倍的特征图
输出:将下采样16倍和32倍的图像特征进行融合,获取降采样16倍的特征图
Tensor([bs * N, 1024, H / 16, W / 16])
Tensor([bs * N,2048,H / 16,W / 16])
Tensor([bs * N,256,H / 16,W / 16])
输入:采用ResNet-50网络提取环视图像特征
输出:输出下采样16倍和32倍的图像特征
输入张量:Tensor([bs * N,3,H,W])
输出张量:Tensor([bs * N,1024,H / 16,W / 16])
输出张量:``Tensor([bs * N,2048,H / 32,W / 32])`
2D Backbone提取图像特征
Neck(CEFPN)
根据上面的介绍可知,位置编码的生成主要包括三个部分,分别是Image Position Embedding,Point Cloud Position Embedding以及Query Embedding;接下来分别介绍一下他们的生成过程。
在BEV空间的网格坐标点利用pos2embed()
函数将二维的横纵坐标点变换到高维的特征空间
# 点云位置编码`bev_pos_embeds`的生成
bev_pos_embeds = self.bev_embedding(pos2embed(self.coords_bev.to(device), num_pos_feats=self.hidden_dim))
def coords_bev(self):
x_size, y_size = (grid_size[0] // downsample_scale,grid_size[1] // downsample_scale)
meshgrid = [[0, y_size - 1, y_size], [0, x_size - 1, x_size]]
batch_y, batch_x = torch.meshgrid(*[torch.linspace(it[0], it[1], it[2]) for it in meshgrid])
batch_x = (batch_x + 0.5) / x_size
batch_y = (batch_y + 0.5) / y_size
coord_base = torch.cat([batch_x[None], batch_y[None]], dim=0) # 生成BEV网格.
coord_base = coord_base.view(2, -1).transpose(1, 0)
return coord_base # shape: (x_size * y_size, 2)
def pos2embed(pos, num_pos_feats=256, temperature=10000):
scale = 2 * math.pi
pos = pos * scale
dim_t = torch.arange(num_pos_feats, dtype=torch.float32, device=pos.device)
dim_t = temperature ** (2 * (dim_t // 2) / num_pos_feats)
pos_x = pos[..., 0, None] / dim_t
pos_y = pos[..., 1, None] / dim_t
pos_x = torch.stack((pos_x[..., 0::2].sin(), pos_x[..., 1::2].cos()), dim=-1).flatten(-2)
pos_y = torch.stack((pos_y[..., 0::2].sin(), pos_y[..., 1::2].cos()), dim=-1).flatten(-2)
posemb = torch.cat((pos_y, pos_x), dim=-1)
return posemb # 将二维的x,y坐标编码成512维的高维向量
利用一个MLP网络进行空间转换,保证通道数量的对齐
Query Embedding
为了让Object Queries、Image Token以及Lidar Token之间计算相似性更加的准确,论文中的Query Embedding会利用Lidar和Camera生成位置编码的逻辑来生成;具体而言Query Embedding = Image Position Embedding(同下面的rv_query_embeds) + Point Cloud Position Embedding(同下面的bev_query_embeds)。
bev_query_embeds生成逻辑
由于论文中的Object Query原本就是在BEV空间进行初始化的,所以直接复用Point Cloud Position Embedding生成逻辑中的位置编码和bev_embedding()函数即可,对应关键代码如下:
def _bev_query_embed(self, ref_points, img_metas):
bev_embeds = self.bev_embedding(pos2embed(ref_points, num_pos_feats=self.hidden_dim))
return bev_embeds # (bs, Num, 256)
rv_query_embeds生成逻辑
上面刚刚提到,Object Query是在BEV坐标系下初始的点,所以论文中为了可以遵循Image Position Embedding的生成过程,就需要先将BEV坐标系下的3D空间点投影到图像坐标系下,然后再利用之前生成Image Position Embedding的处理逻辑,保证生成过程的逻辑相同,核心代码如下:
def _rv_query_embed(self, ref_points, img_metas):
pad_h, pad_w = pad_shape
# 由归一化坐标点映射回正常的roi range下的3D坐标点
ref_points = ref_points * (pc_range[3:] - pc_range[:3]) + pc_range[:3]
points = torch.cat([ref_points, ref_points.shape[:-1]], dim=-1)
points = bda_mat.inverse().matmul(points)
points = points.unsqueeze(1)
points = sensor2ego_mats.inverse().matmul(points)
points =intrin_mats.matmul(points)
proj_points_clone = points.clone()
# 选择有效的投影点
z_mask = proj_points_clone[..., 2:3, :].detach() > 0
proj_points_clone[..., :3, :] = points[..., :3, :] / (
points[..., 2:3, :].detach() + z_mask * 1e-6 - (~z_mask) * 1e-6
)
proj_points_clone = ida_mats.matmul(proj_points_clone)
proj_points_clone = proj_points_clone.squeeze(-1)
mask = (
(proj_points_clone[..., 0] < pad_w)
& (proj_points_clone[..., 0] >= 0)
& (proj_points_clone[..., 1] < pad_h)
& (proj_points_clone[..., 1] >= 0)
)
mask &= z_mask.view(*mask.shape)
coords_d = (
1 + torch.arange(depth_num).float() * (pc_range[4] - 1) / depth_num)
projback_points = (ida_mats.inverse().matmul(proj_points_clone))
projback_points = torch.einsum("bvnc, d -> bvndc", projback_points, coords_d)
projback_points = torch.cat(
[projback_points[..., :3], projback_points.shape[:-1]], dim=-1)
projback_points = (sensor2ego_mats.matmul(intrin_mats).matmul(projback_points))
projback_points = (bda_mat@ projback_points)
projback_points = (projback_points[..., :3] - pc_range[:3]) / (pc_range[3:] - self.pc_range[:3])
rv_embeds = self.rv_embedding(projback_points)
rv_embeds = (rv_embeds * mask).sum(dim=1)
return rv_embeds
通过上述的变换,即完成了BEV空间坐标系下的点先投影到图像坐标系,再利用之前生成Image Position Embedding的处理逻辑生成rv_query_embeds的过程。
最后Query Embedding = rv_query_embeds + bev_query_embeds
首先先放出来CMT和其他自动驾驶感知算法的比较实验,论文作者分别在nuScenes的test和val集上进行了比较,实验结果如下
接下来是CMT创新点的消融实验部分
首先(a)是做了是否采用位置编码的消融实验,通过结果可以看出当同时采用图像和激光雷达的位置编码后,NDS和mAP指标实现了最好的效果。(c)和(f)部分的消融实验主要是对点云主干网络的主干网络类型和体素大小进行了不同的尝试。(d)和(e)部分的消融实验主要是对相机主干网络的类型和输入分辨率的大小进行了不同的尝试。这部分只是做了粗略的概括,如果想要看更多详细消融实验的话,大家可以看下论文原文。
最后放一张CMT的感知结果在nuScenes数据集上可视化结果的展示,通过实验结果可以看出,CMT还是有较好的感知结果的。
目前将各个模态融合在一起去提升模型的感知性能已经是一个比较火热的研究方向(毕竟自动驾驶汽车上配备了多种传感器),同时CMT又是一个完全End-to-End的感知算法(不需要额外的后处理步骤)并在nuScenes数据集上有着SOTA的精度,本文就是对这篇文章进行了较为细致的介绍,希望对大家有所帮助。
原文链接:https://mp.weixin.qq.com/s/Fx7dkv8f2ibkfO66-5hEXA