[CVPR2020] RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds论文浅析

大佬的TensorFlow代码:here
另一个大佬的Pytorch代码:等我看完代码再贴链接,之前那个不太行

keywords


在正式开始讲论文之前,我们先看看效果, 0.04s的inference time
那么咱们正式开始


相关工作

\(_{*篇幅有限,此处不再介绍其他基于投影或基于体素的工作}\)

PointNet++

  • 网络结构

  • 关键组件

    • Samping——FPS(最远点采样)

    顾名思义,每次在点云中采样的点都应该距其他点的距离最远
    举个例子,下图,一个二维欧式空间中,我们需要使用FPS采样4个点;最简单的步骤是:

    1、从七种颜色中随机选择一个 [例,选择二项限的橙猫猫]
    2、寻找距橙猫猫最远的点(欧式距离) [选择一象限的小紫]
    3、寻找距离小紫和橙猫猫都最远的点 [选择三象限的淡淡色]
    4、重复上述步骤,直到采样完成 [最后应该选择四象限的小绿]

    优点:能够尽可能覆盖空间中的所有点
    缺点:计算复杂度高

    下图为一张使用FPS得到的采样点

    • Grouping——ball_query(球查询)

    PointNet++的思路为:在原始点云中采样若干点(后称Centroids),并在Centroids邻域里采样K个点(包括Centroids)构成分组,然后在每个分组内使用PointNet;FPS已经完成了第一步,现在,我们继续使用ball_query找到Centroids周围的K个点
    较于ball_query,大家肯定更熟悉KNN这个算法,所以我们先从KNN入手:

    1、计算周围点和Centroids的距离 [如果使用xyz坐标,就是在欧式空间中的距离;如果使用网络在本层的特征向量,那么就是特征空间中的距离]
    2、每次将距离最近的点与Centroids归为同一组
    3、重复,直到采集到K个点

    ball_query与KNN的区别则在于:ball_query增加了一个参数radius,约束采样点必须在一定半径的球域上,当球域内的点≥待采点K时,同KNN;反之,直接用第一个点的特征补全。

PointCNN

  • 网络结构
  • 说明
    • 由于PointCNN与PointNet++在采样和分组时使用的方法基本相同,即FPS和KNN,此处不再说明,主要就其对于卷积结构的改进进行一些说明。

      在这之前,咱们需要明确一下点云的置换不变性,置换不变性是指点云不管以何种顺序输入网络,都应该得到相同的结果,所以PointNet++使用了PointNet作为每个分组的处理网络,但PointNet这种对每个点单独处理的网络注定不能取得很好地效果;所以,这篇文章提出了一种在点云中的卷积结构,既保证了置换不变性,也能更好地利用邻域内的特征,\(\mathcal{X}conv\)算法如下:


RandLA-Net

对采样策略的分析改进

  • 启发式的采样策略
采样方式Farthest Point Sampling (FPS)Inverse Density Importance Sampling (IDIS)Random Sampling (RS)
复杂度\(\mathcal{O}(N^2)\)\(\mathcal{O}(N)\)\(\mathcal{O}(1)\)
  • 基于学习的采样策略
采样方式Generator-based Sampling (GS)Continuous Relaxation based Sampling (CRS)Policy Gradient based Sampling (PGS)
存在问题效率太低内存占用大在大规模点云上难以收敛

\(_{*部分采样方式我也没用过,各位如果对这部分感兴趣的可以去看看其他教程,我就不在这误人子弟了}\)

可以看出,FPS、IDIS和GS等方法在大规模点云上的效率很低;CRS和PGS等方法也会出现其他问题;所以,作者最后选择了随机的采样策略(即RS),虽然随机采样会带来采样不均匀、丢失关键点等问题,但起码保证了采样过程的效率和稳定性。

网络结构


这部分可以被看作结构中的一个layer,我们按照处理顺序对结构进行分析

  • Local Spatial Encoding
  1. 首先,对于输入,这是我们每次通过随机采样后得到的点Input.shape=(N, 3+d),每个点特征维中的3代表其xyz坐标,后面的d代表特征维度。
  1. 接下来,使用KNN采集每个采样点周围的近邻点(欧氏距离),并将其分组,Grouping.shape=(N, K, 3+d)。
  1. 现在,我们已经完成了采样和分组这两步,得到了N个局部点集,接下来,我们需要对每个局部做处理。
  1. 对每个组内的特征,LocalFeature.shape=(K, 3+d),我们将坐标和特征分开处理(实际上,在代码中这部分是分别输入的),对坐标,我们计算每个点和Centroids之间的各种距离,并将这些距离通过concat连接起来,实验证明,这种冗余特征是有益于模型学习的;将这些通过一个MLP后,我们最后得到\(r_{i}^{k}\).shape=(K, d)的特征,公式如下: \(r_{i}^{k}=MLP(p_{i}\oplus p_{i}^{k}\oplus (p_{i}-p_{i}^{k})\oplus ||p_{i}-p_{i}^{k}||)\),将\(r_{i}^{k}\)\(f_{i}^{k}\)concat起来,我们就得到了组内各点的局部编码。

