【单目3D目标检测】FCOS3D + PGD论文解析与代码复现_pgd3d检测-程序员宅基地

技术标签: 计算机视觉  人工智能  3D目标检测  

前言

本文对OpenMMLab在Monocular 3D detection领域做的两项工作FCOS3D和PGD(也被称作FCOS3D++)进行介绍。

在此之前,建议大家通过这篇博客:“3Dfy” A General 2D Detector: 纯视觉 3D 检测再思考,来回顾单目3D目标检测的更多细节。

 

FCOS3D

Wang, T, Zhu, X, Pang, J, et al. Fcos3d: Fully convolutional one-stage monocular 3d object detection[C]. In Proceedings of the IEEE/CVF International Conference on Computer Vision. 2021: 913-922.
论文
代码

概述

3D检测由于其固有的不适定性,比传统的2D情况更具挑战性,这主要体现在深度信息的缺乏。在本文中,我们通过建立在全卷积单级检测器上的实践来研究这个问题,并提出了一个通用框架FCOS3D。具体而言,我们首先将通常定义的7-DoF 3D位置投影到2D图像上,并获得投影的中心点,与之前的2D中心相比,我们将其命名为3D中心。利用该投影,3D中心包含2.5D信息,即2D位置及其相应深度。2D位置可以进一步减少到从图像上的某个点的2D偏移,这用作可以在不同特征级别之间归一化的唯一2D属性。相比之下,深度、3D尺寸和方向被视为解耦后的3D属性。然后,考虑到对象的2D比例,将对象分布到不同的特征级别,并仅根据训练过程的投影3D中心进行分配。此外,基于3D中心用2D高斯分布重新定义中心度,以拟合3D目标公式。所有这些都使该框架简单而有效,消除了任何2D检测或2D-3D对应先验。

主要创新点

  • 将7-DoF三维属性解耦为2.5D(位置偏移+深度)和3D属性(尺寸和旋转角等)
  • 考虑目标的2D比例,将目标分布到不同的特征级别,并仅根据训练过程的投影三维中心进行分配
  • 使用基于3D中心的2D高斯分布来表示3D Center-ness(来确定哪些点更靠近中心,并抑制远离目标中心的低质量预测)

在这里插入图片描述
 

主要框架结构

全卷积一阶段检测器通常由三个部件组成:用于特征提取的Backbone、用于多级分支构造的Neck和用于密集预测的Head

  • Backbone:使用预训练的ResNet101以及可变形卷积DCN进行特征提取,为了避免更多的内存开销,固定第一个卷积块参数
  • Neck:生成特征层 P3-P7(按照原始 FCOS 获得P3到P5,然后使用两个卷积块对P5进行下采样,以获得P6和P7),每个特征层用于检测不同尺度的目标
  • Head:要处理两个关键问题:
    • 如何将目标分布到不同的特征级别和不同的点?也就是2D引导的多层3D预测
    • 如何设计架构?本文遵循 RetinaNet 和 FCOS,每个包含4个共享参数的卷积层和 small heads 用于不同的 targets 预测,回归分支需要较高的解耦程度,即每个子 targets 都设置一个 head
       

回归目标

在回归分支中,不同于FCOS在2D中的情况(回归每个点到顶部/底部/左侧/右侧的距离,如下图中的 t , b , l , r t,b,l,r t,b,l,r所示),FCOS3D将通常定义的7-DoF回归目标转换为2.5D中心和3D尺寸,其中2.5D中心可以通过相机固有矩阵轻松转换回3D空间。

