【MLP-BEV(4)】BEVDepth论文和代码分析, 第一个对深度质量如何影响整个LSS BEV系统进行彻底分析的论文,显式深度监督

继lift-splat-shoot之后,旷视科技的BEVDepth 最近在低算力平台部署看到供应商有很多的落地了。基于 View-transformation 主流算法方法包括 LSS(Lift,Splat 和 Shoot)和 Transfomer 等:

  • Transformer 方案:使用 Transformer 的注意力机制将 2D 特征融合为 BEV 视角下的 3D 特征。
  • 基于 LSS 的方案:使用深度估计和相机内外参,将 2D 特征映射至 3D 空间。

综合部署难度,基于 LSS 会友好很多,很多还是卷积操作(不知道理解有没有错误)。我们今天就来看看这个可以工程化部署的方案。先从论文开始,然后看看代码。

论文信息:

repo:https://github.com/Megvii-BaseDetection/BEVDepth

paper:https://arxiv.org/pdf/2206.10092

来源:旷视

文章目录

  • 概述
  • [1 介绍](#1 介绍)
  • [2 相关工作](#2 相关工作)
    • [2.3 深度估计](#2.3 深度估计)
  • [2 深度LSS整体结构](#2 深度LSS整体结构)
    • [2.1 基于 Lift-splat 构建的基线 3D 探测器的整体结构](#2.1 基于 Lift-splat 构建的基线 3D 探测器的整体结构)
    • [2.2 简单的实验,以揭示为什么我们观察到先前的现象](#2.2 简单的实验,以揭示为什么我们观察到先前的现象)
    • [2.3 我们讨论了该探测器的三个缺陷,并指出了可能的解决方案](#2.3 我们讨论了该探测器的三个缺陷,并指出了可能的解决方案)
      • 深度不准确
      • [Depth Module Over-fitting](#Depth Module Over-fitting)
      • [Imprecise BEV Semantics](#Imprecise BEV Semantics)
  • [4 BEVDepth](#4 BEVDepth)
    • [Explicit Depth Supervision](#Explicit Depth Supervision)
    • [Camera-aware Depth Prediction](#Camera-aware Depth Prediction)
    • [Depth Refinement Module](#Depth Refinement Module)
    • [View Transformation : Voxel Pooling](#View Transformation : Voxel Pooling)
  • [5 实验](#5 实验)
    • [5.2 消融实验](#5.2 消融实验)
    • [5.3 基准测试结果](#5.3 基准测试结果)
  • (论文结束)
  • [6 代码解析](#6 代码解析)

概述

提出了四个部分内容,包括:

  • 深度估计模块、
  • 深度微调模块去解决不精确的特征非投影所带来的副作用,
  • Voxel 池化(这个应该是核心的部分:关于如何进行View Transformation)
  • 多帧机制。

A camera-awareness depth estimation module is also introduced to facilitate the depth predicting capability. Besides, we design a novel Depth Refinement Module to counter the side effects carried by imprecise feature unpro-jection. Aided by customized Efficient Voxel Pooling and multi-frame mechanism,

1 介绍

BEV 表示非常重要,因为它不仅支持多输入摄像头系统的端到端训练方案,而且还为各种下游任务(例如 BEV 分割、对象检测)提供统一的空间和运动规划。

LSS(Philion 和 Fidler 2020)很好地解决了使用多视图相机进行 3D 感知的可行性。 他们首先使用估计深度将多视图特征"提升"到 3D 平截头体,然后将平截头体"splat"到参考平面上,通常是鸟瞰图 (BEV) 中的平面。

They first "lift" multi-view features to 3D frustums using estimated depth, then "splat" frustums onto a reference plane, usually being a plane in Bird's-Eye-View (BEV).

然而,尽管基于 LSS 的感知算法取得了成功,学习深度却很少被研究。 我们问------这些检测器中学习深度的质量真的满足精确 3D 物体检测的要求吗?

我们首先尝试通过可视化基于 Lift-splat 的探测器中的估计深度(图 1)来定性地回答这个问题。 尽管检测器在 nuScenes(Caesar et al. 2020)基准上达到了 30 mAP,但其深度却出奇的差。 只有少数特征区域可以预测合理的深度并有助于后续任务(参见图 1 中的虚线框),而大多数其他区域则不然。

基于这一观察,我们指出现有Lift-splat中的深度学习机制带来了三个缺陷:

  • 深度间接监督,质量差
  • 大多数像素无法预测合理的深度,这意味着它们在学习阶段没有得到适当的训练。 这让我们对深度模块的泛化能力产生怀疑。
  • 深度较差导致只有部分特征投影到正确的 BEV 位置,从而导致 BEV 语义不精确

此外,我们通过用点云数据生成的地面实况替换 Lift-splat 中的学习深度,揭示了提高深度的巨大潜力。 结果,mAP 和 NDS 均提升了近 20%。 平移误差 (mATE) 也下降,从 0.768 降至 0.393。 这一现象清楚地表明,增强深度是高性能相机 3D 检测的关键。 因此,在这项工作中,我们引入了 BEVDepth,一种新的多视图 3D 检测器,它利用源自点云的深度监督来指导深度学习。 我们是第一个对深度质量如何影响整个系统进行彻底分析的团队。 同时,我们创新性地提出将相机内部和外部编码到深度学习模块中,以便检测器对各种相机设置具有鲁棒性。 最后,进一步引入深度细化模块来细化学习的深度。

为了验证 BEVDepth 的强大功能,我们在 nuScenes(Caesar et al. 2020)数据集(3D 检测领域众所周知的基准)上对其进行了测试。 借助我们定制的高效体素池和多帧融合技术,BEVDepth 在 nuScenes 测试集上实现了 60.9% NDS,在这一具有挑战性的基准测试中达到了新的最先进水平,同时仍然保持了高效率

2 相关工作

2.3 深度估计

常见的是:

  • Fu et al. (Fu et al. 2018) employ a regression method to predict the depth of an image using dilated convolu-
    tion and a scene understanding module.
  • Monodepth (Go-dard, Mac Aodha, and Brostow 2017) predicts depth without supervision using disparity and reconstruction.
  • Monodepth2 (Godard et al. 2019) uses a combination of depthestimation and pose estimation networks to forecast depth in a single frame

一些方法通过构建成本量来预测深度。

MVSNet(Yao et al. 2018)首先将成本体积引入深度估计领域。

基于MVSNet,RMVSNet(Yao et al. 2019)使用GRU来降低内存成本,

MVSCRF(Xue et al. 2019)添加CRF模块,

Cas-cade MVSNet(Gu et al. 2020)将MVSNet改为级联结构。

(Wang et al. 2021a)使用多尺度融合生成深度预测,并引入自适应模块,同时提高性能并减少内存消耗。

(Bae、Budvytis 和 Cipolla 2022)将单视图图像与多视图图像融合,并引入深度采样以降低计算成本。

2 深度LSS整体结构

在本节中,我们首先回顾一下基于 Lift-splat 构建的基线 3D 探测器的整体结构。 然后我们在基础探测器上进行了一个简单的实验,以揭示为什么我们观察到先前的现象。 最后,我们讨论了该探测器的三个缺陷,并指出了可能的解决方案。

2.1 基于 Lift-splat 构建的基线 3D 探测器的整体结构

去掉LSS 的分割头,替换成CenterPoint的检测头,就完成了整个检测器基线。由 图像编码器提前特征,深度网络估计深度(尺度是R×H×W,R 是离散距离)和图像转换(A View Transformer 实现 2D-->3D--> BEV特征 ),以及头(A 3D Detection Head predicting the class, 3D box offset and other attributes.)

2.2 简单的实验,以揭示为什么我们观察到先前的现象

我们将 Lift-splat 的成功归因于部分合理的学习深度。 现在,我们进一步研究该管道的本质,用随机初始化的张量替换深度预测的结果,并在训练和测试阶段冻结它。

结果如表 1 所示。我们惊讶地发现,用随机软值替换 Dpred 后,mAP 仅下降了 3.7% (从 28.2% 到 24.5%)。这也太惊人了吧,深度学习的网络自己有很强的修复能力,或者是这个部分的影响不是很大。

如果深度估计的模块损坏了,必然影响反投影的特征从2D->3D,如果我们采用soft 深度分布仍然可以将部分特征正确投影,但是同时也会引入很多噪声。

我们替换 soft 随机深度,采用硬随机深度(单点激活),mAP下降了6.9,验证了我们的猜想。

这说明只要正确位置的深度有激活,探测头就可以工作。 这也解释了为什么图1中大部分区域的学习深度较差,但检测mAP仍然合理。

2.3 我们讨论了该探测器的三个缺陷,并指出了可能的解决方案

包括:深度不准确(因为间接监督)、深度模块过度拟合(深度估计效果差)以及 BEV (分割)语义不精确。

为了更清楚地展示我们的想法,我们比较了两个基线------一个是基于 LSS 的简单检测器,称为 Base Detector,另一个使用基于点云数据导出的额外深度监督。

深度不准确

在 Base Detector 中,深度模块上的梯度源自检测损失,这是间接的。

因此,我们使用常用的深度估计指标(Eigen,Puhrsch和Fergus 2014)评估nuScenes val上的学习深度,包括尺度不变对数算术误差(SILog),平均绝对相对误差(Abs Rel),平均值 相对误差平方 (Sq Rel) 和均方根误差 (RMSE)。

我们在两种不同的协议下评估两个探测器:

1)每个对象的所有像素

  1. 每个对象的最佳预测像素。

结果如表 2 所示。在评估所有前景区域时,Base Detector 仅达到 3.03 AbsRel,这比现有的深度估计算法差很多(Li et al. 2022a;Bhat、Alhashim 和 Wonka 2021)。 然而,对于Enhanced Detector来说,AbsRel从3.03大幅降低到0.23,成为一个更合理的值。

值得一提的是,最佳匹配协议下的Base Detector 的性能几乎与全区域协议下的 Enhanced Detector 相当。

It is worth men-tioning that performance of Base Detector under the bestmatching protocol is almost comparable to the Enhanced Detector under all-region protocol.

这验证了我们在第 1 节中的假设,即当检测器在没有深度损失的情况下进行训练时(就像 Lift-splat 一样),它仅通过学习部分深度来检测对象。 在最佳匹配协议上应用深度损失后,学习深度进一步提高。 所有这些结果都表明隐式学习的深度是不准确的并且远远不能令人满意

Depth Module Over-fitting

看到 基础检测器只学会了部分区域的深度,我们担心,这个深度学习模块是不是泛化能力很弱?

具体来说,这种方式的检测器学习深度可能对图像大小、相机参数等超参数非常敏感。为了验证这一点,我们选择"图像大小"作为变量,并进行以下实验 为了研究模型的泛化能力:我们首先使用输入大小 256×704 训练基本检测器和增强检测器。 然后我们分别使用 192×640、256×704 和 320×864 尺寸对其进行测试。 如图 2 所示,当测试图像大小与训练图像大小不一致时,基础检测器会损失更多准确度。 增强检测器的性能损失要少得多。 这种现象意味着没有深度损失的模型具有较高的过拟合风险,因此也可能对相机内参、外参或其他超参数中的噪声敏感。

Imprecise BEV Semantics

将图像通过学习深度,投影到截锥体特征,然后采用 Vox-el/Pillar Pooling 操作将它们聚合为 BEV。池化操作仅聚合部分语义信息。

图 3 显示,在没有深度监督的情况下,图像特征无法正确投影。增强型检测器在这种情况下表现更好。 我们假设深度不足对分类任务有害。 然后,我们使用两个模型的分类热图并评估它们的 TP / (TP + FN) 作为比较指标,其中 TP 表示被指定为正样本并被 CenterPoint 正确分类的锚点/特征 head而FN代表相反的意思。 参见表3,增强型检测器在不同的正阈值下始终优于其他检测器。

4 BEVDepth

BEVDepth is a new multi-view 3D detector with reliable depth. It leverages Explicit Depth Supervision on a Camera-aware Depth Prediction Module (DepthNet) with a novel Depth Refinement Module on unprojected frustum features to achieve this.

重点是利用lidar的点云显式深度监督、深度微调、利用cuda实现了高效的体素池化操作、考虑相机外参可能会对结果进行干扰(增加一个网络来学习相机参数作为注意力权重作用于图像和深度特征)、

对比,基线LSS 自底向上方法的会显示的估计每个特征点的距离,但是这些距离是隐式学习的。

Explicit Depth Supervision

在Base Detector中,深度模块的唯一监督来自于检测损失。 然而,由于单目深度估计的困难,单独的检测损失远远不足以监督深度模块。 因此,我们建议使用从点云数据导出的地面实况来监督中间深度预测

步骤是这样的,首先激光数据转换为2.5D坐标(u,v,d),其中u和v表示像素坐标中的坐标,d是深度。如果某个点云的2.5D投影没有落入第i个视图,我们就直接丢弃它。

然后,为了对齐投影点云和预测深度之间的形状,在激光数据上采用a min pooling 和 a one hot。

对于深度损失L深度,我们采用二元交叉熵BCE。

Camera-aware Depth Prediction

根据经典相机模型,估计深度与相机内在特性相关,这意味着将相机内在特性建模到 DepthNet 中并非易事。 当相机可能具有不同的 FOV(例如 nuScenes 数据集)时,这在多视图 3D 数据集中尤其重要。 因此,我们建议利用相机内在作为深度网络的输入之一。

具体来说,首先使用 MLP 层将相机内在的维度放大到特征。 然后,它们被用来通过挤压和激励(Hu、Shen 和 Sun 2018)模块重新加权图像特征 F 2di。

最后,我们将相机的外部参数与其内部参数连接起来,以帮助 DepthNet 了解特征F在自我坐标系统中的空间位置。

ψ as the original DepthNet

ξ denotes the Flatten operation

现有的一项工作(Park et al. 2021b)也利用了相机感知。 他们根据相机 intrinsics 来缩放回归目标,这使得他们的方法很难适应具有复杂相机。

另一方面,我们的方法对深度网络内部的相机参数进行建模,旨在提高中间深度的质量。 受益于 LSS (Philion and Fidler 2020) 的解耦特性,相机感知深度预测模块与检测头隔离,因此在这种情况下不需要更改回归目标,从而具有更大的可扩展性

Depth Refinement Module

为了进一步提高深度质量,我们设计了一种新颖的深度细化模块。其输出最终被重新整形并输入到后续的体素/柱池操作中。

一方面,深度细化模块可以沿深度轴聚合特征,而深度预测置信度较低。 另一方面,当深度预测不准确时,只要感受野足够大,深度细化模块理论上就能将其细化到正确的位置。 总之,Depth Refine-ment Module 赋予 View Transformer 阶段纠正机制,使其能够对那些放置不当的特征进行细化。

View Transformation : Voxel Pooling

高效易部署的 View Transformation

LSS 利用了一种累加和技巧,根据它们对应的 BEV 体素 ID ,将所有视锥特征进行排序、累加求和,然后减去每个 bin 区域边界处的累加和值。该技巧采用的累加和方式只能串行实现,通常通过以下方法实现:

通过 BEVDepth 网络对体素池化(Voxel Pooling)进行改进,为每个像素点不同深度分配一个 CUDA 线程进行加速。

BEVStereo 网络结构在 BEVDepth 的基础上对线程分配方式进行改进,效率进一步提升。

BEVFusion 采用 BEV Pooling 生成 2D BEV 特征。与 LSS 不同,该方法提前计算好视锥特征的体素 ID,并对视锥特征和深度得分做外积,最后通过每个体素分配线程,进行体素内的特征累加。

此类方法均是对视锥特征进行前处理和存储,需要大量内存和计算。

5 实验

5.2 消融实验


深度细化模块旨在通过沿深度轴聚合/细化未投影的特征来细化不满意的深度。 从效率上来说,我们最初在里面采用了3×3的卷积。 在这里,我们消融了不同的内核,包括 1×3、3×1 和 3×3,以研究其机制。

参见表6,当我们在 C D × W C_D×W CD×W维度上使用1×3卷积时,信息不会沿着深度轴交换,检测性能几乎不受影响。 当我们使用 3×1 卷积时,允许特征沿深度轴交互,mAP 和 NDS 相应得到改善。 这类似于使用朴素的 3 × 3 卷积,揭示了该模块的本质

5.3 基准测试结果

在这里,我们简要介绍两个额外的实现,它们对于在 nuScenes 排行榜上获得性能至关重要,即:

高效的体素池和多帧融合。 高效的体素池 Lift-splat 中现有的体素池利用了涉及"排序"和"累积和"操作的"累积和技巧"。

Efficient Voxel Pooling Existing Voxel Pooling in Lift-splat leverages a "cumsum trick" that involves a "sorting" and a "cumulative sum" operations. Both operations are computationally inefficient.

这两种操作的计算效率都很低。 我们建议通过为每个平截头体特征分配一个 CUDA 线程来利用 GPU 的强大并行性,该线程用于将该特征添加到其相应的 BEV 网格。 结果,我们最先进模型的训练时间从 5 天减少到 1.5 天。 唯一的池化操作比 Lift-splat 中的基线快 80 倍。

多帧融合。多帧融合有助于更好地检测物体并赋予模型估计速度的能力。 我们将不同帧的视锥体特征的坐标对齐到当前自我坐标系中,以消除自我运动的影响,然后执行体素池化。 来自不同帧的池化 BEV 特征直接连接并馈送到后续任务。

(论文结束)

6 代码解析

来源,投诉侵权即删除】

环视图片特征提取、深度特征预测、Voxel Pooling(核心?)和Detection Head。

下面的代码是基本上是按照forward的顺序进行的,会对关键代码进行解释以及shape的标注。

1、bevdepth/models/base_bev_depth.py

py 复制代码
class BaseBEVDepth(nn.Module):
    def forward(...):
        if self.is_train_depth and self.training:
            # 训练时 用Lidar的深度来监督 depth_pred
            x, depth_pred = self.backbone(...)
            preds = self.head(x)
        else:
            # x:[1, 160, 128, 128] 关键帧+过渡帧的 bev特征
            x = self.backbone(x, mats_dict, timestamps) 
            # -> bevdepth/layers/backbones/base_lss_fpn.py
            # 解码
            preds = self.head(x)   # 参考centerpoint 

2、bevdepth/layers/backbones/base_lss_fpn.py

py 复制代码
class BaseLSSFPN(nn.Module):
    def __init__(...):
        ...
    def forward(...):
        """
        Args:
            sweep_imgs:[1, 2, 6, 3, 256, 704],关键帧以及过渡帧图片
            mats_dict(dict):
                sensor2ego_mats:相机坐标系->车辆坐标系
                intrin_mats:相机内参
                ida_mats:图像数据增强矩阵
                sensor2sensor_mats:key frame camera to sweep frame camera,关键帧到过渡帧的变化矩阵
                bda_mat:bev特征增强矩阵    
        """
        # 提取关键帧的BEV特征 key_frame_res:[1, 80, 128, 128])
        key_frame_res = self._forward_single_sweep(...)
        
        for sweep_index in range(1, num_sweeps):
            #  提取过渡帧的bev特征
            feature_map = self._forward_single_sweep(...)
            ret_feature_list.append(feature_map)
        if is_return_depth:
            return torch.cat(ret_feature_list, 1), key_frame_res[1]
        return torch.cat(ret_feature_list, 1) 
        
    def _forward_single_sweep(...):
        # 提取环视图片特征
        # img_feats:[1, 1, 6, 512, 16, 44]
        img_feats = self.get_cam_feats(sweep_imgs)
        source_features = img_feats[:, 0, ...]
        
        # 提取Depth以及context
        depth_feature = self._forward_depth_net(...)
        # 预测的距离分布 depth:[6, 112, 16, 44]
        depth = depth_feature[:, :self.depth_channels].softmax(1)
        # 对应论文中的 Context Feature * Depth Distribution 操作
        img_feat_with_depth = ... # 
        
        # 车辆坐标系下的视锥坐标点 geom_xyz:[1, 6, 112, 16, 44, 3] 
        geom_xyz = self.get_geometry(...)
        # 将车辆坐标系的原点移动到左下角
        geom_xyz = ((geom_xyz - (self.voxel_coord - self.voxel_size / 2.0)) /
                    self.voxel_size).int()
        
        # 获得最终BEV特征 [1, 80, 128, 128]
        feature_map = voxel_pooling(...) 
        # -> bevdepth/ops/voxel_pooling/voxel_pooling.py
        if is_return_depth:
            # 训练时需要返回预测的深度,用lidar信号进行监督
            return feature_map.contiguous(), depth
        return feature_map.contiguous()
        
    def _forward_depth_net(...):
        return self.depth_net(feat, mats_dict)
    
    def get_geometry(...):
        """Transfer points from camera coord to ego coord
        Args:
            rots(Tensor): Rotation matrix from camera to ego.
            trans(Tensor): Translation matrix from camera to ego.
            intrins(Tensor): Intrinsic matrix.
            post_rots_ida(Tensor): Rotation matrix for ida.
            post_trans_ida(Tensor): Translation matrix for ida
            post_rot_bda(Tensor): Rotation matrix for bda.
        """
        # self.frustum:[112, 16, 44, 4] 视锥 
        points = self.frustum
        
        #  乘以图像增强的逆矩阵
        points = ida_mat.inverse().matmul(points.unsqueeze(-1))
        
        # lamda * [x,y,1] = [lamda*x,lamda*y,lamda]
        # 像素坐标系转相机坐标系
        points = torch.cat(...)
        
        # cam_to_ego
        combine = sensor2ego_mat.matmul(torch.inverse(intrin_mat))
        points = combine.view(...)
        return points

这里,我想补充一点,看到商汤的鱼眼 BEV方案中对比。这里的depth 是采用depth = depth_feature[:, :self.depth_channels].softmax(1) 均匀采样,商汤采样的的是最大深度均匀采样

采样策略:

最大深度均匀采样主要关注最大深度范围内的均匀性,而不太关心近处或中间深度层次的细节。
均匀深度分布采样则在整个深度范围内追求均匀性,确保每个深度层次都得到同等的关注。

数据分布:

最大深度均匀采样可能导致近处或中间深度层次的点或像素被过度采样,而远处深度层次的点或像素被采样不足。
均匀深度分布采样则在整个深度范围内保持相对均匀的数据分布。

适用场景:

最大深度均匀采样适用于那些主要关注远处目标或障碍物检测的应用,因为远处目标往往对任务结果有更大影响。
均匀深度分布采样适用于需要全面考虑场景深度的应用,如三维重建或增强现实。

鱼眼相机镜头对光线有折射作用,以此获得更大的可视范围,越靠近边缘区域,折射率越大,生成的图片畸变程度越大。

由于畸变,使用均匀的深度分布来生成的点云大部分位于感兴趣范围以外。

言归正传

# 对应Depth Module,由与论文中没有给出该模块的流程图于是按照代码逻辑绘制了一个
class DepthNet(nn.Module):
    def __init__(...):
        ...
        
     def forward(...):
        # 当前帧的相机参数
        mlp_input = ...
        # Norm
        mlp_input = self.bn(mlp_input.reshape(-1, mlp_input.shape[-1])) 
        # 相机参数作为 context的注意力系数
        context_se = self.context_mlp(mlp_input)[..., None, None]
        # 注意力操作
        context = self.context_se(x, context_se)
        # FC
        context = self.context_conv(context)
        # 相机参数作为 Depth的注意力系数
        depth_se = self.depth_mlp(mlp_input)[..., None, None]
        # 注意力操作
        depth = self.depth_se(x, depth_se)
        # FC
        depth = self.depth_conv(depth)
        return torch.cat([depth, context], dim=1)

Depth Module

3、bevdepth/ops/voxel_pooling/voxel_pooling.py

class VoxelPooling(...):
    def forward(...):
        """
        Args:
            geom_xyz:在车辆坐标系下的视锥点,x、y轴的范围为0~127 
            input_features:环视图片特征
            voxel_num: 128 * 128 * 80
        """
        # 为每个视锥点分配一个thread,将在bev特征下,处于相同位置的特征点对应的特征向量相加,具体可以看下方的核函数
        voxel_pooling_ext.voxel_pooling_forward_wrapper(...)
        # -> bevdepth/ops/voxel_pooling/src/voxel_pooling_forward_cuda.cu
        # 最终就得到BEV特征 output_features
        return output_features

4、bevdepth/ops/voxel_pooling/src/voxel_pooling_forward_cuda.cu

由于voxel_pooling代码讲解的资料比较少,根据对下面的代码的理解绘制了voxel_pooling的示意图,在下方的代码注释中会对这个图进行说明。

c 复制代码
void voxel_pooling_forward_kernel_launcher(...){
    dim3 blocks(DIVUP(batch_size * num_points, THREADS_PER_BLOCK)); // 473088 / 128 = 3696 个 block ,排布为 3696*1
    dim3 threads(THREADS_BLOCK_X, THREADS_BLOCK_Y);  // 每个 block中 有 128 个 thread,排布为 32 * 4
    voxel_pooling_forward_kernel<<<blocks, threads, 0, stream>>>(
      batch_size, num_points, num_channels, num_voxel_x, num_voxel_y,
      num_voxel_z, geom_xyz, input_features, output_features, pos_memo);
}

__global__ void voxel_pooling_forward_kernel(...) {
  /*
  Args:
    batch_size:当前block在哪个batch ,假定batchsize==1
    num_points:视锥点个数,473088
    num_channels:特征维度,80
    num_voxel_x:bev特征x大小
    num_voxel_y:bev特征y大小
    geom_xyz:视锥坐标的指针,[1, 473088, 3]
    input_features:输入特征图的指针,[1, 473088, 80]
    output_features:输出特征图的指针,[1, 128, 128, 80]
    pos_memo:记录x,y坐标,[1, 473088, 3]
  */
  # 所有thread 同时计算
  const int bidx = blockIdx.x;   // bidx,当前block在当前grid中x维度的索引
  const int tidx = threadIdx.x;  // tidx,当前thread在当前block中x维度的索引
  const int tidy = threadIdx.y;  // tidy,当前thread在当前block中y维度的索引
  const int sample_dim = THREADS_PER_BLOCK; // sample_dim 128 ,每个block中的thread数量 
  const int idx_in_block = tidy * THREADS_BLOCK_X + tidx;   // 当前thread在当前block中的全局索引

  const int block_sample_idx = bidx * sample_dim; //当前block在当前grid中的全局索引
  const int thread_sample_idx = block_sample_idx + idx_in_block; // 当前thread在当前grid中的全局索引
    
  const int total_samples = batch_size * num_points; // 总thread数量

  __shared__ int geom_xyz_shared[THREADS_PER_BLOCK * 3]; // 128 * 3 共享内存,记录一个block中所有点的坐标

  if (thread_sample_idx < total_samples) {
    // 将一个block中的所有视锥点的坐储存在共享内存geom_xyz_shared中,(所有block同时进行)
    const int sample_x = geom_xyz[thread_sample_idx * 3 + 0];
    const int sample_y = geom_xyz[thread_sample_idx * 3 + 1];
    const int sample_z = geom_xyz[thread_sample_idx * 3 + 2];
    geom_xyz_shared[idx_in_block * 3 + 0] = sample_x;
    geom_xyz_shared[idx_in_block * 3 + 1] = sample_y;
    geom_xyz_shared[idx_in_block * 3 + 2] = sample_z;
    if ((sample_x >= 0 && sample_x < num_voxel_x) &&
        (sample_y >= 0 && sample_y < num_voxel_y) &&
        (sample_z >= 0 && sample_z < num_voxel_z)) {
      pos_memo[thread_sample_idx * 3 + 0] = thread_smple_idx / num_points; // 将z轴变为0
      pos_memo[thread_sample_idx * 3 + 1] = sample_y;  // 保存视锥y坐标
      pos_memo[thread_sample_idx * 3 + 2] = sample_x;  // 保存视锥x坐标
    }
  }

  __syncthreads();
  // 可以分为两个步骤,1、先找到当前视锥点在output_features,也就是BEV特征下索引,再找到当前视锥点在input_features中的索引,然后再将两个位置的特征进行相加,由于input_features可能出现多个索引对应于output_features中的同一个索引,必须使用原子加 atomicAdd,可以参考上方的示意图
  for (int i = tidy;
       i < THREADS_PER_BLOCK && block_sample_idx + i < total_samples;
       i += THREADS_BLOCK_Y) {
    const int sample_x = geom_xyz_shared[i * 3 + 0];
    const int sample_y = geom_xyz_shared[i * 3 + 1];
    const int sample_z = geom_xyz_shared[i * 3 + 2];
    if (sample_x < 0 || sample_x >= num_voxel_x || sample_y < 0 ||
        sample_y >= num_voxel_y || sample_z < 0 || sample_z >= num_voxel_z) {
      continue;
    }
    const int batch_idx = (block_sample_idx + i) / num_points;
    for (int j = tidx; j < num_channels; j += THREADS_BLOCK_X) {
      atomicAdd(&output_features[(batch_idx * num_voxel_y * num_voxel_x +sample_y * num_voxel_x + sample_x) *num_channels +j],input_features[(block_sample_idx + i) * num_channels + j]);
    }
  }
}

(正文完)

在之前写【透视图像目标检测(0)】卷首语写了,虽然深度信息有助于3D场景的理解,但简单地将其作为RGB 图像的额外通道,并不能弥补基于单目图像的方法和基于点云的方法之间的性能差异。

基于点云信息引导的方法。这类算法借助激光的雷达点云信息作为辅助监督进行模型训练,在推理时只需输入图像和单目相机信息。涉及到模型包括:Pseudo lidar、基于FCOS或DenseBox框架的改进的DD3D、CaDDN等。

BEVDepth 是在LSS 这个BEV 领域内的点云监督的论文,第一个对深度质量如何影响整个系统进行彻底分析的团队

相关推荐
qq_419203231 个月前
BEV学习---LSS-1:论文原理及代码串讲
lss