小言_互联网的博客

【三维目标检测】SSN(二)

446人阅读  评论(0)

        SSN数据和源码配置调试过程请参考上一篇博文:【三维目标检测】SSN(一)_Coding的叶子的博客-CSDN博客。本文主要详细介绍SSN网络结构及其运行中间状态。

1 模型总体过程

        SSN主要结构如下图所示,其核心在于提出了shape-aware heads grouping和shape signature结构,前者针对不同类别目标设置不同Head,并得到不同尺度的特征图。相比于其他网络采用单一尺度的特征图,这种方法的可以有效提升精度,但是参数量也大大增加。从实现过程来看,这种结构实际上与增加anchor和FPN的作用相近似。另一方面,读者也可以类比以下yolov5的Head结构。shape signature结构主要是对目标轮廓进行编码,强调了目标轮廓形状的特点,从而最终进一步提升目标检测精度。

        需要注意的是,在接下来即将详细介绍的mmdetection3d SSN程序种仅仅使用了shape-aware heads grouping结构,并没有采用shape signature结构。

2 主要模块解析

2.1 体素化

        源码中用于实现体素化的入口函数为self.voxelize(points),具体实现函数为Voxelization(voxel_size=[0.25, 0.25, 8], point_cloud_range=[-50, -50, -5, 50, 50, 3], max_num_points=20, max_voxels=(30000, 40000), deterministic=True)。函数输入分别为:

        (1)points,Nx4,原始点云,N表示点云数量,4表示特征维度,特征为坐标x、y、z与反射强度r。

        (2)voxel_size:单位体素的尺寸,x、y、z方向上的尺度分别为0.25m、0.25m、8m。

        (3)point_cloud_range:x、y、z方向的距离范围,结合(2)中体素尺寸可以得到总的体素数量为400x400x1。

        (4)max_num_points:定义每个体素中取值点的最大数量,默认为20,在voxelnet中T=35。

        (5)max_voxels:表示含有点云的体素最大数量,训练时为30000,推理时为40000。训练时当数量超过30000时,仅保留30000,当数量不足40000时,则保留全部体素。

        (6)deterministic:取值为True时,表示每次体素化的结果是确定的,而不是随机的。

        体素化输出结果如下:

        (1)voxels:Mx20x4,体素中各个点的原始坐标和反射强度,M(M≤30000)个体素,每个体素最多20个点。

        (2)num_points:Mx1,每个体素中点的数量,最小数量为1,最大数量为20。

        (3)coors:体素自身坐标,坐标值为整数,表示体素的按照单位尺度得到的坐标,Mx4,[batch_id, x, y, z]

2.2 体素特征提取VFE(voxel_encoder)

        类似voxelnet和Pointpillars模型,体素特征通过SVFE层提取,即连续两层VFE,其中VFE层提取体素特征用的是PointNet网络。SVFW详细结构和计算过程请参考:【三维目标检测】VoxelNet(三):模型详解_Coding的叶子的博客-CSDN博客_voxelnet【三维目标检测】Pointpillars(二)_Coding的叶子的博客-CSDN博客_voxels 点云。SVFW体素特征提取的入口函数为self.pts_voxel_encoder(voxels, num_points, coors, img_feats, img_metas)。SSN提取的体素特征voxel_features的维度为Mx64。

2.3 中间特征提取 middle_encoder

        SSN采用的中间特征提取网络为PointPillarsScatter。Pointpillars的中间特征提取层将voxel_features(Mx64)每一维特征投影到各个体素当中,类似于二维图像,Mx64->400x400x1,即Mx64->160000x64,没有取值的地方像素值取为0。Canvas,64x400x400。由于体素在Z轴方向上的长度仅仅为1,所以投影后得到的是一个平面信息,这样后续可以直接用二维卷积进行特征提取,而不需要采用三维卷积或三维稀疏卷积操作。这个操作实际上可以直接用三维稀疏卷积进行替换。中间特征提取层的入口函数为self.pts_middle_encoder(voxel_features, coors, batch_size),输出特征维度为64x400x400。

