【论文复现】DETR3D:3D目标检测

在这里插入图片描述

📝个人主页🌹:Eternity._
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述
在这里插入图片描述

概述


DETR3D介绍了一种多摄像头的三维目标检测的框架。与现有的直接从单目图像中估计3D边界框或者使用深度预测网络从2D信息中生成3D目标检测的输入相比,DETR3D直接在3D空间中进行预测。DETR3D从多个相机图像中提取2D特征,使用3D对象查询的稀疏集来索引这些2D特征。使用相机变换矩阵将3D位置链接到多视图图像。最后对每个目标单独进行边界框预测,使用集合到集合的损失来衡量真值和预测之间的差异。

本文所涉及的所有资源的获取方式:这里

模型结构


DETR3D架构的输入是一组投影矩阵(内参和相对外参的组合)和已知的相机收集的RGB图像,为场景中的物体输出一组3D边界框参数。与过去的方法相比,DETR3D基于一些高级需求构建体系结构。

  • DETR3D将3D信息合并到中间计算中,而不是在图像平面上执行纯粹的2D计算
  • DETR3D不估计密集的三维场景几何,避免相关的重建误差
  • DETR3D避免了NMS等后处理步骤

在这里插入图片描述
 如上图所示,DETR3D使用一个新的集合预测模块来解决这些问题,该模块通过在2D和3D计算之间交替来连接2D特征提取和3D边界框预测。我们的模型包括三个关键部分:

  • 首先,遵循2D视觉中的常见做法,它使用共享ResNet主干从相机图像中提取特征。这些特征可以选择性的由特征金字塔网络增强
  • 一个检测头,以集合感知的方式将计算出的2D特征连接到一组3D边界框预测中。检测头的每一层都是从一组稀疏的对象查询开始,这些查询是从数据中学习的。每个对象查询编码一个3D位置,该位置被投影到相机平面上,用于通过双线性插值收集图像特征。DETR3D使用多头注意力通过结合对象交互来细化对象查询。该层重复多次,在特征采样和对象查询细化之间交替。
  • DETR3D采用了一个集合到集合的损失来训练网络

特征学习


在这里插入图片描述

检测头


在相机输入中检测物体的现有方法通常采用自下而上的方法,该方法预测每张图像的密集边界框,过滤图像之间的冗余框,并在后处理步骤中汇总相机之间的预测。这种范式由两个关键的缺点:密集边界框预测需要精确的深度感知,这本身就是一个具有挑战性的问题;基于NMS的冗余删除和聚合是不可并行化的操作,会引入大量的推理开销。
DETR3D采用下面描述的自顶向下的目标检测头来解决这些问题。它使用L层基于集合的计算从2D特征图中产生边界框估计,每层都遵循如下的步骤:

  • 预测一组与对象查询相关的边界框中心
  • 使用相机变换矩阵将这些中心投影到所有的特征映射中
  • 通过双线性插值采样特征,并将其合并到对象查询中
  • 采用多头注意力机制描述对象的相互作用

在这里插入图片描述
在这里插入图片描述

演示效果


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心逻辑


FPN结构
对经过ResNet的特征图进行特征交互与融合操作

def forward(self, inputs):
        """Forward function."""
        assert len(inputs) == len(self.in_channels)

        # build laterals
        laterals = [
            lateral_conv(inputs[i + self.start_level])
            for i, lateral_conv in enumerate(self.lateral_convs)
        ]

        # build top-down path
        used_backbone_levels = len(laterals)
        for i in range(used_backbone_levels - 1, 0, -1):
            # In some cases, fixing `scale factor` (e.g. 2) is preferred, but
            #  it cannot co-exist with `size` in `F.interpolate`.
            if 'scale_factor' in self.upsample_cfg:
                # fix runtime error of "+=" inplace operation in PyTorch 1.10
                laterals[i - 1] = laterals[i - 1] + F.interpolate(
                    laterals[i], **self.upsample_cfg)
            else: # 使用双线性插值的方法将高层特征图扩大,并且两者相加
                prev_shape = laterals[i - 1].shape[2:]
                laterals[i - 1] = laterals[i - 1] + F.interpolate(
                    laterals[i], size=prev_shape, **self.upsample_cfg)

        # build outputs
        # part 1: from original levels
        # out[0]:[6,256,116,200] out[1]:[6,256,58,100] out[2]:[6,256,29,50]
        outs = [ 
            self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)
        ]
        # part 2: add extra levels
        if self.num_outs > len(outs):
            # use max pool to get more levels on top of outputs
            # (e.g., Faster R-CNN, Mask R-CNN)
            if not self.add_extra_convs:
                for i in range(self.num_outs - used_backbone_levels):
                    outs.append(F.max_pool2d(outs[-1], 1, stride=2))
            # add conv layers on top of original feature maps (RetinaNet)
            else:
                if self.add_extra_convs == 'on_input':
                    extra_source = inputs[self.backbone_end_level - 1]
                elif self.add_extra_convs == 'on_lateral':
                    extra_source = laterals[-1]
                elif self.add_extra_convs == 'on_output':
                    extra_source = outs[-1]
                else:
                    raise NotImplementedError
                outs.append(self.fpn_convs[used_backbone_levels](extra_source))
                # 增加最后一层 out[3]: [6,256,15,25]
                for i in range(used_backbone_levels + 1, self.num_outs):
                    if self.relu_before_extra_convs:
                        outs.append(self.fpn_convs[i](F.relu(outs[-1])))
                    else:
                        outs.append(self.fpn_convs[i](outs[-1]))
        return tuple(outs)

