文章目录

  • 前言
  • FCOS3D
    • 概述
    • 主要创新点
    • 主要框架结构
    • 回归目标
    • 损失函数
    • 推理过程
    • 2D引导的多层3D预测
    • 2D高斯分布的3D中心度
    • 实验设置
    • 源码复现
  • PGD
    • 概述
    • 主要创新点
    • 深度估计
    • 主要框架结构
    • 创新点一:概率表示的不确定性建模DPD_PDP​
    • 创新点二:透视几何体的深度传播DGD_GDG​
    • 最终的深度估计:概率和几何深度估计DDD
    • 源码复现
  • Refernece

前言

本文对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,rt,b,l,rt,b,l,r所示),FCOS3D将通常定义的7-DoF回归目标转换为2.5D中心和3D尺寸,其中2.5D中心可以通过相机固有矩阵轻松转换回3D空间。


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

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

总的来说,分类分支需要输出目标的类别标签和属性标签,而回归分支则需要预测Δx,Δy,d,w,l,h,θ,vx,vy,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,其中ppp是预测框的类概率,遵循原始论文的设置α=0.25,γ=2\alpha=0.25,\gamma=2α=0.25,γ=2
    Lcls=−α(1−p)γlog⁡pL_{c l s}=-\alpha(1-p)^\gamma \log pLcls​=−α(1−p)γlogp
  • 属性分类,使用softmax分类损失,表示为LattrL_{attr}Lattr​
  • 回归分支,对Δx,Δy,d,w,l,h,θ,vx,vy\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分类损失并表示为LdirL_{dir}Ldir​,对Centerness ccc使用二元交叉熵(BCE)损失函数并表示为LctL_{ct}Lct​
    Lloc=∑b∈(Δx,Δy,d,w,l,h,θ,vx,vy)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=1Npos(βclsLcls+βattrLattr+βlocLloc+βdirLdir+βctLct)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=Npos​1​(βcls​Lcls​+βattr​Lattr​+βloc​Lloc​+βdir​Ldir​+βct​Lct​)

