pointnet在训练点云前会将点云进行归一化,这个归一化是在dataloader中进行,且采用numpy进行处理,源码如下:
def pc_normalize(pc):
l = pc.shape[0]
centroid = np.mean(pc, axis=0)#求这个batch点云的均值
pc = pc - centroid
m = np.max(np.sqrt(np.sum(pc**2, axis=1)))#求这个batch点云的模的最大值
pc = pc / m
return pc
代码和原理当然都很简单。但是当网络训练完成后进行网络推理测试时,如果不再希望把点云放在dataloader中进行前处理,而是希望直接加载到tensor中进行批量归一化。这个过程当然也十分简单,但其中有容易出错的地方,此处做一个记录,也给大家提个醒。
先举一个简单的例子,此处定义一个mini-batch的点云:
p = torch.FloatTensor( [[[0, 3, 5],[0, 6, 8]],[[0, 3, 3],[0, 4, 4]]] )#torch.size([2, 2, 3])
即此处有batch_size=2, 每个batch中有两个点,我们打算对这个mini-batch的点云进行归一化。
中心点的求法当然很简单,如下:
centroid = torch.mean(p, axis=1)
p = p-centroid.unsqueeze(1)
此处为什么要unsqueeze(1)?此时得到的centroid为
tensor([[0.0000, 4.5000, 6.5000],
[0.0000, 7.0000, 3.5000]])
是一个torch.size([2, 3])的tensor,首先需要改变维度成torch.size([2, 1, 3]), 然后pytorch将其广播成torch.size([2, 2, 3]), 即在第一个维度上为我们复制点,每个batch中的点减去同一个中心点。
接下来就要求每个batch中点云的模了。求模的公式代码非常绕,我们先不上代码。此处我们先假设第一个batch的模为2, 第二个batch的模为3。接下来就需要在p中让每个batch的点云分别除以它们的模。我们当然想通过广播的机制去实现。我们定义一个模的tensor:
n = torch.tensor([[[2], [3]]])#torch.Size([2, 1])
print(p/n)#为了方便对比数值,此处我们先不减去中心值
结果为:
tensor([[[0.0000, 1.5000, 2.5000],
[0.0000, 2.0000, 2.6667]],
[[0.0000, 1.5000, 1.5000],
[0.0000, 1.3333, 1.3333]]])
此时我们发现,与我们预期不一致。结果为对每个batch的第一个点除以2,对第2个点除以3。为什么会出现这样的结果呢?
实际上此处的n会被pytorch做如下的维度变换:
torch.Size([2, 1])-->torch.Size([1, 2, 1])-->torch.Size([2, 2, 3])
pytorch会首先在第0维度unsqueeze一个维度,再用广播机制补全tensor。为了验证,我们可以把n的维度改变一下:
n = torch.tensor([[[2], [3]]])#torch.Size([1, 2, 1])
print(p/n)#为了方便对比数值,此处我们先不减去中心值
得到的结果与前述相同。
由此,我们应该改变一下n的维度,将n改成如下维度:
n = torch.tensor([[[2]],[[3]]])#torch.Size([2, 1, 1])
print(p/n)#为了方便对比数值,此处我们先不减去中心值
此时得到的结果如下:
tensor([[[0.0000, 1.5000, 2.5000],
[0.0000, 3.0000, 4.0000]],
[[0.0000, 1.0000, 1.0000],
[0.0000, 1.3333, 1.3333]]])
此时得到我们想要的结果,即对第1batch中的点除以2,对第2个batch中的点除以3。因为n中第0个维度为2,pytorch会将torch.Size([2, 1, 1])广播torch.Size([2, 2, 3])。
接下来我们将整个归一化过程完整代码写出:
p = torch.FloatTensor( [[[0, 3, 5],[0, 6, 8]],[[0, 10, 3],[0, 4, 4]]] )
centroid = torch.mean(p, axis=1)
p = p - centroid.unsqueeze(1)
m = torch.max(torch.sqrt(torch.sum(p**2, axis=2)), axis=1,keepdim=True)[0]
print(p/m.unsqueeze(1))
归一化结果为:
tensor([[[ 0.0000, -0.7071, -0.7071],
[ 0.0000, 0.7071, 0.7071]],
[[ 0.0000, 0.9864, -0.1644],
[ 0.0000, -0.9864, 0.1644]]])
中心点为:
tensor([[0.0000, 4.5000, 6.5000],
[0.0000, 7.0000, 3.5000]])
原始点减去中心点为:
p = p - centroid.unsqueeze(1)
#tensor([[[ 0.0000, -1.5000, -1.5000],
# [ 0.0000, 1.5000, 1.5000]],
#[[ 0.0000, 3.0000, -0.5000],
# [ 0.0000, -3.0000, 0.5000]]])
模为:
tensor([[[2.1213]],
[[3.0414]]])
整个过程原理很简单,但由于tensor有三个维度,在操作时容易出错,在确定代码前还是仔细调试为好,否则送入网络的数据不对很难找出问题出在哪里。各位有什么更好的方法欢迎提出。