OpenPCDet 模块解析(以kitti数据+SECOND为例)-1

在这里插入图片描述

OpenPCDet架构流程如图,接下来我们将以second模型为例,以kitti数据集说明模型总体处理流程。主要包括:

数据增强、数据预处理、模型构建、后处理、训练、测试等。

数据增强DATA_AUGMENTOR

  • 类:DataAugmentor
  • 代码文件:pcdet/datasets/augmentor/data_augmentor.py
  • 配置文件:tools/cfgs/dataset_configs/kitti_dataset.yaml

  • 增强操作:包括gt_sampling、random_world_flip、random_world_rotation、random_world_scaling。
    • gt_sampling:通过采样其他场景的gt增加样本;其中,SAMPLE_GROUPS即SECOND的创新点-数据扩充的目标个数;
    • random_world_flip:沿x轴和y轴随机翻转;
    • random_world_rotation:进行随机旋转;
    • random_world_scaling:执行随机缩放。

数据预处理 DATA_PROCESSOR

  • 类:DataProcessor
  • 代码文件:pcdet/datasets/processor/data_processor.py
  • 配置文件:tools/cfgs/dataset_configs/kitti_dataset.yaml

  • 预处理操作:
    • mask_points_and_boxes_outside_range:用于裁剪超出范围的点和框,可减少不必要的计算和提高模型效率。
      REMOVE_OUTSIDE_BOXES 如果设置为True,则会移除(或掩码)那些超出特定范围(如POINT_CLOUD_RANGE: [0, -40, -3, 70.4, 40, 1])的点云和边界框。

    • shuffle_points:打乱点的顺序,可以帮助模型在训练时更好地泛化,因为每次迭代输入数据的顺序都会不同。
      如上设置,训练集(‘train’)的点会被打乱,而测试集(‘test’)的点则不会被打乱。

    • transform_points_to_voxels:将点云转换为体素形式,送入模型训练;
      VOXEL_SIZE: 定义体素(voxel)的大小。体素是3D空间中的小立方体,用于将点云数据转换为规则的结构化数据。如上设置[0.05, 0.05, 0.1]意味着体素在x和y方向上的大小是0.05米,在z方向上的大小是0.1米。

      MAX_POINTS_PER_VOXEL: 定义每个体素中可以包含的最大点数。如果体素中的点数超过这个值,多余的点可能会被丢弃或进行某种形式的聚合。如上设置,每个体素最多可以有5个点。

      MAX_NUMBER_OF_VOXELS: 这是一个字典,定义了在不同数据集分割中可以使用的最大体素数量。这有助于控制内存使用和计算效率。如上设置,训练集(‘train’)最多可以有16000个体素,而测试集(‘test’)最多可以有40000个体素。

模型构建

模型类:SECONDNet(继承Detector3DTemplate)

代码文件:pcdet/models/detectors/second_net.py

配置文件:tools/cfgs/kitti_models/second.yaml

VFE-MeanVFE

代码文件:pcdet/models/backbones_3d/vfe/mean_vfe.py

在OpenPCDet中,MeanVFE通过对每个体素内的点特征进行求和并求均值来生成体素特征。具体来说,MeanVFE以每个体素为单位,对其内部的点对应的特征进行求和,然后基于体素内部点的数量对特征求均值,从而得到每个体素的特征。这种方法假设每个体素中至少有一个点,如果体素完全为空,通常在预处理时会被过滤或处理掉‌。

BACKBONE_3D-VoxelBackBone8x

代码文件:pcdet/models/backbones_3d/spconv_backbone.py

VoxelBackBone8x接收MeanVFE模块的结果,经过精心设计好的3D稀疏卷积和3D稀疏子流卷积的有效组合,得到输出特征,并送入HeightCompression作深度方向的压缩。这里8x表示8倍下采样

在Second原始论文中给出了BACKBONE_3D部分的示意图,但是这与OpenPCDet中Second具体的网络参数有所差异,注意区分。下图是Second中BACKBONE_3D部分的表示。

OpenPCDet中SECOND的BACKBONE_3D具体实现:

  • 卷积操作:post_act_block
