【MMDetection3D】VoxelNet Related Code In MMDetection3D

VoxelNet Related Code In MMDetection3D

mmdetection3d/mmdet3d/models/task_modules/voxel

第一个有关VoxelNet的代码位于mmdetection3d/mmdet3d/models/task_modules/voxel这个package中。由于MMDetection3D的版本从2023年开始将默认主分支为1.1分支,所以将以1.1版本作为讲解。其它版本可以在MMDetection3DGitHub Repository主页点击T,然后输入VoxelNet搜索相关文件,找到和下述代码类似的.py文件即可。

这个package中主要文件是voxel_generator.py,定义的类和函数如下:

  • class VoxelGenerator()
  • def points_to_voxel()
  • def _points_to_voxel_reverse_kernel()
  • def _points_to_voxel_kernel()

测试代码位于:mmdetection3d/tests/test_models/test_task_modules/test_voxel/test_voxel_generator.py

class VoxelGenerator()

用一个类来实现voxel的generating。类中除了一些辅助函数(@property装饰的打印属性的函数,__repr__的打印类的函数)外,主要就是初始化函数和生成函数了,代码段如下:

class VoxelGenerator(object):
    """用numpy实现的voxel generator.
    Args:
        voxel_size (list[float]): 一个体素的大小.
        point_cloud_range (list[float]): 点云的范围.
        max_num_points (int): 每个体素中最大的点数量.
        max_voxels (int, optional): 最大体素数量. 默认为: 20000.
    """

    def __init__(self,
                 voxel_size,
                 point_cloud_range,
                 max_num_points,
                 max_voxels=20000):

				# [0, -40, -3, 70.4, 40, 1], xyzxyz, minmax, 参考VoxelNet论文
        point_cloud_range = np.array(point_cloud_range, dtype=np.float32)
        # [0.2, 0.2, 0.4], xyz, 参考VoxelNet论文
        voxel_size = np.array(voxel_size, dtype=np.float32)
				# [352, 400, 10], xyz, 参考VoxelNet论文
        grid_size = (point_cloud_range[3:] -
                     point_cloud_range[:3]) / voxel_size
        grid_size = np.round(grid_size).astype(np.int64)

        self._voxel_size = voxel_size
        self._point_cloud_range = point_cloud_range
        self._max_num_points = max_num_points
        self._max_voxels = max_voxels
        self._grid_size = grid_size

    def generate(self, points):
        """给定点云points生成voxels."""
        return points_to_voxel(points, self._voxel_size,
                               self._point_cloud_range, self._max_num_points,
                               True, self._max_voxels)

def points_to_voxel()

在下面展示的代码中舍弃一些数据类型的转换部分,只提取比较核心的变量和代码。

def points_to_voxel(points,
                    voxel_size,
                    coors_range,
                    max_points=35,
                    reverse_index=True,
                    max_voxels=20000):
    """将KITTI点云(N, >=3)转换为体素.
    Args:
        points (np.ndarray): [N, ndim]. points[:, :3] 包含xyz, points[:, 3:] 包含像反射
						强度这样的其它信息.
        voxel_size (list, tuple, np.ndarray): [3] xyz, 指示体素大小.
        coors_range (list[float] | tuple[float] | ndarray]): 体素的范围 (也是点云的范围).
            格式为: xyzxyz, minmax
        max_points (int): 指定一个体素中包含的最多点数.
        reverse_index (bool): 是否返回反转坐标.
            如果点是xyz格式坐标并且这个参数为True, 那么输出坐标格式就是zyx, 但是特征中点通常
						为xyz格式.
        max_voxels (int): 这个函数创建的最多体素个数. 
            对于SECOND论文方法, 20000是不错的选择. 在这个函数之前, 点应该被随机打乱, 因为这
						个参数会导致舍弃一些点.

    Returns:
        tuple[np.ndarray]:
            voxels: [M, max_points, ndim] float tensor. only contain points.
            coordinates: [M, 3] int32 tensor.
            num_points_per_voxel: [M] int32 tensor.
    """
		# (10, 400, 352)
    voxelmap_shape = (coors_range[3:] - coors_range[:3]) / voxel_size
    voxelmap_shape = tuple(np.round(voxelmap_shape).astype(np.int32).tolist())
    if reverse_index:
        voxelmap_shape = voxelmap_shape[::-1]
    # don't create large array in jit(nopython=True) code.
		# 维护一个(20000,)的矩阵来记录每个体素中的点数量, 初始化为0
    num_points_per_voxel = np.zeros(shape=(max_voxels, ), dtype=np.int32)
		# 维护一个(10, 400, 352)的矩阵来记录有效体素编号, 初始化为-1
    coor_to_voxelidx = -np.ones(shape=voxelmap_shape, dtype=np.int32)
		# 维护一个(20000, 35, 3)的矩阵来记录每个有效体素中每个点的坐标反射强度等信息, 初始化为0
    voxels = np.zeros(
        shape=(max_voxels, max_points, points.shape[-1]), dtype=points.dtype)
		# 维护一个(20000, 3)的矩阵来记录有效体素在所有体素中的三维索引
    coors = np.zeros(shape=(max_voxels, 3), dtype=np.int32)
    if reverse_index:
        voxel_num = _points_to_voxel_reverse_kernel(
            points, voxel_size, coors_range, num_points_per_voxel,
            coor_to_voxelidx, voxels, coors, max_points, max_voxels)

    else:
        voxel_num = _points_to_voxel_kernel(points, voxel_size, coors_range,
                                            num_points_per_voxel,
                                            coor_to_voxelidx, voxels, coors,
                                            max_points, max_voxels)
		# 得到有效体素在所有体素中的三维索引, [M, 3]
    coors = coors[:voxel_num]
		# 每个有效体素中每个点的坐标反射强度等信息
    voxels = voxels[:voxel_num]
		# 每个有效体素中的点数量
    num_points_per_voxel = num_points_per_voxel[:voxel_num]

    return voxels, coors, num_points_per_voxel