在这里插入图片描述
回归2.5D中心可以进一步减少为回归从中心到特定前景点的偏移 Δ x , Δ y \Delta x,\Delta y Δx,Δy、 以及其相应的深度 d d d,对于3D尺寸,预测以下属性:

  • w , l , h w,l,h w,l,h:目标的长宽高
  • θ \theta θ:偏航角(以重力方向为轴,周期为 π \pi π
  • v x , v y v_x,v_y vx,vy:目标沿x方向和y方向的速度
  • C θ C_{\theta} Cθ:即2-bin direction classification,考虑目标具有相反方向的情况,具有相同的 s i n ( θ ) sin(\theta) sin(θ)
  • c c c:即3D Center-ness,3D目标中心ness c。它作为一个软二进制分类器来确定哪些点更靠近中心,并有助于抑制那些远离对象中心的低质量预测

总的来说,分类分支需要输出目标的类别标签和属性标签,而回归分支则需要预测 Δ x , Δ y , d , w , l , h , θ , v x , v y , C θ , c \Delta x,\Delta y,d,w,l,h,\theta,v_x,v_y,C_{\theta},c Δx,Δy,d,w,l,h,θ,vx,vy,Cθ,c这些属性。

 

损失函数

对于分类分支和不同的回归分支,FCOS3D分别定义其损失,并对其进行加权求和:

  • 目标分类,使用Focal Loss,其中 p p p是预测框的类概率,遵循原始论文的设置 α = 0.25 , γ = 2 \alpha=0.25,\gamma=2 α=0.25,γ=2
    L c l s = − α ( 1 − p ) γ log ⁡ p L_{c l s}=-\alpha(1-p)^\gamma \log p Lcls=α(1p)γlogp
  • 属性分类,使用softmax分类损失,表示为 L a t t r L_{attr} Lattr
  • 回归分支,对 Δ x , Δ y , d , w , l , h , θ , v x , v y \Delta x,\Delta y,d,w,l,h,\theta,v_x,v_y Δx,Δy,d,w,l,h,θ,vx,vy使用Smooth L1损失函数,对方向分类 C θ C_{\theta} Cθ使用Softmax分类损失并表示为 L d i r L_{dir} Ldir,对Centerness c c c使用二元交叉熵(BCE)损失函数并表示为 L c t L_{ct} Lct
    L l o c = ∑ b ∈ ( Δ x , Δ y , d , w , l , h , θ , v x , v y ) SmoothL1 ⁡ ( Δ b ) L_{l o c}=\sum_{b \in\left(\Delta x, \Delta y, d, w, l, h, \theta, v_x, v_y\right)} \operatorname{SmoothL1}(\Delta b) Lloc=b(Δx,Δy,d,w,l,h,θ,vx,vy)SmoothL1(Δb)
  • 最终损失 L = 1 N p o s ( β c l s L c l s + β a t t r L a t t r + β l o c L l o c + β d i r L d i r + β c t L c t ) L=\frac{1}{N_{p o s}}\left(\beta_{c l s} L_{c l s}+\beta_{a t t r} L_{a t t r}+\beta_{l o c} L_{l o c}+\beta_{d i r} L_{d i r}+\beta_{c t} L_{c t}\right) L=Npos1(βclsLcls+βattrLattr+βlocLloc+βdirLdir+βctLct)

在代码中,各个损失函数的定义如下,可以看到实际代码中, 属性分类 L a t t r L_{attr} Lattr使用的是BCE损失函数,而不是softmax分类损失

 

推理过程

给定输入图像,通过网络进行推理,获取带有 class scores, attribute scores 和 center-ness 预测结果的 bounding boxes,之后将class score 和 centerness 相乘作为每个预测框的confidence,并在鸟瞰图中进行旋转非最大抑制(NMS),以获得最终结果。
 

2D引导的多层3D预测

为了训练具有FPN的检测器,我们需要设计一种将目标分配到不同级别特征层的策略,FCOS讨论了两个关键问题:

  • 与anchor-based方法相比,如何使anchor-free检测器实现类似的Best Possible Recall(BPR)
  • 由地面真值框重叠引起的难以解决的模糊问题
    针对第一个问题,FCOS通过FPN的多级预测可以改善BPR,甚至比anchor-based方法获得更好的结果,因此FCOS3D也引入FPN的多级预测
    针对第二个问题:
  • FCOS对于不同级别的特征图匹配不同大小的目标,考虑到2D检测的规模与3D检测需要关注的区域的大小直接一致,FCOS3D借助于3D bounding boxes的8个顶点在平面坐标系下的最大坐标和最小坐标(计算投影的3D边界框的外部矩形来生成2D边界框)来匹配不同层次的feature map,在该分配步骤中仅使用2D检测来过滤无意义的目标,完成目标分配后,FCOS3D的回归目标仅包括3D目标的相关属性
  • 对于正样本分配的歧义性问题,即当一个点位于同一要素级别中的多个GT框内时,应将哪个框指定给它?FCOS使用 area-based 方法解决该歧义性问题,即当两个样本都符合要求时选尺寸小的样本;FCOS3D则认为这种方式对大目标不友好,提出了一种新的 dist-based 方案提升了精度,即挑选与中心更近的样本作为回归目标,因为更靠近物体中心的点可以获得更全面和平衡的局部区域特征,从而容易地产生更高质量的预测
  • 除了上面的正样本分配方法,FCOS3D还提出了一种基于 3d-center 来确定正样本的方法,即只有和中心点距离小于 1.5 x stride(该级别特征图的步长) 的样本算作正样本
  • 对每个回归分支的结果增加一个 scale 变换能涨点,该 scale 参数设置为网络可学习

 

2D高斯分布的3D中心度

FCOS为抑制远离目标中心的预测目标,增加了center-ness分支:
c = min ⁡ ( l ∗ , r ∗ ) max ⁡ ( l ∗ , r ∗ ) × min ⁡ ( t ∗ , b ∗ ) max ⁡ ( t ∗ , b ∗ ) c=\sqrt{\frac{\min \left(l^*, r^*\right)}{\max \left(l^*, r^*\right)} \times \frac{\min \left(t^*, b^*\right)}{\max \left(t^*, b^*\right)}} c=max(l,r)min(l,r)×max(t,b)min(t,b)
由于3D回归目标被更改为基于3D center-based 的范式,所以FCOS3D通过以投影的3D中心为原点的2D高斯分布来定义center-ness,其二维高斯分布简化为:
c = e − α ( ( Δ x ) 2 + ( Δ y ) 2 ) c=e^{-\alpha\left((\Delta x)^2+(\Delta y)^2\right)} c=eα((Δx)2+(Δy)2)

实验设置

实验数据集:NuScenes
评价指标

  • Average Precision metric(AP),使用地平面上的 2D center 与 GT 的距离 d 作为 threshold 进行匹配,避免使用 3D IoU 作为 threshold 对目标尺寸和朝向敏感的问题,其中 C \mathbb{C} C表示所有的类别, D = { 0.5 , 1 , 2 , 4 } \mathbb{D}=\{0.5,1,2,4\} D={ 0.5,1,2,4}表示四个距离阈值:
    m A P = 1 ∣ C ∣ ∣ D ∣ ∑ c ∈ C ∑ d ∈ D A P c , d m A P=\frac{1}{|\mathbb{C}||\mathbb{D}|} \sum_{c \in \mathbb{C}} \sum_{d \in \mathbb{D}} A P_{c, d} mAP=CD1cCdDAPc,d
  • 五种True Positive metrics
    • Average Translation Error (ATE): 2d 下的中心距离差距 (m)
    • Average Scale Error (ASE): 1-IoU,IoU为对齐 translation 和 orientation 后计算的值
    • Average Orientation Error (AOE):smallest yaw angle difference(radians)
    • Average Velocity Error (AVE): 速度差异的 L2-Norm (m/s)
    • Average Attribute Error (AAE):1−acc,其中 acc 指代属性分类准确度
  • NuScenes Detection Score(DNS),传统的mAP结合了对检测目标的位置、大小和方向的评估,但仍无法捕获该设置中的某些信息(如速度和属性),因此nuScenes提出了一个更全面、解耦但简单的度量,即NDS:
    N D S = 1 10 [ 5 m A P + ∑ m T P ∈ T P ( 1 − min ⁡ ( 1 , m T P ) ) ] N D S=\frac{1}{10}\left[5 m A P+\sum_{m T P \in \mathbb{T} P}(1-\min (1, m T P))\right] NDS=101[5mAP+mTPTP(1min(1,mTP))]

源码复现

【MMDetection3D】基于单目(Monocular)的3D目标检测入门实战
官方源码:mmdetection3d

mmdetection3d算法库及nuScenes数据集的下载、配置可以参考官方博客:基于视觉的 3D 检测,本文不再赘述。

  • 执行下面命令开始训练,主要要提前修改数据集路径:
CUDA_VISIBLE_DEVICES=0,1 tools/dist_train.sh configs/fcos3d/fcos3d_r101_caffe_fpn_gn-head_dcn_2x8_1x_nus-mono3d.py 2
  • FCOS3D完整的网络结构如下(为了便于观察,去掉了backbone中的layer2-4层):
FCOSMono3D(
  (backbone): ResNet(
    (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
    (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
    (layer1): ResLayer(
      (0): Bottleneck(
        (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
        (downsample): Sequential(
          (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        )
      )
      (1): Bottleneck(
        (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
      )
      (2): Bottleneck(
        (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
        (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
        (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (relu): ReLU(inplace=True)
      )
    )
    // 以下三层省略
    (layer2):
    (layer3): 
    (layer4): 
  )
  init_cfg={
    'type': 'Pretrained', 'checkpoint': 'open-mmlab://detectron2/resnet101_caffe'}
  (neck): FPN(
    (lateral_convs): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1))
      )
      (1): ConvModule(
        (conv): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1))
      )
      (2): ConvModule(
        (conv): Conv2d(2048, 256, kernel_size=(1, 1), stride=(1, 1))
      )
    )
    (fpn_convs): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      )
      (1): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      )
      (2): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      )
      (3): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
      )
      (4): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
      )
    )
  )
  init_cfg={
    'type': 'Xavier', 'layer': 'Conv2d', 'distribution': 'uniform'}
  (bbox_head): FCOSMono3DHead(
    (loss_cls): FocalLoss()
    (loss_bbox): SmoothL1Loss()
    (loss_dir): CrossEntropyLoss(avg_non_ignore=False)
    (loss_attr): CrossEntropyLoss(avg_non_ignore=False)
    (cls_convs): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
      (1): ConvModule(
        (conv): ModulatedDeformConv2dPack(
          (conv_offset): Conv2d(256, 27, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        )
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (reg_convs): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
      (1): ConvModule(
        (conv): ModulatedDeformConv2dPack(
          (conv_offset): Conv2d(256, 27, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        )
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_cls_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_cls): Conv2d(256, 10, kernel_size=(1, 1), stride=(1, 1))
    (conv_reg_prevs): ModuleList(
      (0): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (1): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (2): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (3): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (4): None
    )
    (conv_regs): ModuleList(
      (0): Conv2d(256, 2, kernel_size=(1, 1), stride=(1, 1))
      (1): Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))
      (2): Conv2d(256, 3, kernel_size=(1, 1), stride=(1, 1))
      (3): Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))
      (4): Conv2d(256, 2, kernel_size=(1, 1), stride=(1, 1))
    )
    (conv_dir_cls_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_dir_cls): Conv2d(256, 2, kernel_size=(1, 1), stride=(1, 1))
    (conv_attr_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_attr): Conv2d(256, 9, kernel_size=(1, 1), stride=(1, 1))
    (conv_centerness_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 64, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_centerness): Conv2d(64, 1, kernel_size=(1, 1), stride=(1, 1))
    (scales): ModuleList(
      (0): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
      )
      (1): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
      )
      (2): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
      )
      (3): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
      )
      (4): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
      )
    )
    (loss_centerness): CrossEntropyLoss(avg_non_ignore=False)
  )
)
  • 训练结束后,执行以下命令进行测试及可视化:
