这篇文章将会介绍点云数据的一些基本用法。(本教程可视化的点云数据为官方图片,自己可以根据手头数据进行测试,或者去官方github主页下载对应测试数据)
本教程的第一部分是读取点云数据并将其可视化。
print("Load a ply point cloud, print it, and render it")
pcd = o3d.io.read_point_cloud("../../TestData/fragment.ply")
print(pcd)
print(np.asarray(pcd.points))
o3d.visualization.draw_geometries([pcd], zoom=0.3412,
front=[0.4257, -0.2125, -0.8795],
lookat=[2.6172, 2.0475, 1.532],
up=[-0.0694, -0.9768, 0.2024])
>>>>Load a ply point cloud, print it, and render it
>>>>geometry::PointCloud with 196133 points.
[[0.65234375 0.84686458 2.37890625]
[0.65234375 0.83984375 2.38430572]
[0.66737998 0.83984375 2.37890625]
...
[2.00839925 2.39453125 1.88671875]
[2.00390625 2.39488506 1.88671875]
[2.00390625 2.39453125 1.88793314]]
read_point_cloud 用来读取点云数据。他尝试通过文件扩展名来解码文件。支持的文件格式在上一节有介绍。
draw_geometries 可视化点云数据。使用鼠标可以查看不同视角的数据。
这个图看着像一个密集表面,实际上还是由无数个点组成。可视化的GUI支持多个快捷键。比如可以通过 - 键来缩小点云中点的尺寸。
Note:
在GUI窗口按 h 键可以输出所有快捷键的列表,关于可视化更多的信息,后面会在介绍。
Note:
在OS X,GUI的快捷键可能不会生效,请使用pythonw替代python来启动Python
体素降采样通过使用规则提速网格从输入点云创造一致化降采样点云。这通常在点云处理任务的预处理步骤,这个算法分为两步:
print("Downsample the point cloud with a voxel of 0.05")
downpcd = pcd.voxel_down_sample(voxel_size=0.05)
o3d.visualization.draw_geometries([downpcd], zoom=0.3412,
front=[0.4257, -0.2125, -0.8795],
lookat=[2.6172, 2.0475, 1.532],
up=[-0.0694, -0.9768, 0.2024])
>>>>Downsample the point cloud with a voxel of 0.05
点云的基本操作还包括定点法线估计。按 n 查看点云法线。使用 - 和 + 可以缩放法线长度。
print("Recompute the normal of the downsampled point cloud")
downpcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
o3d.visualization.draw_geometries([downpcd], zoom=0.3412,
front=[0.4257, -0.2125, -0.8795],
lookat=[2.6172, 2.0475, 1.532],
up=[-0.0694, -0.9768, 0.2024],
point_show_normal=True)
>>>>Recompute the normal of the downsampled point cloud
estimate_normals 计算每个点的法线。这个函数通过对邻点采用协方差分析(covariance analysis)来计算主轴。
这个函数使用KDTreeSearchParamHybrid类的一个实例作为参数。这里有两个关键参数是radius = 0.1 & max_nn = =30 ,用来设置搜索半径和邻域最大点数。这里设置搜索半径为10cm,并且只考虑邻域内的30个点,以此来节约计算时间。
Note:
协方差分析会产生两个方向相反的法线候选,如果不考虑全局结构的话,二者都是正确的。这就是所谓的法向问题。如果法线存在open3d会尝试将法线定位与原始法线对齐。否则open3d将会随机选择。如果需要考虑朝向的话,可以使用函数:orient_normals_to_align_with_direction和orient_normals_towards_camera_location。
通过downpcd的normals参数可以检索估计的点的法线。
print("Print a normal vector of the 0th point")
print(downpcd.normals[0])
>>>>Print a normal vector of the 0th point
>>>>[-0.21838377 -0.94240442 -0.25334252]
请使用help(downpcd)来查看其他变量。法线向量可以通过np.asarry来转化成numpy数组。
print("Print the normal vectors of the first 10 points")
print(np.asarray(downpcd.normals)[:10, :])
>>>>Print the normal vectors of the first 10 points
>>>>[[-0.21838377 -0.94240442 -0.25334252]
[-0.39147152 -0.43746664 -0.8095511 ]
[-0.00694405 -0.99478075 -0.10179902]
[-0.00399871 -0.99965423 -0.02598917]
[-0.93768261 -0.07378998 0.3395679 ]
[-0.43476205 -0.62438493 -0.64894177]
[-0.09739809 -0.9928602 -0.06886388]
[-0.11728718 -0.95516445 -0.27185399]
[-0.01038945 -0.99968858 -0.02268921]
[-0.00816546 -0.99965616 -0.02491762]]
更过和numpy有关的信息后续会讲解,也可以直接查看有关的官方文档。
print("Load a polygon volume and use it to crop the original point cloud")
vol = o3d.visualization.read_selection_polygon_volume("../../TestData/Crop/cropped.json")
chair = vol.crop_point_cloud(pcd)
o3d.visualization.draw_geometries([chair], zoom=0.7,
front=[0.5439, -0.2333, -0.8060],
lookat=[2.4615, 2.1331, 1.338],
up=[-0.1781, -0.9708, 0.1608])
>>>>加载一个多边形卷并使它裁剪点云
read_selection_polygon_volume函数是读取一个指定多边形区域得json文件。
vol.crop_point_cloud(pcd)过滤掉点,只保留椅子部分。
print("Paint chair")
chair.paint_uniform_color([1, 0.706, 0])
o3d.visualization.draw_geometries([chair], zoom=0.7,
front=[0.5439, -0.2333, -0.8060],
lookat=[2.4615, 2.1331, 1.338],
up=[-0.1781, -0.9708, 0.1608])
椅子上色
paint_uniform_color给所有的点上一个统一的颜色,颜色是在RGB空间得[0,1]范围内得值。
点云几何类型和其他类型一样,也有包围框。当前,open3d实现了两个包围框接口,同时他们也可以用来裁剪几何图形。
aabb = chair.get_axis_aligned_bounding_box()
aabb.color = (1,0,0)
obb = chair.get_oriented_bounding_box()
obb.color = (0,1,0)
o3d.visualization.draw_geometries([chair, aabb, obb], zoom=0.7,
front=[0.5439, -0.2333, -0.8060],
lookat=[2.4615, 2.1331, 1.338],
up=[-0.1781, -0.9708, 0.1608])
点云得凸包是包含所有点的最小凸集。open3d实现了计算凸包的方法:compute_convex_hull.这个接口的实现基于Qhull。关于凸包的中文解释我这里给出百度百科链接。
下面的代码中首先从三角网格中采样一个点云,之后计算凸包并且返回,返回类型为三角网格。最后将会用红色的线可视化凸包。
pcl = o3dtut.get_bunny_mesh().sample_points_poisson_disk(number_of_points=2000)
hull, _ = pcl.compute_convex_hull()
hull_ls = o3d.geometry.LineSet.create_from_triangle_mesh(hull)
hull_ls.paint_uniform_color((1, 0, 0))
o3d.visualization.draw_geometries([pcl, hull_ls])
downloading bunny mesh
extract bunny mesh
给定一个点云,比如深度传感器,我们想将局部的点分组/聚合在一起,这时我们就需要聚类算法。open3d实现了DBSCAN[Ester1996] 算法,这是一种基于密度的聚类算法。(这里上面的链接里包含原论文名称,需要可自行查找,这里我也给出百度百科链接****建议大家看原论文)。该算法接口为cluster_dbscan,有两个必须的参数:eps表示聚类的领域距离,min_points表示聚类的最小点数。该函数返回一个label,其中label为-1表示为噪声。
pcd = o3d.io.read_point_cloud("../../TestData/fragment.ply")
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm:
labels = np.array(pcd.cluster_dbscan(eps=0.02, min_points=10, print_progress=True))
max_label = labels.max()
print(f"point cloud has {max_label + 1} clusters")
colors = plt.get_cmap("tab20")(labels / (max_label if max_label > 0 else 1))
colors[labels < 0] = 0
pcd.colors = o3d.utility.Vector3dVector(colors[:, :3])
o3d.visualization.draw_geometries([pcd], zoom=0.455,
front=[-0.4999, -0.1659, -0.8499],
lookat=[2.1813, 2.0619, 2.0999],
up=[0.1204, -0.9852, 0.1215])
>>>>point cloud has 10 clusters
Note:
该算法会对所有在半径内得所有点的邻域进行预计算,如果半径选的过大会占用大量的内存。
open3d还支持使用RANSAC从点云中分割几何基元。要查找点云中最有可能存在的平面,我们使用segement_plane函数。这个函数需要三个参数,destance_threshold定义了一个点到一个估计平面的最大距离,这些距离内的点被认为是内点(inlier),ransac_n定义了使用随机抽样估计一个平面的点的个数,num_iterations定义了随机平面采样和验证的频率(迭代次数)。这个函数返回(a,b,c,d)作为一个平面,对于平面上每个点(x,y,z)我们有ax+by+cz+d=0。这个函数还会返回内点索引的列表。
pcd = o3d.io.read_point_cloud("../../TestData/fragment.pcd")
plane_model, inliers = pcd.segment_plane(distance_threshold=0.01,
ransac_n=3,
num_iterations=1000)
[a, b, c, d] = plane_model
print(f"Plane equation: {a:.2f}x + {b:.2f}y + {c:.2f}z + {d:.2f} = 0")
inlier_cloud = pcd.select_by_index(inliers)
inlier_cloud.paint_uniform_color([1.0, 0, 0])
outlier_cloud = pcd.select_by_index(inliers, invert=True)
o3d.visualization.draw_geometries([inlier_cloud, outlier_cloud], zoom=0.8,
front=[-0.4999, -0.1659, -0.8499],
lookat=[2.1813, 2.0619, 2.0999],
up=[0.1204, -0.9852, 0.1215])
>>>>Plane equation: -0.06x + -0.10y + 0.99z + -1.06 = 0
假如你想从一个给定视角去渲染一个点云,但是背景的点在前景也会被看到,这是因为并没有其他点把它遮挡住。为此看可以使用一种隐点去除算法。open3d实现了[Katz2007] 文章中的算法,该算法从没有重建和法线估计的给定视角去近似可视化点云。
print("Convert mesh to a point cloud and estimate dimensions")
pcd = o3dtut.get_armadillo_mesh().sample_points_poisson_disk(5000)
diameter = np.linalg.norm(np.asarray(pcd.get_max_bound()) - np.asarray(pcd.get_min_bound()))
o3d.visualization.draw_geometries([pcd])
print("Define parameters used for hidden_point_removal")
camera = [0, 0, diameter]
radius = diameter * 100
print("Get all points that are visible from given view point")
_, pt_map = pcd.hidden_point_removal(camera, radius)
print("Visualize result")
pcd = pcd.select_by_index(pt_map)
o3d.visualization.draw_geometries([pcd])