PointNet论文原文:
PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation
PointNet官方代码是使用tensorflow实现的:
PointNet官方实现-TensorFlow
因为我写的代码是Pytorch的,我想用Pytorch来实现点的特征提取,我使用了下面的库:
1. 为什么要提出PointNet这种技术
在2D图像和3D体素中都可以运用卷积来进行特征的提取,但是这种卷积的方法并不适用于点云,因为点云是高自由度的,用论文里的话说,
point clouds or meshes are not in a regular format
,所以没有办法做一些指定锚点、并在局部共享权重等等在regular format下可以做的操作,所以作者提出了PointNet来对点云中点的特征进行处理。如果是mesh的话,就在mesh表面进行点采样以后再使用PointNet进行处理。
2. 之前运用的一些技术(Related Work)
点云特征:
大部分点云的特征都是针对于特定的任务进行手工设计的,且这些特征对于transformation而言是不变的。但对于特定任务而言,找到一组最优的特征组合是一件非常不容易的事情。
运用在3D数据上的深度学习:
-
体素CNN(卷积神经网络):在体素shape上使用神经网络进行3D卷积,但由于体素稀疏的问题,这种表达限制了分辨率,且会产生大量的计算消耗。FPNN和Vote3D提出了特定的方法来解决稀疏性的问题,但实际上操作仍然是在稀疏的体素上进行的,处理较大的点云对这种设计而言仍然是一种不小的挑战。
-
多视角CNN:这种方法将3D点云或形状通过渲染的方式形成2D图像,因为图像CNN领域发展得已经很好了,所以这一类方法在形状分类和检索任务上取得了主导性能。但是这种方法很难被拓展到逐点分类和形状补全的3D任务上。
-
Spectral CNN(不太会翻译,我翻译为频谱CNN,我也不是很了解这种做法):只适用于流形网格,很难拓展到非流形网格。举一个非流形网格的例子:如果一个网格有一些尖点,或者两个面共用一条边但边的法线不同,这种网格也不是流形
-
基于特征的DNN(残差神经网络):将3D数据转换成一个特征向量,并将其通过一个全连接网络进行分类,作者认为这种方法受限于特征提取的表达能力
无序集上的深度学习:
从数据结构的视角来看点云,其实是一组无序的向量集合。大部分深度学习的工作都聚焦在规则输入的表达上,比如序列、图像和体素。Oriol Vinyals等人注意到了这个问题,使用了一种带有注意力机制的
read-process-write
网络来处理无序输入,从结果看来他们的网络有能力去排序这些无序的输入。但是他们聚焦泛型集的应用和NLP的应用,缺乏了几何上的应用。
3. Pipeline理解
我在这里mask了原本pipeline的一些内容,mask掉的地方就是在PointNet核心架构上的一些运用(比如运用到分类和分割任务中)
这个网络架构的输入是
n*3
的点云,然后在开始的时候运用了一个
T-Net
进行配准得到对齐后的
n*3
点云,无论你输入点云经过怎样的刚性变换,只要是相同的点云,最终在canonical space中都会被align到相同的位置。
接着就是通过一个MLP来提取逐点特征得到每个点64维的特征
n*64
,将点云的特征在高维空间中再通过
T-Net
再进行一次配准,就是做了一个类似于特征对齐的操作。
再往后就是进一步提取点的信息,通过MLP什么的,得到每个点1024维的特征,输出是
n*1024
。
再通过这
n*1024
的信息获得一个
1024
维的全局特征,表示输入点云的整体全局特征。
我认为这个网络的好处就是加上了一个align的操作,无论相同的点云发生了怎样的刚性变换,都保证其特征本身不会发生改变。
基于以上架构,实际上:
-
分类(Classification)任务就是将核心pipeline得到的global feature输入到一个MLP里面,让MLP做分类
-
分割(Segmentation)任务就是结合global feature和点的feature,进一步地提取点特征,实现分割(当然,这里只使用了global的特征和点的特征,没有包含局部点的信息,这也是PointNet的弊端,也引出了后来作者进一步提出PointNet++这个架构,在PointNet++架构里就进一步运用了局部的信息,以产生更好的结果)
4. 代码的相关运用
其实我所需要用到的就是PointNet中点云的特征和提取,直接指向Pytorch代码的这个部分:
pointnet_utils.py
这里面就是pointnet的实现核心:
-
STN3d
的作用就是学习一个transformation matrix,来保持在输入点云或提取特征在刚性变换后,仍然可以被align到一个canonical space中(也就是将有刚性变换的输入进行了对齐)
-
STNkd
就是学习k维的刚性变换矩阵,实现对齐
-
PointNetEncoder
相当于原文中得到了global feature的那部分,从红框处可以很明显地看出来:
既然我想要的是特征提取的这部分,实际上就是在最大池化层之前的结果,也就是在上图红框上的
x=self.bn3(self.conv3(x))
就可以得到我想要的
N×features_channel
特征值,所以如果想要特征实现,只需要使用这一部分的代码即可:
def forward(self, x):
B, D, N = x.size()
trans = self.stn(x)
x = x.transpose(2, 1)
if D > 3:
feature = x[:, :, 3:]
x = x[:, :, :3]
x = torch.bmm(x, trans)
if D > 3:
x = torch.cat([x, feature], dim=2)
x = x.transpose(2, 1)
x = F.relu(self.bn1(self.conv1(x)))
if self.feature_transform:
trans_feat = self.fstn(x)
x = x.transpose(2, 1)
x = torch.bmm(x, trans_feat)
x = x.transpose(2, 1)
else:
trans_feat = None
pointfeat = x
x = F.relu(self.bn2(self.conv2(x)))
x = self.bn3(self.conv3(x))
return x