Nuscenes——实现世界坐标3D点投影到像素坐标系中

本文介绍了如何在mmdetection3d中实现3D目标检测框的2D投影过程,详细解析了从世界坐标系到像素坐标系的转换步骤,并通过代码示例展示了具体的实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BBOX投影

首先在mmdetection3d/tools/data_converter/nuscenes_converter.py中,get_2d_boxes()可以直接从nuscenes原始sample数据中获取已标注的3D box信息,因此该函数就可以实现整体投影过程。

投影原理

投影过程分为以下几步:

  • 世界坐标系 ——> Ego坐标系(自身)

    • 这里需要世界坐标系原点变换到自身的 平移 T+旋转 R
      在这里插入图片描述
  • Ego坐标系 ——> 相机坐标系

    • 这里需要自身原点位置变换到相机位置的 平移 T+旋转 R(即相机外参) ;
      在这里插入图片描述
  • 相机坐标系 ——> 像素坐标系

    • 此处需要相机内参即可;
      在这里插入图片描述

重要概念

  • box坐标点的origin
    在Nuscenes中,默认get_box()函数获取到的中心点为

代码重构

  • 重构get_2d_boxes()函数如下:(改动处均已标注出)
def get_2d_boxes(nusc,
                 sample_data_token: str,
                 visibilities: List[str],
                 mono3d=True):
    """Get the 2D annotation records for a given `sample_data_token`.

    Args:
        sample_data_token (str): Sample data token belonging to a camera
            keyframe.
        visibilities (list[str]): Visibility filter.
        mono3d (bool): Whether to get boxes with mono3d annotation.

    Return:
        list[dict]: List of 2D annotation record that belongs to the input
            `sample_data_token`.
    """

    # Get the sample data and the sample corresponding to that sample data.
    sd_rec = nusc.get('sample_data', sample_data_token)

    assert sd_rec[
        'sensor_modality'] == 'camera', 'Error: get_2d_boxes only works' \
        ' for camera sample_data!'
    if not sd_rec['is_key_frame']:
        raise ValueError(
            'The 2D re-projections are available only for keyframes.')

    s_rec = nusc.get('sample', sd_rec['sample_token'])
    
    # --------------------------------------------
    # 获取图像,用于可视化
    cam_rec = nusc.get('sample_data', s_rec['data']['CAM_FRONT'])
    imgname = os.path.join('/home/pc/Workspaces/Reaserch/mmdetection3d/data/nuscenes/trainval', \
                                cam_rec['filename'])
	# --------------------------------------------
	
    # Get the calibrated sensor and ego pose
    # record to get the transformation matrices.
    cs_rec = nusc.get('calibrated_sensor', sd_rec['calibrated_sensor_token'])
    pose_rec = nusc.get('ego_pose', sd_rec['ego_pose_token'])
    camera_intrinsic = np.array(cs_rec['camera_intrinsic'])

    # Get all the annotation with the specified visibilties.
    ann_recs = [
        nusc.get('sample_annotation', token) for token in s_rec['anns']
    ]
    ann_recs = [
        ann_rec for ann_rec in ann_recs
        if (ann_rec['visibility_token'] in visibilities)
    ]

    repro_recs = []
    box_list = []       # visualization
    for ann_rec in ann_recs:
        # Augment sample_annotation with token information.
        ann_rec['sample_annotation_token'] = ann_rec['token']
        ann_rec['sample_data_token'] = sample_data_token

        # Get the box in global coordinates.
        box = nusc.get_box(ann_rec['token'])

        # Move them to the ego-pose frame.
        box.translate(-np.array(pose_rec['translation']))
        box.rotate(Quaternion(pose_rec['rotation']).inverse)

        # Move them to the calibrated sensor frame.
        box.translate(-np.array(cs_rec['translation']))
        box.rotate(Quaternion(cs_rec['rotation']).inverse)

        # Filter out the corners that are not in front of the calibrated
        # sensor.
        corners_3d = box.corners()
        in_front = np.argwhere(corners_3d[2, :] > 0).flatten()
        corners_3d = corners_3d[:, in_front]

        # Project 3d box to 2d.
        corner_coords = view_points(corners_3d, camera_intrinsic,
                                    True).T[:, :2].tolist()

        # Keep only corners that fall within the image.
        final_coords = post_process_coords(corner_coords)

        # Skip if the convex hull of the re-projected corners
        # does not intersect the image canvas.
        if final_coords is None:
            continue
        else:
            min_x, min_y, max_x, max_y = final_coords

        # Generate dictionary record to be included in the .json file.
        repro_rec = generate_record(ann_rec, min_x, min_y, max_x, max_y,
                                    sample_data_token, sd_rec['filename'])

        # If mono3d=True, add 3D annotations in camera coordinates
        if mono3d and (repro_rec is not None):
            loc = box.center.tolist()

            dim = box.wlh
            dim[[0, 1, 2]] = dim[[1, 2, 0]]  # convert wlh to our lhw
            dim = dim.tolist()

            rot = box.orientation.yaw_pitch_roll[0]
            rot = [-rot]  # convert the rot to our cam coordinate

            global_velo2d = nusc.box_velocity(box.token)[:2]
            global_velo3d = np.array([*global_velo2d, 0.0])
            e2g_r_mat = Quaternion(pose_rec['rotation']).rotation_matrix
            c2e_r_mat = Quaternion(cs_rec['rotation']).rotation_matrix
            cam_velo3d = global_velo3d @ np.linalg.inv(
                e2g_r_mat).T @ np.linalg.inv(c2e_r_mat).T
            velo = cam_velo3d[0::2].tolist()

            repro_rec['bbox_cam3d'] = loc + dim + rot
            repro_rec['velo_cam3d'] = velo

            center3d = np.array(loc).reshape([1, 3])
            
            
            center2d = points_cam2img(
                center3d, camera_intrinsic, with_depth=False)
            repro_rec['center2d'] = center2d.squeeze().tolist()
            
            # normalized center2D + depth
            # if samples with depth < 0 will be removed
            # --------------------------------------------
            # if repro_rec['center2d'][2] <= 0:
                # continue
            # --------------------------------------------

            ann_token = nusc.get('sample_annotation',
                                 box.token)['attribute_tokens']
            if len(ann_token) == 0:
                attr_name = 'None'
            else:
                attr_name = nusc.get('attribute', ann_token[0])['name']
            attr_id = nus_attributes.index(attr_name)
            repro_rec['attribute_name'] = attr_name
            repro_rec['attribute_id'] = attr_id
            # --------------------------------------------
            box_list.append(torch.tensor(repro_rec['bbox_cam3d']))
        	# --------------------------------------------
        repro_recs.append(repro_rec)
    # --------------------------------------------
    from mmdet3d.core.visualizer.image_vis import draw_camera_bbox3d_on_img
    import cv2
    img = cv2.imread(imgname)
    from mmdet3d.core.bbox.structures.cam_box3d import CameraInstance3DBoxes
    bbox_cam3d = torch.stack(box_list, dim=0) # repro_rec['bbox_cam3d']
    bbox_cam3d = CameraInstance3DBoxes(bbox_cam3d,origin=(0.5,0.5,0.5))
    draw_camera_bbox3d_on_img(bbox_cam3d,
                        raw_img=img,
                        cam2img=camera_intrinsic,
                        img_metas=None,
                        color=(0, 255, 0),
                        thickness=1)
    # --------------------------------------------

