专栏算法工具链J6 工具链 BEVPoolV2 算子使用教程 【1】-BEVPoolV2 算子详解

J6 工具链 BEVPoolV2 算子使用教程 【1】-BEVPoolV2 算子详解

momo(社区版)2024-12-26
282
0

1. 引言

当前,地平线 J6 工具链已经全面支持了 BEVPooling V2 算子,并与 mmdetection3d 的实现完成了精准对齐。然而,需要注意的是,此算子因其内在的复杂性以及相关使用示例的稀缺,致使部分用户在实际运用过程中遭遇了与预期不符的诸多问题。
在这样的背景下,本文首先会对 BEVPooling V2 的实现进行全方位、细致入微的剖析讲解,,让复杂的原理变得清晰易懂。随后,还会通过代表性的示例,来进一步强化用户对该算子使用方法的认知和理解。

2. BEVPoolV2算子

BEVPoolv2是 BEVPoolv1的优化版本,其优化了图像特征到 BEV特征的转换过程,实现了在计算和存储方面极大的降低。本章首先说明BEVPoolv2 相对于BEVPoolV2的优化点,然后剖析BEVPoolV2源码。

2.1 先说说BEVPoolv1

BEVPoolv2是 BEVPoolv1的优化版本,其优化了图像特征到 BEV特征的转换过程,实现了在计算和存储方面极大的降低。BEVPoolv1 (左)和 BEVPoolv2(右) 的示意图如下:

BEVPoolv1的主要计算流程如下:

  1. 首先将视锥点云特征reshape成MxC,其中M=BxNxDxHxW。

  2. 然后将get_geometry()输出的空间点云转换到体素坐标下,得到对应的体素坐标。并通过范围参数过滤掉无用的点。

  3. 将体素坐标展平(voxel index),reshape成一维的向量,然后对体素坐标中B、X、Y、Z的位置索引编码,然后对位置进行argsort,这样就把属于相同BEV pillar的体素放在相邻位置,得到点云在体素中的索引。

  4. 然后是一个神奇的操作,对每个体素中的点云特征进行sumpooling,代码中使用了cumsum_trick,巧妙地运用前缀和以及上述argsort的索引。输出是去重之后的Voxel特征,BxCxZxXxY。
  5. 最后使用unbind将Z维度切片,然后cat到C的维度上。代码中Z维度为1,实际效果就是去掉了Z维度,输出为BxCxXxY的BEV 特征图。

BEVPoolV1 方法具有计算效率相对较高以及融合效果良好的优点,但其缺点也较为明显,即需要对大尺度的视锥体特征进行显式计算、存储及预处理,该视锥体的尺度为(N,D,H,W,C),其中 N 表示相机数量,D 代表深度,H 和 W 分别为特征的高和宽,C 则是特征的通道数。在处理高分辨率图像时,计算量会大幅增加,从而导致推理速度受到限制。

2.2 BEVPoolv2

2.2.1 实现思路及性能

BEVPoolv2的思路如上图右侧所示,其避免了显式计算、存储和预处理视锥体特征,通过离线计算视锥索引和体素索引的对应关系表,在推理过程中固定使用该表,直接根据视锥索引找到对应的图像特征和深度特征进行计算,大大降低了显存占用,并加快了处理速度。其思路可以总结为以下步骤:

  1. 离线进行预计算和预处理:体素索引和视锥体索引;

  2. 输入深度分数、图像特征;

  3. 通过视锥体索引,找到对应深度分数和特征;

  4. 相同体素内的视锥体点通过累积求和进行聚合。

从下图可以看出,BEVPoolv2在 TensorRT的推理速度是Lift Splat Shoot(BEVPoolv1)之前最快实现的15.1倍(depth=118),同时,BEVPoolv2也大大减少了内存消耗。

2.2.2 实现代码解析

首先根据 depth 数值,构建单个相机的视锥空间

可以这样形象地去理解:有 DxHxW 个格子,每个格子都有三个元素,分别用来存放这个视锥格子对应的像素坐标 (u, v) 以及它和像平面的距离。

预计算体素索引和视锥体索引

计算每个相机图像对应的视锥在lidar坐标系中的位置
get_lidar_coor 函数将视锥空间的点坐标从图像坐标系转换为 LiDAR 坐标系,经过一系列的坐标变换,包括相机内参、旋转、平移和数据增强补偿。

step1:

通过图像增强补偿,去掉视锥点云在图像预处理中因旋转和平移引入的变换,使其回归到未经增强的状态。

 

 

step2:

将点从相机坐标转换到车辆坐标系(ego 坐标系)。主要通过相机内参矩阵的逆和 sensor2ego 矩阵完成。

 

step3:

将点云坐标应用 Bird's-eye view 的数据增强变换。这一步通常用于生成增强后的 BEV(鸟瞰视图)表示,以便进行进一步的目标检测或场景分割。