def _points_to_voxel_reverse_kernel()

这是整个转换过程的核心部分。

在下面展示的代码中舍弃一些无关的注释,只展示核心代码。

def _points_to_voxel_reverse_kernel(points,
                                    voxel_size,
                                    coors_range,
                                    num_points_per_voxel,
                                    coor_to_voxelidx,
                                    voxels,
                                    coors,
                                    max_points=35,
                                    max_voxels=20000):
    """将KITTI点(N, >=3)转换为体素.
    Args:
        points (np.ndarray): [N, ndim]. points[:, :3] 包含xyz. points[:, 3:] 包含像反射
						强度这样的其它信息.
        voxel_size (list, tuple, np.ndarray): [3] xyz, 指明体素大小
        coors_range (list[float] | tuple[float] | ndarray]): 体素范围.
            格式为: xyzxyz, minmax
        num_points_per_voxel (int): 每个体素中点的数量.
        coor_to_voxel_idx (np.ndarray): 形状为 (D, H, W) 的体素格, 表明了每个对应体素的
						索引.
        voxels (np.ndarray): 创建空体素.
        coors (np.ndarray): 创建每个体素的坐标.
        max_points (int): 指定一个体素中包含的最多点数.
        max_voxels (int): 这个函数创建的最多体素数.

    Returns:
        tuple[np.ndarray]:
            voxels: Shape [M, max_points, ndim], only contain points.
            coordinates: Shape [M, 3].
            num_points_per_voxel: Shape [M].
    """
		# 点云中共N个点
    N = points.shape[0]
		# xyz维度为3
    ndim = 3
    ndim_minus_1 = ndim - 1
		# (352, 400, 10)
    grid_size = (coors_range[3:] - coors_range[:3]) / voxel_size
    grid_size = np.round(grid_size, 0, grid_size).astype(np.int32)
		# 一个点的坐标(3,), 初始化为0
    coor = np.zeros(shape=(3, ), dtype=np.int32)
		# 体素编号, 从0开始
    voxel_num = 0
		# 指示当前处理的点是否在范围内
    failed = False
		# 遍历点云中每一个点
    for i in range(N):
				# 重置failed
        failed = False
				# 遍历这个点的xyz坐标信息
        for j in range(ndim):
						# 得到点在x或y或z维度上的体素序号
            c = np.floor((points[i, j] - coors_range[j]) / voxel_size[j])
						# 根据这个维度的体素序号判断这个点是否有效
            if c < 0 or c >= grid_size[j]:
								# 如果无效, 设置failed
                failed = True
								# 直接跳出循环, 不再判断其它xyz的维度
                break
						# 如果点是有效的就存储这个点所在体素各维度的索引值到coor, 按zyx
            coor[ndim_minus_1 - j] = c
				# 如果这个点不在有效范围内就继续处理下一个点
        if failed:
            continue
				# 如果点在有效范围内
				# 获取当前这个点的体素索引值, 如果之前该体素没有点, 得到-1, 否则为k
        voxelidx = coor_to_voxelidx[coor[0], coor[1], coor[2]]
				# 如果这个体素之前没有点
        if voxelidx == -1:
						# 那么更新当前的voxelidx
            voxelidx = voxel_num
						# 如果当前有效体素总数已经超过最大体素数量则提前结束对当前点的处理
            if voxel_num >= max_voxels:
                continue
						# 否则将体素总数+1
            voxel_num += 1
						# 更新当前点所在体素的编号
            coor_to_voxelidx[coor[0], coor[1], coor[2]] = voxelidx
						# 存储这个体素的索引值
            coors[voxelidx] = coor
				# 获取当前点所在体素的点数
        num = num_points_per_voxel[voxelidx]
				# 如果没有超过当前体素采样的最大点数
        if num < max_points:
						# 将当前点的坐标信息存入
            voxels[voxelidx, num] = points[i]
						# 这个体素中的点数量+1
            num_points_per_voxel[voxelidx] += 1
		# 最后返回整个体素中有效的体素数量
    return voxel_num

