归一化到单位球
参考:https://github.com/yanx27/Pointnet_Pointnet2_pytorch
data_utils/ModelNetDataLoader.py
# 对单个样本处理,在dataset的__getitem__函数中。
point_set[:, 0:3] = pc_normalize(point_set[:, 0:3])
def pc_normalize(pc):
centroid = np.mean(pc, axis=0)
pc = pc - centroid
m = np.max(np.sqrt(np.sum(pc**2, axis=1)))
pc = pc / m
return pc
# 版本2
def normalize_points(points):
r"""Normalize point cloud to a unit sphere at origin."""
points = points - points.mean(axis=0)
points = points / np.max(np.linalg.norm(points, axis=1))
return points
采样方法
随机采样
从给定的点云数据中随机采样指定数量的点,并返回采样后的点云数据。
如果 num_points 大于 num_samples,则选择前 num_samples 个索引,表示从中随机抽样 num_samples 个点;否则,需要重复抽样直到达到 num_samples 个点。
【来源:GEOtransformer-dataset】
if self.twice_sample:
# twice sample on both point clouds
ref_points, ref_normals = random_sample_points(ref_points, self.num_points, normals=ref_normals)
src_points, src_normals = random_sample_points(src_points, self.num_points, normals=src_normals)
def random_sample_points(points, num_samples, normals=None):
r"""Randomly sample points."""
num_points = points.shape[0]
sel_indices = np.random.permutation(num_points)
if num_points > num_samples:
sel_indices = sel_indices[:num_samples]
elif num_points < num_samples:
num_iterations = num_samples // num_points
num_paddings = num_samples % num_points
all_sel_indices = [sel_indices for _ in range(num_iterations)]
if num_paddings > 0:
all_sel_indices.append(sel_indices[:num_paddings])
sel_indices = np.concatenate(all_sel_indices, axis=0)
points = points[sel_indices]
if normals is not None:
normals = normals[sel_indices]
return points, normals
else:
return points
最远点采样
让采样尽可能覆盖全局。
来源:https://github.com/yanx27/Pointnet_Pointnet2_pytorch
从1024个点采样到512个点的索引值,根据索引的位置,在原始点云中,找到这512个点。
单个样本的最远点采样
def farthest_point_sample(point, npoint):
"""
Input:
xyz: pointcloud data, [N, D]
npoint: number of samples
Return:
xyz: sampled pointcloud, [npoint, D]
"""
N, D = point.shape
xyz = point[:,:3]
centroids = np.zeros((npoint,))
distance = np.ones((N,)) * 1e10
farthest = np.random.randint(0, N)
for i in range(npoint):
centroids[i] = farthest
centroid = xyz[farthest, :]
dist = np.sum((xyz - centroid) ** 2, -1)
mask = dist < distance
distance[mask] = dist[mask]
farthest = np.argmax(distance, -1)
point = point[centroids.astype(np.int32)]
return point
带batchsize的最远点采样
new_xyz = index_points(xyz, farthest_point_sample(xyz, S))
def farthest_point_sample(xyz, npoint):
"""
例如:从1024个点采样到512个,这里为512个点的索引值。
Input:
xyz: pointcloud data, [B, N, 3]
npoint: number of samples
Return:
centroids: sampled pointcloud index, [B, npoint]
"""
device = xyz.device
B, N, C = xyz.shape
centroids = torch.zeros(B, npoint, dtype=torch.long).to(device)
distance = torch.ones(B, N).to(device) * 1e10
farthest = torch.randint(0, N, (B,), dtype=torch.long).to(device) # 第一个采样点选随机初始化的索引
batch_indices = torch.arange(B, dtype=torch.long).to(device)
for i in range(npoint):
centroids[:, i] = farthest
centroid = xyz[batch_indices, farthest, :].view(B, 1, 3) # 得到当前采样点的坐标 B*3
dist = torch.sum((xyz - centroid) ** 2, -1) # 计算当前采样点与其他点的距离
mask = dist < distance # 选择距离最近的来更新距离(更新维护这个表)
distance[mask] = dist[mask]
farthest = torch.max(distance, -1)[1] # 重新计算得到最远点索引(在更新的表中选择距离最大的那个点)
return centroids
def index_points(points, idx):
"""
根据索引的位置,在原始点云中,找到这512个点。
Input:
points: input points data, [B, N, C]
idx: sample index data, [B, S]
Return:
new_points:, indexed points data, [B, S, C]
"""
device = points.device
B = points.shape[0]
view_shape = list(idx.shape)
view_shape[1:] = [1] * (len(view_shape) - 1)
repeat_shape = list(idx.shape)
repeat_shape[0] = 1
batch_indices = torch.arange(B, dtype=torch.long).to(device).view(view_shape).repeat(repeat_shape)
new_points = points[batch_indices, idx, :]
return new_points
球查询
参考:参考:https://github.com/yanx27/Pointnet_Pointnet2_pytorch
models/pointnet2_utils.py
group_idx = query_ball_point(radius, K, xyz, new_xyz) # 返回的是索引
grouped_xyz = index_points(xyz, group_idx) # 得到各个组中实际点
grouped_xyz -= new_xyz.view(B, S, 1, C) # 去mean,new_xyz相当于簇的中心点
def query_ball_point(radius, nsample, xyz, new_xyz):
"""
Input:
radius: local region radius
nsample: max sample number in local region
xyz: all points, [B, N, 3]
new_xyz: query points, [B, S, 3]
Return:
group_idx: grouped points index, [B, S, nsample] #[B,512,16]
"""
device = xyz.device
B, N, C = xyz.shape
_, S, _ = new_xyz.shape
group_idx = torch.arange(N, dtype=torch.long).to(device).view(1, 1, N).repeat([B, S, 1])
sqrdists = square_distance(new_xyz, xyz) # 得到B N M ,(就是N个点中每一个和M中每一的欧氏距离)
group_idx[sqrdists > radius ** 2] = N # 找到距离大于给定半径的设置成一个N值(1024)索引
group_idx = group_idx.sort(dim=-1)[0][:, :, :nsample] # 做升序排序,后面的都是大的值(1024)
group_first = group_idx[:, :, 0].view(B, S, 1).repeat([1, 1, nsample]) # 如果半径内的点没那么多,就直接用第一个点来代替了。
mask = group_idx == N
group_idx[mask] = group_first[mask]
return group_idx
def square_distance(src, dst):
"""
Calculate Euclid distance between each two points.
src^T * dst = xn * xm + yn * ym + zn * zm;
sum(src^2, dim=-1) = xn*xn + yn*yn + zn*zn;
sum(dst^2, dim=-1) = xm*xm + ym*ym + zm*zm;
dist = (xn - xm)^2 + (yn - ym)^2 + (zn - zm)^2
= sum(src**2,dim=-1)+sum(dst**2,dim=-1)-2*src^T*dst
Input:
src: source points, [B, N, C]
dst: target points, [B, M, C]
Output:
dist: per-point square distance, [B, N, M]
"""
B, N, _ = src.shape
_, M, _ = dst.shape
dist = -2 * torch.matmul(src, dst.permute(0, 2, 1))
dist += torch.sum(src ** 2, -1).view(B, N, 1)
dist += torch.sum(dst ** 2, -1).view(B, 1, M)
return dist
噪声
# random jitter
if self.noise_magnitude is not None:
ref_points = random_jitter_points(ref_points, scale=0.01, noise_magnitude=self.noise_magnitude)
src_points = random_jitter_points(src_points, scale=0.01, noise_magnitude=self.noise_magnitude)
def random_jitter_points(points, scale, noise_magnitude=0.05):
r"""Randomly jitter point cloud."""
noises = np.clip(np.random.normal(scale=scale, size=points.shape), a_min=-noise_magnitude, a_max=noise_magnitude)
points = points + noises
return points
打乱点云
# random shuffle
ref_points, ref_normals = random_shuffle_points(ref_points, normals=ref_normals)
src_points, src_normals = random_shuffle_points(src_points, normals=src_normals)
def random_shuffle_points(points, normals=None):
r"""Randomly permute point cloud."""
indices = np.random.permutation(points.shape[0])
points = points[indices]
if normals is not None:
normals = normals[indices]
return points, normals
else:
return points