python tools/test.py configs/fcos3d/fcos3d_r101_caffe_fpn_gn-head_dcn_2x8_1x_nus-mini-mono3d.py work_dirs/fcos3d_r101_caffe_fpn_gn-head_dcn_2x8_1x_nus-mini-mono3d/latest.pth --show --show-dir ./outputs/fcos3d/

结果如下:

可以看到检测到的重叠框非常多,效果很差,分析可知应该是NMS阈值和得分阈值设置过低导致,修改/mmdetection3d/configs/_base_/models/fcos3d.py中的test_cfg,将score_thr设置为0.2:

    test_cfg=dict(
        use_rotate_nms=True,
        nms_across_levels=False,
        nms_pre=1000,
        nms_thr=0.8,
        score_thr=0.2,
        min_bbox_size=0,
        max_per_img=200))

再次进行测试和可视化,结果如下:

 

PGD

Wang T, Xinge Z, Pang J, et al. Probabilistic and geometric depth: Detecting objects in perspective[C]. Conference on Robot Learning(PMLR). 2022: 1475-1485.
论文
代码

很有意思的是,这篇PGD的作者是FCOS3D原班人马,可以认为是FCOS3D++。FCOS3D是基于Direct Regression的,而PGD则是Geometry-based,是在FCOS3D的基础上,利用提出的head定制模块对深度回归部分进行了改进。

