结果:
IA-SSD为目前最快的3d点云目标检测网络,在单个RTX 2080Ti上速度高达 85 FPS!
论文地址:https://arxiv.org/abs/2203.11139
代码:https://github.com/yifanzhang713/IA-SSD 演示视频: https://www.youtube.com/watch?v=3jP2o9KXunA
85FPS的速度是由低内存占用量和高度并行性实现的,所以不能够用在实时应用中,论文中也有比较:
“Mem”和“Paral”表示推理期间每帧的GPU内存占用量以及可以在一个RTX2080Ti(11GB)上并行化的最大批量数量。”速度⊥”, ”速度⊤” 是处理一帧或全负载GPU存储器时的推理速度,†表示将场景分成四个平行部分以加速第一采样层。
可以看到,得益于IA-SSD极小的显存占用,在测试时可以并行处理100帧点云图像,从而做到极快的速度,但在实时应用中不会同时得到多帧点云图像,所以这里的高速度具有一定的局限性,不过IA-SSD极小的显存占用仍然是一个优点。
出发点:
通过实验发现,许多重要的前景点在最后的边界框回归步骤之前已经被丢弃了。因此其检测性能,特别是对行人等小物体的检测,受到了根本上的限制:
图示为前景点(即汽车、行人和自行车)的实例召回率
16384个点的输入点云通过4个下采样层逐渐下采样到256个点,可以看到使用D-FPS方法最后256个点的召回率在行人上降至不足70%,而文章中提出的方法仍能保持95%以上。注意,论文所提出的方法,前两层中使用的还是D-FPS,只有最后两层不同。
解决方案:
为了尽可能地保留前景点,转向利用每个点的潜在语义,因为随着分层聚合在每一层进行操作,学习到的点特征可能包含更丰富的语义信息。基于这一思想,提出了以下两种面向任务的采样方法:
1.Class-aware Sampling 类别感知采样
该采样策略旨在学习每个点的语义,以实现选择性下采样。
两个MLP层附加到编码层以进一步估计每个点的语义类别。从原始边界框注释生成的逐点一热语义标签用于监督。
for k in range(len(confidence_mlp)):
shared_mlp.extend([
nn.Conv1d(out_channels,
confidence_mlp[k], kernel_size=1, bias=False),
nn.BatchNorm1d(confidence_mlp[k]),
nn.ReLU()
])
out_channels = confidence_mlp[k]
shared_mlp.append(
nn.Conv1d(out_channels, num_class, kernel_size=1, bias=True),
)
(128,1024)--> (128,1024) --> (3,1024) 1024个点,每个点为3个类别的得分
(256,512)--> (256,512) --> (3,512) 512个点,每个点为3个类别的得分
cls_features_max, class_pred = cls_features_tmp.max(dim=-1)
score_pred = torch.sigmoid(cls_features_max) # B,N
score_picked, sample_idx = torch.topk(score_pred, npoint, dim=-1)
取得分高的前n个点
交叉熵损失计算:
2.Centroid-aware Sampling 质心感知采样
赋予更接近实例质心的点更高的权重
f*,b*,l*,r*,u*,d*分别表示一个点到边界框的6个表面(前,后,左,右,上和下)的距离
在这种情况下,更接近盒子质心的点可能具有更高的掩模得分(最大值为1),而位于表面上的点将具有0的掩模得分。在训练期间,软点掩模将用于基于空间位置为边界框内的点分配不同的权重,因此隐含地将几何先验结合到网络训练中。
加权交叉熵损失计算:
3.Contextual Centroid Prediction 上下文质心预测
仿照VoteNet网络,预测一个与中心的偏移。质心预测损失公式如下:
网络结构:
输入点云首先经过几个SA层,然后是实例感知的下采样,以逐步减少内存和计算成本。保留的代表点进一步输入上下文质心感知模块,例如中心预测和建议生成。最后输出3D边框和相关的类标签。
网络可分为两部分:
- backbone_3d: IASSD_Backbone
- point_head : IASSD_Head
IASSD_Backbone有六层:
层数 | 采样方法 | Grouping半径 | 点数 | 特征 | 注 |
0 | D-FPS | [0.2,0.8] | 4096 | 64 | |
1 | D-FPS | [0.8,1.6] | 1024 | 128 | |
2 | Ctr-aware | [1.6,4.8] | 512 | 256 | |
3 | Ctr-aware | 无 | 256 | 256 | 无Grouping,只从512个点中选256个前景概率高的 |
4 | Vote | 无 | 256 | 非采样,通过256个点的特征投票得到物体中心点,即产生新的256个点 | |
5 | 无 | [4.8, 6.4] | 256 | 512 | 无采样,由vote产生的点grouping得到近邻点聚合特征 |
IASSD_Head:
center_cls_preds = self.cls_center_layers(center_features) # (total_centers, num_class)
center_box_preds = self.box_center_layers(center_features) # (total_centers, box_code_size)
输出: center_cls_preds (256,3)
center_box_preds (256,30)
总结思考:
IA-SSD专注于前景点的采样,设计了一种显存占用很小,效果达到了SOTA的网络,但在每个点的语义预测时方法较简单,可能会受到类别不平衡分布的影响。
另外,在检测中只考虑了前景点,背景点在类别和回归框预测中难道没有影响吗, 能否将部分背景点也考虑在内呢?
本人小白一枚,有不对的地方望大佬指正。
代码解读:
本文代码是基于OpenPCDet实现的,所以如果你了解OpenPCDet,那么看懂IA-SSD的代码并不难。
贴上整体的网络结构方便理解:
IASSD(
(backbone_3d): IASSD_Backbone(
(SA_modules): ModuleList(
(0): PointnetSAModuleMSG_WithSampling( #第一层网络,可对照前面的表格
(groupers): ModuleList(
(0): QueryAndGroup() #最远点采样
(1): QueryAndGroup()
)
(mlps): ModuleList( #grouping后的点送入mlp
(0): Sequential(
(0): Conv2d(4, 16, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(16, 16, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(16, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
(1): Sequential(
(0): Conv2d(4, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(32, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
)
(aggregation_layer): Sequential( #聚合两个半径grouping得到的特征
(0): Conv1d(96, 64, kernel_size=(1,), stride=(1,), bias=False)
(1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
)
(1): PointnetSAModuleMSG_WithSampling(
(groupers): ModuleList(
(0): QueryAndGroup()
(1): QueryAndGroup()
)
(mlps): ModuleList(
(0): Sequential(
(0): Conv2d(67, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
(1): Sequential(
(0): Conv2d(67, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(64, 96, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(96, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
)
(aggregation_layer): Sequential(
(0): Conv1d(256, 128, kernel_size=(1,), stride=(1,), bias=False)
(1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(confidence_layers): Sequential( #预测每个点前景的概率
(0): Conv1d(128, 128, kernel_size=(1,), stride=(1,), bias=False)
(1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv1d(128, 3, kernel_size=(1,), stride=(1,))
)
)
(2): PointnetSAModuleMSG_WithSampling(
(groupers): ModuleList(
(0): QueryAndGroup()
(1): QueryAndGroup()
)
(mlps): ModuleList(
(0): Sequential(
(0): Conv2d(131, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(128, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
(1): Sequential(
(0): Conv2d(131, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(128, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
)
(aggregation_layer): Sequential(
(0): Conv1d(512, 256, kernel_size=(1,), stride=(1,), bias=False)
(1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(confidence_layers): Sequential(
(0): Conv1d(256, 256, kernel_size=(1,), stride=(1,), bias=False)
(1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv1d(256, 3, kernel_size=(1,), stride=(1,))
)
)
(3): PointnetSAModuleMSG_WithSampling( #这层网络只选前景点概率大的256个点,
(groupers): ModuleList() #不grouping
(mlps): ModuleList()
)
(4): Vote_layer( #预测质心
(mlp_modules): Sequential(
(0): Conv1d(256, 128, kernel_size=(1,), stride=(1,), bias=False)
(1): BatchNorm1d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(ctr_reg): Conv1d(128, 3, kernel_size=(1,), stride=(1,))
)
(5): PointnetSAModuleMSG_WithSampling(
(groupers): ModuleList(
(0): QueryAndGroup()
(1): QueryAndGroup()
)
(mlps): ModuleList(
(0): Sequential(
(0): Conv2d(259, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(256, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(256, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
(1): Sequential(
(0): Conv2d(259, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Conv2d(256, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(4): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Conv2d(512, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(8): ReLU()
)
)
(aggregation_layer): Sequential(
(0): Conv1d(1536, 512, kernel_size=(1,), stride=(1,), bias=False)
(1): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
)
)
)
(point_head): IASSD_Head(
(cls_loss_func): WeightedClassificationLoss()
(reg_loss_func): WeightedSmoothL1Loss()
(ins_loss_func): WeightedClassificationLoss()
(cls_center_layers): Sequential(
(0): Linear(in_features=512, out_features=256, bias=False)
(1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Linear(in_features=256, out_features=256, bias=False)
(4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Linear(in_features=256, out_features=3, bias=True)
)
(box_center_layers): Sequential(
(0): Linear(in_features=512, out_features=256, bias=False)
(1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Linear(in_features=256, out_features=256, bias=False)
(4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU()
(6): Linear(in_features=256, out_features=30, bias=True)
)
)
)