所谓的索引滤波压根就不是滤波,只是根据索引,将点选出而已,总共有两种选取方法,一是选取索引对应的点;二是选取索引之外的点。
import open3d as o3d import numpy as np pcd = o3d.io.read_point_cloud("rabbit.pcd") idx = np.arange(10000) # 索引对应的点 pIn = pcd.select_by_index(idx) pIn.paint_uniform_color([1, 0, 0]) # 索引外的点云 pOut = pcd.select_by_index(idx, invert=True) pOut.paint_uniform_color([0, 1, 0]) o3d.visualization.draw_geometries([pIn, pOut])python运行
效果为

这两中滤波方法都是先得到符合要求的点索引,然后通过索引滤波,将这些点挑选出来,输出输出为滤波后的点云和点的索引号。
# 上接索引滤波的内容 pcd1 = copy.deepcopy(pcd).translate((20, 0, 0)) # 统计滤波,参数分别表示K邻域点的个数和标准差乘数 sPcd, sInd = pcd1.remove_statistical_outlier(6, 2.0) pcd2 = copy.deepcopy(pcd).translate((40, 0, 0)) # 半径滤波,输入参数为邻域球内最少点数和邻域半径 rPcd, rInd = pcd2.remove_radius_outlier(9, 0. ) o3d.visualization.draw_geometries([sPcd, rPcd])python运行
效果如下

这两种算法的逻辑是一样的,对于某点 x x x,选取距离 x x x最近的一些点,如果这些点的标准差小于设定值,则符合统计滤波的标准;如果均小于邻域半径,则符合半径滤波的标准。
体素滤波则会为点云构建三维体素格网,然后输出格网内的点云质心,所以这种滤波方式没有索引号的概念,其输入参数为体素尺寸。
downpcd = pcd.voxel_down_sample(20)python运行
DBSCAN,即Density-Based Spatial Clustering of Applications with Noise,基于密度的噪声应用空间聚类。
在DBSCAN算法中,将数据点分为三类:
可见,DBSCAN算法需要两个参数,分别是邻域半径 ε \varepsilon ε和点数 M M M。
在open3d中,提供了cluster_dbscan接口,
import open3d as o3d import numpy as np import matplotlib.pyplot as plt pcd = o3d.io.read_point_cloud("rabbit.pcd") eps = 0.5 # 同一聚类中最大点间距 M = 50 # 有效聚类的最小点数 Labels = np.array(pcd.cluster_dbscan(eps, M)) print(np.max(Labels)) # 得到结果为3 cs = plt.get_cmap("jet")(Labels/3) # 伪彩映射 cs[labels < 0] = 0 # labels = -1 的簇为噪声,以黑色显示 pcd.colors = o3d.utility.Vector3dVector(cs[:, :3]) o3d.visualization.draw_geometries([pcd])python运行
结果如图所示

RANSAC,即RANdom SAmple Consensus,随机抽样一致算法。
以平面上的点集举例,假设点集中有一条直线 L L L, L L L外的点很少,均为噪声。
那么第一步,随机选取两个点连成一条直线 L ^ \hat L L^,那么这条直线有可能就是 L L L,也有可能是噪声连出来的莫名其妙的一条线。
接下来,随机抽取点集中的一些点,如果随机抽取的大部分点都落在 L L L附近,那么就说明 L ^ \hat L L^有很大的概率就是 L L L;否则说明不太像是 L L L。随着抽取出的直线越来越多,最后可以得到最接近 L L L的直线,从而完成了对点集的分割。
在Open3d中,提供了基于RANSAC算法的平面分割接口segment_plane
pcd = o3d.io.read_point_cloud("rabbit.pcd") d = 0.2 # 内点到平面模型的最大距离 n = 5 # 用于拟合平面的采样点数 nIter = 50 # 最大迭代次数 # 返回模型系数plane和内点索引ids,并赋值 plane, ids = pcd.segment_plane(d, n, nIter) # 平面方程 [a, b, c, d] = plane # 平面内点点云 iCloud = pcd.select_by_index(ids) iCloud.paint_uniform_color([0, 0, 1.0]) # 平面外点点云 oCloud = pcd.select_by_index(ids, invert=True) oCloud.paint_uniform_color([1.0, 0, 0]) # 可视化平面分割结果 o3d.visualization.draw_geometries([iCloud, oCloud])python运行
最后得到的结果为

本来以为平面会出现在兔子的底座上,没想到最后兔子被一分为二了……