Surface reconstruction 表面重建
在许多情况下,我们希望生成密集的3D几何体,即三角形网格(triangle mesh)。然而,从多视点立体方法或深度传感器中,我们只能获得非结构化的点云。要从此非结构化输入中获取三角形网格,我们需要执行表面重建。在文献中存在几种方法,Open3D目前实现了以下方法:
Alpha shapes(阿尔法形状)
Ball pivoting(球旋转)
Poisson surface reconstruction(泊松表面重建)
Alpha shapes
阿尔法形状[Edelsbrunner1983]是凸壳的推广。正如这里[https://graphics.stanford.edu/courses/cs268-11-spring/handouts/AlphaShapes/as_fisher.pdf]所描述的,人们可以直观地将阿尔法形状想象成:想象一大堆冰淇淋,其中包含这些点作为硬巧克力块。使用其中一个球形冰淇淋勺子,我们雕刻出冰淇淋块的所有部分,我们可以在不碰到巧克力块的情况下到达,从而甚至在内部雕刻出孔洞(例如,只需从外面移动勺子就无法到达的部分)。我们最终将得到一个由大写字母、弧线和点边界的(不一定是凸的)物体。如果我们现在将所有圆面拉直为三角形和线段,则可以直观地描述所谓的 alpha 形状S。
Open3D 实现了涉及权衡参数alpha的方法create_from_point_cloud_alpha_shape。
bunny = o3d.data.BunnyMesh() mesh = o3d.io.read_triangle_mesh(bunny.path) mesh.compute_vertex_normals() pcd = mesh.sample_points_poisson_disk(750) o3d.visualization.draw_geometries([pcd]) alpha = 0.03 print(f"alpha={alpha:.3f}") mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd, alpha) mesh.compute_vertex_normals() o3d.visualization.draw_geometries([mesh], mesh_show_back_face=True)
该实现基于点云的凸壳。如果我们想从给定的点云中计算多个 alpha 形状,那么我们可以通过只计算凸壳一次并将其传递给 create_from_point_cloud_alpha_shape来节省一些计算。
tetra_mesh, pt_map = o3d.geometry.TetraMesh.create_from_point_cloud(pcd) for alpha in np.logspace(np.log10(0.5), np.log10(0.01), num=4): print(f"alpha={alpha:.3f}") mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape( pcd, alpha, tetra_mesh, pt_map) mesh.compute_vertex_normals() o3d.visualization.draw_geometries([mesh], mesh_show_back_face=True)
Ball pivoting
球枢轴算法(ball pivoting algorithm,BPA)[Bernardini1999]是一种与阿尔法形状相关的表面重建方法。直观地说,想想一个具有给定半径的3D球,我们将其落在点云上。如果它击中任何3个点(并且它没有落在这3个点上),它就会创建一个三角形。然后,算法开始从现有三角形的边缘旋转,每次它击中球没有落下的3个点时,我们都会创建另一个三角形。
Open3D 在create_from_point_cloud_ball_pivoting中实现了此方法。该方法接受与在点云上旋转的各个球的半径相对应的参数列表radii。
Note:此方法假设点云具有法线。
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) o3d.visualization.draw_geometries([pcd]) radii = [0.005, 0.01, 0.02, 0.04] rec_mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting( pcd, o3d.utility.DoubleVector(radii)) o3d.visualization.draw_geometries([pcd, rec_mesh])
Poisson surface reconstruction
泊松曲面重构方法 [Kazhdan2006] 求解了一个正则化优化问题,得到了一个光滑的曲面。因此,泊松曲面重建可能比上述方法更可取,因为它们会产生非平滑的结果、因为PointCloud点也是所得三角形网格的点vertices,无需任何修改。
Open3D实现了该方法create_from_point_cloud_poisson,该方法基本上是Kazhdan代码的包装器。该函数的一个重要参数depth是定义用于表面重建的八叉树的深度,因此意味着所得三角形网格的分辨率。depth值越高,表示网格具有更多细节。
Note:此方法假设点云具有法线。
import open3d as o3d eagle_path = r'../data/EaglePointCloud.ply' pcd = o3d.io.read_point_cloud(eagle_path) print(pcd) o3d.visualization.draw_geometries([pcd], zoom=0.664, front=[-0.4761, -0.4698, -0.7434], lookat=[1.8900, 3.2596, 0.9284], up=[0.2304, -0.8825, 0.4101]) print('run Poisson surface reconstruction') with o3d.utility.VerbosityContextManager( o3d.utility.VerbosityLevel.Debug) as cm: mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( pcd, depth=9) print(mesh) o3d.visualization.draw_geometries([mesh], zoom=0.664, front=[-0.4761, -0.4698, -0.7434], lookat=[1.8900, 3.2596, 0.9284], up=[0.2304, -0.8825, 0.4101])
泊松表面重建也会在低点密度区域创建三角形,甚至推断到某些区域(见上面鹰输出的底部)。函数create_from_point_cloud_poisson具有第二个返回值densities,该值指示每个顶点的密度。低密度值意味着顶点仅由输入点云中的少量点支持。
在下面的代码中,我们使用伪彩色在3D中可视化密度。紫色表示低密度,黄色表示高密度。
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], zoom=0.664, front=[-0.4761, -0.4698, -0.7434], lookat=[1.8900, 3.2596, 0.9284], up=[0.2304, -0.8825, 0.4101])
我们可以进一步使用密度值来删除具有低支撑的顶点和三角形。在下面的代码中,我们删除了密度值低于0.01所有密度值的分位数的所有顶点(和连接的三角形)。
print('remove low density vertices') vertices_to_remove = densities < np.quantile(densities, 0.01) mesh.remove_vertices_by_mask(vertices_to_remove) print(mesh) o3d.visualization.draw_geometries([mesh], zoom=0.664, front=[-0.4761, -0.4698, -0.7434], lookat=[1.8900, 3.2596, 0.9284], up=[0.2304, -0.8825, 0.4101])
Normal estimation 法线估计
在上面的例子中,我们假设点云具有指向外部的法线。但是,并非所有点云都已附带相关的法线。Open3D 可用estimate_normals估计点云法线,其局部拟合每个 3D 点的平面以推导出法线。但是,估计的法线可能不是一致的。 orient_normals_consistent_tangent_plane使用最小生成树传播法线。
bunny = o3d.data.BunnyMesh() gt_mesh = o3d.io.read_triangle_mesh(bunny.path) pcd = gt_mesh.sample_points_poisson_disk(5000) pcd.normals = o3d.utility.Vector3dVector(np.zeros( (1, 3))) # invalidate existing normals pcd.estimate_normals() o3d.visualization.draw_geometries([pcd], point_show_normal=True) pcd.orient_normals_consistent_tangent_plane(100) o3d.visualization.draw_geometries([pcd], point_show_normal=True)