def _points_to_voxel_kernel()

这也是整个转换过程的核心部分。和前面的代码只有一行不同:coor[ndim_minus_1 - j] = c。在此不赘述。

mmdetection3d/mmdet3d/models/voxel_encoders

第二个有关VoxelNet的代码位于mmdetection3d/mmdet3d/models/voxel_encoders这个package中。由于MMDetection3D的版本从2023年开始将默认主分支为1.1分支,所以将以1.1版本作为讲解。其它版本可以在MMDetection3DGitHub Repository主页点击T,然后输入VoxelNet搜索相关文件,找到和下述代码类似的.py文件即可。

这个package中主要文件是utils.py,定义的类和函数如下:

  • class VFELayer()

class VFELayer()

这个类所定义的应该就是VoxelNet论文中的Voxel Feature Encoding Layer.

class VFELayer(nn.Module):
    """Voxel Feature Encoder layer.
		voxel encoder包括一系列这样的层.
    这个模块不支持平均池化, 只支持使用最大池化来聚集VFE内的特征.
    
    Args:
        in_channels (int): 输入通道数.
        out_channels (int): 输出通道数.
        norm_cfg (dict): normalization层的配置字典.
        max_out (bool): 是否聚合每个体素内点特征并且只返回体素特征. 
        cat_max (bool): 是否拼接聚合的特征和点特征. 
    """

    def __init__(self,
                 in_channels,
                 out_channels,
                 norm_cfg=dict(type='BN1d', eps=1e-3, momentum=0.01),
                 max_out=True,
                 cat_max=True):
        super(VFELayer, self).__init__()
        self.fp16_enabled = False  # 是否使用float point 16 type
        self.cat_max = cat_max
        self.max_out = max_out
        # self.units = int(out_channels / 2)

        self.norm = build_norm_layer(norm_cfg, out_channels)[1]  # 参考mmcv.cnn中函数
        self.linear = nn.Linear(in_channels, out_channels, bias=False)

    def forward(self, inputs):
        """前向传播函数.
        Args:
            inputs (torch.Tensor): 形状为 (N, M, C) 的体素特征.
                N是体素个数, M是体素中点个数, C是点特征的通道数.
        Returns:
            torch.Tensor: 体素特征. 下面有三种模式, 不同模式下特征有不同含义.
                - `max_out=False`: 返回形状为(N, M, C)的point-wise features.
                - `max_out=True` and `cat_max=False`: 返回形状为(N, C)的聚合体素特征.
                - `max_out=True` and `cat_max=True`: 返回形状为(N, M, C)的拼接
											point-wise features.
        """
        # [K, T, 7] tensordot [7, units] = [K, T, units]
        voxel_count = inputs.shape[1]

        x = self.linear(inputs)
        x = self.norm(x.permute(0, 2, 1).contiguous()).permute(0, 2,
                                                               1).contiguous()
        pointwise = F.relu(x)
        # [K, T, units]
        if self.max_out:
            aggregated = torch.max(pointwise, dim=1, keepdim=True)[0]
        else:
            # this is for fusion layer
            return pointwise

        if not self.cat_max:
            return aggregated.squeeze(1)
        else:
            # [K, 1, units]
            repeated = aggregated.repeat(1, voxel_count, 1)
            concatenated = torch.cat([pointwise, repeated], dim=2)
            # [K, T, 2 * units]
            return concatenated 
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
mmdetection3d是一个基于PyTorch的开源目标检测框架,专注于3D场景中的目标检测任务。以下是使用mmdetection3d的一般步骤: 1. 安装依赖:首先,确保你的环境中已经安装好了PyTorch和CUDA。然后,使用pip安装mmdetection3d及其依赖库: ``` pip install mmcv-full open3d==0.9.0 git clone https://github.com/open-mmlab/mmdetection3d.git cd mmdetection3d pip install -v -e . ``` 2. 数据准备:准备好自己的训练数据集,并将其转换为mmdetection3d支持的数据格式。你可以参考官方文档了解数据准备的细节:https://mmdetection3d.readthedocs.io/en/latest/tutorials/data_preparation.html 3. 配置模型:根据你的任务和数据集,选择合适的配置文件,并对其进行修改。在mmdetection3d的`configs`目录下可以找到各种预定义配置文件,你可以根据自己的需求进行修改。 4. 训练模型:使用命令行启动训练过程。示例命令如下: ``` ./tools/dist_train.sh configs/your_config_file.py num_gpu ``` 其中,`your_config_file.py`是你修改后的配置文件名,`num_gpu`是你要使用的GPU数量。 5. 测试模型:训练完成后,可以使用命令行进行模型的测试。示例命令如下: ``` ./tools/dist_test.sh configs/your_config_file.py checkpoint_file num_gpu --eval ``` 其中,`checkpoint_file`是模型的路径。 这是一个简单的使用流程,更详细的使用说明和示例可以参考mmdetection3d的官方文档:https://mmdetection3d.readthedocs.io/

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值