open3d点云采样和滤波

文章目录

    • 索引和采样
    • 点云滤波
    • 曲面滤波
    • Taubin滤波

索引和采样

尽管点云的本质是三元数组的列表,但并没有提供类似方括号这种索引方案,而需通过【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])

效果如下,左侧为原始点云,右侧为索引之后的点云,其中红色表示选中的点,黑色表示索引之外的点。

open3d点云采样和滤波_第1张图片

有时,点云中点数太多,其处理和可视化过程会花费大量时间,为此需要对数据进行下采样,即进行抽稀处理,下面是三种不同的下采样结果

open3d点云采样和滤波_第2张图片

代码如下

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)

效果如下

open3d点云采样和滤波_第3张图片

【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做临近点的加权平均,二者滤波函数分别为

  • 均值滤波 v i = ∑ n ∈ N v n ∣ N ∣ v_i=\frac{\sum_{n\in N} v_n}{|N|} vi=NnNvn
  • Laplace滤波 v i = v i ⋅ λ ∑ n ∈ N w n v n − v i v_i=v_i\cdot\lambda\sum_{n\in N}w_nv_n-v_i vi=viλnNwnvnvi

其中, N N N为距离 v i v_i vi最近的点集(包括 v i v_i vi), ∣ N ∣ |N| N为点的个数。

这两种滤波方法,旨在将点云“抹匀”,而锐化滤波则倾向于挑刺,以凸显出不光滑的部分,其算法与均值滤波相似,但要用当前值减去其相邻点的平均值。

这三种滤波效果如下

open3d点云采样和滤波_第4张图片

第一排是均值滤波,第二排是拉普拉斯滤波;第三排是锐化滤波,实现代码如下

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)
  • 【filter_smooth_simple】为均值滤波函数,输入参数为迭代次数
  • 【filter_smooth_laplacian】为拉普拉斯滤波函数,输入参数为迭代次数和 λ \lambda λ
  • 【filter_sharpen】为锐化滤波函数,输入参数为迭代次数

Taubin滤波

均值滤波和拉普拉斯滤波有个共同的问题,随着临近点的数目不断增大,可以发现这个图形变得越来越细,为了解决这个问题,需要用到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=IW,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])

效果为

open3d点云采样和滤波_第5张图片

可见,就算临近点数达到了50,也没有出现变细的情况。

你可能感兴趣的:(python教程,Python,python,open3d,点云,滤波,点云处理)