CenterPoint数据和源码配置调试过程请参考上一篇博文:https://blog.csdn.net/suiyingy/article/details/128002709。本文主要详细介绍CenterPoint网络结构及其运行中间状态。
1 CenterPoint模型总体过程
CenterPoint模型的整体结构如下图所示,由最初的一阶段模型扩展为了两阶段模型。第二阶段负责对第一阶段的检测结果进行微调修正,与基于候选框的两阶段目标检测思想基本一致。这里重点介绍CenterPoint的第一个阶段,并且单阶段的CenterPoint可直接完成对三维目标的检测。
2 主要模块解析
2.1 体素化
体素化是指对三维空间进行划分成等间隔的立体网格。CenterPoint通过体素化来完成对点云的采样,并且均匀间隔有利于后续进行三维卷积操作。该体素化过程将体素在x、y、z方向上的尺度分别设置为0.075m、0.075m、0.2m,每个体素中最多保留10个点,并且训练阶段体素数量最多为90000个。按照单位体素尺度直接进行计算得到体素总数量为85017600,而实际保留的90000个仅占千分之一左右,因此保留的体素相对整个体素空间来说是稀疏的。
源码中用于实现体素化的入口函数为self.voxelize(points),具体实现函数为Voxelization(voxel_size=[0.075, 0.075, 0.2], point_cloud_range=[-54, -54, -5.0, 54, 54, 3.0], max_num_points=10, max_voxels=(90000, 120000), deterministic=True)。函数输入分别为:
(1)points,Nx5,原始点云,N表示点云数量,5表示特征维度,特征为坐标x、y、z、反射强度和激光雷达线束序号(0~31,32线激光雷达)。
(2)voxel_size:单位体素的尺寸,x、y、z方向上的尺度分别为0.075m、0.075m、0.2m。
(3)point_cloud_range:x、y、z方向的距离范围,结合(2)中体素尺寸可以得到总的体素数量为1440x1440x41,即85017600。
(4)max_num_points:定义每个体素中取值点的最大数量,取值为10。
(5)max_voxels:表示含有点云的体素最大数量,训练时最大值为90000,推理时最大值设置为120000。
(6)deterministic:取值为True时,表示每次体素化的结果是确定的,而不是随机的。
体素化结果输出保存在pts_feats中,主要包含三部分:
(1)voxels:Mx10x5,体素中各个点的原始坐标和反射强度,M(M≤90000)个体素,每个体素最多10个点。
(2)num_points:Mx1,每个体素中点的数量,最小数量为1,最大数量为10。
(3)coors_batch:体素自身坐标,坐标值为整数,表示体素的按照单位尺度得到的坐标,Mx4,[batch_id, x, y, z]
2.2 体素特征提取VFE(voxel_encoder)
在voxelnet中,体素特征通过SVFE层提取,即连续两层VFE,其中VFE层提取体素特征用的是PointNet网络。而在该源码中,VFE层被进行了简化HardSimpleVFE(voxel_encoder),即对每个体素中的点求平均值,用平均值作为体素特征,取平均时点的数量由num_points决定。Mx10x5的voxels经过VFE后的维度为Mx5(voxel_features),即在第二个维度点的数量上进行了平均。体素特征提取相当于用新的5个维度特征来表示体素内一组点的共同特征。体素特征提取的入口函数为self.pts_voxel_encoder(voxels, num_points, coors)。
2.3 中间特征提取 middle_encoder
类比VoxelNet中的CML(Convolutional Middle Layer)层,voxelnet中直接用三维卷积进行特征提取,而CenterPoint则采用了连续三维稀疏卷积进行特征提取,函数入口为self.pts_middle_encoder(voxel_features, coors, batch_size)。三维稀疏卷积只是普通三维卷积的一种快速计算方法。因而,其卷积过程可完全当作普通卷积操作去理解。根据卷积移动计算过程,卷积结果通常是使特征图尺寸按照步长设置保持不变或逐渐减小,另一方面通道数也会随着网络深度逐步增加。整体效果可看作用更小的特征图、更多的维度(通道数量)来表征原来的目标属性。
以下三维稀疏卷积用SPConv(C1,C2,K)表示,C1表示输入通道,C2表示输出通道,K表示卷积步长。为了进行稀疏卷积操作,原始特征需要进行稀疏表征。稀疏表征包含两个关键部分,即稀疏点特征和特征网格。稀疏点特征维度为MxC,M表示点的数量,C表示特征为维度,一般由输出通道数决定。特征网格维度为DxHxW,稀疏点最终需要对应到稀疏网格的具体位置,即网格特征不为零的点。稀疏网格也就是体素经过三维卷积后产生的特征图,可类比二维图像像素进行理解。因而,输出特征维度为(CxD)xHxW,在DxHxW大小的特征图之中只有M个点是非零特征,其它地方的特征均为零。CenterPoint提取的空间特征spatial_features维度为256x180x180,具体稀疏卷积提取过程如下。
voxel_features,Mx5, 41x1440x1440 -> SPConv(5, 16, 1) Mx16,41x1440x1440,x
-> SPConv(16, 16, 1)、SPConv(16, 16, 1)、SPConv(16, 32, 2) M1x32,21x720x720,x1
-> SPConv(32, 32, 1)、SPConv(32, 32, 1)、SPConv(32, 64, 2) M2x64,11x360x360,x2
-> SPConv(64, 64, 1)、SPConv(64, 64, 1)、SPConv(64, 128, 2) M3x128,5x180x180,x3
-> SPConv(128, 128, 1)、SPConv(128, 128, 1) M4x128,5x180x180,x4
-> SPConv(128, 128, [2, 1, 1]) M5x128,2x180x180,即128x2x180x180
-> Resshape,256x180x180,spatial_features
2.4 主干网络特征提取backbone
CenterPoint的主干网络采用的是SECOND结构,通过两条通道提取两种不同尺度的特征图。第一条通路是空间特征spatial_features 256x180x180经连续6个3x3二维卷积得到128x180x180维度的特征,记为out1。第二条通路是out1继续经过连续6个3x3二维卷积(其中第一个步长为2)得到256x90x90维度的特征,记为out2。out1和out2为主干网络输出结果。主干网络关键入口函数为self.pts_backbone(x)。
out1:256x180x180 -> 128x180x180
Sequential(
(0): Conv2d(256, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(1): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(4): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
(6): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(7): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(8): ReLU(inplace=True)
(9): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(10): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(11): ReLU(inplace=True)
(12): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(13): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(14): ReLU(inplace=True)
(15): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(16): BatchNorm2d(128, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(17): ReLU(inplace=True)
)
Out2:128x180x180 -> 256x90x90
Sequential(
(0): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(1): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(4): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
(6): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(7): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(8): ReLU(inplace=True)
(9): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(10): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(11): ReLU(inplace=True)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(13): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(14): ReLU(inplace=True)
(15): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(16): BatchNorm2d(256, eps=0.001, momentum=0.01, affine=True, track_running_stats=True)
(17): ReLU(inplace=True)
)
Out = [out1, out2] [128x180x180, 256x90x90]
2.5 上采样拼接 self.neck
Neck网络分别对out1、out2通过二位逆卷积操作进行上采样,out1的维度从128x180x180转换为256x180x180,out2的维度也从256x90x90转换为256x180x180,两者维度完全相同。out1和out2拼接后得到Neck网络的输出结果,即pts_feats,维度为512x180x180。拼接的主要目的是进行特征融合,实现深层特征和浅层特征融合。深层特征通常特征图尺度较小,总体卷积视野更广。浅层特征通常尺度较大,更能反映局部特征。这种特征融合将局部特征与其周围的语义特征进行了融合。函数入口为 self.pts_neck(x),返回的特征为extract_pts_feat函数最终结果,即pts_feats。
分别对out1、out2进行上采样:
out1:128x180x180 -> 256x180x180
out2:256x90x90 -> 256x180x180
拼接out:256x180x180、256x180x180 -> 512x180x180 (pts_feats)
2.6 CenterHead
程序中选择了10个类别的数据,并将他们重新归为如下6个大类。算法预测对每个大类建立一个预测分支,即task。每个task负责预测一个大类的目标,包括目标的xy位置偏移、高度、尺寸、角度、速度和热力图等6种属性结果。CenterPoint为每一个大目标类别设置了一个Head结果,主要原因在于同一个热力图特征只能预测一个类别,多个类别需要不同的热力图特征。由于输入数据被分为了6个大类,且每个热力图仅预测一类目标,因而模型预测Head也被分为6个子任务。每个子任务的Head分别预测相应类别目标的位置偏移(reg,2x180x180)、高度(height,1x180x180)、尺寸(dim,3x180x180)、偏航角(rot,2x180x180)、速度(vel,2x180x180)、热力图(heatmap,Kx180x180)。其中,热力图维度中的K表示每个大类下的子类别个数,进步确定一个热力图只能预测一个类别。CenterPoint Head的函数入口为self.pts_bbox_head(pts_feats)。
Neck结构提取后的特征pts_neck(512x180x180)会再次通过一次卷积Conv2d(512, 64)操作,特征维度降为64x180x180。新的特征则分别经过卷积操作得到各个Head的预测结果,分别如下所示。
- 位置偏移reg:Conv2d(64,64)、Conv2d(64,2) -> 2x180x180
- 高度height:Conv2d(64,64)、Conv2d(64,1) -> 1x180x180
- 尺寸dim:Conv2d(64,64)、Conv2d(64,3) -> 3x180x180
- 偏航角正余弦rot:Conv2d(64,64)、Conv2d(64,2) -> 2x180x180
- 速度vel:Conv2d(64,64)、Conv2d(64,2) -> 2x180x180
- 热力图heatmap:Conv2d(64,64)、Conv2d(64,K) -> Kx180x180
6个任务的Head中除热力图的第二个卷积通道维度会随着对应大类中小类数目的变化有所差异,其它卷积维度和运算过程完全一致。
2.7 损失函数
CenterPoint损失主要包括热力图损失和回归损失两大部分,并且分别针对6个task进行计算与合并。热力图用于预测目标中心,而自身与类别相关。因而,热力图决定了目标的有无和分类。热力图的损失函数为GaussianFocalLoss。位置偏移、高度、尺寸、角度和速度等其它预测值的损失函数为L1Loss,并且速度损失权重为0.2,其它权重均为1.0。损失计算的函数入口为self.pts_bbox_head.loss(*loss_inputs)。
在计算回归损失时,CenterPoint仅计算正样本对应的特征图位置的预测特征,并且将正样本的最大数量限制为500,即每个点云中最多包含500个目标。
2.8 顶层结构
顶层结构主要包含以下三部分:
(1)特征提取:self.extract_feat,包括体素化、体素特征提取、中间特征提取、主干特征提取以及Neck上采样特征拼接,得到512x180x180特征pts_feats。
(2)损失计算:包括Head和损失计算,得到6个任务分支的全部损失。
#img_feats为图像特征,仅使用点云时则不作考虑
def forward_train(self, points=None, img_metas=None, gt_bboxes_3d=None, gt_labels_3d=None, gt_labels=None, gt_bboxes=None, img=None, proposals=None, gt_bboxes_ignore=None):
img_feats, pts_feats = self.extract_feat(points, img=img, img_metas=img_metas)
losses = dict()
if pts_feats:
losses_pts = self.forward_pts_train(pts_feats, gt_bboxes_3d, gt_labels_3d, img_metas, gt_bboxes_ignore)
losses.update(losses_pts)
return losses
3 训练命令
python tools/train.py configs/centerpoint/centerpoint_0075voxel_second_secfpn_4x8_cyclic_20e_nus.py