文章目录
- BaseBEVBackbone模块
- 1. BaseBEVBackbone初始化
- 2. BaseBEVBackbone前向传播
OpenPCDet的整个结构图:
BaseBEVBackbone模块
在进行了bev视图的特征转换后,随后进行backbone2d模块进行进一步的特征处理,在PointPillars中选择的是BaseBEVBackbone模块,在这一部分的模块选择上只有两个,如下所示:
# 根据MODEL中的BACKBONE_2D确定选择的模块
__all__ = {
'BaseBEVBackbone': BaseBEVBackbone,
'BaseBEVBackboneV1': BaseBEVBackboneV1
}
这部分具体的作用是对提取出来的bev特征矩阵进行进一步的提取,所以主要是模型上的处理,比较少特征上的处理。参考的结构可以看PointPillars论文上的图,从图上面来查看结构还是比较简单的:
此时的batch_dict如下所示,已经构建出来了spatial_feature,也就是上图所示的伪图像特征:
1. BaseBEVBackbone初始化
这里更加yaml文件来进行模型的搭建,包含了上采样block的重复次数,输出维度;以及下采样的输出维度和采样步长等设置。
BACKBONE_2D:
NAME: BaseBEVBackbone
LAYER_NUMS: [3, 5, 5] # 每层下采样模块block的重复次数
LAYER_STRIDES: [2, 2, 2]
NUM_FILTERS: [64, 128, 256] # 每层下采样channel维度
UPSAMPLE_STRIDES: [1, 2, 4] # 上采样步长,用于扩大特征尺寸
NUM_UPSAMPLE_FILTERS: [128, 128, 128] # 每个尺度的输出channel
更具配置文件设置的模型结构如下所示,需要注意的是下采样过程中存在一个ZeroPad2d的操作:
BaseBEVBackbone(
# 下采样部分
(blocks): ModuleList(
(0): Sequential(
(0): ZeroPad2d((1, 1, 1, 1))
(1): Conv2d(64, 64, kernel_size=(3, 3), stride=(2, 2), bias=False)
(2): BatchNorm2d(64, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(3): ReLU()
(4): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(5): BatchNorm2d(64, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(6): ReLU()
(7): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(8): BatchNorm2d(64, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(9): ReLU()
(10): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(11): BatchNorm2d(64, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(12): ReLU()
)
(1): Sequential(
(0): ZeroPad2d((1, 1, 1, 1))
(1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), 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()
)
(2): 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()
)
)
# 上采样部分
(deblocks): ModuleList(
(0): Sequential(
(0): ConvTranspose2d(64, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): ReLU()
)
(1): Sequential(
(0): ConvTranspose2d(128, 128, kernel_size=(2, 2), stride=(2, 2), bias=False)
(1): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): ReLU()
)
(2): Sequential(
(0): ConvTranspose2d(256, 128, kernel_size=(4, 4), stride=(4, 4), bias=False)
(1): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): ReLU()
)
)
)
这里的ZeroPad2d操作其实就是在特征矩阵的上下左右各打了一层0的补丁来填充,这样进行3x3步长为2的卷积时特征图的尺寸就是直接的减半操作。而且,在nn.Conv中也是有padding这个参数的,所以以下两种代码是等价的:
# 第一种
(0): ZeroPad2d((1, 1, 1, 1))
(1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), bias=False)
# 第二种
(1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=1, bias=False)
2. BaseBEVBackbone前向传播
在传入BaseBEVBackbone模块是的batch_dict数据格式如下所示,后续的操作就是对spatial_features进行特征处理。
在对原始的spatial_features特征进行3层block的分别得到的特征结果以及对进行上采样的结果分别如下所示:
随后,将上采样的ups列表中的3个不同尺度处理的特征图进行concat拼接在一起,得到了(16, 384, 246, 216)的特征矩阵,这个特征矩阵就是backbone_2d提取到的特征矩阵,这与论文中的backbone_2d结构图是完全一样的,然后不同的是第一个block重复次数是3,第二和第三个block的重复次数都是5,在yaml文件中可以自行配置。
将此拼接后的特征同样保存在batch_dict中,传入到pointpillars算法中的最后一个模块,就是dense_head部分,此时的data_dict更新情况如下所示:
这部分的模型处理代码比较简单,就不贴上来了。对着结构图就可以很容易的实现模型的搭建以及前向传播过程。