概述

当前的单目3D检测可以简化为实例深度估计问题:不准确的实例深度阻碍了所有其他3D属性预测,无法提高整体检测性能。先前的方法使用额外繁琐的深度估计模型来补充2D检测器的深度信息,或者直接将深度视为3D定位任务的一个维度来简化框架,但仍然使用简单的方法,以回归的方式从孤立的实例或像素中估计深度。我们观察到,除了每个对象本身,其他对象在图像中共存,它们之间的几何关系可能是保证准确估计的有价值的约束。受这些观察的启发,我们提出了概率和几何深度(PGD),该方法联合利用概率深度不确定性和共存对象之间的几何关系,以实现精确的深度估计。具体而言,由于在这种不适定环境中,每个实例的初步深度估计通常是不准确的,因此我们结合了概率表示来捕获估计深度的不确定性。我们首先将深度值划分为一组区间,并通过分布的期望值计算深度,来自分布的top-k置信分数的平均值被视为深度的不确定性。
 

主要创新点

  • PGD结合概率表示来捕获深度估计的不确定性,具体而言,首先将深度值划分为一系列离散的区间,然后通过分布的期望来计算深度值,从分布中得到的top-k的置信度的平均值视作深度的不确定性,如下图(a)所示
  • 为了构建几何关系图,PGD构建了一个深度传播图来利用上下文信息促进深度估计。每个实例深度的不确定性为实例深度传播提供了有效指引。利用这一整体机制,可以很容易地利用高置信度确定预测,更重要的是,利用基于图的协同机制可以更精确地预测深度,如下图(b)所示
  • 在KITTI 3D汽车检测基准上,PGD在性能和速度方面都显著优于其他工作,如下图(c)所示

