【单目3D目标检测】FCOS3D + PGD论文解析与代码复现

这篇具有很好参考价值的文章主要介绍了【单目3D目标检测】FCOS3D + PGD论文解析与代码复现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文对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(来确定哪些点更靠近中心,并抑制远离目标中心的低质量预测)

pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉
 

主要框架结构

全卷积一阶段检测器通常由三个部件组成:用于特征提取的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空间。

pgd3d检测,3D目标检测,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。它作为一个软二进制分类器来确定哪些点更靠近中心,并有助于抑制那些远离对象中心的低质量预测
pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

总的来说,分类分支需要输出目标的类别标签和属性标签,而回归分支则需要预测 Δ 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分类损失

pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

 

推理过程

给定输入图像,通过网络进行推理,获取带有 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 参数设置为网络可学习
pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

 

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/

结果如下:
pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

可以看到检测到的重叠框非常多,效果很差,分析可知应该是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))

再次进行测试和可视化,结果如下:
pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

 

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)所示

pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

 

深度估计

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

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

pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉
因此,PGD一方面建模了深度估计的不确定性,另一方面通过透视几何关系建立这些具有不确定性的检测目标之间的深度传播图,通过全局的信息来增强深度估计的准确度

 

主要框架结构

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

pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉
 

创新点一:概率表示的不确定性建模 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
pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

 

创新点二:透视几何体的深度传播 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)

pgd3d检测,3D目标检测,3D目标检测,人工智能,计算机视觉

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文章来源地址https://www.toymoban.com/news/detail-787833.html

到了这里,关于【单目3D目标检测】FCOS3D + PGD论文解析与代码复现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FCOS3D: Fully Convolutional One-Stage Monocular 3D Object Detection

    Paper name FCOS3D: Fully Convolutional One-Stage Monocular 3D Object Detection Paper Reading Note URL: https://arxiv.org/pdf/2104.10956.pdf 基于 Fcos 改进的 3d 检测方案,在 NeurIPS 2020 的 nuScenes 3d 检测比赛上取得了第一名成绩 Fcos3d 方案 将 7-DoF 3D 目标解耦为 2D 和 3D 的属性 考虑对象的二维比例,将对象分布到

    2023年04月08日
    浏览(66)
  • mmdetection3d-之(一)--FCOS3d训练nuscenes-mini数据集

    参考网上的博客,出现各种错误,最大的是: AssertionError: Samples in split doesn\\\'t match samples in predictions. 给了解决方案,也不知道那个数字是怎么来的。索性自己来一遍,参考了github issue。   第一步,下载数据集并解压: 第二步,修改代码 tools/create_data.py   第三步,制作数据

    2024年02月15日
    浏览(48)
  • 【单目3D目标检测】SMOKE论文解析与代码复现

    在正篇之前,有必要先了解一下yacs库,因为SMOKE源码的参数配置文件,都是基于yacs库建立起来的,不学看不懂啊!!!! yacs是一个用于定义和管理参数配置的库(例如用于训练模型的超参数或可配置模型超参数等)。yacs使用yaml文件来配置参数。另外,yacs是在py-fast -rcnn和

    2024年02月09日
    浏览(53)
  • 【3D目标检测】基于伪雷达点云的单目3D目标检测方法研宄

    本文是基于单目图像的3D目标检测方法,是西安电子科技大学的郭鑫宇学长的硕士学位论文。 【2021】【单目图像的3D目标检测方法研究】 研究的问题: 如何提高伪点云的质量 伪点云体系中如何提高基于点云的检测算法的效果 提出的方法: 一种基于置信度的伪点云采样方法

    2024年02月06日
    浏览(59)
  • 睿智的目标检测——Pytorch搭建YoloV7-3D单目图像目标检测平台

    睿智的目标检测——Pytorch搭建YoloV7-3D单目图像目标检测平台 学习前言 源码下载 YoloV7-3D改进的部分(不完全) YoloV7-3D实现思路 一、整体结构解析 二、网络结构解析 1、主干网络Backbone介绍 2、构建FPN特征金字塔进行加强特征提取 3、利用Yolo Head获得预测结果 三、预测结果的解

    2024年02月16日
    浏览(44)
  • 【MMDetection3D】基于单目(Monocular)的3D目标检测入门实战

    本文简要介绍单目(仅一个摄像头)3D目标检测算法,并使用MMDetection3D算法库,对KITTI(SMOKE算法)、nuScenes-Mini(FCOS3D、PGD算法)进行训练、测试以及可视化操作。   单目3D检测,顾名思义,就是只使用一个摄像头采集图像数据,并将图像作为输入送入模型进,为每一个感兴

    2024年02月03日
    浏览(49)
  • 【利用MMdetection3D框架进行单目3D目标检测(smoke算法】

    mmdetection3d是OpenMMLab开发的3D目标检测开源工具箱,里面包含了许多经典的3D目标检测算法,包含了单目3D目标检测、多目3D目标检测、点云3D目标检测、多模态3D目标检测等各个方向。我们只需要把相应的算法权重下载下来,并调用相应接口即可进行检测。 mmdetection3d的安装需要

    2024年02月13日
    浏览(48)
  • 基于mmdetection3d的单目3D目标检测模型,效果远超CenterNet3D

    使用 mmDetection3D 进行 单目3D 目标检测:基于 KITTI 数据集的实践 在计算机视觉领域,3D 目标检测一直是一个备受关注的研究方向。随着深度学习的发展,越来越多的工具和框架涌现出来,为研究者和开发者提供了更多的选择。本文将介绍如何使用 mmDetection3D 这一强大的框架进

    2024年04月23日
    浏览(37)
  • 【3D目标检测】Fastpillars-2023论文

    论文:fastpillars.pdf https://arxiv.org/abs/2302.02367 作者:东南大学,美团 代码:https://github.com/StiphyJay/FastPillars (暂未开源) 讲解:https://mp.weixin.qq.com/s/ocNH2QBoD2AeK-rLFK6wEQ PointPillars简单地利用max-pooling操作来聚合所有在支柱中使用点特征,这会大量减少本地细粒度信息,尤其会降低

    2024年02月03日
    浏览(45)
  • 【3D目标检测】学习过的论文整理

    本文将我所学习过的3D目标检测论文按照自己的理解进行分类,并介绍每篇论文研究的问题与采用的方法,格式如下: 【年份】【模型简写】【论文名称】【笔记链接】 研究的问题: 问题1 问题2 提出的方法: 方法1 方法2 【2021】【无】【3D Object Detection for Autonomous Driving: A

    2023年04月18日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包