代码路径:BEVDET/mmdet3d/models/necks/view_transformer.py

 

 

计算索引关系
此部分的主要实现函数是voxel_pooling_prepare_v2 。函数的主要功能是:
  • 将输入的视锥空间坐标 coor 转换为体素(voxel)空间坐标。
  • 生成每个点在深度维度(depth)、特征维度(feature)和 BEV中的索引。

  • 对体素内点进行排序并划分为连续的区间(interval),为后续基于体素的操作(如 pooling)做准备。

step1:深度索引

ranks_depth 是用于标识每个点在所有深度栅格中的唯一索引。

 

 

step2:特征索引

ranks_feat 是将特征索引(以 D 维度复用)扩展到每个深度栅格点,并最终展平成一维。

 

step3:体素离散化

coor 由连续的坐标值离散化为整数体素坐标。
  • grid_lower_bound 是栅格的最小边界。
  • grid_interval 是体素的间隔大小。

结果是将连续的点云位置转换为体素空间的坐标。

 

step4:扩展 batch 信息

将 batch 索引添加到 coor 中,构造形状为 (num_points, 4) 的张量,其中每行表示 (x, y, z, batch_idx)。

 

step5:筛选有效体素

kept 是一个布尔张量,用于过滤掉位于体素范围之外的点。体素范围由 grid_size 定义。

 

step6:生成 BEV 索引

将每个体素的 (x, y, z, batch_idx) 转换为全局唯一的索引 ranks_bev。

公式分解:

  • coor[:, 3]:批次索引的偏移。
  • coor[:, 2]:深度索引的偏移。
  • coor[:, 1] 和 coor[:, 0]:平面索引的偏移。

 

 

step7:排序

将属于同一体素的点排序,使其在张量中相邻。

 

step8:找到区间起点和长度

通过对 ranks_bev 的相邻元素进行比较,找到每个体素中点云的起点和长度:
  • interval_starts:每个体素中第一个点的索引。
  • interval_lengths:每个体素中点的数量。

 

 

返回值

 

  • ranks_bev : 一维tensor,数量与有效的视锥数量一致,每个元素存放bev空间中voxel 的索引值;包含多段连续重复元素,注意:并不是所有voxel都被视锥栅格击中,会有大量的空voxel(fbocc作者统计将近50%,所以只有被击中的voxel 的index会留在这里)
  • ranks_depth: 一维tensor,数量与有效的视锥数量一致,每个元素存放depth score的索引值
  • ranks_feat: 一维tensor,数量与有效的视锥数量一致,每个元素存放context feat的索引值
  • interval_starts: 一维tensor,数量与voxel的数量一致,每个元素标识着ranks_bev feat的每段"连续片段"的起点
  • interval_lengths:一维tensor,数量与voxel的数量一致,每个元素标识着ranks_bev feat的每段"连续片段"的长度
代码路径:BEVDET/mmdet3d/models/necks/view_transformer.py

voxel_pooling计算

预计算体素索引和视锥索引后,将其与 backbone输出的context_feat 和depth score一起输入到voxel_pooling_v2函数中进行计算。相关代码如下所示:
代码路径:BEVDET/mmdet3d/models/necks/view_transformer.py
voxel_pooling的核心函数为bev_pool_v2,其核心功能为:
  1. 前向传播
    1. bev_pool_v2_kernel:实现pooling 的核心操作。将 3D 空间中的深度和特征映射到 BEV 表示中。
    2. bev_pool_v2:封装了内核的调用,提供方便的接口。
  2. 反向传播
    1. bev_pool_grad_kernel:计算pooling 操作的梯度,包括对深度图和特征图的梯度。
    2. bev_pool_v2_grad:封装内核调用,用于梯度计算。
  3. 优化特性
    1. 使用 CUDA 内核并行计算,充分利用 GPU 的计算能力。

    2. 通过索引 (ranks_*) 和区间信息 (interval_starts, interval_lengths) 高效定位需要处理的数据。
核心函数bev_pool_v2_kernel实现为:
代码路径:BEVDET/mmdet3d/ops/bev_pool_v2/src/bev_pool_cuda.cu

参考链接

  1. BEVPoolv2论文:https://arxiv.org/abs/2211.17111

  2. mmdet3d实现代码:https://github.com/HuangJunJie2017/BEVDet/blob/6fd935a084d403d097d5e2f18a45568e11bf3dc0/mmdet3d/ops/bev_pool_v2/bev_pool.py#L95

  3. https://zhuanlan.zhihu.com/p/557613388

  4. https://zhuanlan.zhihu.com/p/675738148

 

 

 

 

 

 

 

算法工具链
社区征文杂谈技术深度解析征程6
评论0
0/1000