在代码中,各个损失函数的定义如下,可以看到实际代码中, 属性分类LattrL_{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}表示四个距离阈值:
    mAP=1∣C∣∣D∣∑c∈C∑d∈DAPc,dm A P=\frac{1}{|\mathbb{C}||\mathbb{D}|} \sum_{c \in \mathbb{C}} \sum_{d \in \mathbb{D}} A P_{c, d}mAP=∣C∣∣D∣1​c∈C∑​d∈D∑​APc,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:
    NDS=110[5mAP+∑mTP∈TP(1−min⁡(1,mTP))]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+mTP∈TP∑​(1−min(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整体框架的基础上,主要关注实例深度估计的难题,首先引入概率深度估计模块来建模不确定性,然后从深度传播图中得到几何深度,最后融合二者得到最终的深度预测值

创新点一:概率表示的不确定性建模DPD_PDP​

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

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

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

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

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

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

创新点二:透视几何体的深度传播DGD_GDG​

利用孤立实例的深度预测DLD_LDL​和不确定性估计的深度置信分数,我们可以进一步基于上下文几何关系构建传播图。考虑典型的驾驶场景:可以利用一般约束,即几乎所有物体都在地面上。针对深度估计问题,我们提出了一种几何深度传播机制,考虑了实例之间的相互依赖性。已知相机的内参矩阵:
P=(f0cu−fbx0fcv−fby001−fbz)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=⎝⎛​f00​0f0​cu​cv​1​−fbx​−fby​−fbz​​⎠⎞​

其中各参数含义如下:

  • fff:相机焦距,考虑到大多数相机在uuu轴和vvv轴上共享相同的焦距,因此这里用单个fff表示焦距
  • cu,cvc_u,c_vcu​,cv​:相机在图像中的水平和垂直位置
  • bx,by,bzb_x,b_y,b_zbx​,by​,bz​:相对于参考相机的基线(KITTI中非零,NuScenes为零)

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

为了简化结果,将v0v_0v0​替换为v+cvv+c_vv+cv​,其中vvv表示目标到地平线的距离(如下图所示,向下为正方向),然后我们得到:
vd=f(y−by+cvbz)v d=f\left(y-b_y+c_v b_z\right)vd=f(y−by​+cv​bz​)

uuu的关系类似。考虑到所有对象都在地面上的约束,对象的底部中心始终共享相同的yyy(相机坐标中的高度),因此接下来主要考虑vvv的关系。给定两个物体1和2,它们的中心深度之间的关系为:
d2=v1v2d1+fv2(y2−y1)≈v1v2d1+f2v2(h13D−h23D)≜d1→2Pd_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}^Pd2​=v2​v1​​d1​+v2​f​(y2​−y1​)≈v2​v1​​d1​+2v2​f​(h13D​−h23D​)≜d1→2P​

对于一幅图像上的n个目标,可以根据上述公式定义他们之间的几何深度信息:
diG=∑j=1ksj→iedj→iPd_i^G=\sum_{j=1}^k s_{j \rightarrow i}^e d_{j \rightarrow i}^PdiG​=j=1∑k​sj→ie​dj→iP​

其中,sj→ie\boldsymbol{s}_{j \rightarrow i}^esj→ie​与目标之间的距离, kkk为选定的与目标iii置信度sj→ies_{j→i}^esj→ie​ 最高的目标集合。值得注意的是, DGD_GDG​没有可学习的参数,不参与网络的反向转播过程。

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

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

网络的深度估计包含两个方面:局部的深度估计DLD_LDL​以及基于目标之间几何关系的深度估计DGD_GDG​,其中α∈RH×Wα∈R^{H×W}α∈RH×W为可学习参数:
D=σ(α)∘DL+(1−σ(α))∘DGD=\sigma(\alpha) \circ D_L+(1-\sigma(\alpha)) \circ D_GD=σ(α)∘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

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

  1. 【单目3D目标检测】SMOKE论文解析与代码复现

    文章目录 yacs Introduction Usage SMOKE Preface Abstract Contributions Pipeline Backbone Head Branch Orie ...

  2. 【单目3D目标检测】MonoDLE论文精读与代码解析

    文章目录 Preface Abstract Contributions Diagnostic Experiments Pipeline Revisiting Center Detection Trai ...

  3. 【单目3D目标检测】MonoFlex论文精读与代码解析

    文章目录 Preface Abstract Contributions Pipeline Problem Definition Decoupled Representations of Objects ...

  4. 单目3D目标检测DEVIANT源码解析

    目前文档只包含outputs = model(inputs,coord_ranges,calibs,K=50,mode='test')之后,前向推理的源码解析,附带有测试程序 DEVIANT: Dep ...

  5. 单目三维目标检测之CaDDN论文阅读

    文章目录 CaDDN: Categorical Depth Distribution Network for Monocular 3D Object Detection 作者和机构信息: Abstra ...

  6. MonoCon:使用辅助学习的单目3D目标检测框架(AAAI 2022)

    作者丨慕弋云子@知乎 来源丨https://zhuanlan.zhihu.com/p/455897310 编辑丨3D视觉工坊 本文已被收录在单目3D目标检测的综述文章中.如果你对单目3D目标检测的相关 ...

  7. DD3D:基于预训练的单目3D目标检测

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 来源丨CV研习社 作者丨元气满满的打工人 文章导读 导读:3D目标检测的主要应用场景就是自动驾驶,虽然 ...

  8. ICCV2021|单目3D目标检测真的需要伪激光雷达吗?

    作者丨agent@知乎 来源丨https://zhuanlan.zhihu.com/p/406918022 编辑丨3D视觉工坊 Paper: arxiv.org/pdf/2108.0641 Code: ...

  9. 首个实时单目3D目标检测算法:RTM3D,代码将开源

    o 点击我爱计算机视觉标星,更快获取CVML新技术 基于单目图像的3D目标检测是在输入RGB图像的情况下估计目标的3D包围框,在自动驾驶领域非常有用. 今天来自中科院沈阳自动化所等单位的学者公布论文提 ...

最新文章

  1. jenkins部署web项目
  2. 当上 CTO 才发现:程序员时常犯的 4 个错误有多可怕!
  3. python调用qq互联_Django项目中实现使用qq第三方登录功能
  4. access month函数用法_学会了这7个EXCEL日期函数技巧,老板再让你加班,你找我!...
  5. js中的extend的用法及其JS中substring与substr的区别
  6. vue2 父子组件传参 回调函数使用
  7. ORB-SLAM2代码思维导图
  8. SQL:PostgreSQL设置自增序列
  9. PHP简单留言板代码
  10. 层次分析法软件操作步骤(yaahp)
  11. 如何让阿三 Windows 10、11 的恢复分区(Recovery Partition)恢复到 “盖茨” 模式
  12. 华为交换机主备命令_华为交换机命令汇总
  13. GBT 31000-2015 社会治安综合治理基础数据规范 数据项 编码
  14. 【转】问答 - 挑灯看剑 的最新日记
  15. 元素的隐藏和显示(v-show指令)
  16. 【报告分享】2020中国教育培训移动应用发展研究报告-TalkingData(附下载)
  17. 高清视频体验大幅提升,来数数我们应用了哪些新算法
  18. 计算机网络——DV和LS算法笔记
  19. VRTK4 入门指南
  20. sock_raw和sock_packet

热门文章

  1. [iOS UI进阶 - 0] Quiartz2D
  2. idea基础配置(史上最全,你想要的全都有)
  3. Linux网络服务之部署YUM仓库
  4. Taro开发微信小程序(一)
  5. 深度linux玩大话西游2,大话西游价值8000的稀有端网游动力首发
  6. DPDK性能影响因素分析
  7. 华东师范大学 计算机 博士 毕业论文,华师大软件学院历年培养研究生学位论文情况.docx...
  8. mysql 外键_MySQL 基本语句十 Primary Key amp; Foriegn Key(更新6/10/2020)
  9. c# 调用系统默认图片浏览器打开图片
  10. 菲律宾当地的蜂窝网络情况