『OPEN3D』1.4 点云表面重建

目录

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中,为我们提供了如下几种的表面重建方法:

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拥有更多的细节。

create_from_point_cloud_poisson的参数
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结果展示没有了之前底部的多余信息。

猜你喜欢

转载自blog.csdn.net/qq_41366026/article/details/129330287