如有错误,恳请指出。
这篇文章主要是介绍点云采样与聚类的实现。
点云下采样是对点云以一定的采样规则重新进行采样,目的是在保证点云整体几何特征不变的情况下,降低点云的密度,进而可以降低相关处理的数据量和算法复杂度。下面介绍三种下采样方式:体素下采样、均匀下采样、随机下采样
体素下采样就是把落在每个体素中的点用一个点来表示。这个点的坐标取值就是体素中所有点坐标的平均值,也就是体素中各个点的质心,点云质心可以通过pcd.get_center()或者是简单的np.mean(axis=1)来获得。
Open3d中体素的下采样函数有两个接口:voxel_down_sample
与voxel_down_sample_and_trace
,参数说明如下所示:
def voxel_down_sample(self, voxel_size): # real signature unknown; restored from __doc__
"""
voxel_down_sample(self, voxel_size)
Function to downsample input pointcloud into output pointcloud with a voxel. Normals and colors are averaged if they exist.
Args:
voxel_size (float): Voxel size to downsample into.
Returns:
open3d.geometry.PointCloud 直接返回点云pcd
"""
pass
def voxel_down_sample_and_trace(self, voxel_size, min_bound, max_bound, approximate_class=False): # real signature unknown; restored from __doc__
"""
voxel_down_sample_and_trace(self, voxel_size, min_bound, max_bound, approximate_class=False)
Function to downsample using PointCloud.VoxelDownSample. Also records point cloud index before downsampling
Args:
voxel_size (float): Voxel size to downsample into.
min_bound (numpy.ndarray[numpy.float64[3, 1]]): Minimum coordinate of voxel boundaries 体素边界的最小坐标
max_bound (numpy.ndarray[numpy.float64[3, 1]]): Maximum coordinate of voxel boundaries 体素边界的最大坐标
approximate_class (bool, optional, default=False) :approximate_class就是用来控制采样后的点云颜色
当approximate_class=True时,体素采样后的点的颜色由体素中大多数点的颜色决定
当approximate_class=False时,体素采样后的点的颜色由体素中所有点的平均颜色决定
Returns:
Tuple[open3d.geometry.PointCloud, numpy.ndarray[numpy.int32[m, n]], List[open3d.utility.IntVector]]
返回两个内容:稀疏后的点云pcd 与 稀疏后的点云在原点云中的索引
"""
pass
参考代码:
def open3d_downsample():
# 读取点云文件
pcd_path = r"E:\Study\Machine Learning\Dataset3d\points_pcd\cat.pcd"
pcd = open3d.io.read_point_cloud(pcd_path)
pcd = open3d.geometry.PointCloud(pcd)
# 数值越大,下采样倍数越高,也就越稀疏
voxel_size = 10
pcd_sample1 = deepcopy(pcd)
pcd_sample1.translate([60, 0, 0]) # x轴左移60(向左)
pcd_sample1.paint_uniform_color(color=[0, 0, 1]) # 蓝色
pcd_sample1 = pcd_sample1.voxel_down_sample(voxel_size=voxel_size)
bound = (pcd_sample1.get_min_bound(), pcd_sample1.get_max_bound())
print(bound)
pcd_sample2 = deepcopy(pcd)
pcd_sample2.translate([0, 0, 60]) # z轴正向移60(向上)
pcd_sample2.paint_uniform_color(color=[0, 1, 0]) # 绿色
pcd_sample2 = pcd_sample2.voxel_down_sample_and_trace(voxel_size=voxel_size,
min_bound=pcd_sample2.get_min_bound() + 10,
max_bound=pcd_sample2.get_max_bound() - 10,
approximate_class=False)[0]
bound = (pcd_sample2.get_min_bound(), pcd_sample2.get_max_bound())
print(bound)
# 可视化点云列表
open3d.visualization.draw_geometries([pcd, pcd_sample1, pcd_sample2],
window_name="pointcloud downsampler",
width=800,
height=600)
均匀采样是指每隔固定的点数采样一次。样本按点的顺序执行,始终选择从第 1 个点开始,而不是随机选择。显然点存储的顺序不同,得到的结果也会不一样。从这个角度来看,这种方法比较适合有序点云的降采样。
如果点云本身不均匀,那么以固定点数采样很有可能造成某一部分的点云没被采样到。相比于体素的采样方法,点云均匀采样后的点数是固定可控的,而体素采样后的点云数量是不可控的。(ps:随机采样后的点数也是可控的)
参数说明:
def uniform_down_sample(self, every_k_points): # real signature unknown; restored from __doc__
"""
uniform_down_sample(self, every_k_points)
Function to downsample input pointcloud into output pointcloud uniformly. The sample is performed in the order of the points with the 0-th point always chosen, not at random.
Args:
every_k_points (int): Sample rate, the selected point indices are [0, k, 2k, ...]
Returns:
open3d.geometry.PointCloud
"""
pass
参考代码:
# 均匀下采样
pcd_sample4 = deepcopy(pcd)
pcd_sample4.translate([-60, 0, 0]) # z轴负向移60(向下)
pcd_sample4 = pcd_sample4.uniform_down_sample(every_k_points=10)
print('pcd_sample3 center: ', pcd_sample4.get_center())
在原始点云中随机采样一定点数的点,open3d中的随机采样函数为random_down_sample,其参数是采样后点云数量相对于原始点云数量的比例。
参数说明:
def random_down_sample(self, sampling_ratio): # real signature unknown; restored from __doc__
"""
random_down_sample(self, sampling_ratio)
Function to downsample input pointcloud into output pointcloud randomly. The sample is generated by randomly sampling the indexes from the point cloud.
Args:
sampling_ratio (float): Sampling ratio, the ratio of number of selected points to total number of points[0-1]
Returns:
open3d.geometry.PointCloud
"""
pass
参考代码:
# 随机下采样
pcd_sample3 = deepcopy(pcd)
pcd_sample3.translate([0, 0, -60]) # z轴负向移60(向下)
pcd_sample3 = pcd_sample3.random_down_sample(sampling_ratio=0.2)
print('pcd_sample3 center: ', pcd_sample3.get_center())
除open3d的方法外,也可以自己自定义随机采样的方式(利用numpy的随机模块),这样会更加灵活。numpy的随机模块笔记:Numpy | np.random随机模块的使用介绍
points = np.array(pcd.points)
n = np.random.choice(len(points), 500, replace=False) # 从points中以相同的几率随机采样500个点,同时还可以设置权重p,返回的是原始点云中的索引
pcd.points = open3d.utility.Vector3dVector(points[n])
完整代码:
def open3d_downsample():
# 读取点云文件
pcd_path = r"E:\Study\Machine Learning\Dataset3d\points_pcd\cat.pcd"
pcd = open3d.io.read_point_cloud(pcd_path)
pcd = open3d.geometry.PointCloud(pcd)
pcd.paint_uniform_color(color=[0.5, 0.5, 0.5])
# 数值越大,下采样倍数越高
voxel_size = 10
pcd_sample1 = deepcopy(pcd)
pcd_sample1.translate([60, 0, 0]) # x轴左移60(向左)
pcd_sample1.paint_uniform_color(color=[0, 0, 1]) # 蓝色
pcd_sample1 = pcd_sample1.voxel_down_sample(voxel_size=voxel_size)
bound = (pcd_sample1.get_min_bound(), pcd_sample1.get_max_bound())
print(bound)
pcd_sample2 = deepcopy(pcd)
pcd_sample2.translate([0, 0, 60]) # z轴正向移60(向上)
pcd_sample2.paint_uniform_color(color=[0, 1, 0]) # 绿色
pcd_sample2 = pcd_sample2.voxel_down_sample_and_trace(voxel_size=voxel_size,
min_bound=pcd_sample2.get_min_bound() + 10,
max_bound=pcd_sample2.get_max_bound() - 10,
approximate_class=False)[0]
bound = (pcd_sample2.get_min_bound(), pcd_sample2.get_max_bound())
print(bound)
# 随机下采样
pcd_sample3 = deepcopy(pcd)
pcd_sample3.translate([0, 0, -60]) # z轴负向移60(向下)
pcd_sample3 = pcd_sample3.random_down_sample(sampling_ratio=0.2)
print('pcd_sample3 center: ', pcd_sample3.get_center())
# 均匀下采样
pcd_sample4 = deepcopy(pcd)
pcd_sample4.translate([-60, 0, 0]) # z轴负向移60(向下)
pcd_sample4 = pcd_sample4.uniform_down_sample(every_k_points=10)
print('pcd_sample3 center: ', pcd_sample4.get_center())
# 可视化点云列表
open3d.visualization.draw_geometries([pcd, pcd_sample1, pcd_sample2, pcd_sample3, pcd_sample4],
window_name="pointcloud downsampler",
width=800,
height=600)
参考资料:
1. 三种点云下采样方法(一) — open3d python
2. 三种点云下采样方法(二)— open3d python
在sklearn工具包中,有大量的聚类算法提供使用,这里的点云数据可以有效的配置sklearn工具包,以实现效果。其中,open3d也自带了dbscan聚类的使用接口。这里不涉及具体的聚类算法解析。
open3d中DBSCAN聚类方法的函数为cluster_dbscan,且有且仅有这么一种聚类算法,所以建议使用sklearn进行聚类。(可以跳过这节直接看下一节)
第一个参数eps表示DBSCAN算法确定点密度时和邻近点的距离大小,即考虑eps距离范围内的点进行密度计算,如果点云数据过于稀疏,那么这里的参数需要设置得大一些,确保部分点可以实现连通。min_points表示组成一类最少需要多少个点。print_progress可以用来显示运行的进度。
labels返回聚类成功的类别,-1表示没有分到任何类中的点,原始点云中每个点都会分别得到一个类别标签。
参考代码:
# 点云聚类测试
def open3d_cluster():
# 读取点云文件
pcd_path = r"E:\Study\Machine Learning\Dataset3d\points_pcd\cat.pcd"
pcd = open3d.io.read_point_cloud(pcd_path)
pcd = open3d.geometry.PointCloud(pcd)
# 聚类距离设置为4,组成一类至少需要20个点
labels = pcd.cluster_dbscan(eps=4, min_points=20, print_progress=True)
max_label = max(labels)
print(max_label)
# 随机构建n+1种颜色,这里需要归一化
colors = np.random.randint(1, 255, size=(max_label + 1, 3)) / 255.
colors = colors[labels] # 每个点云根据label确定颜色
colors[np.array(labels) < 0] = 0 # 噪点配置为黑色
pcd.colors = open3d.utility.Vector3dVector(colors) # 格式转换(由于pcd.colors需要设置为Vector3dVector格式)
# 可视化点云列表
open3d.visualization.draw_geometries([pcd],
window_name="cluster",
width=800,
height=600)
我们可以利用sklearn中丰富的聚类算法库,为点云进行聚类。Sklearn中的参数在 机器学习学习专栏 中有介绍到。
这里尝试将猫点云聚类成3类,并用不同的颜色进行显示
import open3d
import numpy as np
from copy import deepcopy
from sklearn import cluster
def open3d_sklearn_cluster():
# 读取点云文件
pcd_path = r"E:\Study\Machine Learning\Dataset3d\points_pcd\cat.pcd"
pcd = open3d.io.read_point_cloud(pcd_path)
pcd = open3d.geometry.PointCloud(pcd)
pcd.paint_uniform_color(color=[0, 0, 0])
# 点云聚类
n_clusters = 3 # 聚类簇数
points = np.array(pcd.points)
kmeans = cluster.KMeans(n_clusters=n_clusters, random_state=42)
kmeans.fit(points)
labels = kmeans.labels_
# 显示颜色设置
colors = np.random.randint(0, 255, size=(n_clusters, 3)) / 255
colors = colors[labels]
pcd_cluster = deepcopy(pcd)
pcd_cluster.translate([50, 0, 0])
pcd_cluster.colors = open3d.utility.Vector3dVector(colors)
# 点云可视化
open3d.visualization.draw_geometries([pcd, pcd_cluster],
window_name="sklearn cluster",
width=800,
height=600)
Sklearn聚类的效果还是相当好的,直接将猫规规矩矩的分成了3个部分。
def open3d_sklearn_cluster():
# 读取点云文件
pcd_path = r"E:\Study\Machine Learning\Dataset3d\points_pcd\cat.pcd"
pcd = open3d.io.read_point_cloud(pcd_path)
pcd = open3d.geometry.PointCloud(pcd)
pcd.paint_uniform_color(color=[0, 0, 0])
# 点云聚类
points = np.array(pcd.points)
dbscan = cluster.DBSCAN(eps=4, min_samples=20) # 使用DBSCAN算法进行聚类
dbscan.fit(points)
labels = dbscan.labels_
# 显示颜色设置
colors = np.random.randint(0, 255, size=(max(labels) + 1, 3)) / 255 # 需要设置为n+1类,否则会数据越界造成报错
colors = colors[labels] # 很巧妙,为每个label直接分配一个颜色
colors[labels < 0] = 0 # 噪点直接设置为0,用黑色显示
pcd_cluster = deepcopy(pcd)
pcd_cluster.translate([50, 0, 0])
pcd_cluster.colors = open3d.utility.Vector3dVector(colors)
# 点云可视化
open3d.visualization.draw_geometries([pcd, pcd_cluster],
window_name="sklearn cluster",
width=800,
height=600)
与open3d官方实现的效果类似,但是sklearn可以有效兼容,也就是说点云聚类直接使用sklearn工具即可。其他的聚类算法直接根据API进行稍微更改即可使用,这里的代码参数也不过介绍,在我的机器学习专栏中有笔记介绍过,详细见:机器学习学习专栏
参考资料:
1. 八种点云聚类方法(一)— DBSCAN
2. 八种点云聚类方法(二)— KMeans
3. Sklearn官方手册:cluster部分
RANSAC为Random Sample Consensus的缩写,即随机抽样一致性,它是根据一组包含异常数据的样本数据集,计算出数据的数学模型参数,得到有效样本数据的算法。RANSAC算法的基本理论基础是大数定律,也就是当采样数达到一定数量后采样的数据就会符合它自身原有的概率属性。这是一种通过概率的方式来进行拟合。
以RANSAC平面分割为例,由于三个点可以确定一个平面,因此RANSAC会随机选择三个点来构建一个平面,并用点云中实际上有多少个点落到这个平面上来作为评估这个平面的正确程度。当随机抽样的次数足够多时,我们有较大概率获得所需要的平面: A x + B y + C z + D = 0 Ax + By + Cz + D = 0 Ax+By+Cz+D=0
def segment_plane(self, distance_threshold, ransac_n, num_iterations, seed=None): # real signature unknown; restored from __doc__
"""
segment_plane(self, distance_threshold, ransac_n, num_iterations, seed=None)
Segments a plane in the point cloud using the RANSAC algorithm.
Args:
distance_threshold (float): Max distance a point can be from the plane model, and still be considered an inlier.
ransac_n (int): Number of initial points to be considered inliers in each iteration.
num_iterations (int): Number of iterations.
seed (Optional[int], optional, default=None): Seed value used in the random generator, set to None to use a random seed value with each function call.
Returns:
Tuple[numpy.ndarray[numpy.float64[4, 1]], List[int]]
返回: (A,B,C,D)作为一个平面,对于平面上每个点(x,y,z)满足上面的平面方程; 这个函数还会返回内点索引的列表
"""
测试代码:
def open3d_segment():
# 读取点云文件
pcd_path = r"E:\Study\Machine Learning\Dataset3d\points_pcd\cat.pcd"
pcd = open3d.io.read_point_cloud(pcd_path)
pcd = open3d.geometry.PointCloud(pcd)
pcd.paint_uniform_color(color=[0.5, 0.5, 0.5])
plane_model, inliers = pcd.segment_plane(distance_threshold=1, ransac_n=10, 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")
colors = np.array(pcd.colors)
colors[inliers] = [0, 0, 1] # 平面内的点设置为蓝色
pcd.colors = open3d.utility.Vector3dVector(colors)
# 点云可视化
open3d.visualization.draw_geometries([pcd],
window_name="segment",
width=800,
height=600)
参考资料:
两种点云分割(一)— RANSAC分割平面
简要归纳各函数的使用方法,最后贴上完整代码与展示效果
open3d中对应的函数为TriangleMesh.create_from_point_cloud_alpha_shape
,其关键参数为alpha。alpha是该方法在搜索外轮廓时的半径大小。alpha值越小,网格的细节就越多,分辨率越高。
# 1. Alpha shapes轮廓提取
pcd_rc1 = deepcopy(pcd)
pcd_rc1.translate([-100, 0, 50])
mesh_rc1 = open3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd_rc1, alpha=6)
mesh_rc1 = open3d.geometry.TriangleMesh(mesh_rc1)
mesh_rc1.paint_uniform_color([1, 0, 0]) # 红色
open3d中对应的函数为TriangleMesh.create_from_point_cloud_ball_pivoting
,其关键参数为radii。radii是滚球的半径,而且可以设置多个值,也就是可以用多个尺寸的滚球来进行三角面构建。重建需要先计算点云法向量。
# 2. Ball pivoting滚球算法
pcd_rc2 = deepcopy(pcd)
pcd_rc2.translate([-50, 0, 50])
pcd_rc2.estimate_normals( # 法向量计算
search_param=open3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30)
)
radii = [2, 5, 8]
mesh_rc2 = open3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
pcd=pcd_rc2,
radii=open3d.utility.DoubleVector(radii)
)
mesh_rc2 = open3d.geometry.TriangleMesh(mesh_rc2)
mesh_rc2.paint_uniform_color([0, 1, 0]) # 绿色
open3d中对应的函数为 TriangleMesh.create_from_point_cloud_poisson
。该函数的一个重要参数是depth,它定义了用于曲面重建的八叉树的深度,因此表示生成的三角形网格的分辨率。depth值越高,网格的细节就越多,分辨率越高。
create_from_point_cloud_poisson除返回重建的表面之外,还会返回各处重建后的点密度,通过设置一个阈值来去除一些低密度处的重建结果(可以配合分位点来设置)。重建需要先计算点云法向量。
# 3. Poisson泊松曲面重建
pcd_rc3 = deepcopy(pcd)
pcd_rc3.translate([0, 0, 50])
pcd_rc3.estimate_normals( # 法向量计算
search_param=open3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30)
)
mesh_rc3, densities = open3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd_rc3, depth=12)
mesh_rc3 = open3d.geometry.TriangleMesh(mesh_rc3)
# 设置阈值去除低密度重建结果
threshold_value = np.quantile(densities, 0.35) # 寻找低密度列表中35%的分位数,返回的是数值而不是索引
vertices_to_remove = densities < threshold_value # 低于分位数的值设置为False, 依次对其进行消除
mesh_rc3.remove_vertices_by_mask(vertices_to_remove)
mesh_rc3.paint_uniform_color([0, 0, 1])
open3d中对应的函数为 VoxelGrid.create_from_point_cloud
,其关键参数体素尺寸voxel_size=0.05。尺寸越小,网格的细节就越多,分辨率越高。严格上来说,这种方法只是一种下采样的效果。
# 4. Voxel体素重建
pcd_rc4 = deepcopy(pcd)
pcd_rc4.translate([50, 0, 50])
pcd_rc4.paint_uniform_color(color=[0, 1, 1])
mesh_rc4 = open3d.geometry.VoxelGrid.create_from_point_cloud(pcd_rc4, voxel_size=2)
完整代码展示:
def open3d_reconstruction():
# 读取点云文件
pcd_path = r"E:\Study\Machine Learning\Dataset3d\points_pcd\cat.pcd"
pcd = open3d.io.read_point_cloud(pcd_path)
pcd = open3d.geometry.PointCloud(pcd)
pcd.paint_uniform_color(color=[0, 0, 0])
# 1. Alpha shapes轮廓提取
pcd_rc1 = deepcopy(pcd)
pcd_rc1.translate([-100, 0, 50])
mesh_rc1 = open3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd_rc1, alpha=6)
mesh_rc1 = open3d.geometry.TriangleMesh(mesh_rc1)
mesh_rc1.paint_uniform_color([1, 0, 0]) # 红色
# 2. Ball pivoting滚球算法
pcd_rc2 = deepcopy(pcd)
pcd_rc2.translate([-50, 0, 50])
pcd_rc2.estimate_normals( # 法向量计算
search_param=open3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30)
)
radii = [2, 5, 8]
mesh_rc2 = open3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
pcd=pcd_rc2,
radii=open3d.utility.DoubleVector(radii)
)
mesh_rc2 = open3d.geometry.TriangleMesh(mesh_rc2)
mesh_rc2.paint_uniform_color([0, 1, 0]) # 绿色
# 3. Poisson泊松曲面重建
pcd_rc3 = deepcopy(pcd)
pcd_rc3.translate([0, 0, 50])
pcd_rc3.estimate_normals( # 法向量计算
search_param=open3d.geometry.KDTreeSearchParamHybrid(radius=0.01, max_nn=30)
)
mesh_rc3, densities = open3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd_rc3, depth=12)
mesh_rc3 = open3d.geometry.TriangleMesh(mesh_rc3)
# 设置阈值去除低密度重建结果
threshold_value = np.quantile(densities, 0.35) # 寻找低密度列表中35%的分位数,返回的是数值而不是索引
vertices_to_remove = densities < threshold_value # 低于分位数的值设置为False, 依次对其进行消除
mesh_rc3.remove_vertices_by_mask(vertices_to_remove)
mesh_rc3.paint_uniform_color([0, 0, 1])
# 4. Voxel体素重建
pcd_rc4 = deepcopy(pcd)
pcd_rc4.translate([50, 0, 50])
pcd_rc4.paint_uniform_color(color=[0, 1, 1])
mesh_rc4 = open3d.geometry.VoxelGrid.create_from_point_cloud(pcd_rc4, voxel_size=2)
# 点云可视化
pcd.translate([-25, 0, 0])
open3d.visualization.draw_geometries([pcd, mesh_rc1, mesh_rc2, mesh_rc3, mesh_rc4],
window_name="rebuild",
width=800,
height=600)
参考资料:
三维点云重建 — open3d python