def post_act_block(in_channels, out_channels, kernel_size, indice_key=None, stride=1, padding=0,
                   conv_type='subm', norm_fn=None):

    if conv_type == 'subm':
        conv = spconv.SubMConv3d(in_channels, out_channels, kernel_size, bias=False, indice_key=indice_key)
    elif conv_type == 'spconv':
        conv = spconv.SparseConv3d(in_channels, out_channels, kernel_size, stride=stride, padding=padding,
                                   bias=False, indice_key=indice_key)
    elif conv_type == 'inverseconv':
        conv = spconv.SparseInverseConv3d(in_channels, out_channels, kernel_size, indice_key=indice_key, bias=False)
    else:
        raise NotImplementedError

    m = spconv.SparseSequential(
        conv,
        norm_fn(out_channels),
        nn.ReLU(),
    )

    return m

从post_act_block 中可以看出spconv有3种3D稀疏卷积:SubMConv3d、SparseConv3d和SparseInverseConv3d;

  • spconv.SparseConv3d即标准的稀疏卷积层,在进行卷积操作时,可以改变数据的稀疏性质,即可以在输出中创建新的非零元素;适用于当你需要改变体素的密度或特征表示时,例如在下采样(降低分辨率)或在学习阶段的初期改变特征维度。
  • spconv.SubMConv3d即子流形卷积,是一种更为特化的卷积层,用于保持数据的稀疏结构、以及计算量。在执行子流形卷积时,不会创建新的非零元素,适用于在保持当前稀疏结构不变的情况下增加网络深度,通常用于特征提取和增加网络深度,而不增加额外的稀疏性。
  • spconv的3D卷积构造时多了一个indice_key,这是为了在indice相同的情况下重复利用计算好的’rulebook’和’hash表’,减少计算。因为submconv3d不会改变输入输出位置索引以及输出特征图空间形状,所以如果本层的outids, indice_pairs, indice_pair_num, out_spatial_shape等与之前计算过的层相同,这里直接使用,避免重复计算。如下:在spconv.SparseSequential中3个block堆叠(依次为稀疏卷积、子流形卷积、子流形卷积),最后一个submconv3d可以复用第二个submconv3d的indice,'subm2’的indice_key被复用。
        self.conv2 = spconv.SparseSequential(
            # [1600, 1408, 41] <- [800, 704, 21]
            block(16, 32, 3, norm_fn=norm_fn, stride=2, padding=1, indice_key='spconv2', conv_type='spconv'),
            block(32, 32, 3, norm_fn=norm_fn, padding=1, indice_key='subm2'),
            block(32, 32, 3, norm_fn=norm_fn, padding=1, indice_key='subm2'),
        )
  • forward
def forward(self, batch_dict):
    """
        Args:
            batch_dict:
                batch_size: int
                vfe_features: (num_voxels, C)
                voxel_coords: (num_voxels, 4), [batch_idx, z_idx, y_idx, x_idx]
        Returns:
            batch_dict:
                encoded_spconv_tensor: sparse tensor
        """
    voxel_features, voxel_coords = batch_dict['voxel_features'], batch_dict['voxel_coords']
    batch_size = batch_dict['batch_size']
    input_sp_tensor = spconv.SparseConvTensor(
        features=voxel_features,
        indices=voxel_coords.int(),
        spatial_shape=self.sparse_shape,
        batch_size=batch_size
    )

    x = self.conv_input(input_sp_tensor)

    x_conv1 = self.conv1(x) #子流形卷积
    x_conv2 = self.conv2(x_conv1) #稀疏卷积->子流形卷积->子流形卷积
    x_conv3 = self.conv3(x_conv2) #稀疏卷积->子流形卷积->子流形卷积
    x_conv4 = self.conv4(x_conv3) #稀疏卷积->子流形卷积->子流形卷积

    # for detection head
    # [200, 176, 5] -> [200, 176, 2]
    out = self.conv_out(x_conv4)

    batch_dict.update({
        'encoded_spconv_tensor': out,
        'encoded_spconv_tensor_stride': 8
    })
    batch_dict.update({
        'multi_scale_3d_features': {
            'x_conv1': x_conv1,
            'x_conv2': x_conv2,
            'x_conv3': x_conv3,
            'x_conv4': x_conv4,
        }
    })
    batch_dict.update({
        'multi_scale_3d_strides': {
            'x_conv1': 1,
            'x_conv2': 2,
            'x_conv3': 4,
            'x_conv4': 8,
        }
    })

    return batch_dict

对于VoxelBackbone8x模块的前向推理(forward)部分,其输入字典中最重要的内容为vfe_features和voxel_coords。他们分别表示有效的输入特征,以及这些有效特征的空间位置。voxel_features的size为(N,4),通常不同帧点云的有效特征的数量是不同的,N表示当前batch中总的有效输入特征的数量。可见,就VoxelBackBone8x模块来说,输入feature map其实是不固定的。具体到无论是3D标准稀疏卷积还是3D子流形卷积,其输入特征也是可变的。

