Sampling + Grouping主要用于将整个点云分散成局部的group,对每一个group都可以用PointNet单独的提取局部的全局特征。Sampling + Grouping需要用到前面分析定义的那些函数,分成了sample_and_group和sample_and_group_all两个函数,其区别在于sample_and_group_all直接将所有点作为一个group。
sample_and_group的实现步骤入下:
- 先用farthest_point_sample函数实现最远点采样FPS得到采样点的索引,再通过index_points将这些点的从原始点中挑出来,作为new_xyz
- 利用query_ball_point和index_points将原始点云通过new_xyz 作为中心分为npoint个球形区域其中每个区域有nsample个采样点
- 每个区域的点减去区域的中心值
- 如果每个点上面有新的特征的维度,则用新的特征与旧的特征拼接,否则直接返回旧的特征
sample_and_group_all直接将所有点作为一个group,即增加一个长度为1的维度而已,当然也存在拼接新的特征的过程。
def sample_and_group(npoint, radius, nsample, xyz, points, returnfps=False):
"""
Input:
npoint: number of samples,FPS采样点的数量
radius: local region radius,球形区域所定义的半径
nsample: max sample number in local region,球形区域所能包围的最大的点数量
xyz: input points position data, [B, N, 3]
points: input points data, [B, N, D],D不包含坐标数据x,y,z
Return:
new_xyz: sampled points position data, [B, npoint, 3]
new_points: sampled points data, [B, npoint, nsample, 3+D]
"""
B, N, C = xyz.shape
S = npoint
# 从原点云中挑出最远点采样的采样点为new_xyz
fps_idx = farthest_point_sample(xyz, npoint) # [B, npoint]
torch.cuda.empty_cache()
#通过index_points将FPS采样点从原始点中挑出来
#new_xyz代表中心点,此时维度为[B, S, 3]
new_xyz = index_points(xyz, fps_idx)
torch.cuda.empty_cache()
# idx:[B, npoint, nsample] 代表npoint个球形区域中每个区域的nsample个采样点的索引
idx = query_ball_point(radius, nsample, xyz, new_xyz)
torch.cuda.empty_cache()
# grouped_xyz:[B, npoint, nsample, C],
#通过index_points将所有group内的nsample个采样点从原始点中挑出来
grouped_xyz = index_points(xyz, idx)
torch.cuda.empty_cache()
# grouped_xyz减去中心点:每个区域的点减去区域的中心值
grouped_xyz_norm = grouped_xyz - new_xyz.view(B, S, 1, C)
torch.cuda.empty_cache()
# 如果每个点上面有新的特征的维度,则用新的特征与旧的特征拼接,否则直接返回旧的特征
if points is not None:
#通过index_points将所有group内的nsample个采样点从原始点中挑出来,得到group内点的除坐标维度外的其他维度的数据
grouped_points = index_points(points, idx)
#dim=-1代表按照最后的维度进行拼接,即相当于dim=3
new_points = torch.cat([grouped_xyz_norm, grouped_points], dim=-1) # [B, npoint, nsample, C+D]
else:
new_points = grouped_xyz_norm
if returnfps:
return new_xyz, new_points, grouped_xyz, fps_idx
else:
return new_xyz, new_points
def samle_and_group_all(xyz, points):
'''
Input:
xyz: input points position data, [B, N, 3]
points: input points data, [B, N, D]
Return:
new_xyz: sampled points position data, [B, 1, 3]
new_points: sampled points data, [B, 1, N, 3+D]
直接将所有点作为一个group,即增加一个长度为1的维度而已
'''
device = xyz.device
B, N, C =xyz.shape
#new_xyz代表中心点,用原点表示
new_xyz = torch.zeros(B, 1, C).to(device)
# grouped_xyz减去中心点:每个区域的点减去区域的中心值,由于中心点为原点,所以结果仍然是grouped_xyz
grouped_xyz = xyz.view(B, 1, N, C)
# 如果每个点上面有新的特征的维度,则用新的特征与旧的特征拼接,否则直接返回旧的特征
if points is not None:
#view(B, 1, N, -1),-1代表自动计算,即结果等于view(B, 1, N, D)
new_points = torch.cat([grouped_xyz, points.view(B, 1, N, -1)], dim=-1)
else:
new_points = grouped_xyz
return new_xyz, new_points
torch.cuda.empty_cache():
Pytorch已经可以自动回收我们不用的显存,类似于python的引用机制,当某一内存内的数据不再有任何变量引用时,这部分的内存便会被释放。但有一点需要注意,当我们有一部分显存不再使用的时候,这部分释放的显存通过Nvidia-smi命令是看不到的,举个例子:
device = torch.device('cuda:0')
# 定义两个tensor
dummy_tensor_4 = torch.randn(120, 3, 512, 512).float().to(device) # 120*3*512*512*4/1000/1000 = 377.48M
dummy_tensor_5 = torch.randn(80, 3, 512, 512).float().to(device) # 80*3*512*512*4/1000/1000 = 251.64M
# 然后释放
dummy_tensor_4 = dummy_tensor_4.cpu()
dummy_tensor_2 = dummy_tensor_2.cpu()
# 这里虽然将上面的显存释放了,但是我们通过Nvidia-smi命令看到显存依然在占用
torch.cuda.empty_cache()
# 只有执行完上面这句,显存才会在Nvidia-smi中释放
Pytorch的开发者也对此进行说明了,这部分释放后的显存可以用,只不过不在Nvidia-smi中显示罢了。