3.3 PointNet layer( 三 )

< distancedistance[mask] = dist[mask]# 更新distances,记录样本中每个点距离所有已出现的采样点的最小距离farthest = torch.max(distance, -1)[1] # 返回最远点索引return centroids
3.
这一层使用Ball query方法对 采样的点生成个对应的局部区域,根据论文中的意思,这里使用到两个超参数 ,一个是每个区域中点的数量K,另一个是query的半径r 。这里半径应该是占主导的,在某个半径的球内找点,点的数量上限是K 。球的半径和每个区域中点的数量都是超参数 。
经过操作,我们已经得到了点云P中M个中心点,group的操作就是以每个中心点为圆心,人工设定半径r,每个圆内部的点作为一个局部区域,再利用接下来的提取特征 。这里需要注意的是,为了方便batch操作,每一个局部区域内的点的数量是一致的,都为K,如果某个圆内的点的数量小于K, 则可以重复采样圆内的点,达到数量K;如果某个区域内的点的数量大于K, 则随机选择K个点,即可 。group操作得到的结果如下图所示,可以形成很多小的局部区域 。
Group的代码该怎么写呢? 首先得先考虑清楚一下问题:
group的输入和输出是什么? 输入: 完整点云(B, N, 3), 中心点(B, M), 半径r, 前面提到的数量K; 输出: 很多个局部小点云(B, M, K, 3)或其索引(B, M, K),下面代码中返回的是索引值, shape为(B, M, K)
难点在于向量化实现选择K个点: 在圆内大于K和小于K时是如何操作的 。
具体代码参考如下:
def gather_points(points, inds):''':param points: shape=(B, N, C):param inds: shape=(B, M) or shape=(B, M, K):return: sampling points: shape=(B, M, C) or shape=(B, M, K, C)'''device = points.deviceB, N, C = points.shapeinds_shape = list(inds.shape)inds_shape[1:] = [1] * len(inds_shape[1:])repeat_shape = list(inds.shape)repeat_shape[0] = 1batchlists = torch.arange(0, B, dtype=torch.long).to(device).reshape(inds_shape).repeat(repeat_shape)return points[batchlists, inds, :]def ball_query(xyz, new_xyz, radius, K):''':param xyz: shape=(B, N, 3):param new_xyz: shape=(B, M, 3):param radius: int:param K: int, an upper limit samples:return: shape=(B, M, K)'''device = xyz.deviceB, N, C = xyz.shapeM = new_xyz.shape[1]grouped_inds = torch.arange(0, N, dtype=torch.long).to(device).view(1, 1, N).repeat(B, M, 1)dists = get_dists(new_xyz, xyz)grouped_inds[dists > radius] = Ngrouped_inds = torch.sort(grouped_inds, dim=-1)[0][:, :, :K]grouped_min_inds = grouped_inds[:, :, 0:1].repeat(1, 1, K)grouped_inds[grouped_inds == N] = grouped_min_inds[grouped_inds == N]return grouped_inds
3. layer
经过了和group操作,整个大点云被分成了很多个有的小点云, 整个完整点云可表示为shape=(B, M, K, C0)的, M表示中心点的数量, K表示每个中心点的球邻域内选择的点的数量, C0是特征维度, 初始输入点位C0=3或C0=6(加上信息) 。
接下来就是利用对每个小点云P’(shape=(K, C0))进行特征提取 。对小点云P’中的每个点连续进行 1d卷积 + bn + relu 操作,学习每个点的特征, 最后在K通道上进行最大值和平均值池化,得到当前小点云的特征F(shape=(C, )),.
这里实现时并没有直接用nn.,而是使用了nn.,size=1, 本质应该是一样的 。每个小点云P’(K, C0)经过得到特征F(C, ), 那么一个batch的数据(shape=(B, M, K, C0)), 经过模块后, 将会得到维度为(B, M, C)的特征. 这部分代码比较简洁,就是的常规操作,部分代码如下:
self.backbone = nn.Sequential()for i, out_channels in enumerate(mlp):self.backbone.add_module('Conv{}'.format(i),nn.Conv2d(in_channels, out_channels, 1,stride=1, padding=0, bias=False))if bn:self.backbone.add_module('Bn{}'.format(i),nn.BatchNorm2d(out_channels))self.backbone.add_module('Relu{}'.format(i), nn.ReLU())in_channels = out_channels