在这里插入图片描述

 

深度估计

Oracle使用不同的数据集和指标进行分析,从左到右:KITTI上基于3D IoU的mAP、NuScenes检测分数(NDS)和NuScenes上基于距离的mAP。依次用真值来替换 3D 检测器不同输出结果时最终的检测性能(注意是替换不同 attribute 的 dense prediction map,这样可以将回归目标建模所带来的影响包含在内)。

可以发现,在深度估计的准确率只有当前水平时,其他的回归目标用真值替代并不能带来预期提升,反而有时候甚至会有副作用。而当深度估计准确时,检测性能可以实现质的提升。因此可以推断,纯视觉 3D 检测问题在当前发展阶段几乎可以被归结为一个 instance depth estimation 问题。

在这里插入图片描述
因此,PGD一方面建模了深度估计的不确定性,另一方面通过透视几何关系建立这些具有不确定性的检测目标之间的深度传播图,通过全局的信息来增强深度估计的准确度

 

主要框架结构

PGD在FCOS3D整体框架的基础上,主要关注实例深度估计的难题,首先引入概率深度估计模块来建模不确定性,然后从深度传播图中得到几何深度,最后融合二者得到最终的深度预测值

在这里插入图片描述
 

创新点一:概率表示的不确定性建模 D P D_P DP

从这一部分开始,本文将围绕着 概率表示的局部深度估计+基于目标几何关系的深度估计 这两部分进行讨论,会出现大量复杂的数学推理和表示。

对于一阶段检测器,直接深度估计一般是沿着回归分支的一个small head,输出密集的深度图: D R ∈ R H × W D_R \in \mathbb{R}^{H \times W} DRRH×W。本文在此基础上,考虑到深度值在一定范围内是连续的,将深度区间均匀量化为一组离散值,设置等距间隔,将其视为分类任务,离散化网络的输出为:
D P = ω T  softmax  ( D P M ) D_P=\omega^T \text { softmax }\left(D_{P M}\right) DP=ωT softmax (DPM)

其中, ω \omega ω为人为设置的间隔点, D P M D_{PM} DPM为深度值离散区间分类输出的feature map(这一块我也不太明白,可能不对)。每个孤立实例的局部深度估计为:
D L = σ ( λ ) D R + ( 1 − σ ( λ ) ) D P D_L=\sigma(\lambda) D_R+(1-\sigma(\lambda)) D_P DL=σ(λ)DR+(1σ(λ))DP

其中, λ \lambda λ为数据不可知的参数, σ \sigma σ为sigmoid函数。

在代码中,这一部分主要分为三步

  • 首先在head回归分支中增加一个深度概率预测值 D P M D_{PM} DPM的输出
  • 然后对深度概率预测值 D P M D_{PM} DPM进行解码
    • D P M D_{PM} DPM按照划分的间隔点数 C C C以及间隔区间 U U U进行加权计算,得到加权值 w w w
    • 然后将加权值 w w w和经过softmax处理后的深度概率值 D P M D_{PM} DPM相乘,得到解码后的深度概率值 D P M D_{PM} DPM
  • 最后将直接回归得到的深度值 D R D_{R} DR和深度概率值 D P M D_{PM} DPM进行加权,得到最终的局部深度估计值 D L D_{L} DL

 

创新点二:透视几何体的深度传播 D G D_G DG

利用孤立实例的深度预测 D L D_L DL和不确定性估计的深度置信分数,我们可以进一步基于上下文几何关系构建传播图。考虑典型的驾驶场景:可以利用一般约束,即几乎所有物体都在地面上。针对深度估计问题,我们提出了一种几何深度传播机制,考虑了实例之间的相互依赖性。已知相机的内参矩阵:
P = ( f 0 c u − f b x 0 f c v − f b y 0 0 1 − f b z ) P=\left(\begin{array}{cccc} f & 0 & c_u & -f b_x \\ 0 & f & c_v & -f b_y \\ 0 & 0 & 1 & -f b_z \end{array}\right) P=f000f0cucv1fbxfbyfbz

