自学内容网 自学内容网

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

part1(数据增强、预处理、VFE、backbone3d、backbone2d)请看:OpenPCDet 模块解析(以kitti数据+SECOND为例)-1-CSDN博客

part2 主要介绍dense head,参考:https://zhuanlan.zhihu.com/p/553467504

3.5 DENSE_HEAD-AnchorHeadSingle

代码文件:pcdet/models/dense_heads/anchor_head_single.py

配置:

    DENSE_HEAD:
        NAME: AnchorHeadSingle
        CLASS_AGNOSTIC: False

        USE_DIRECTION_CLASSIFIER: True
        DIR_OFFSET: 0.78539
        DIR_LIMIT_OFFSET: 0.0
        NUM_DIR_BINS: 2

        ANCHOR_GENERATOR_CONFIG: [
            {
                'class_name': 'Car',
                'anchor_sizes': [[3.9, 1.6, 1.56]],
                'anchor_rotations': [0, 1.57],
                'anchor_bottom_heights': [-1.78],
                'align_center': False,
                'feature_map_stride': 8,
                'matched_threshold': 0.6,
                'unmatched_threshold': 0.45
            },
            {
                'class_name': 'Pedestrian',
                'anchor_sizes': [[0.8, 0.6, 1.73]],
                'anchor_rotations': [0, 1.57],
                'anchor_bottom_heights': [-0.6],
                'align_center': False,
                'feature_map_stride': 8,
                'matched_threshold': 0.5,
                'unmatched_threshold': 0.35
            },
            {
                'class_name': 'Cyclist',
                'anchor_sizes': [[1.76, 0.6, 1.73]],
                'anchor_rotations': [0, 1.57],
                'anchor_bottom_heights': [-0.6],
                'align_center': False,
                'feature_map_stride': 8,
                'matched_threshold': 0.5,
                'unmatched_threshold': 0.35
            }
        ]

anchor分类和box预测处理流程

  • AnchorHeadSingle类(继承AnchorHeadTemplate):用于anchor分类和box预测;大致流程见如下注释:
def forward(self, data_dict):
        # 读取上一步backbone处理过的特征信息
        spatial_features_2d = data_dict['spatial_features_2d']

        # 对每个位置的特征信息进行6个先验框的类别(3个类别)预测 --> (batch_size, 18, 200, 176)
        cls_preds = self.conv_cls(spatial_features_2d)
        # 每个坐标点上面6个先验框的参数预测,其中每个先验框需要预测7个参数,分别是(x, y, z, w, l, h, θ) --> (batch_size, 42, 200, 176)
        box_preds = self.conv_box(spatial_features_2d)

        # 维度调整,将类别放置在最后一维度,[N, H, W, C] --> (batch_size, 200, 176, 18)
        cls_preds = cls_preds.permute(0, 2, 3, 1).contiguous()  # [N, H, W, C]
        # 维度调整,将先验框调整参数放置在最后一维度   [N, H, W, C] --> (batch_size ,200, 176, 42)
        box_preds = box_preds.permute(0, 2, 3, 1).contiguous()  # [N, H, W, C]
        
        # 将类别和先验框调整预测结果放入前向传播字典中
        self.forward_ret_dict['cls_preds'] = cls_preds
        self.forward_ret_dict['box_preds'] = box_preds

        # 进行方向分类预测
        if self.conv_dir_cls is not None:
            # 1个位置有6个先验框,每个先验框都要预测为两个方向中的其中一个方向 --> (batch_size, 12, 200, 176)
            dir_cls_preds = self.conv_dir_cls(spatial_features_2d)
            # 维度调整, 将先验框方向预测结果放到最后一个维度中,[N, H, W, C] --> (batch_size, 200, 176, 12)
            dir_cls_preds = dir_cls_preds.permute(0, 2, 3, 1).contiguous()
            # 将方向预测结果放入前向传播字典中
            self.forward_ret_dict['dir_cls_preds'] = dir_cls_preds
        else:
            dir_cls_preds = None

        # 如果是在训练模式的时候,需要对每个先验框分配GT来计算loss
        if self.training:
            targets_dict = self.assign_targets(
                gt_boxes=data_dict['gt_boxes']
            )
            # 将GT分配结果放入前向传播字典中
            self.forward_ret_dict.update(targets_dict)

        # 如果不是训练模式,则直接生成进行box的预测
        if not self.training or self.predict_boxes_when_training:
            batch_cls_preds, batch_box_preds = self.generate_predicted_boxes(
                batch_size=data_dict['batch_size'],
                cls_preds=cls_preds, box_preds=box_preds, dir_cls_preds=dir_cls_preds
            )
            data_dict['batch_cls_preds'] = batch_cls_preds
            data_dict['batch_box_preds'] = batch_box_preds
            data_dict['cls_preds_normalized'] = False

        return data_dict

数据维度变换:

(Pdb) spatial_features_2d.shape
torch.Size([3, 512, 200, 176])
(Pdb) cls_preds.shape
torch.Size([3, 200, 176, 18])
(Pdb) box_preds.shape
torch.Size([3, 200, 176, 42])
(Pdb) dir_cls_preds.shape
torch.Size([3, 200, 176, 12])