MAP_TO_BEV-HeightCompression

代码文件:pcdet/models/backbones_2d/map_to_bev/height_compression.py

HeightCompression将3维数据沿着Z轴方向压缩到2维bev map。

进行压缩不必担心产生影响的原因是:在KITTI数据集中没有物体在Z轴重叠的情况;

深度方向压缩包括以下几点好处:

  • 简化了网络检测头的设计难度(将3D检测框降到2D,又回到了我们的2D目标检测老本行);
  • 增加了高度方向上的感受野(多个深度方法进行合并,1x128 + 1x128 = 256 实际上把两层特征图和成了一层,感受野1->2);
  • 加快了网络的训练、推理速度 (3维度点云数据降到了2维图像数据,各个处理都减少了一个维度的计算);

压缩方法:直接将深度方向与特征维度进行堆叠,或者直接将深度方向的信息进行求和。目前代码里用的是堆叠的方法。

(Pdb) spatial_features.shape
torch.Size([3, 128, 2, 200, 176])
(Pdb) spatial_features = spatial_features.view(N, C * D, H, W)
(Pdb) spatial_features.shape
torch.Size([3, 256, 200, 176])

BACKBONE_2D-BaseBEVBackbone

BaseBEVBackbone 是一种基于 BEV(鸟瞰图)的三维物体检测模型的骨干网络(Backbone Network)。其中BEV 是一种将三维点云数据投影到俯视图(bird’s-eye view)平面的方法,以简化物体检测问题,该过程由以上HeightCompression模块实现。

BaseBEVBackbone 主要负责处理点云数据并提取特征,以供后续的目标检测任务使用。这种骨干网络通常由一系列卷积层、池化层和其他特征提取模块组成,以捕获点云数据中的关键特征,从而帮助识别和定位三维空间中的物体。

代码文件:pcdet/models/backbones_2d/base_bev_backbone.py

配置:

    BACKBONE_2D:
        NAME: BaseBEVBackbone

        LAYER_NUMS: [5, 5]
        LAYER_STRIDES: [1, 2]
        NUM_FILTERS: [128, 256]
        UPSAMPLE_STRIDES: [1, 2]
        NUM_UPSAMPLE_FILTERS: [256, 256]

BaseBEVBackbone用于对上一层进行Z轴压缩后的特征进行2d卷积提取特征。这里对特征图分别进行了不同尺度的下采样然后再进行上采样后在通道维度进行拼接。

经过HeightCompression得到的BEV特征图是:

(batch_size, 128*2, 200, 176)

下采样分支一:(batch_size, 128*2, 200, 176) --> (batch,128, 200, 176)

反卷积分支一:(batch, 128, 200, 176) --> (batch, 256, 200, 176)

(Pdb) self.blocks[0]
Sequential(
  (0): ZeroPad2d((1, 1, 1, 1))
  (1): Conv2d(256, 128, kernel_size=(3, 3), stride=(1, 1), bias=False)
  (2): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (3): ReLU()
  (4): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (5): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (6): ReLU()
  (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (8): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (9): ReLU()
  (10): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (11): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (12): ReLU()
  (13): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (14): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (15): ReLU()
  (16): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (17): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (18): ReLU()
)
(Pdb) self.deblocks[0]
Sequential(
  (0): ConvTranspose2d(128, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (1): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (2): ReLU()
)

下采样分支二:(batch_size, 128*2, 200, 176) --> (batch,128, 200, 176)

反卷积分支二:(batch, 256, 100, 88) --> (batch, 256, 200, 176)

(注意stride=(2, 2))

(Pdb) self.blocks[1]
Sequential(
  (0): ZeroPad2d((1, 1, 1, 1))
  (1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), bias=False)
  (2): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (3): ReLU()
  (4): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (5): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (6): ReLU()
  (7): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (8): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (9): ReLU()
  (10): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (11): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (12): ReLU()
  (13): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (14): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (15): ReLU()
  (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (17): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (18): ReLU()
)
(Pdb) self.deblocks[1]
Sequential(
  (0): ConvTranspose2d(256, 256, kernel_size=(2, 2), stride=(2, 2), bias=False)
  (1): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
  (2): ReLU()
)

最终将结构在通道维度上进行拼接的特征图维度:(batch, 256 * 2, 200, 176)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JoannaJuanCV

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值