其中各参数含义如下:

  • f f f:相机焦距,考虑到大多数相机在 u u u轴和 v v v轴上共享相同的焦距,因此这里用单个 f f f表示焦距
  • c u , c v c_u,c_v cu,cv:相机在图像中的水平和垂直位置
  • b x , b y , b z b_x,b_y,b_z bx,by,bz:相对于参考相机的基线(KITTI中非零,NuScenes为零)

给定相机坐标系下某点的3D位置 x 3 D = ( x , y , z , 1 ) T \mathbf{x}^{3 \mathrm{D}}=(x, y, z, 1)^T x3D=(x,y,z,1)T,可以利用相机内参矩阵 P P P,将其投影为图像中的2D位置 x 2 D = ( u ′ , v ′ , 1 ) T \mathbf{x}^{2 \mathbf{D}}=\left(u^{\prime}, v^{\prime}, 1\right)^T x2D=(u,v,1)T
d x 2 D = P x 3 D d \mathbf{x}_{\mathbf{2 D}}=P \mathbf{x}_{3 \mathrm{D}} dx2D=Px3D

为了简化结果,将 v 0 v_0 v0替换为 v + c v v+c_v v+cv,其中 v v v表示目标到地平线的距离(如下图所示,向下为正方向),然后我们得到:
v d = f ( y − b y + c v b z ) v d=f\left(y-b_y+c_v b_z\right) vd=f(yby+cvbz)

在这里插入图片描述

u u u的关系类似。考虑到所有对象都在地面上的约束,对象的底部中心始终共享相同的 y y y(相机坐标中的高度),因此接下来主要考虑 v v v的关系。给定两个物体1和2,它们的中心深度之间的关系为:
d 2 = v 1 v 2 d 1 + f v 2 ( y 2 − y 1 ) ≈ v 1 v 2 d 1 + f 2 v 2 ( h 1 3 D − h 2 3 D ) ≜ d 1 → 2 P d_2=\frac{v_1}{v_2} d_1+\frac{f}{v_2}\left(y_2-y_1\right) \approx \frac{v_1}{v_2} d_1+\frac{f}{2 v_2}\left(h_1^{3 D}-h_2^{3 D}\right) \triangleq d_{1 \rightarrow 2}^P d2=v2v1d1+v2f(y2y1)v2v1d1+2v2f(h13Dh23D)d12P

对于一幅图像上的n个目标,可以根据上述公式定义他们之间的几何深度信息:
d i G = ∑ j = 1 k s j → i e d j → i P d_i^G=\sum_{j=1}^k s_{j \rightarrow i}^e d_{j \rightarrow i}^P diG=j=1ksjiedjiP

其中, s j → i e \boldsymbol{s}_{j \rightarrow i}^e sjie与目标之间的距离, k k k为选定的与目标 i i i置信度 s j → i e s_{j→i}^e sjie 最高的目标集合。值得注意的是, D G D_G DG没有可学习的参数,不参与网络的反向转播过程。

这一部分代码中并没有体现,详情可查看这篇issue
 

最终的深度估计:概率和几何深度估计 D D D

网络的深度估计包含两个方面:局部的深度估计 D L D_L DL以及基于目标之间几何关系的深度估计 D G D_G DG,其中 α ∈ R H × W α∈R^{H×W} αRH×W为可学习参数:
D = σ ( α ) ∘ D L + ( 1 − σ ( α ) ) ∘ D G D=\sigma(\alpha) \circ D_L+(1-\sigma(\alpha)) \circ D_G D=σ(α)DL+(1σ(α))DG

源码复现

【MMDetection3D】基于单目(Monocular)的3D目标检测入门实战
官方源码:mmdetection3d

训练、测试及可视化同FCOS3D,在此不再赘述。