特征采样
BEV查询和特征图进行交互

def feature_sampling(mlvl_feats, reference_points, pc_range, img_metas):
    lidar2img = [] # bev空间像图像空间的转换
    for img_meta in img_metas:
        lidar2img.append(img_meta['lidar2img'])
    lidar2img = np.asarray(lidar2img) # new_tensor使dtype和device与ref_p相同,大小与lidar2img相同
    # [1,6,4,4]
    lidar2img = reference_points.new_tensor(lidar2img) # (B, N, 4, 4)
    reference_points = reference_points.clone()
    reference_points_3d = reference_points.clone()
    # [1,900,3] # 分别将每个点投影到BEV网格上的范围
    reference_points[..., 0:1] = reference_points[..., 0:1]*(pc_range[3] - pc_range[0]) + pc_range[0]
    reference_points[..., 1:2] = reference_points[..., 1:2]*(pc_range[4] - pc_range[1]) + pc_range[1]
    reference_points[..., 2:3] = reference_points[..., 2:3]*(pc_range[5] - pc_range[2]) + pc_range[2]
    # reference_points (B, num_queries, 4)
    # 最后拼接全是1的形式,形成齐次化坐标系方便后续的转换
    reference_points = torch.cat((reference_points, torch.ones_like(reference_points[..., :1])), -1)
    
    B, num_query = reference_points.size()[:2]
    num_cam = lidar2img.size(1)
    # ref_p:[1,900,4]->[1,1,900,4]->[1,6,900,4]->[1,6,900,4,1] lid:[1,6,1,4,4]->[1,6,900,4,4]
    reference_points = reference_points.view(B, 1, num_query, 4).repeat(1, num_cam, 1, 1).unsqueeze(-1)
    lidar2img = lidar2img.view(B, num_cam, 1, 4, 4).repeat(1, 1, num_query, 1, 1)
    # [1,6,900,4,1]->[1,6,900,4] 和特征图无关,此时生成齐次化坐标系的形式
    reference_points_cam = torch.matmul(lidar2img, reference_points).squeeze(-1)
    eps = 1e-5
    # z轴需要大于0否则不处于该相机平面上,ones_likes是防止其中的取值为负
    mask = (reference_points_cam[..., 2:3] > eps)
    reference_points_cam = reference_points_cam[..., 0:2] / torch.maximum(
        reference_points_cam[..., 2:3], torch.ones_like(reference_points_cam[..., 2:3])*eps)
    # 此时将其进行归一化,并且获得边界框的中心点坐标的形式
    reference_points_cam[..., 0] /= img_metas[0]['img_shape'][0][1]
    reference_points_cam[..., 1] /= img_metas[0]['img_shape'][0][0]
    reference_points_cam = (reference_points_cam - 0.5) * 2
    # 将不在该图像上且缩放大于1的数据进行处理
    mask = (mask & (reference_points_cam[..., 0:1] > -1.0) 
                 & (reference_points_cam[..., 0:1] < 1.0) 
                 & (reference_points_cam[..., 1:2] > -1.0) 
                 & (reference_points_cam[..., 1:2] < 1.0))
    # mask:[1,6,900,1]->[1,6,1,900,1,1]->[1,1,900,6,1,1]
    mask = mask.view(B, num_cam, 1, num_query, 1, 1).permute(0, 2, 3, 1, 4, 5)
    mask = torch.nan_to_num(mask)
    sampled_feats = []
    
    for lvl, feat in enumerate(mlvl_feats):
        B, N, C, H, W = feat.size() # [1,6,256,116,200]
        feat = feat.view(B*N, C, H, W)
        # ref_p: [1,6,900,2]->[6,900,1,2] 
        reference_points_cam_lvl = reference_points_cam.view(B*N, num_query, 1, 2)
        # sample_f: [6,256,900,1]->[1,6,256,900,1]->[1,256,900,6,1]
        # 根据ref_p给定的网格信息将将feat特征图的值给填充到网格中此时每个query包含的从特征图中提取的信息
        sampled_feat = F.grid_sample(feat, reference_points_cam_lvl)
        sampled_feat = sampled_feat.view(B, N, C, num_query, 1).permute(0, 2, 3, 1, 4)
        sampled_feats.append(sampled_feat)
    sampled_feats = torch.stack(sampled_feats, -1)
    # [1,256,900,6,1,4]
    sampled_feats = sampled_feats.view(B, C, num_query, num_cam,  1, len(mlvl_feats))
    # 3d: [1,900,3]坐标直接是bev坐标的形式,是query从256下采样到3得到的结果。
    # samp_f: [1,256,900,6,1,4] 是获取不同特征图的组合然后组合在一起
    # mask: [1,1,900,6,1,1] 获得在每个相机图像下每个query是否会投影上去
    return reference_points_3d, sampled_feats, mask