这一模块的功能是:将组内各点的位置特征进行一定处理之后,与各点原本特征concat,丰富了模型可学习的特征,代码实现如下:

class LocalSpatialEncoding(nn.Module):
 def __init__(self, d, num_neighbors, device):
 super(LocalSpatialEncoding, self).__init__()
 self.num_neighbors = num_neighbors
 self.mlp = SharedMLP(10, d, bn=True, activation_fn=nn.ReLU())
 self.device = device
 def forward(self, coords, features, knn_output):
	# KNN
 idx, dist = knn_output
 B, N, K = idx.size()
	# concat position feature
 extended_idx = idx.unsqueeze(1).expand(B, 3, N, K)
 extended_coords = coords.transpose(-2,-1).unsqueeze(-1).expand(B, 3, N, K)
 neighbors = torch.gather(extended_coords, 2, extended_idx)
 concat = torch.cat((
 extended_coords,
 neighbors,
 extended_coords - neighbors,
 dist.unsqueeze(-3)
 ), dim=-3).to(self.device)
	# concat r and f
 return torch.cat((
 self.mlp(concat),
 features.expand(B, -1, N, K)
 ), dim=-3)
  • Attentive Pooling
    实际上,这个部分就相当于PointNet中的MaxPooling结构,如果我们不讨论内部结构,那么这个模块完成的任务就是将\(\hat{f_{i}^{k}}\).shape=(K, 2d)通过pooling变为output.shape=(1, d'),后面作者也做了实验证明了他提出的Attnpool确实更有效。

formula of maxpool and attnpool

\(maxpool = \max(Linear(feature))\)

\(attnpool = Linear\{\sum[feature\odot softmax(Linear(feature))]\}\)

上述公式描述了Attentive pooling的实现,可以发现,作者使用类似注意力机制的方式得到了各特征各分量的注意力得分,使用element-wise multiplication与feature相乘。下面是代码实现,很简单。

 # computing attention scores
 scores = self.score_fn(x.permute(0,2,3,1)).permute(0,3,1,2)
 # sum over the neighbors
 features = torch.sum(scores * x, dim=-1, keepdim=True) # shape (B, d_in, N, 1)
 return self.mlp(features)

至此,我们完成了RandLA-Net中基本单元的构建,我们姑且把它称为RandLA-Net Vanilla(笑),接下来就像ResNet一样,往上堆layer就行了(暴言)

  • Dilated Residual Block
    这部分在结构上没啥内容可讲,实际上就是两层RandLA-Net Vanilla加上一个残差连接而已;剩下的内容留到实验部分一起说。

在讲实验之前,先来看看整体的模型结构先,确实是堆layer(笑)

实验

\(_{*作者的训练设备AMD 3700X @3.6GHz CPU + NVIDIA RTX2080Ti GPU.}\)

  • 关于各种采样方式的效率

    很直观,可以看到随机采样在效率和内存占用上都有非常优秀的表现

  • 消融实验

    对这个表做一些解释:

(1)去掉LocalSE模块

可以看出,去掉LocalSE模块后,对mIoU的影响较大,因为这种组内的局部几何关系在点云中是很有必要的;之前也提到,PointNet就缺少这种点与点之间的几何关系特征。

(2~4)将AttnPool换为Max、Mean、SumPool

证明了作者的AttnPool要优于其它池化方式

(5)简化残差模块

不知道各位是否还记得之前我们埋的坑?随机采样的策略可能会导致关键点的丢失,从而导致模型效果不好这里作者分析,如果使用两层RandLA-Net Vanilla之后再进行残差连接,那么每个采样点都会学到周围\(K^2\)范围内的点特征[可以理解为感受野],感受野大了,采样点能够学到关键点的概率也就增大了;所以,效果也就更好。

  • 对比实验
    无非是突出了自己的方法使用的点云规模更大,效果更好了。毕竟是顶会,没点SOTA怎么行。

写在最后

姑且算是讲完了,我也要去跑复现了,到时候有心得就再开一坑。PS:没想到搞复现最难的不是看代码,是下数据集,semanticKITTI...😿校园网流量不够了。

作者:给阿姨倒NaN杯Null原文地址:https://www.cnblogs.com/myblog-paper/p/17220432.html

%s 个评论

要回复文章请先登录注册