Visualize point cloud 点云可视化
读取点云文件并可视化:
import open3d as o3d import numpy as np #读取点云文件(.ply、.pcd、.xzy等格式) pcd = o3d.io.read_point_cloud(filepath) #可视化点云,用鼠标可以选择视图,+-(小键盘区可能不行,用主键盘区的+-)可以修改点大小 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])
其中:
read_point_cloud 从文件读取点云。支持的文件类型有.ply, .pcd, .xyz等。
draw_geometries 可视化点云。可以在可视化窗口中用鼠标切换视角。也可以用键盘进行一些操作,如-可以减小点的大小。按H获取按键说明。
open3D 自带了一些点云数据,用下面的方法可以从github下载点云文件:
print("Load a ply point cloud, print it, and render it") ply_point_cloud = o3d.data.PLYPointCloud() pcd = o3d.io.read_point_cloud(ply_point_cloud.path) 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])
由于github直接连不上去(网络经常抽风),这里手动复制下载地址通过代理下载(fragment.ply)。
import open3d as o3d import numpy as np print("Load a ply point cloud, print it, and render it") ply_point_cloud_path = r'fragment.ply' #读取ply文件 pcd = o3d.io.read_point_cloud(ply_point_cloud_path) print(pcd) print(np.asarray(pcd.points)) #可视化ply文件 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])
Voxel downsampling 体素下采样
体素下采样
使用规格体素网格进行标准下采样。通常作为点云任务的预处理。算法有两步:
1.将点放入体素
2.每个被占用的体素通过平均内部的所有点来生成一个点。
import open3d as o3d import numpy as np print("Load a ply point cloud, print it, and render it") ply_point_cloud_path = r'fragment.ply' #读取ply文件 pcd = o3d.io.read_point_cloud(ply_point_cloud_path) print(pcd) print("Downsample the point cloud with a voxel of 0.05") downpcd = pcd.voxel_down_sample(voxel_size=0.05) print(downpcd) 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])
Vertex normal estimation 顶点法线估计
点的法线估计。按N
查看法线。-
+
可以控制法线显示的长度。
estimate_normals
计算每个点的法线,该函数使用协方差分析查找相邻点并计算相邻点的主轴。
该函数将类KDTreeSearchParamHybrid
的实例作为参数。两个关键参数radius = 0.1
max_nn = 30
,指定搜索半径
和最大最近邻数
。示例的搜索半径为10cm,最大考虑30个邻居,以节省计算时间。
import open3d as o3d import numpy as np print("Load a ply point cloud, print it, and render it") ply_point_cloud_path = r'fragment.ply' #读取ply文件 pcd = o3d.io.read_point_cloud(ply_point_cloud_path) print(pcd) print("Downsample the point cloud with a voxel of 0.05") downpcd = pcd.voxel_down_sample(voxel_size=0.05) print(downpcd) 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)
Access estimated vertex normal 访问估计的顶点法线
通过downpcd.normals[idx]
访问法线
print("Print a normal vector of the 0th point") print(downpcd.normals[0])
要查看其他变量,请使用help(downpcd)
。法线数组可以使用 np.asarray
转换为 numpy 数组。
print("Print the normal vectors of the first 10 points") print(np.asarray(downpcd.normals)[:10,:])
Crop point cloud 裁剪点云
import open3d as o3d print("Load a polygon volume and use it to crop the original point cloud") demo_crop_data_point_cloud_path = r'fragment.ply' demo_crop_data_cropped_json_path = r'cropped.json' pcd = o3d.io.read_point_cloud(demo_crop_data_point_cloud_path) vol = o3d.visualization.read_selection_polygon_volume(demo_crop_data_cropped_json_path) 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)
过滤点,只保留椅子。
Paint point cloud 点云涂色
paint_uniform_color
将所有点颜色变为统一颜色。 颜色形式是RGB , 值在[0, 1]范围 。
#Paint 涂色 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])
Point cloud distance 点云距离
Open3D 提供了计算从源点云到目标点云的距离的方法compute_point_cloud_distance
,它为源点云中的每个点计算到目标点云中最近点的距离。
在下面的示例中,我们使用该函数来计算两个点云之间的差异。请注意,此方法还可用于计算两个点云之间的倒角(Chamfer)距离。
import open3d as o3d import numpy as np # Load data demo_crop_data_point_cloud_path = r'fragment.ply' demo_crop_data_cropped_json_path = r'cropped.json' pcd = o3d.io.read_point_cloud(demo_crop_data_point_cloud_path) vol = o3d.visualization.read_selection_polygon_volume(demo_crop_data_cropped_json_path) chair = vol.crop_point_cloud(pcd) #计算距离,去除椅子 dists = pcd.compute_point_cloud_distance(chair) dists = np.asarray(dists) ind = np.where(dists > 0.01)[0] pcd_without_chair = pcd.select_by_index(ind) o3d.visualization.draw_geometries([pcd_without_chair], zoom=0.3412, front=[0.4257, -0.2125, -0.8795], lookat=[2.6172, 2.0475, 1.532], up=[-0.0694, -0.9768, 0.2024])
Bounding volumes 边界框
PointCloud
几何类型与 Open3D 中的所有其他几何体类型一样具有边界体积块(Bounding
volumes)。目前,Open3D 实现了AxisAlignedBoundingBox
和OrientedBoundingBox
,也可用于裁剪几何图形。
#轴对齐边框 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])
Convex hull 凸壳
点云的凸壳是包含所有点的最小凸集。Open3D 包含计算点云的凸壳的方法compute_convex_hull
。该实现基于 Qhull。
在下面的示例代码中,我们首先从网格中对点云进行采样,并计算作为三角形网格返回的凸壳。然后,我们将凸壳可视化为红色。
import open3d as o3d bunny_path = r'BunnyMesh.ply' mesh = o3d.io.read_triangle_mesh(bunny_path) mesh.compute_vertex_normals() pcl = 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])
DBSCAN clustering DBSCAN聚类
给定点云,我们希望将局部点云聚集在一起。为此,我们可以使用聚类算法。Open3D实现了
DBSCAN [Ester1996],这是一种基于密度的聚类算法。该算法在 cluster_dbscan中实现,需要两个参数:eps定义到聚类中相邻元素的距离,min_points定义形成聚类所需的最小点数。该函数返回lebels ,其中标签-1指噪声。
import open3d as o3d import numpy as np from matplotlib import pyplot as plt ply_point_cloud_path = 'fragment.ply' pcd = o3d.io.read_point_cloud(ply_point_cloud_path) 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])
此算法预计算所有点的 epsilon 半径内的所有邻居。如果所选的 epsilon 太大,则可能需要大量内存。
Plane segmentation 平面分割
Open3D 还支持使用 RANSAC 对点云中的几何基元进行分割。要查找点云中支撑最大的平面,我们可以使用segment_plane
。该方法有三个参数:distance_threshold
定义点到估计平面的最大距离才能被视为入值(inlier),ransac_n定义随机采样点数,以及num_iterations定义随机平面采样和验证的频率。然后,该函数返回平面(a,b,c,d),以便对于平面上的每个点(x,y,z),我们都有ax+by+cz+d=0 。该函数进一步返回inlier点的索引列表。
import open3d as o3d pcd_point_cloud_path = r'fragment.pcd' pcd = o3d.io.read_point_cloud(pcd_point_cloud_path) 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])
Hidden point removal 隐藏点移除
假设您想从给定的视点渲染点云,但背景中的点泄漏到前景中,因为它们没有被其他点遮挡。为此,我们可以应用隐藏点删除算法。在 Open3D 中,[实现了 Katz2007] 的方法,该方法从给定视图近似点云的可见性,而无需表面重建或法向估计。
import open3d as o3d import numpy as np print("Convert mesh to a point cloud and estimate dimensions") armadillo_path = r'ArmadilloMesh.ply' mesh = o3d.io.read_triangle_mesh(armadillo_path) mesh.compute_vertex_normals() pcd = 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])