Open3d学习计划(3)点云

Open3D是一个开源库,支持快速开发和处理3D数据。Open3D在c++和Python中公开了一组精心选择的数据结构和算法。后端是高度优化的,并且是为并行化而设置的。

本系列学习计划有Blue同学作为发起人,主要以Open3D官方网站的教程为主进行翻译与实践的学习计划。点云PCL公众号作为免费的3D视觉,点云交流社区,期待有使用Open3D或者感兴趣的小伙伴能够加入我们的翻译计划,贡献免费交流社区,为使用Open3D提供中文的使用教程。

点云(Point Cloud)

这篇文章将会介绍点云数据的一些基本用法。(本教程可视化的点云数据为官方图片,自己可以根据手头数据进行测试,或者去官方github主页下载对应测试数据:https://github.com/intel-isl/Open3D/tree/master/examples/TestData)

可视化点云

本教程的第一部分是读取点云数据并将其可视化。

代码语言:javascript
复制
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 键可以输出所有快捷键的列表,关于可视化更多的信息,后面会在介绍。在OS X,GUI的快捷键可能不会生效,请使用pythonw替代python来启动Python

体素降采样

体素降采样通过使用规则提速网格从输入点云创造一致化降采样点云。这通常在点云处理任务的预处理步骤,这个算法分为两步:

  • 把点云装进体素网格
  • 把每个被占据的体素中的点做平均,取一个精确的点。
代码语言:javascript
复制
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.0

顶点法线估计

点云的基本操作还包括定点法线估计。按 n 查看点云法线。使用 - 和 + 可以缩放法线长度。

代码语言:javascript
复制
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参数可以检索估计的点的法线。

代码语言:javascript
复制
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数组。

更过和numpy有关的信息后续会讲解,也可以直接查看有关的官方文档。

裁剪点云

代码语言:javascript
复制
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)过滤掉点,只保留椅子部分。

点云上色

代码语言:javascript
复制
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实现了两个包围框接口,同时他们也可以用来裁剪几何图形。

代码语言:javascript
复制
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(http://www.qhull.org/)。

下面的代码中首先从三角网格中采样一个点云,之后计算凸包并且返回,返回类型为三角网格。最后将会用红色的线可视化凸包。

DBSCAN 聚类

给定一个点云,比如深度传感器,我们想将局部的点分组/聚合在一起,这时我们就需要聚类算法。open3d实现了DBSCAN[Ester1996] 算法,这是一种基于密度的聚类算法。(这里上面的链接里包含原论文名称,需要可自行查找,这里我也给出百度百科链接****建议大家看原论文)。该算法接口为cluster_dbscan,有两个必须的参数:eps表示聚类的领域距离,min_points表示聚类的最小点数。该函数返回一个label,其中label为-1表示为噪声。

代码语言:javascript
复制
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。这个函数还会返回内点索引的列表。

代码语言:javascript
复制
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实现了[http://www.open3d.org/docs/release/tutorial/reference.html#Katz2007] 文章中的算法,该算法从没有重建和法线估计的给定视角去近似可视化点云。

代码语言:javascript
复制
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])
代码语言:javascript
复制
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])

如果你对Open3D感兴趣,或者正在使用该开源方案,就请加入我们,一起翻译,一起学习,贡献自己的力量,目前阶段主要以微信群为主,有意者发送“Open3D学习计划”到公众号后台,和更多热爱分享的小伙伴一起交流吧!如果翻译的有什么问题或者您有更好的意见,请评论交流!!!!

以上内容如有错误请留言评论,欢迎指正交流。如有侵权,请联系删除