投影效果

在这里插入图片描述

点投影

在这里插入图片描述

### BEVFormer项目代码详解及架构说明 #### 1. 项目概述 BEVFormer 是一种基于鸟瞰图(Bird's Eye View, BEV)表示的方法,旨在利用时序和空间信息来完成多任务目标检测和其他下游任务[^2]。其核心思想在于构建一个端到端的 BEV 特征生成器,该生成器能够有效地融合来自多个传感器的数据并生成一致性的 BEV 表示。 --- #### 2. 代码结构分析 ##### 2.1 数据处理模块 BEVFormer 的数据处理部分主要依赖于 nuScenes 数据集[^1]。nuScenes 提供了丰富的标注信息,包括 LiDAR 云、摄像头图像以及对应的标签文件。以下是数据处理的关键组件: - **Dataset 类**: 定义了如何加载和预处理 nuScenes 数据。 - `NuScenesDataset` 负责读取原始数据并将其转换为适合模型输入的形式。 - 使用 `collate_fn` 函数对批次中的样本进行堆叠操作。 - **Data Augmentation**: 实现了一系列增强方法以提高模型泛化能力,例如随机裁剪、旋转和平移等。 ```python from mmdet3d.datasets import NuScenesDataset dataset = NuScenesDataset( ann_file='data/nuscenes/annotations.json', pipeline=pipeline, data_root='data/nuscenes' ) ``` --- ##### 2.2 模型架构设计 ###### (1) 主干网络 Backbone 主干网络负责提取二维特征图。通常采用 ResNet 或 Swin Transformer 来作为基础骨架网路。这些骨干网络可以从 RGB 图像中捕获高层次语义信息。 ###### (2) 预测头 Prediction Head 预测头用于解码最终的任务输出。对于三维物体检测任务来说,它会生成边界框参数;而对于地图分割任务,则会产生像素级分类结果。 ###### (3) 时间序列建模 Temporal Modeling 为了充分利用历史帧的信息,BEVFormer 设计了一种特殊的注意力机制——**时空交叉注意层(Spatial-Temporal Cross Attention Layer)**。此模块允许当前时刻的状态与过去若干帧之间建立联系,从而增强了动态场景的理解能力。 ```python class BEVFormer(nn.Module): def __init__(self, backbone, neck=None, bbox_head=None, train_cfg=None, test_cfg=None): super(BEVFormer, self).__init__() # 初始化各子模块 self.backbone = build_backbone(backbone) if neck is not None: self.neck = build_neck(neck) self.bbox_head = build_head(bbox_head) self.train_cfg = train_cfg self.test_cfg = test_cfg def forward(self, img_metas, img_inputs, prev_bev=None): ... ``` --- #### 3. 关键技术解析 ##### 3.1 BEV 特征生成过程 通过投影变换将不同视角下的观测映射至统一坐标系下形成全局视图。具体而言,先计算每张图片对应的空间位置关系矩阵,再应用双线性插值法得到连续分布的结果。 ##### 3.2 多任务学习框架 除了基本的目标定位功能外,还可以扩展其他辅助分支比如车道线识别或者可行驶区域判断等功能。这种联合训练方式有助于提升整体性能表现。 --- #### 4. 训练与评估流程 按照官方文档描述,在实际运行过程中需遵循如下配置步骤: - 设置超参:定义 epoch 数量、batch size 及优化算法等相关选项; - 加载权重:如果存在预训练好的 checkpoint 文件则可以直接导入继续微调; - 启动脚本:执行命令行指令启动整个实验周期直至收敛结束为止。 ```bash # 开始训练 python tools/train.py configs/bevformer/bevformer_base.py --work-dir work_dirs/bevformer/ ``` --- #### 5. 总结 综上所述,BEVFormer 不仅实现了高质量的三维感知效果而且具备较强的灵活性适应多种应用场景需求。未来研究方向可能涉及更高效的推理速度改进方案或是针对特定领域定制版本开发等方面探索可能性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值