PGD整体框架中的backbone和neck与FCOS3D类似,但Head有很大改动,这里给出mmdetection3d中关于PGD检测头的配置信息:

  (bbox_head): PGDHead(
    (loss_cls): FocalLoss()
    (loss_bbox): SmoothL1Loss()
    (loss_dir): CrossEntropyLoss(avg_non_ignore=False)
    (loss_attr): CrossEntropyLoss(avg_non_ignore=False)
    (cls_convs): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
      (1): ConvModule(
        (conv): ModulatedDeformConv2dPack(
          (conv_offset): Conv2d(256, 27, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        )
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (reg_convs): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
      (1): ConvModule(
        (conv): ModulatedDeformConv2dPack(
          (conv_offset): Conv2d(256, 27, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        )
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_cls_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_cls): Conv2d(256, 10, kernel_size=(1, 1), stride=(1, 1))
    (conv_reg_prevs): ModuleList(
      (0): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (1): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (2): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (3): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
      (4): None
      (5): ModuleList(
        (0): ConvModule(
          (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
          (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
          (activate): ReLU(inplace=True)
        )
      )
    )
    (conv_regs): ModuleList(
      (0): Conv2d(256, 2, kernel_size=(1, 1), stride=(1, 1))
      (1): Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))
      (2): Conv2d(256, 3, kernel_size=(1, 1), stride=(1, 1))
      (3): Conv2d(256, 1, kernel_size=(1, 1), stride=(1, 1))
      (4): Conv2d(256, 2, kernel_size=(1, 1), stride=(1, 1))
      (5): Conv2d(256, 4, kernel_size=(1, 1), stride=(1, 1))
    )
    (conv_dir_cls_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_dir_cls): Conv2d(256, 2, kernel_size=(1, 1), stride=(1, 1))
    (conv_attr_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_attr): Conv2d(256, 9, kernel_size=(1, 1), stride=(1, 1))
    (conv_depth_cls_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 256, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_depth_cls): Conv2d(256, 6, kernel_size=(1, 1), stride=(1, 1))
    (conv_centerness_prev): ModuleList(
      (0): ConvModule(
        (conv): Conv2d(256, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (gn): GroupNorm(32, 64, eps=1e-05, affine=True)
        (activate): ReLU(inplace=True)
      )
    )
    (conv_centerness): Conv2d(64, 1, kernel_size=(1, 1), stride=(1, 1))
    (scales): ModuleList(
      (0): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
        (3): Scale()
      )
      (1): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
        (3): Scale()
      )
      (2): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
        (3): Scale()
      )
      (3): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
        (3): Scale()
      )
      (4): ModuleList(
        (0): Scale()
        (1): Scale()
        (2): Scale()
        (3): Scale()
      )
    )
    (loss_centerness): CrossEntropyLoss(avg_non_ignore=False)
    (loss_depth): SmoothL1Loss()
    (loss_bbox2d): SmoothL1Loss()
    (loss_consistency): GIoULoss()
  )

 

Refernece

“3Dfy” A General 2D Detector: 纯视觉 3D 检测再思考

27. FCOS3D - 单阶段 3D 目标检测 (anchor-free)

单目3D目标检测论文汇总(一)

自动驾驶 2D 单目\双目\多目视觉方法 一(Pseudo-LiDAR,Mono3D,FCOS3D,PSMNet)

CoRL 2021单目三维目标检测算法PGD

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43799388/article/details/127536981

智能推荐

定义100学生C语言,全国计算机二级C语言真题最新100套-3正听学院-程序员宅基地

文章浏览阅读39次。2015全国计算机二级C语言真题最新100套-2,正听学院官方整理收集!第03套:程序通过定义学生结构体变量,存储了学生的学号、姓名和3门课的成绩。所有学生数据均以二进制方式输出到文件中。函数fun的功能是重写形参filename所指文件中最后一个学生的数据,即用新的学生数据覆盖该学生原来的数据,其它学生的数据不变。请在程序的下划线处填入正确的内容并把下划线删除, 使程序得出正确的结果。注意:源程..._所有定义,a=100;则语句计算机二级真题

多个绝对值相加求最大值问题_多个绝对值相加求最值问题-程序员宅基地

文章浏览阅读5.6k次。今天的内容以绝对值的加法最小值为例,减法也类似,有兴趣的可以自己琢磨一下,此类绝对值的最小值求法被很多老师称之为“奇尖偶平中间最小”,最近遇到一个与此相关的题目,就借此说一下这个问题。此类问题常见于不等式选讲的绝对值不等式部分,早些年也以函数的形式出现过,现在常见于各高校的自招题目中,以下面两个简单的函数为例:1.例:y=|x-1|+|x+2|这种函数求最值可通过分段去掉绝对值符号,作图来求最小值..._多个绝对值相加的最值速算

手机闪存速度排行_带大家了解一下 手机闪存UFS和EMMC的区别(科普)-程序员宅基地

文章浏览阅读3.6k次。目前市面上的手机运存通常分为两种一种是UFS和EMMC。我们先来介绍UFS,UFS是2011年电子设备工程联合委员会(Joint Electron Device En gineering Council,简称JEDEC)发布了第一代通用闪存存储(Universal Flash Storage,简称UFS)标准,即UFS 2.0的前身。希望能够替代eMMC,可惜事与愿违,第一代的UFS并不受欢迎,因..._闪存对加载图片有影响吗