部署方式


# 安装python=3.7 cu111 torch1.9.0 torchvision 1.10.0
# 需要网上下载轮子采用pip install安装,这里以linux为例
conda create -n detr3d python=3.7
wget https://download.pytorch.org/whl/cu111/torch-1.9.0%2Bcu111-cp37-cp37m-linux_x86_64.whl
wget https://download.pytorch.org/whl/cu111/torch-1.10.0%2Bcu111-cp37-cp37m-linux_x86_64.whl

# 安装MMCV
pip install mmcv-full==1.4.0 -f https://download.openmmlab.com/mmcv/dist/cu111/torch1.9.0/index.html

# 安装MMDetection
git clone https://github.com/open-mmlab/mmdetection.git
cd mmdetection
git checkout v2.19.0 
sudo pip install -r requirements/build.txt
sudo python3 setup.py develop
cd ..

# 安装MMSeg
sudo pip install mmsegmentation==0.14.1

# 安装MMDEtection3D
git clone  https://github.com/open-mmlab/mmdetection3d.git
cd mmdetection3d
git checkout v0.17.3 
sudo pip install -r requirements/build.txt
sudo python3 setup.py develop
cd ..

参考文献


github地址
论文地址


编程未来,从这里启航!解锁无限创意,让每一行代码都成为你通往成功的阶梯,帮助更多人欣赏与学习!

更多内容详见:这里

### 关于DQ-DETR的代码实现与教程 对于希望深入了解并实践DQ-DETR模型的研究人员和开发者而言,获取官方或社区维护的良好文档化的代码库至关重要。目前关于DQ-DETR的信息主要集中在学术会议论文及其附带资源上。 #### 官方GitHub仓库 通常情况下,研究团队会在发布新方法的同时公开相应的源代码以便同行评审和其他研究人员能够重现实验结果。对于DQ-DETR来说,在ECCV 2024的相关介绍中提到该工作已经开源[^2]。因此建议访问作者们指定的GitHub页面来获得最权威的第一手资料: ```plaintext https://github.com/author-repo/DQ-DETR ``` 请注意实际链接需根据具体发布的地址调整。 #### 社区贡献版本 除了官方提供的实现外,活跃的技术交流平台上也可能存在由第三方开发者的复现尝试或是改进版。这些非官方但经过验证有效的实现同样具有很高的参考价值。可以关注像Hugging Face Model Hub这样的平台,以及通过搜索引擎查找其他可能存在的高质量移植版本。 #### 学习路径指导 为了更好地理解和应用这一先进的目标检测技术,可以从以下几个方面入手准备: 1. **掌握基础知识** - 熟悉PyTorch框架下的深度学习操作。 - 掌握Transformer架构的工作原理。 2. **阅读原始文献** - 细读ECCV 2024发表的文章《DQ-DETR》全文,理解其创新之处和技术细节。 3. **参与讨论论坛** - 加入专门的知识分享社群如3D视觉从入门到精通知识星球[^1],这里聚集了许多志同道合的朋友共同探讨前沿话题。 4. **动手实践编码** - 尝试按照官方指南搭建环境,并逐步调试直至成功运行示例程序。 - 对比不同参数设置下性能差异,积累调参经验。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值