先验anchor生成

从上面流程可以看出对每个anchor进行回归和分类,得到了每个点为中心所产生anchor的类别预测结果,方向分类结果,box回归结果。但每个anchor是如何产生的呢?这就需要我们来看一下第二个类了——AnchorGenerator类:用于生成anchor。

代码文件:pcdet/models/dense_heads/target_assigner/anchor_generator.py

配置:

        ANCHOR_GENERATOR_CONFIG: [
            {
                'class_name': 'Car',
                'anchor_sizes': [[3.9, 1.6, 1.56]],
                'anchor_rotations': [0, 1.57],
                'anchor_bottom_heights': [-1.78],
                'align_center': False,
                'feature_map_stride': 8,
                'matched_threshold': 0.6,
                'unmatched_threshold': 0.45
            },
            {
                'class_name': 'Pedestrian',
                'anchor_sizes': [[0.8, 0.6, 1.73]],
                'anchor_rotations': [0, 1.57],
                'anchor_bottom_heights': [-0.6],
                'align_center': False,
                'feature_map_stride': 8,
                'matched_threshold': 0.5,
                'unmatched_threshold': 0.35
            },
            {
                'class_name': 'Cyclist',
                'anchor_sizes': [[1.76, 0.6, 1.73]],
                'anchor_rotations': [0, 1.57],
                'anchor_bottom_heights': [-0.6],
                'align_center': False,
                'feature_map_stride': 8,
                'matched_threshold': 0.5,
                'unmatched_threshold': 0.35
            }
        ]

anchor的生成基于以下内容:

①在3D世界中,车、人、自行车尺寸大小相对固定,所以我们可以采用KITTI每个类别的平均长宽高来做为anchor的大小。车:[3.9, 1.6, 1.56] 人:[0.8, 0.6, 1.73] 自行车:[1.76, 0.6, 1.73](详见tools/cfgs/kitti_models/second.yaml配置)

②每个anchor被指定九个信息,其中包含两个one-hot向量,一个用于方向分类,一个用于类别分类,还有一个七维的向量(x,y,z,dx,dy,dz,rot)。

经过该类处理后我们最终可以得到三个类别的anchor,维度都是[z, y, x, num_size, num_rot, 7], 其中num_size是每个类别有几个尺度(1个);num_rot为每个anchor有几个方向类别(2个,一个0度一个90度);7维向量表示为 [x, y, z, dx, dy, dz, rot](每个anchor box的信息)。

GT和先验anchor的匹配

在预测过程中,我们将同一点不同类别的anchor堆叠在了一起,所以我们在这里需要按照类别进行Target assignment操作,即逐帧逐类对生成的anchor进行匹配,这一步为我们后面计算loss打下了基础。

代码文件:

pcdet/models/dense_heads/target_assigner/axis_aligned_target_assigner.py

配置:

        TARGET_ASSIGNER_CONFIG:
            NAME: AxisAlignedTargetAssigner
            POS_FRACTION: -1.0
            SAMPLE_SIZE: 512
            NORM_BY_NUM_EXAMPLES: False
            MATCH_HEIGHT: False
            BOX_CODER: ResidualCoder

其中,AxisAlignedTargetAssigner 类中的assign_targets函数完成了对一帧点云数据中所有类别和锚点的正负样本分配;assign_targets_single函数则完成了对一帧中每个类别的真实框和锚点的正负样本分配。因此,一个Batch样本中,对于每个类别和每一帧点云数据,都需要进行一次锚点和真实框的匹配。

box编码方式ResidualCoder,此处根据论文中的公式对正样本的anchor_box和与之对应的GT-box的7个回归参数进行编码。

代码文件:pcdet/utils/box_coder_utils.py

3.6 Loss计算

loss函数构成:

其中:

β 1 = 1.0 用于控制类别损失 ; β 2 = 2.0 用于控制 a n c h o r 回归损失 ; β 3 = 0.2 用于控制方向分类损失 ; L r e g − θ :角度回归; L r e g − o t h e r :位置和尺度回归 \beta_1 = 1.0 用于控制类别损失; \\\beta_2 = 2.0 用于控制anchor回归损失; \\\beta_3 = 0.2 用于控制方向分类损失; \\L_{reg-{\theta}}:角度回归; \\L_{reg-{other}}:位置和尺度回归 β1=1.0用于控制类别损失;β2=2.0用于控制anchor回归损失;β3=0.2用于控制方向分类损失;Lregθ:角度回归;Lregother:位置和尺度回归

分类loss

作者在这里使用了RetinaNet网络中的focal loss来缓解类别不平衡的问题:

代码文件:pcdet/models/dense_heads/anchor_head_template.py

AnchorHeadTemplate类内的get_cls_layer_loss函数,用于计算分类loss

回归损失

代码文件:

pcdet/models/dense_heads/anchor_head_template.py

AnchorHeadTemplate类内的get_box_reg_layer_loss函数,用于计算box回归loss


原文地址:https://blog.csdn.net/zfjBIT/article/details/145259363

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!