SASSD数据和源码配置调试过程请参考上一篇博文:【目标检测>三维目标检测】SASSD(一)_Coding的叶子的博客-CSDN博客。本文主要详细介绍SASSD网络结构及其运行中间状态。
1 SASSD模型总体过程
SASSD模型的整体结构如下图所示。SASSD与基于Anchor的目标检测模型的结构基本保持一致,其核心特点在于采用了一个类似语义分割的辅助网络(Auxiliary Network)来学习区分前景点和背景点,从而辅助候选框特征提取,使产生的候选框质量更高。辅助网络这一点与PointRCNN和VoteNet的部分思想很接近。不同之处在于,SASSD提取体素中心点特征时融合了不同尺度下近邻体素的特征,从而使得网络可以有效获取到局部结构特征(Structure Aware)。另一方面,这个辅助网络仅在训练时用到,在推理时则完全拆解下来,从而使得模型在实际部署过程种不增加额外开销。因此,SASSD辅助网络的作用在于提高模型训练精度。
2 主要模块解析
2.1 体素化
源码中用于实现体素化的入口函数为self.voxelize(points),具体实现函数为Voxelization(voxel_size=[0.05, 0.05, 0.1], point_cloud_range=[0, -40, -3, 70.4, 40, 1], max_num_points=5, max_voxels=16000, deterministic=True)。函数输入分别为:
(1)points,Nx4,原始点云,N表示点云数量,4表示特征维度,特征为坐标x、y、z与反射强度r。
(2)voxel_size:单位体素的尺寸,x、y、z方向上的尺度分别为0.05m、0.05m、0.1m。
(3)point_cloud_range:x、y、z方向的距离范围,结合(2)中体素尺寸可以得到总的体素数量为1408x1600x41,即92364800(41x1600x1408)。
(4)max_num_points:定义每个体素中取值点的最大数量,默认为5,在voxelnet中T=35。
(5)max_voxels:表示含有点云的体素最大数量,默认为16000。当数量超过16000时,仅保留16000,当数量不足16000时,则保留全部体素。
(6)deterministic:取值为True时,表示每次体素化的结果是确定的,而不是随机的。
体素化输出结果如下:
(1)voxels:Mx5x4,体素中各个点的原始坐标和反射强度,M(M≤16000)个体素,每个体素最多5个点。
(2)num_points:Mx1,每个体素中点的数量,最小数量为1,最大数量为5。
coors:体素自身坐标,坐标值为整数,表示体素的按照单位尺度得到的坐标,Mx4,[batch_id, x, y, z]
2.2 体素特征提取VFE(voxel_encoder)
在voxelnet中,体素特征通过SVFE层提取,即连续两层VFE,其中VFE层提取体素特征用的是PointNet网络。而在该源码中,VFE层被进行了简化HardSimpleVFE(voxel_encoder),即对每个体素中的点求平均值,用平均值作为体素特征,取平均时点的数量由num_points决定。Mx5x4的voxels经过VFE后的维度为Mx4(voxel_features),即在第二个维度点的数量上进行了平均。体素特征提取相当于用新的4个维度特征来表示体素内一组点的共同特征。体素特征提取的入口函数为self.voxel_encoder(voxel_dict['voxels'], voxel_dict['num_points'], voxel_dict['coors'])
2.3 稀疏卷积特征提取 middle_encoder
类比VoxelNet中的CML(Convolutional Middle Layer)层,voxelnet中直接用三维卷积进行特征提取,而CenterPoint则采用了连续三维稀疏卷积进行特征提取,函数入口为self.pts_middle_encoder(voxel_features, coors, batch_size)。三维稀疏卷积只是普通三维卷积的一种快速计算方法。CenterPoint提取的空间特征spatial_features维度为256x180x180,具体稀疏卷积提取过程如下。
SparseEncoderSASSD
voxel_features(30920x4)->稀疏表示,Mx4,41x1600x1408, x
-> 3维稀疏卷积SPConv(4, 16, 1),Mx16,41x1600x1408, x
-> 3维稀疏卷积SPConv(16, 16, 1),Mx16,41x1600x1408, x1
-> 3维稀疏卷积SPConv(16, 32, 2)、SPConv(32, 32, 1)、SPConv(32, 32, 1),M2x32,21x800x704, x2
-> 3维稀疏卷积SPConv(32, 64, 2)、SPConv(64, 64, 1, 1)、SPConv(64, 64, 1),M3x64,11x400x352, x3
-> 3维稀疏卷积SPConv(64, 64, 2)、SPConv(64, 64, 1)、SPConv(64, 64, 1),M4x64,5x200x176, x4
-> 3维稀疏卷积SPConv(64, 128, [2, 1, 1]) M5x128,2x200x176,即128x2x200x176
-> Resshape,256x200x176,spatial_features
2.4 辅助网络 auxiliary network
2.3节中间特征层除提取到空间特征spatial_features之外,还提取到了不同尺度的体素特征x1、x2、x3、x4。体素特征提取时每个体素的特征坐标用内部点的平均坐标来表示,这样可以用一个点来近似表示相应体素。辅助网络将距离平均坐标点最近的K个体素坐标的特征加权求和作为取特征,类似于PointNet++中的上采样操作。这相当于为每个体素平均中心点赋予了新的不同尺度特征。
加权后特征经过全连接分类和回归得到point_cls(Mx1)和point_reg (Mx3)。
主要程序过程如下。
points_mean = torch.zeros_like(voxel_features)
points_mean[:, 0] = coors[:, 0] #batch_id
points_mean[:, 1:] = voxel_features[:, :3]#voxel内点坐标平均值
#x1
M x 16
p0 = self.make_auxiliary_points(encode_features[0], points_mean, offset=(0, -40., -3.), voxel_size=(.1, .1, .2))
Mx32
p1 = self.make_auxiliary_points(encode_features[1], points_mean, offset=(0, -40., -3.), voxel_size=(.2, .2, .4))
Mx64
p2 = self.make_auxiliary_points(encode_features[2],points_mean,offset=(0, -40., -3.),voxel_size=(.4, .4, .8))
pointwise = torch.cat([p0, p1, p2], dim=-1) Mx112
Linear(112, 64) Mx64
pointwise = self.point_fc(pointwise)
Linear(64, 1) Mx1
point_cls = self.point_cls(pointwise)
Linear(64, 3) Mx3
point_reg = self.point_reg(pointwise)
point_misc = (points_mean, point_cls, point_reg)
2.5 辅助损失
SSD的辅助网络损失计算函数入口为self.middle_encoder.aux_loss(*point_misc, gt_bboxes_3d)。输入只有gt_boxes_3d而没有label说明只会做前景和背景的区分。真实标签主要通过根据点是否在目标内进行判别。中心回归偏移center_offset 是体素中心相对于所在目标中心的偏移。辅助损失中类别损失函数为FocalLoss,回归损失函数为SmoothL1Loss。
pts_in_flag, center_offset = self.calculate_pts_offsets(new_xyz, boxes3d)
#点是否在目标内。共K个真实目标,独热码的形式进行表述
KxM Mx3
pts_in_flag = points_in_boxes_all(points[None, ...], boxes[None, ...])
pts_label Mx1标签
FocalLoss
aux_loss_cls = sigmoid_focal_loss(point_cls, rpn_cls_target, weight=cls_weights, avg_factor=pos_normalizer)
前景点和背景点判别
aux_loss_reg = smooth_l1_loss(point_reg, center_targets, beta=1 / 9.)
SmoothL1loss
2.6 主干网络backbone特征提取
SASSD的主干网络采用的是SECOND结构,通过两条同类提取两种不同尺度的特征图。第一条通路是2.3中的空间特征spatial_features 256x200x176经连续6个3x3卷积得到128x200x176维度的特征,记为out1。第二条通路是out1继续经过连续6个3x3卷积(其中第一个步长为2)得到256x100x88维度的特征,记为out2。out1和out2为主干网络输出结果。主干网络关键入口函数为self.backbone(feats_dict['spatial_features']) 。
输入:x = self.backbone(feats_dict['spatial_features'])
out1:256x200x176 -> 128x200x176
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:128x200x176 -> 256x100x88
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] [128x200x176, 256x100x88]
2.7 上采样拼接 self.neck
Neck网络分别对out1、out2进行上采样,out1的维度从128x200x176转换为256x200x176,out2的维度也从256x2100x88转换为256x200x176,两者维度完全相同。out1和out2拼接后得到Neck网络的输出结果,即neck_feats,维度为512x200x176。
分别对out1、out2进行上采样:
out1:128x200x176 -> 256x200x176
out2:256x100x88 -> 256x200x176
拼接out:256x200x176、256x200x176 -> 512x200x176 (neck_feats)
2.8 检测头 self.bbox_head
200x176维度特征图上每个位置对应三种尺寸、两种方向共6种候选框anchor。
分类head:512x200x176特征经过conv_cls(512, 18)得到18x200x176个预测结果,对应6个候选框和3种目标类别。
位置head:512x200x176特征经过conv_reg(512,42)得到42x200x176个预测结果,对应6个候选框和7个位置参数(x, y ,z, l, w, h, θ)。
方向head:512x200x176特征经过conv_reg(512,12)得到12x200x176个预测结果,对应6个候选框和两个方向参数。
Anchor3DHead(
(loss_cls): FocalLoss()
(loss_bbox): SmoothL1Loss()
(loss_dir): CrossEntropyLoss(avg_non_ignore=False)
(conv_cls): Conv2d(512, 18, kernel_size=(1, 1), stride=(1, 1))
(conv_reg): Conv2d(512, 42, kernel_size=(1, 1), stride=(1, 1))
(conv_dir_cls): Conv2d(512, 12, kernel_size=(1, 1), stride=(1, 1))
loss_inputs = outs + (gt_bboxes_3d, gt_labels_3d, img_metas)
losses = self.bbox_head.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)
2.9 RPN损失函数
RPN损失函数详细介绍请参考【目标检测>三维目标检测】VoxelNet(三):模型详解_Coding的叶子的博客-CSDN博客_voxelnet,主要包括分类损失FocalLoss、三维目标框回归损失SmoothL1Loss和方向损失CrossEntropyLoss。
2.10 总体损失函数
总体损失函数包括辅助损失和rpn损失,如下所示。
aux_loss_cls:FocalLoss
aux_loss_reg:SmoothL1Loss
loss_cls:FocalLoss
loss_bbox:SmoothL1Loss
loss_dir: CrossEntropyLoss
2.11 顶层结构
顶层结构主要包含以下三部分:
(1)特征提取:self.extract_feat,得到 512x200x176特征,见2.7节。
(2)检测头:见2.8节。
(3)损失函数:见2.10节。
def forward_train(self, points, img_metas, gt_bboxes_3d, gt_labels_3d, gt_bboxes_ignore=None):
x, point_misc = self.extract_feat(points, img_metas, test_mode=False)
aux_loss = self.middle_encoder.aux_loss(*point_misc, gt_bboxes_3d)
outs = self.bbox_head(x)
loss_inputs = outs + (gt_bboxes_3d, gt_labels_3d, img_metas)
losses = self.bbox_head.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)
losses.update(aux_loss)
return losses
3 训练命令
python tools/train.py configs/sassd/sassd_6x8_80e_kitti-3d-3class.py