目录
1 点云法线一致性估计
2 点云的表面重建
1 Alpha shapes reconstruction
2 Ball pivoting reconstruction
3 poisson surface reconstruction
1 点云法线一致性估计
在点云处理的章节中已经介绍使用estimate_normals来生成点云的发现信息,但该方法通过拟合局部3D点来生成法线信息,因此生成的法线朝向一致性不够好。此处使用最小生成树来传播法线的方向,提高朝向的一致性。该方法为orient_normals_consistent_tangent_plane
import numpy as np
import open3d as o3d
import copy
if __name__ == '__main__':
ply_point_cloud = o3d.data.PLYPointCloud()
pcd: o3d.geometry.PointCloud = o3d.io.read_point_cloud(ply_point_cloud.path)
pcd = pcd.voxel_down_sample(voxel_size=0.06)
# pcd = gt_mesh.sample_points_poisson_disk(5000)
# invalidate existing normals
# 使得原有的法线信息失效
pcd.normals = o3d.utility.Vector3dVector(np.zeros((1, 3)))
pcd.estimate_normals()
# o3d.visualization.draw_geometries([pcd], point_show_normal=True)
pcd_spanning_tree: o3d.geometry.PointCloud = copy.deepcopy(pcd)
# 参数K用于构造用于传播法线方向的黎曼图的k个最近邻数。
pcd_spanning_tree.orient_normals_consistent_tangent_plane(k=300)
pcd_spanning_tree.translate([4, 0, 0])
o3d.visualization.draw_geometries([pcd_spanning_tree, pcd], point_show_normal=True)
可以看到经过法线一致性调整后,法线朝向的一致性变得统一。
2 点云的表面重建
之前我们已经介绍了点云(point cloud)和面片(mesh)的内容,并且可以将mesh通过采样的方法得到点云信息,但是如何将稀疏的无结构点云重新生成表面mesh呢? 这就涉及到点云的表面重建的内容了。
在open3d中,为我们提供了如下几种的表面重建方法:
-
Alpha shapes [Edelsbrunner1983]
-
Ball pivoting [Bernardini1999]
-
Poisson surface reconstruction [Kazhdan2006]
1 Alpha shapes reconstruction
alpha shapes 实际上是一种点云的边界提取算法,如下图所示:
选取半径为alpha大小的圆,并将此圆在空间的无序点云上进行滚动,然后绘制出轮廓线,该轮廓线就是点云的表面信息,如果alpha足够大,可以将该发放看做是求取点云的convex hull(凸包计算)。详细的解析可以看斯坦福的报告 。
代码示例:
import copy
import open3d as o3d
if __name__ == "__main__":
bunny = o3d.data.BunnyMesh()
mesh = o3d.io.read_triangle_mesh(bunny.path)
mesh.compute_vertex_normals()
# 对mesh进行采样,得到点云
pcd = mesh.sample_points_poisson_disk(850)
print("Displaying input pointcloud ...")
o3d.visualization.draw_geometries([pcd])
# 设置alpha shapes中的参数alpha
alpha = 0.02
mesh_list = [pcd]
for index in range(1, 4):
print(f"alpha={alpha:.3f}")
print('Running alpha shapes surface reconstruction ...')
alpha = alpha * index
tetra_mesh, pt_map = o3d.geometry.TetraMesh.create_from_point_cloud(pcd)
mesh: o3d.geometry.TriangleMesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(
pcd, alpha)
mesh.compute_triangle_normals(normalized=True)
mesh = mesh.translate([0.2 * index, 0, 0])
mesh_list.append(copy.deepcopy(mesh))
del mesh
print("Displaying reconstructed mesh ...")
# 显示时,mesh_show_back_face=True : mesh内部视角也显示mesh面片
o3d.visualization.draw_geometries(mesh_list, mesh_show_back_face=True)
2 Ball pivoting reconstruction
Ball pivoting算法的实现思路与alpha shapes接近;想象一下, 一个给定半径大小的球体朝点云扔去,如果球体击中任意三个点且没有重中穿过,则该三个点创建一个triangles mesh;然后该算法以现有的这个triangles mesh的边沿开始进行周围的迭代,当击中另外三个点没有穿过时,创建另一个三角形。
import open3d as o3d
if __name__ == "__main__":
bunny = o3d.data.BunnyMesh()
gt_mesh = o3d.io.read_triangle_mesh(bunny.path)
gt_mesh.compute_vertex_normals()
pcd = gt_mesh.sample_points_poisson_disk(3000)
print("Displaying input pointcloud ...")
# o3d.visualization.draw_geometries([pcd])
radii = [0.005, 0.01, 0.02, 0.04]
print('Running ball pivoting surface reconstruction ...')
# pcd中需要包含法线信息
rec_mesh: o3d.geometry.TriangleMesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
pcd, o3d.utility.DoubleVector(radii))
print("Displaying reconstructed mesh ...")
o3d.visualization.draw_geometries([rec_mesh, pcd])
3 poisson surface reconstruction
其中上面的点为点云的信息,mesh根据点云的信息进行生成
3 poisson surface reconstruction
poisson surface reconstruction解决正则优化问题来生成更加平滑的表面信息,基于此,而上面提及的重建算法直接使用point cloud不加以修改的作为mesh的顶点(vertice)。
open3d实现该方法通过create_from_point_cloud_poisson,实际时对GitHub - mkazhdan/PoissonRecon: Poisson Surface Reconstruction的封装实现。该算法的一个重要参数时depth,用于控制octree的深度;该参数可以控制生成triangle mesh的精细程度。越大的depth,生成的mesh拥有更多的细节。
pcd | 包含法线信息的待重建点云 |
depth | (int ,可选参数,默认=8) 曲面重建树的最大深度,在深度D处运行对应于分辨率不大于2^D*2^D*2^D的网格上进行求解。注:由于重建时根据采样密度调整OCTtree,因此此处指定的重建深度只是一个上限。 |
width | (float ,可选参数,默认=0)指定最细级别的八叉树单元格的目标宽度;如果指定了深度,则此参数忽略。 |
scale | (float,可选,默认值=1.1)指定用于重建的立方体直径与样本边界立方体直径之间的比率。 |
linear_fit | (bool,可选,默认值=False)如果为true,重建时将使用线性插值来估计iso顶点的位置 |
n_threads | (int,可选,默认值=-1)用于重建的线程数,设置为-1表示自动选择 |
import open3d as o3d
import numpy as np
if __name__ == "__main__":
eagle = o3d.data.EaglePointCloud()
pcd: o3d.geometry.PointCloud = o3d.io.read_point_cloud(eagle.path)
R = pcd.get_rotation_matrix_from_xyz((np.pi, -np.pi / 4, 0))
pcd.rotate(R, center=(0, 0, 0))
pcd = pcd.voxel_down_sample(voxel_size=0.08)
print('Displaying input pointcloud ...')
# o3d.visualization.draw_geometries([pcd])
print('Running Poisson surface reconstruction ...')
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
pcd, depth=9)
print('Displaying reconstructed mesh ...')
pcd = pcd.translate([10, 0, 0])
o3d.visualization.draw_geometries([mesh, pcd])
Poisson surface reconstruction(泊松表面重建)也会在点云密度较低的地方创建mesh,甚至会向外延伸到其他区域,如下图底部区域所示;因此create_from_point_cloud_poisson函数的第二个返回值(densities)代表了每一个顶点的密度信息;低密度代表着该顶点(vertex)仅从一小部分的输入点云中生成。
可以根据返回的密度信息来删除低密度区域的顶点(vertices)和mesh,这里使用numpy.quantile来删除出小于X分位数的密度值的顶点(vertices)和mesh。
import copy
import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt
if __name__ == "__main__":
eagle = o3d.data.EaglePointCloud()
pcd: o3d.geometry.PointCloud = o3d.io.read_point_cloud(eagle.path)
R = pcd.get_rotation_matrix_from_xyz((np.pi, -np.pi / 4, 0))
pcd.rotate(R, center=(0, 0, 0))
# pcd = pcd.voxel_down_sample(voxel_size=0.08)
print('Displaying input pointcloud ...')
# o3d.visualization.draw_geometries([pcd])
print('Running Poisson surface reconstruction ...')
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
pcd, depth=9)
print('Displaying reconstructed mesh ...')
mesh = mesh.translate([5, 0, 0])
o3d.visualization.draw_geometries([mesh, pcd], mesh_show_back_face=True)
# 密度的cmap
print('visualize densities')
densities = np.asarray(densities)
density_colors = plt.get_cmap('plasma')(
(densities - densities.min()) / (densities.max() - densities.min()))
density_colors = density_colors[:, :3]
density_mesh = o3d.geometry.TriangleMesh()
density_mesh.vertices = mesh.vertices
density_mesh.triangles = mesh.triangles
density_mesh.triangle_normals = mesh.triangle_normals
density_mesh.vertex_colors = o3d.utility.Vector3dVector(density_colors)
o3d.visualization.draw_geometries([density_mesh])
density_mesh.translate([5, 0, 0])
# 删除分位数小于0.005的顶点和mesh
print('remove low density vertices')
vertices_to_remove = densities < np.quantile(densities, 0.005)
mesh_remove:o3d.geometry.PointCloud = copy.deepcopy(mesh)
mesh_remove.remove_vertices_by_mask(vertices_to_remove)
mesh_removed = mesh_remove.translate([10, 0, 0])
print(mesh_removed)
o3d.visualization.draw_geometries([mesh_removed, density_mesh, mesh, pcd])
上面结果图片中,最左边为原始的点云数据,其次时poisson重建的结果,其次时poisson重建的密度信息,最后时删除X分位后得到的重建结果。 可以看到删除低密度区域的顶点和mesh信息后,mesh结果展示没有了之前底部的多余信息。