20210327Java网络编程_5gbsoh-程序员宅基地

文章浏览阅读155次。Java网络编程编辑时间:2021/03/27读完本节:大概花费29钟,共2909词文章目录Java网络编程1.网络编程概述2.通信信息要素3.TCP网络编程4.UDP网络编程5.URL编程***STAY ANGER!!!***1.网络编程概述Java从语言级上提供了对网络应用程序的支持,Java提供的网络类库,可以实现便捷的网络连接,联网的底层细节被隐藏Java的本机安装系统里,由JVM进行控制,并且Java实现了一个跨平台的网络库,用户面对的是一个统一的网络编程环境计算机网络:把分布在不同_5gbsoh

条件变量condition_variable实现线程同步_线程同步condition variables-程序员宅基地

文章浏览阅读531次。基于条件变量实现多线程同步,结合mutex互斥信号量,atomic原子变量。_线程同步condition variables

解决uView2.0 Picker 选择器 (u-datetime-picker) 背景滚动,页面穿透的问题_u-datetime-picker 开始滑动-程序员宅基地

文章浏览阅读1.3k次。解决uView2.0 Picker 选择器 (u-datetime-picker) 背景滚动,页面穿透的问题_u-datetime-picker 开始滑动

随便推点

禁止ie自动跳转edge_禁用ie 跳edge-程序员宅基地

文章浏览阅读712次,点赞9次,收藏3次。因为微软对ie已经彻底停止维护了,对于没有升级系统的用户来说,会自动更新edge然后将ie给禁止使用。下面方法有效的解决windows10下,禁止ie自动跳转edge。_禁用ie 跳edge

mybatis 中的<![CDATA[ ]]>用法及说明_mybatis <![cdata[-程序员宅基地

文章浏览阅读2.8k次。如上SQL :遇到 < 特殊字符需要转义为 <,因为有的特殊字符xml不识别,会直接报异常,因此会使用到 ,由它包裹的SQL和特殊字符将忽略转义,最后拼接为SQL语句select * from user where isDeleted = 0 and age < 18。[CDATA[ ]]> 在mybatis、ibatis等书写SQL的xml中比较常见,是一种XML语法,他的作用是 可以忽略xml的转义(在该标签中的语句和字符原本是什么样的,在拼接成SQL后还是什么样的)_mybatis

TMS VCL UI包功能和特点_tms vcl ui pack-程序员宅基地

文章浏览阅读422次。事实上,您可以使用此产品以自己的观点开发和编写软件,在该软件包中放置600多台计算机,更简单、更快。此编写程序是专门为Delphi和C++Builder编程语言发布的。使用此产品最有趣的方法之一是管理和编程您的编程项目。您可以在任务列表和要执行的任务列表旁边的工作计划主页上使用此产品。这允许您以最高质量的软件生产项目推进您的软件。此外,此工具中丰富的所有活动都将帮助您开发最佳计划。你可以根据他们的需要,将自己的团队项目转变为最佳的做事方式。你的工作有各种各样的工具和实用工具。简化的Windows软件编程。_tms vcl ui pack

Armstrong公理系统_若有x→y,x→z,则x→yz。-程序员宅基地

文章浏览阅读7.7k次,点赞6次,收藏47次。Armstrong公理的推论合并规则:若X→Y,X→Z同时在R上成立,则X→YZ在R上也成立。分解规则:若X→W在R上成立,且属性集Z包含于W,则X→Z在R上也成立。伪传递规则:若X→Y在R上成立,且WY→Z,则XW→Z。函数依赖的公理系统一、Armstrong公理系统设关系模式R<U,F>,其中U为属性集,F是U上的一组函数依赖,那么有如下推理规则:A1自反律:若Y..._若有x→y,x→z,则x→yz。

mac下Charles安装(超详细)_charlesmac安装证书-程序员宅基地

文章浏览阅读3.6k次,点赞6次,收藏12次。当我们打开www.baidu.com的时候,我们发现https是带锁的。3.我们需要打开浏览器设置,在浏览器搜索栏输入证书,点击安全。1.https网站信息,是不能直接抓取,我们需要安装证书。双击证书,点击信任按钮,我们要选择始终信任。7.验证一下,看到就可以正常访问https。6.接下来对charles进行配置。此时我们可以看到证书是不被信任的。我们需要保存结尾为cer的证书。关闭之后,就变成了受信任的图标。_charlesmac安装证书

nginx+keepalived集群搭建-主备-程序员宅基地

文章浏览阅读204次。单机部署可参考:https://blog.csdn.net/ym5209999/article/details/119897237。