2.4 主干网络特征提取

        SSN主干网络采用了SECOND FPN结构,函数入口为self.pts_backbone(x),输出三种不同尺度的特征64x200x200、128x100x100、256x50x50。

        backbone:SECOND

        1、2.3中out 64x400x400经连续4个3x3卷积(第一个步长为2):64x200x200,out1

        2、out1 64x200x200经连续6个3x3卷积(第一个步长为2):128x100x100,out2

        3、out2 128x100x100经连续6个3x3卷积(第一个步长为2):256x50x50,out3


  
  1. SECOND(
  2.   (blocks): ModuleList(
  3.     ( 0): Sequential(
  4.       ( 0): Conv2d( 64, 64, kernel_size=( 3, 3), stride=( 2, 2), padding=( 1, 1), bias= False)
  5.       ( 1): NaiveSyncBatchNorm2d( 64, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  6.       ( 2): ReLU(inplace= True)
  7.       ( 3): Conv2d( 64, 64, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  8.       ( 4): NaiveSyncBatchNorm2d( 64, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  9.       ( 5): ReLU(inplace= True)
  10.       ( 6): Conv2d( 64, 64, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  11.       ( 7): NaiveSyncBatchNorm2d( 64, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  12.       ( 8): ReLU(inplace= True)
  13.       ( 9): Conv2d( 64, 64, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  14.       ( 10): NaiveSyncBatchNorm2d( 64, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  15.       ( 11): ReLU(inplace= True)
  16.     )
  17.     ( 1): Sequential(
  18.       ( 0): Conv2d( 64, 128, kernel_size=( 3, 3), stride=( 2, 2), padding=( 1, 1), bias= False)
  19.       ( 1): NaiveSyncBatchNorm2d( 128, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  20.       ( 2): ReLU(inplace= True)
  21.       ( 3): Conv2d( 128, 128, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  22.       ( 4): NaiveSyncBatchNorm2d( 128, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  23.       ( 5): ReLU(inplace= True)
  24.       ( 6): Conv2d( 128, 128, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  25.       ( 7): NaiveSyncBatchNorm2d( 128, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  26.       ( 8): ReLU(inplace= True)
  27.       ( 9): Conv2d( 128, 128, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  28.       ( 10): NaiveSyncBatchNorm2d( 128, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  29.       ( 11): ReLU(inplace= True)
  30.       ( 12): Conv2d( 128, 128, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  31.       ( 13): NaiveSyncBatchNorm2d( 128, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  32.       ( 14): ReLU(inplace= True)
  33.       ( 15): Conv2d( 128, 128, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  34.       ( 16): NaiveSyncBatchNorm2d( 128, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  35.       ( 17): ReLU(inplace= True)
  36.     )
  37.     ( 2): Sequential(
  38.       ( 0): Conv2d( 128, 256, kernel_size=( 3, 3), stride=( 2, 2), padding=( 1, 1), bias= False)
  39.       ( 1): NaiveSyncBatchNorm2d( 256, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  40.       ( 2): ReLU(inplace= True)
  41.       ( 3): Conv2d( 256, 256, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  42.       ( 4): NaiveSyncBatchNorm2d( 256, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  43.       ( 5): ReLU(inplace= True)
  44.       ( 6): Conv2d( 256, 256, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  45.       ( 7): NaiveSyncBatchNorm2d( 256, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  46.       ( 8): ReLU(inplace= True)
  47.       ( 9): Conv2d( 256, 256, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  48.       ( 10): NaiveSyncBatchNorm2d( 256, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  49.       ( 11): ReLU(inplace= True)
  50.       ( 12): Conv2d( 256, 256, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  51.       ( 13): NaiveSyncBatchNorm2d( 256, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  52.       ( 14): ReLU(inplace= True)
  53.       ( 15): Conv2d( 256, 256, kernel_size=( 3, 3), stride=( 1, 1), padding=( 1, 1), bias= False)
  54.       ( 16): NaiveSyncBatchNorm2d( 256, eps= 0.001, momentum= 0.01, affine= True, track_running_stats= True)
  55.       ( 17): ReLU(inplace= True)
  56.     )
  57.   )
  58. )

2.5 上采样拼接 self.neck

        Neck层分别对out1、out2、out3上采样后进行拼接,函数入口为self.pts_neck(x),拼接后输出特征pts_feats维度为384x200x200:

        out1:64x200x200 -> 128x200x200

        out2:128x100x100 -> 128x200x200

        out3:256x50x50 -> 128x200x200

       拼接out:128x200x200、128x200x200、128x200x200 ->384x200x200 (self.extract_feat)

2.6 检测头 bbox_head

        SSN的head结构入口函数为self.pts_bbox_head(pts_feats)。由于不同类别目标的点云自身在轮廓结构上有所差异,SSN设计针对不同类别设置了不同的Head。各个单独的Head与常规的RPN Head一致,主要包括分类head、位置head和方向head。另一方面,根据特征图越小视野越大这一卷积操作特征,我们知道小的特征图适合检测大目标。因而,针对车辆和卡车这种大目标,特征图尺度由200x200降为100x100。

        各个Head分支参数分别如下,将所有结果进行拼接可得到分类分数cls_score(500000x10)、位置bbox_pred(500000x9)和方向dir_cls_preds(500000x2),其中500000是anchor总数。位置的9个维度分别为中心偏移(dx、dy、dz)、尺寸对数(log(dw)、log(dl)、log(dz))、角度rz、速度(vx、vy)。


  
  1. 12种anchor、 2个类别(bicycle、motorcycle)、特征图200x200 -> 160000
  2. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 40) -> cls_score 40x200x200 -> 160000x10
  3. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 36) ->bbox_pred 36x200x200 -> 160000x9
  4. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 8) ->bbox_pred 8x200x200 -> 160000x2
  5. 22种anchor、 1个类别(pedestrian)、特征图200x200 -> 80000
  6. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 20) -> cls_score 20x200x200 -> 80000x10
  7. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 18) ->bbox_pred 18x200x200 -> 80000x9
  8. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 4) ->bbox_pred 4x200x200 -> 80000x2
  9. 32种anchor、 2个类别(traffic_cone、barrier)、特征图200x200 -> 160000
  10. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 40) -> cls_score 40x200x200 -> 160000x10
  11. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 36) ->bbox_pred 36x200x200 -> 160000x9
  12. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 8) ->bbox_pred 8x200x200 -> 160000x2
  13. 42种anchor、 1个类别(car)、特征图100x100 -> 20000
  14. pts_feats、Cls Conv2d( 384, 64, 2)、Conv2d( 64, 64)、Conv2d( 64, 20) -> cls_score 20x100x100 -> 20000x10
  15. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 18) ->bbox_pred 18x100x100 -> 20000x9
  16. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 4) ->bbox_pred 4x100x100 -> 20000x2
  17. 52种anchor、 4个类别(truck、trailer、bus、construction_vehicle)、特征图100x100 -> 80000
  18. pts_feats、Cls Conv2d( 384, 64, 2)、Conv2d( 64, 64)、Conv2d( 64, 80) -> cls_score 80x100x100 -> 80000x10
  19. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 72) ->bbox_pred 72x100x100 -> 80000x9
  20. pts_feats、Cls Conv2d( 384, 64)、Conv2d( 64, 64)、Conv2d( 64, 16) ->bbox_pred 16x100x100 -> 80000x2
  21. Outs  outs = self.pts_bbox_head(pts_feats)
  22. cls_score: 500000x10
  23. bbox_pred:500000x9
  24. dir_cls_preds:500000x2

2.7 损失函数

        losses = self.pts_bbox_head.loss(*loss_inputs, gt_bboxes_ignore=gt_bboxes_ignore)

2.7.1 标签计算.

        根据Iou为每个anchor选择匹配的真实目标框

  1. 找到最大Iou的真实目标框和索引
  2. Iou小于指定阈值如0.4的anchor设置为负样本,对应gt_inds序号为0。
  3. Iou大于指定阈值如0.6的anchor设置为正样本,对应gt_inds需要设置为样本标签序号,从1开始。
  4. gt_inds取值为-1的点对应样本介于正负样本之间的情况。

        目标位置采用偏移回归的方式,如下所示。其中,a表示anchor,g表示真实标签,t表示模型预测标签。


  
  1. za = za + ha / 2
  2. zg = zg + hg / 2
  3. diagonal = torch.sqrt(la** 2 + wa** 2)
  4. xt = (xg - xa) / diagonal
  5. yt = (yg - ya) / diagonal
  6. zt = (zg - za) / ha
  7. lt = torch.log(lg / la)
  8. wt = torch.log(wg / wa)
  9. ht = torch.log(hg / ha)
  10. rt = rg - ra
  11. limited_val = val - torch.floor(val / period + offset) * period

2.7.2 损失计算

        SSN总体损失包括目标类别损失、方向损失和位置偏移回归损失。目标类别和方向的损失函数分别为FocalLoss和CrossEntropyLoss。位置偏移回归的9个维度损失函数均为SmoothL1Loss,并且速度(最后两个维度)损失权重为0.2,其它权重均为1.0。在实验过程中,随机选择了两组样本,其负样本的点数量为999823,而正样本数量仅为91。针对这种正负样本不均衡的情况,目标类别损失函数采用了FocalLoss。


  
  1. loss_cls = self.loss_cls(cls_score, labels, label_weights, avg_factor=num_total_samples)
  2. loss_cls FocalLoss
  3. [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.2, 0.2]
  4. loss_bbox = self.loss_bbox(bbox_pred, bbox_targets, bbox_weights, avg_factor=num_total_samples)
  5. SmoothL1Loss
  6. loss_dir = self.loss_dir(dir_cls_preds, dir_targets, dir_weights, avg_factor=num_total_samples)
  7. CrossEntropyLoss

2.8 顶层结构

        顶层结构主要包含以下三部分:

        (1)特征提取:self.extract_feat,通过PointPillars网路机构进行特征提取,输出结果见2.5节。

        (2)bbox Head:结果预测,见2.6节。

        (3)损失函数:见2.7节。


  
  1. 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):
  2. img_feats, pts_feats = self.extract_feat(points, img=img, img_metas=img_metas)
  3. losses_pts = self.forward_pts_train(pts_feats, gt_bboxes_3d, gt_labels_3d, img_metas, gt_bboxes_ignore)
  4. losses.update(losses_pts)
  5.      return losses

3 训练命令

python tools/train.py configs/ssn/hv_ssn_secfpn_sbn-all_2x16_2x_nus-3d.py

4 运行结果

【python三维深度学习】python三维点云从基础到深度学习_Coding的叶子的博客-CSDN博客_python点云目标检测从三维点云基础知识到深度学习,将按照以下目录持续进行更新。更新完成的部分可以在三维点云专栏中查看。含数据与python源码。https://blog.csdn.net/suiyingy/article/details/124017716


转载:https://blog.csdn.net/suiyingy/article/details/125899992
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场