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

news2024/12/23 13:43:57

文章目录

前言

本文对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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/14749.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

删除或者移动文件/文件夹时,提示:文件/文件夹正在使用

问题 有时候我们在移动或者删除文件/文件夹时,系统会提示“文件正在使用”。 操作无法完成,因为其中的文件夹或者文件已经在另一程序中打开 请关闭该文件夹或文件,然后重试。 这是因为文件夹中的某个文件被打开了,或者该文件或文件…

VR云游带你玩转智慧文旅,解决景区营销痛点

有人说防控措施正在逐步放开,大家那颗热爱旅游的心是不是正在蠢蠢欲动了呢?不要急,先来一波VR云游助助兴吧! 朝游青山暮游雪, 上午还在十里长湖、八里磨山, 下午便在毓秀金陵、钟山龙蟠, 看大…

[附源码]SSM计算机毕业设计超市订单管理系统JAVA

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

前端使用nginx部署项目到服务器

目录 一、下载nginx 二、启动 三、验证 四、windows的操作指南 五、目录介绍 六、部署 一、下载nginx 下载地址:https://nginx.org/en/download.html 二、启动 两种方法: 1) 直接双击该目录下的"nginx.exe",即…

设置代理服务器

挂载代理 前提: 同一个局域网,有一个IP白名单可以访问网络(win10)。想实现其他机器(linux)共同访问。 先将机器(linux)设置固定IP,同一网关下。静态IP设置,保证能PING通。将win机器…

雪佛兰畅巡新能源电动汽车CANBUS总线适配及汽车远程控制车联网系统

随着智能交通的发展,中国作为全球最大的汽车市场,车联网的市场容量巨大,国内车联网摸索了很多年,前装也还是属于霸屏,提供信息娱乐等初期阶段。互联网对汽车行业确实推动了不少,汽车公司也与互联网融合做过…

淘宝天猫CTO若海:沉浸式的消费体验是下一步发力方向

​每年双 11 开卖的那一刻,千万用户同时在线下单,那个瞬间服务器的压力是平时流量的数百倍,淘宝系统是否能够稳定支撑,是每年所有人关注的热点话题。 时至今日,丝般顺滑已经逐渐成为稳态。从今年开始,双 11…

使用minio进行文件存储

title: 使用Minio存储文件对象 一. Docker拉取镜像(确保自己的服务器已经安装Docker) docker pull minio/minio二. 启动一个miniio容器 docker run --name minio -p 9090:9000 -p 9999:9999 -d \ --restartalways -e \ "MINIO_ROOT_USERminio&qu…

“码二代”从喜欢益智游戏到找最短路线,编程思维是如何培养的?

前言 1842年,“数字女王”的阿达洛芙莱斯(Ada Lovelace)编写了历史上首款电脑程序,至今已有200多年的历史。 (文末送读者福利) 在这个特别的日子里,我们要为大家介绍的是一个来自小小“码二代…

线性表详细讲述(带图)

文章目录线性表---顺序表和链表1.线性表2.顺序表2.1概念2.2 静态顺序表与动态顺序表2.3接口的实现2.3.1顺表的初始化2.3.2扩容2.3.3顺序表尾插2.3.4顺序表的尾删2.3.5顺序表的头插2.3.6顺序表的头删2.3.7顺序表的查找2.3.8顺序表的任意位置插入2.3.9顺序表的任意位置删除2.3.10…

[附源码]java毕业设计民宿网站管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

【DL】linux服务器上安装Anaconda3

1.本地连接远程服务器 使用MobaXterm连接远程服务器 2.下载Anaconda3安装包 安装包下载地址 https://www.anaconda.com/ 因为我们要在linux服务器上安装,因此选择linux安装包 下载完成以后,将安装包拖进服务器 3.安装Anaconda3 打开终端,输入以下命令,目的是赋权限 c…

Vue路由

参考文献:Vue中的路由 一、路由理解: 一个路由就是一组映射关系(key,value),多个路由需要路由器(router)进行管理。其中key是路径,value是组件。作用:设定访…

【C++笔试强训】第二十六天

🎇C笔试强训 博客主页:一起去看日落吗分享博主的C刷题日常,大家一起学习博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话:夜色难免微凉,前方必有曙光 🌞。 💦&a…

springboot security 集成 cas 问题 No subject alternative names present

springboot security 集成 cas 问题 No subject alternative names present前言一、问题1.实际问题二、大海捞针1.星星之火2.通用方法啰嗦一句解决2.新建三个类配置文件修改前言 场景: 在一次springboot security 集成 cas开发中,代码报错:j…

轻松掌握 jQuery 基础

文章目录💖 前言💖 jQuery简介💖 jQuery安装及使用💖 jQuery的$💞 选择器💞 功能函数前缀💞 window.onload💞 创建DOM元素💖 投票快捷键💖 前言 随着JavaScri…

手机怎么把图片转换成Word?这个小妙招大家要学会

使用手机怎么把图片转换成Word文档呢?大家在使用图片文件来办公的时候,有的图片里面有成段成段的文字,不仅阅读起来很不方便,还很难修改内容,这时候我们可以将图片里的内容转换成Word文档,就可以解决这一问…

利用gdal把多张tif合成一张大图

目录gdalwarpgdalbuildvrt有时候从网站下载遥感影像时,因为选定区域的遥感影像太大,下载后往往是自动就给切片下载了。特别是当选定区域特别大时,最后形成的切片会有几十甚至上百小块,且这些小块都没有重叠的地方,虽然…

MySQL8.0优化 - 事务的ACID特性

文章目录学习资料事务事务的ACID特性原子性(atomicity)一致性(consistency)隔离性(isolation)持久性总结学习资料 【MySQL数据库教程天花板,mysql安装到mysql高级,强!硬…

以梦为马,不负韶华|电巢科技延安大学飞鹰计划实习班精彩回顾

时光流淌无声,昨天仿佛还初次见面,今天却又是一年的尾声。你是否结交到亲密的小伙伴?你是否感受到团队合作的魅力?你是否在延大这片沃土得到成长?假如你还没答案,那么看看其他人的回答。 在延安大学&#x…