尽管点云的本质是三元数组的列表,但并没有提供类似方括号这种索引方案,而需通过【select_by_index】方法来实现,其输入参数有二,ids为点号列表,invert参数默认为否,表示返回ids中的点,否则将返回ids之外的点,示例如下
import open3d as o3d
import numpy as np
from copy import deepcopy
pcdPath = o3d.data.KnotMesh()
pcd = o3d.io.read_point_cloud(pcdPath.path)
mesh = o3d.io.read_triangle_mesh(pcdPath.path)
mesh.compute_vertex_normals()
idx = np.arange(700)
pcdSlct = deepcopy(pcd).translate((200, 0, 0))
# 索引对应的点
pIn = pcdSlct.select_by_index(idx)
# 索引外的点云
pOut = pcdSlct.select_by_index(idx, invert=True)
pIn.paint_uniform_color([1, 0, 0])
pOut.paint_uniform_color([0, 0, 0])
o3d.visualization.draw_geometries([pcd, mesh, pIn, pOut])
效果如下,左侧为原始点云,右侧为索引之后的点云,其中红色表示选中的点,黑色表示索引之外的点。
有时,点云中点数太多,其处理和可视化过程会花费大量时间,为此需要对数据进行下采样,即进行抽稀处理,下面是三种不同的下采样结果
代码如下
meshes, downs = [], []
for i in range(3):
m = deepcopy(mesh).translate((200*i, 0, 0))
meshes.append(m)
p = deepcopy(pcd).translate((200*i, 0, 0))
downs.append(p)
downs[0] = downs[0].random_down_sample(0.3)
downs[1] = downs[1].uniform_down_sample(3)
downs[2] = downs[2].voxel_down_sample(20)
o3d.visualization.draw_geometries(meshes + downs)
其中,
【random_down_sample】是随机下采样,根据采样率,随机采集一些点,所以采样结果几乎失去了原始点云的轮廓信息。
【uniform_down_sample】是等序下采样,根据输入的采样间隔,则取等间隔的点的序号。
【voxel_down_sample】是体素下采样,会构建三维体素格网,然后输出格网内的点云质心,而非原始数据;若存在法线或颜色,则通通取均值。从处理结果来看,这种采样得到的点云,的确点和点之间分布更加均匀。
点云数据在获取之时,很容易出现类似Nan或Inf等值,在滤波之前,往往需要将这些坏点剔除。
【remove_non_finite_points】即可提供此功能,其输入为两个布尔型参数:remove_nan和remove_infinite。
在点云处理过程中,最常用的点云滤波方法是统计滤波和半径滤波,二者的滤波逻辑相似,都是先得到符合要求的点索引,然后通过索引滤波,将这些点挑选出来,输出滤波后的点云和点的索引号。
pcd1 = deepcopy(downs[0]).translate((200, 0, 0))
pcd2 = deepcopy(downs[0]).translate((400, 0, 0))
sPcd, sInd = pcd1.remove_statistical_outlier(30, 2.0)
rPcd, rInd = pcd2.remove_radius_outlier(5, 20)
o3d.visualization.draw_geometries([downs[0], sPcd, rPcd] + meshes)
效果如下
【remove_statistical_outlier】即为统计滤波函数,参数分别表示K邻域点的个数和标准差乘数。算法逻辑为,对于某点 x x x,选取距离 x x x最近的一些点,如果这些点的标准差小于设定值,则符合统计滤波的标准。
【remove_radius_outlier】是半径滤波函数,输入参数为邻域球内最少点数和邻域半径,对于某点 x x x,选取距离 x x x最近的一些点,如果均小于邻域半径,则符合半径滤波的标准。
上述两种滤波方法,都只利用了点云临近点的位置信息,但并没有使用法线。如果有了法线,那么点云中实际上就针对法线定义了一组组平面,换言之,点云升级为了曲面。若将法线作为滤波时需要考虑的因素,那么点云滤波也就升级为了曲面滤波。
均值滤波和Laplace滤波是最常用的曲面滤波方法,都是对顶点 v i v_i vi做临近点的加权平均,二者滤波函数分别为
其中, N N N为距离 v i v_i vi最近的点集(包括 v i v_i vi), ∣ N ∣ |N| ∣N∣为点的个数。
这两种滤波方法,旨在将点云“抹匀”,而锐化滤波则倾向于挑刺,以凸显出不光滑的部分,其算法与均值滤波相似,但要用当前值减去其相邻点的平均值。
这三种滤波效果如下
第一排是均值滤波,第二排是拉普拉斯滤波;第三排是锐化滤波,实现代码如下
import numpy as np
# 添加噪声的曲面
meshNoise = deepcopy(mesh)
vertices = np.asarray(meshNoise.vertices)
vertices += np.random.uniform(0, 5, size=vertices.shape)
meshNoise.vertices = o3d.utility.Vector3dVector(vertices)
meshNoise.compute_vertex_normals()
simPara = (1,10,50)
lapPara = [[1,0.5], [10,0.5], [50,0.5]]
sharpPara = [1, 2, 3]
meshAve = [deepcopy(meshNoise)]
meshLap = [deepcopy(meshNoise).translate((0,200,0))]
meshSharp = [deepcopy(mesh).translate((0,400,0))]
for i in range(3):
dx = 250 * (i+1)
tmp = deepcopy(meshAve[0]).translate((dx,0,0))
tmp = tmp.filter_smooth_simple(simPara[i])
meshAve.append(tmp)
tmp = deepcopy(meshLap[0]).translate((dx,0,0))
tmp = tmp.filter_smooth_laplacian(*lapPara[i])
meshLap.append(tmp)
tmp = deepcopy(meshSharp[0]).translate((dx,0,0))
tmp = tmp.filter_sharpen(sharpPara[i])
meshSharp.append(tmp)
meshes = meshAve + meshLap + meshSharp
for m in meshes:
m.compute_vertex_normals()
o3d.visualization.draw_geometries(meshes)
均值滤波和拉普拉斯滤波有个共同的问题,随着临近点的数目不断增大,可以发现这个图形变得越来越细,为了解决这个问题,需要用到Taubin滤波。
Taubin滤波在Laplace滤波的基础上又加了一个负的 μ \mu μ参数,且要求 0 < λ < − μ 0<\lambda<-\mu 0<λ<−μ。
记第 i i i个点的坐标为 ( x i , y i , z i ) (x_i, y_i, z_i) (xi,yi,zi),则所有点的坐标可以组成一个 N × 3 N\times 3 N×3的矩阵,记作 X X X。令
X ′ = ( I − λ K ) X X ′ ′ = ( I − μ K ) X X'=(I-\lambda K)X\\ X''=(I-\mu K)X\\ X′=(I−λK)XX′′=(I−μK)X
其中, I I I是单位矩阵, K = I − W K=I-W K=I−W,W为 N × N N\times N N×N的加权矩阵,其元素 W i j W_{ij} Wij表示第 i , j i,j i,j的两个点是否相邻,如果不相邻则置零。
则经过 M M M次迭代之后,得到
X M = ( ( I − λ K ) ( I − λ K ) ) M X X^M=((I-\lambda K)(I-\lambda K))^MX XM=((I−λK)(I−λK))MX
taibin = []
for i in [1, 10, 50]:
taibin.append(mesh.filter_smooth_taubin(i))
taibin[-1].compute_vertex_normals()
taibin[-1].translate([200*len(taibin),0,0])
o3d.visualization.draw_geometries(taibin+[mesh])
效果为
可见,就算临近点数达到了50,也没有出现变细的情况。