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−θ:角度回归;Lreg−other:位置和尺度回归
分类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)!