PointCNN原理+代码讲解

各位周末好。

PointCNN的动机是这样的:

图中,小写字母f表示该点的特征,脚标相同的f表示对应点的特征也相同,作者告诉我们,图里有两个问题存在:

首先,假设一种情形,图ii和图iii是两种物体,但是,我们按照一定顺序排列,得到特征序列(fa,fb,fc,fd),碰巧这两个物体的特征还都一样,所以,这两种原本不同的物体,就被判定为同一种物体了。其次,另一种情形,图iii和图iv,从形状来看,本来是同一种物体,但是由于顺序选取不恰当,图iii的特征序列是(fa,fb,fc,fd),而图iv是(fc,fa,fb,fd),这就不好意思了,你俩不一样。

作者就觉得,这两种问题不解决,点云识别就做不好。所以,他想了个办法,训练一种变换,经过变换之后,让ii和iii的特征不同,让iii和iv的特征相同。这种变换怎么得到呢?用深度网络训练。作者将其命名为X-transformation。

接着,作者又再想了,图像处理领域的卷积操作,是对领域进行的,那在点云里,也可以使用这种思路啊,只要先找好领域就行了。最后就是这么实现的。

代码解释

下面是代码部分:

P.S.作者的Github维护的很好,经常更新,而且对读者的答疑也很及时,大赞。

看过代码的同学都知道,作者的核心思想X变换在代码pointcnn.py的xconv中,根据算法流程,可以把这部分代码划分成‘特征提取’和‘X矩阵训练’两块。下面分开来说。

用于提取邻域特征的只有两个dense层(也叫fc层/MLP),很简单地将尺度为(P,K,C)的邻域结构升维到了(P,K,C’)。

# Prepare featuresto be transformed

nn_fts_from_pts_0 =pf.dense(nn_pts_local_bn, C_pts_fts, tag +'nn_fts_from_pts_0',is_training)#fc1, (N, P, K, C_pts_fts)nn_fts_from_pts =pf.dense(nn_fts_from_pts_0, C_pts_fts, tag +'nn_fts_from_pt',is_training)#fc2, features, f_deltaiffts isNone: nn_fts_input = nn_fts_from_ptselse: nn_fts_from_prev = tf.gather_nd(fts,indices, name=tag +'nn_fts_from_prev') nn_fts_input = tf.concat([nn_fts_from_pts,nn_fts_from_prev], axis=-1, name=tag +'nn_fts_input')

接下来作者没有使用pointnet中的maxpooling,这一点论文中也提到了,因为作者认为训练出一个X变换来能达到更好的效果。

而关于X变换,居然有三层,第一层是卷积层,让人很吃惊,卷积核是1*k的,也就是在邻域维度上,直接把k个邻域点汇聚到一个点上,且用了K×K个卷积层,把特征维度升高到k*k,维度从(P,K,C)变成了(P,1,K×K);

紧接着,作者用了两个dense层,保持了这个结构

X_1 = pf.dense(X_0,K * K, tag +'X_1', is_training, with_bn=False)#in the center point dimensional,P decrease to 1.

X_2 = pf.dense(X_1,K * K, tag +'X_2', is_training, with_bn=False, activation=None)#(N, P, 1,K*K)

然后reshape成(P,K,K),这就得到了X-transporm矩阵。

X = tf.reshape(X_2,(N, P, K, K), name=tag +'X')

与上文得到的特征图进行卷积相乘。

fts_X =tf.matmul(X, nn_fts_input, name=tag +'fts_X')

最后是卷积操作,输出通道数为C,(1, K)尺度的卷积核用来把K邻域融合,此处有别于Pointnet++中的池化操作。

fts =pf.separable_conv2d(fts_X, C, tag +'fts', is_training, (1, K),depth_multiplier=depth_multiplier)#输出(N, P, 1, C)

returntf.squeeze(fts, axis=2, name=tag +'fts_3d')#输出(N, P, C)

坦白说,看完以后挺吃惊的。因为看上去训练X变换矩阵的复杂性比特征提取部分还要大。(事实上PointCNN中的变换矩阵已经比PointNet中的T-net代码量小多了)。

小结

总的来说,作者的出发点是解决点云排序问题,从代码来看,更确切地说,是K邻域的排序问题。提取特征的时候,小心翼翼,邻域维度上不敢轻举妄动,因为诸如卷积操作等都会因排序的变换而使结果发生变化,因此在这个维度上,pointnet用了maxpooling,本文作者没有进行任何操作,就静静地让邻域的点,各自升高了维度,然后听侯差遣。但是在训练X变换矩阵的时候,就放开手脚、大开大合了,为了得到K*K的矩阵,不管物理意义,进行各种维度变化,可以说无所顾忌了。

又想到另一个问题,因为这里解决的是邻域点的排序问题,那你从一堆点云里选择中心点的时候,中心点的排序就不要紧了吗?或者中心点的选择如果每次都不一样的话,即使能够解决邻域的排序问题,但是这些邻域压根可能就互不相同。因此,今年新出来的SONet解决了这个问题。将点云在空间的分布进行了唯一的提取。这个思路还是挺棒的,实验效果也超越了PointNet++和PointCNN。但是运行代码需要配置的东西太复杂,我一直都没搞好,这也算一个小小的缺陷吧。

代码GitHub地址:https://github.com/yangyanli/PointCNN

论文地址:https://arxiv.org/abs/1801.07791

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180526G0J2AW00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券