自学内容网 自学内容网

点云与Open3D

点云数据介绍

点云与三维图像的关系:三维图像是一种特殊的信息表达形式,其特征是表达的空间中三个维度的数据,表现形式包括:

  • 深度图(以灰度表达物体与相机的距离),
  • 几何模型(由CAD软件建立),
  • 点云模型(所有逆向工程设备都将物体采样成点云)。

和二维图像相比,三维图像借助第三个维度的信息,可以实现天然的物体——背景解耦。
点云数据是最为常见也是最基础的三维模型。点云模型往往由测量直接得到,每个点对应一个测量点,未经过其他处理手段,故包含了最大的信息量。
这些信息隐藏在点云中需要以其他提取手段将其萃取出来,提取点云中信息的过程则为三维图像处理。

点云是某个坐标系下的点的数据集。点包含了丰富的信息,包括三维坐标 X,Y,Z、颜色、分类值、强度值、时间等等。点云在组成特点上分为两种,一种是有序点云,一种是无序点云。

  • 有序点云:一般由深度图还原的点云,有序点云按照图方阵一行一行的,从左上角到右下角排列,当然其中有一些无效点。有序点云按顺序排列,可以很容易的找到它的相邻点信息。有序点云在某些处理的时候还是很便利的,但是很多情况下是无法获取有序点云的。
  • 无序点云:无序点云就是其中的点的集合,点排列之间没有任何顺序,点的顺序交换后没有任何影响。是比较普遍的点云形式,有序点云也可看做无序点云来处理。

在这里插入图片描述

在这里插入图片描述

相较于2维图像,其点的值代表的是色彩,点云数据的点不是像素信息,而是位置信息。

Open3D

Open3D是一个开源库,支持快速开发处理3D数据的软件。0pen3D后端是用C++实现的,经过高度优化并通过Pvthon的前端接口公开。Open3D提供了三种数据结构:点云Q(point coud)、网格(mesh)和RGB-D图像。对于每个表示,open3D都实现了一整套基本处理算法,如I/0、采样、可视化和数据转换。此外,还包括一些常用的算法,如法线估计、ICP配准等。

在这里插入图片描述

下面,将以Open3D为工具,进行对点云数据的相关操作。

点云基础操作

RGBD转换为PCD

import open3d as o3d

color_raw = o3d.io.read_image("../RGBD/color/00000.jpg")
depth_raw = o3d.io.read_image("../RGBD/depth/00000.png")
#创建一个rgbd图像
rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(
    color_raw, depth_raw)
print(rgbd_image)

#使用matplotlib显示图像
import matplotlib.pyplot as plt
plt.subplot(1, 2, 1)
plt.title('Redwood grayscale image')
plt.imshow(rgbd_image.color)
plt.subplot(1, 2, 2)
plt.title('Redwood depth image')
plt.imshow(rgbd_image.depth)
plt.show()

#rgbd 转==》pcd ,并显示
pcd = o3d.geometry.PointCloud.create_from_rgbd_image(
    rgbd_image,
    o3d.camera.PinholeCameraIntrinsic(
        o3d.camera.PinholeCameraIntrinsicParameters.PrimeSenseDefault))
# Flip it, otherwise the pointcloud will be upside down
pcd.transform([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])
o3d.visualization.draw_geometries([pcd])
o3d.io.write_point_cloud("../save.pcd", pcd, format='auto', write_ascii=False, compressed=False, print_progress=True)

RGB图像

在这里插入图片描述

深度图像

在这里插入图片描述

PLY转PCD

import open3d as o3d

def convert_ply_to_pcd(ply_file, pcd_file):
    # 读取PLY文件
    point_cloud = o3d.io.read_point_cloud(ply_file)
    # 保存为PCD文件
    o3d.io.write_point_cloud(pcd_file, point_cloud)
# 使用示例
ply_file_path = '../bun000.ply'  #填入ply文件的路径
pcd_file_path = '../bun000.pcd'  #填入pcd文件的路径
convert_ply_to_pcd(ply_file_path, pcd_file_path)

点云空间搜索

无论点云的来源如何,点云终归是离散了的点集,点与点的关联是孤立的。在诸多点云相关的应用中,恢复点云数据的拓扑关系,得到点与点之间的邻近关系,都是首要操作。

K-DTree

k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构。主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。K-D树是二进制空间分割树的特殊的情况。 索引结构中相似性查询有两种基本的方式:一种是范围查询(range searches), 另一种是K近邻查询(K-neighbor searches)。

  • 范围查询就是给定查询点和查询距离的阈值,从数据集中找出所有与查询点距离小于阈值的数据;
  • K近邻查询是给定查询点及正整数K,从数据集中找到距离查询点最近的K个数据,当K=1时,就是最近邻查询(nearest neighbor searches)。

o3d.geometry.KDTreeFlann(pcd):创建KDTree
search_knn_vector_3d(search Pt,k):K近邻搜索
search radius vector 3d(search pt,radius):半径R近邻搜索
search_hybrid_vector_3d(search_pt,radius,max nn):混合邻域搜索,返回半径 radius 内不超过 max_nn 个近邻点

应用:利用kd树评估点云分辨率
用点云分辨率(也有叫点间距、密度的)来评价点云的疏密程度。常用的计算方法就是针对点云中的每个点P,首先计算P的最近点Q到它的欧式距离。然后把所有点的计算结果取均值作为点间距。

import open3d as o3d
import numpy as np


print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("bun000.pcd")
print(pcd)

# 将点云设置为灰色
pcd.paint_uniform_color([0.5, 0.5, 0.5])

# 建立KDTree
pcd_tree = o3d.geometry.KDTreeFlann(pcd)

# 将第1500个点设置为紫色
pcd.colors[1500] = [0.5, 0, 0.5]

# 使用K近邻,将第1500个点最近的5000个点设置为蓝色
print("使用K近邻,将第1500个点最近的5000个点设置为蓝色")
k = 2000   # 设置K的大小
[num_k, idx_k, _] = pcd_tree.search_knn_vector_3d(pcd.points[1500], k)    # 返回邻域点的个数和索引
np.asarray(pcd.colors)[idx_k[1:], :] = [0, 0, 1]  # 跳过最近邻点(查询点本身)进行赋色
print("k邻域内的点数为:", num_k)

# 使用半径R近邻,将第1500个点半径(0.02)范围内的点设置为红色
print("使用半径R近邻,将第1500个点半径(0.02)范围内的点设置为红色")
radius = 0.02   # 设置半径大小
[num_radius, idx_radius, _] = pcd_tree.search_radius_vector_3d(pcd.points[1500], radius)   # 返回邻域点的个数和索引
np.asarray(pcd.colors)[idx_radius[1:], :] = [1, 0, 0]  # 跳过最近邻点(查询点本身)进行赋色
print("半径r邻域内的点数为:", num_radius)

# 使用混合邻域,将半径R邻域内不超过max_num个点设置为绿色
print("使用混合邻域,将第1500个点半径R邻域内不超过max_num个点设置为绿色")
max_nn = 200   # 半径R邻域内最大点数
[num_hybrid, idx_hybrid, _] = pcd_tree.search_hybrid_vector_3d(pcd.points[1500], radius, max_nn)
np.asarray(pcd.colors)[idx_hybrid[1:], :] = [0, 1, 0]  # 跳过最近邻点(查询点本身)进行赋色
print("混合邻域内的点数为:", num_hybrid)

print("->正在可视化点云...")
o3d.visualization.draw_geometries([pcd])

在这里插入图片描述

OCTree

八叉树(Octree)是一种树数据结构,其中每个内部节点有八个子节点。八叉树通常用于三维点云的空间划分。八叉树的非空叶节点包含属于同一空间细分的一个或多个点。八叉树是三维空间的有用描述,可用于快速査找附近的点。Open3D具有几何体类型的八叉树,可用于创建、搜索和遍历具有用户指定的最大树深度的八又树 max_depth
左边是八叉树地图,右面是八叉树。

在这里插入图片描述

从点云中构建Octree

可以使用 convert_from_point_c1oud方法从点云构造八叉树。通过遵循从根节点到“最大深度”(depth max depth)处的相应叶节点的路径,将每个点插入到树中。随着树深度的增加,内部(最终是叶子)节点表示三维空间的较小分区。
如果点云具有颜色,则对应的叶节点将采用上次插入点的颜色。 sizeexpand 参数会增加根八叉树节点的大小,使其略大于原始点云边界以容纳所有点。

import open3d as o3d
import numpy as np

# --------------------------- 加载点云 ---------------------------
print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("bun000.pcd")
print("原始点云:", pcd)

# ------------------------- 构建Octree --------------------------
print('octree 分割')
octree = o3d.geometry.Octree(max_depth=5)
octree.convert_from_point_cloud(pcd, size_expand=0.1)
print("->正在可视化Octree...")
o3d.visualization.draw_geometries([octree])

随着max_depth的值不断变大,其节点越细腻,max_depth=1
在这里插入图片描述
max_depth=3时:
在这里插入图片描述
max_depth=5时:

在这里插入图片描述

点云滤波

体素下采样

在这里,我们需要先来了解体素的概念:
体素体积元素(Volume Pixel)的简称,包含体素的立体可以通过立体渲染或者提取给定阈值轮廓的多边形等值面表现出来。一如其名,体素是数字数据于三维空间分割上的最小单位,体素用于三维成像、科学数据与医学影像等领域。概念上类似二维空间的最小单位——像素,像素用在二维计算机图像的影像数据上。有些真正的三维显示器运用体素来描述它们的分辨率,举例来说:可以显示512×512×512体素的显示器。

点云体素化

import open3d as o3d
import numpy as np

# --------------------------- 加载点云 ---------------------------
print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("../bunny.pcd")
print("原始点云:", pcd)

# --------------------------- 体素化点云 -------------------------
print('执行体素化点云')
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=0.005)
print("正在可视化体素...")
o3d.visualization.draw_geometries([voxel_grid])

在这里插入图片描述

复杂点云体素化

import open3d as o3d
import numpy as np

# ---------------------- 定义点云体素化函数 ----------------------
def get_mesh(_relative_path):
    mesh = o3d.io.read_triangle_mesh(_relative_path)
    mesh.compute_vertex_normals()
    return mesh

# ------------------------- 点云体素化 --------------------------
print("->正在进行点云体素化...")
_relative_path = "bunny.ply"    # 设置相对路径
N = 2000        # 将点划分为N个体素
pcd = get_mesh(_relative_path).sample_points_poisson_disk(N)

# fit to unit cube
pcd.scale(1 / np.max(pcd.get_max_bound() - pcd.get_min_bound()),
          center=pcd.get_center())
pcd.colors = o3d.utility.Vector3dVector(np.random.uniform(0, 1, size=(N, 3)))
print("体素下采样点云:", pcd)
print("正在可视化体素下采样点云...")
o3d.visualization.draw_geometries([pcd])

print('执行体素化点云')
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=0.05)
print("正在可视化体素...")
o3d.visualization.draw_geometries([voxel_grid])

体素下采样使用常规体素网格从输入点云创建统一下采样的点云。 它通常用作许多点云处理任务的预处理步骤。 该算法分两个步骤运行:

  1. 将点云进行进行体素划分,点被存储到体素中;
  2. 每个占用的体素通过平均内部的所有点来生成精确的一个点。一般而言取体素内点云的质心作为该体素的点的位置。
import open3d as o3d
import numpy as np

print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("../bun000.pcd")
print(pcd)

print("->正在可视化原始点云")
#o3d.visualization.draw_geometries([pcd])

print("->正在体素下采样...")
voxel_size = 0.001
downpcd = pcd.voxel_down_sample(voxel_size)
print(downpcd)

运行结果如下:

->正在加载点云... 
PointCloud with 40256 points.
->正在可视化原始点云
->正在体素下采样...
PointCloud with 376 points.
->正在可视化下采样点云

Process finished with exit code 0

原始点云图像可视化结果

在这里插入图片描述
voxel_size = 0.01时的体素下采样结果
在这里插入图片描述

voxel_size = 0.001时的体素下采样结果
在这里插入图片描述

统计滤波

statistical outlier_removal 会移除距离相邻点更远的点。它需要两个输入参数:
num neighbors指定为了计算给定点的平均距离,需要考虑多少个邻居。
std ratio即K邻域的点数允许根据点云平均距离的标准偏差设置阈值水平。该数值越低,滤除的点数就越多

import open3d as o3d
import numpy as np

print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("../bun000.pcd")
print("原始点云:", pcd)

# ------------------------- 统计滤波 --------------------------
print("->正在进行统计滤波...")
num_neighbors = 200 # K邻域点的个数
std_ratio = 2.0 # 标准差乘数
# 执行统计滤波,返回滤波后的点云sor_pcd和对应的索引ind
sor_pcd, ind = pcd.remove_statistical_outlier(num_neighbors, std_ratio)
sor_pcd.paint_uniform_color([0, 0, 1])
print("统计滤波后的点云:", sor_pcd)
sor_pcd.paint_uniform_color([0, 0, 1])
# 提取噪声点云
sor_noise_pcd = pcd.select_by_index(ind,invert = True)
print("噪声点云:", sor_noise_pcd)
sor_noise_pcd.paint_uniform_color([1, 0, 0])
# ===========================================================

# 可视化统计滤波后的点云和噪声点云
o3d.visualization.draw_geometries([sor_pcd, sor_noise_pcd])

输出结果如下:

->正在加载点云... 
原始点云: PointCloud with 40256 points.
->正在进行统计滤波...
统计滤波后的点云: PointCloud with 38287 points.
噪声点云: PointCloud with 1969 points.

Process finished with exit code 0

统计滤波后的可视化效果
在这里插入图片描述

噪声点云可视化
在这里插入图片描述
两者放到一起的效果
在这里插入图片描述

半径滤波

radius_outlier_removal 移除给定球体中几乎没有邻居的点。需要两个参数:

  • num points,邻域球内的最少点数,低于该值的点
  • radius,为噪声点邻域半径的大小
import open3d as o3d
import numpy as np


print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("../bun000.pcd")
print("原始点云:", pcd)

# ------------------------- 半径滤波 --------------------------
print("->正在进行半径滤波...")
num_points = 600  # 邻域球内的最少点数,低于该值的点为噪声点
radius = 0.5    # 邻域半径大小
# 执行半径滤波,返回滤波后的点云sor_pcd和对应的索引ind
sor_pcd, ind = pcd.remove_radius_outlier(num_points, radius)
sor_pcd.paint_uniform_color([0, 0, 1])
print("半径滤波后的点云:", sor_pcd)
sor_pcd.paint_uniform_color([0, 0, 1])
# 提取噪声点云
sor_noise_pcd = pcd.select_by_index(ind,invert = True)
print("噪声点云:", sor_noise_pcd)
sor_noise_pcd.paint_uniform_color([1, 0, 0])
# ===========================================================

# 可视化半径滤波后的点云和噪声点云
o3d.visualization.draw_geometries([sor_pcd, sor_noise_pcd])

从结果来看,并没有获取到噪声点云,该处存疑

->正在加载点云... 
原始点云: PointCloud with 40256 points.
->正在进行半径滤波...
半径滤波后的点云: PointCloud with 40256 points.
噪声点云: PointCloud with 0 points.
[Open3D WARNING] The number of points is 0 when creating axis-aligned bounding box.
Process finished with exit code 0

在这里插入图片描述

点云特征提取

法线估计

平面的法线是垂直于它的单位向量。在点云的表面的法线被定义为垂直于与点云表面相切的平面的向量。表面法线也可以计算点云中一点的法线,被认为是一种十分重要的性质。常常在被使用在很多计算机视觉的应用里面,比如可以用来推出光源的位置。

estimate_normals计算每个点的法线。 该函数查找相邻点并使用协方差分析计算相邻点的主轴。该函数将KDTreeSearchParamHybrid类的实例作为参数。 两个关键参数radius = 0.1max_nn = 30指定搜索半径和最大最近邻居。 它的搜索半径为10厘米,最多可考虑30个邻居,以节省计算时间。

import open3d as o3d
import numpy as np

print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("bunny.pcd")
print(pcd)

print("->正在估计法线并可视化...")
radius = 0.01   # 搜索半径
max_nn = 30     # 邻域内用于估算法线的最大点数
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius, max_nn))     # 执行法线估计
o3d.visualization.draw_geometries([pcd], point_show_normal=True)

print("->正在打印前10个点的法向量...")
print(np.asarray(pcd.normals)[:10, :])
->正在加载点云... 
PointCloud with 40256 points.
->正在估计法线并可视化...
->正在打印前10个点的法向量...

[[ 0.77476957  0.0802818  -0.62712594]
 [ 0.73903438  0.08114461 -0.66876284]
 [ 0.84369636 -0.03701667 -0.53554292]
 [ 0.82255391  0.02284367 -0.56822816]
 [ 0.76720104  0.04538926 -0.6397987 ]
 [ 0.7161365   0.0340495  -0.69712922]
 [ 0.68580508  0.04654068 -0.72629564]
 [ 0.67949057  0.04708572 -0.73217177]
 [ 0.67285149  0.01863072 -0.73954295]
 [ 0.65776332 -0.01315822 -0.75310974]]

Process finished with exit code 0

在这里插入图片描述

点云分割

根据空间、几何和纹理等特征点进行划分,同一划分内的点云拥有相似的特征。

点云分割的目的是分块,从而便于单独处理。

DBSCAN 聚类分割(运行时间较长)

DBSCAN (基于密度的带噪声应用空间聚类), 其通过点的密度来识别聚类集群。聚类通常位于高密度区域,而异常值往往位于低密度区域。使用该算法的 3个主要优势(根据该算法的先驱者的说法)是:它只需要最少的领域知识,可以发现任意形状的聚类,并且对于大型数据库来说非常高效。

Open3D实现了DBSCAN,这是一种基于密度的聚类算法口。该算法在 c1uster_dbscan 中实现,需要两个参数:

  • eps 为同一簇内的最大点间距,
  • min_points 定义有效聚类的最小点数。

函数返回标签 label,其中 label = -1 表示噪声。

import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt

print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("test.pcd")
print(pcd)

print("->正在DBSCAN聚类...")
eps = 0.5           # 同一聚类中最大点间距
min_points = 50     # 有效聚类的最小点数
with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm:
    labels = np.array(pcd.cluster_dbscan(eps, min_points, print_progress=True))
max_label = labels.max()    # 获取聚类标签的最大值 [-1,0,1,2,...,max_label],label = -1 为噪声,因此总聚类个数为 max_label + 1
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  # labels = -1 的簇为噪声,以黑色显示
pcd.colors = o3d.utility.Vector3dVector(colors[:, :3])
o3d.visualization.draw_geometries([pcd])

运行结果如下:

->正在加载点云... 
PointCloud with 40256 points.
->正在DBSCAN聚类...
[Open3D DEBUG] Precompute neighbors.
[Open3D DEBUG] Done Precompute neighbors.
[Open3D DEBUG] Compute Clusters
Precompute neighbors.[========================================] 100%
[Open3D DEBUG] Done Compute Clusters: 1
point cloud has 1 clusters
Clustering[========================================] 100%

Process finished with exit code 0

如下面在对兔子进行分割时,由于其只有兔子这一个实体,因此其并没有表现出不同类别物体的分割效果,这是正常的。

在这里插入图片描述

其运行过程中要使用GPU,并且在一瞬间的利用率会较高

在这里插入图片描述

随后,我们对室内场景进行点云分割,运行这个分割过程耗时很长,大概一个小时

->正在加载点云... 
PointCloud with 267129 points.
->正在DBSCAN聚类...
[Open3D DEBUG] Precompute neighbors.
[Open3D DEBUG] Done Precompute neighbors.
[Open3D DEBUG] Compute Clusters
Precompute neighbors.[========================================] 100%
[Open3D DEBUG] Done Compute Clusters: 1
point cloud has 1 clusters
[Open3D WARNING] [ViewControl] SetViewPoint() failed because window height and width are not set.
Clustering[========================================] 100%

Process finished with exit code 0

在这里插入图片描述

PANSNC平面分割

算法原理和流程

RANSAC(Random Sample Consensus)是一种迭代的参数估计算法,主要用于从包含大量噪声数据的样本中估计模型参数。其核心思想是通过随机采样和模型验证来找到数据中最符合模型假设的点。RANSAC 算法步骤:
1.初始化:设置最大迭代次数 max iterations 和内点阈值 distance threshold
2.随机采样:从数据集中随机选择最小数量的样本点来拟合模型(例如,拟合平面需要三个点)。
3.模型估计:使用选定的样本点计算模型参数(例如,平面的方程)。
4.模型验证:计算所有数据点到模型的距离,将距离小于 distance threshold 的点标记为内点。
5.评估模型:计算内点的数量,如果内点数量超过预定的阈值并且模型质量优于之前的模型,则更新最佳模型
6.重复:重复上述步骤,直到达到最大迭代次数或者找到最优模型。

distance_threshold: 用于定义点到平面的最大距离阈值,单位为米如果点到平面的距离小于此阈值,则认为该点属于平面。默认值为0.01
ransac_n: 用于RANSAC算法的迭代次数。RANSAC是一种随机采样一致性算法,用于估计平面模型参数。默认值为3
num_iterations: 在RANSAC算法中,每次迭代时使用的随机样本点的数量。默认值为10000

应用场景

RANSAC 平面分割算法广泛应用于计算机视觉和点云处理领域,特别适用于以下场景:
1.点云平面分割:从三维点云数据中提取平面,如地面、墙壁等。
2.图像拼接:用于匹配图像中的特征点并估计变换矩阵。
3.3D 物体识别:从点云数据中提取特定形状或结构。

import open3d as o3d

print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("test.pcd")
print(pcd)

print("->正在RANSAC平面分割...")
distance_threshold = 0.2    # 内点到平面模型的最大距离
ransac_n = 3                # 用于拟合平面的采样点数
num_iterations = 1000       # 最大迭代次数

# 返回模型系数plane_model和内点索引inliers,并赋值
plane_model, inliers = pcd.segment_plane(distance_threshold, ransac_n, num_iterations)

# 输出平面方程
[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([0, 0, 1.0])
print(inlier_cloud)

# 平面外点点云,标记为红色
outlier_cloud = pcd.select_by_index(inliers, invert=True)
outlier_cloud.paint_uniform_color([1.0, 0, 0])
print(outlier_cloud)

# 可视化平面分割结果
o3d.visualization.draw_geometries([inlier_cloud, outlier_cloud])

运行结果如下:

->正在加载点云... 
PointCloud with 267129 points.
->正在RANSAC平面分割...
Plane equation: -0.01x + 0.66y + 0.75z + 1.14 = 0
PointCloud with 147574 points.
PointCloud with 119555 points.

Process finished with exit code 0

原始点云可视化:

在这里插入图片描述
PANSNC平面分割效果
在这里插入图片描述

但事实上,点云中有多个平面,上述方法只能分割出一个平面,那么该如何分割多个平面呢?

import open3d as o3d
import numpy as np
import random


def plane_detection(pcd, tolerance = 50):

    current_pcd = pcd
    planes = []
    while len(current_pcd.points) > tolerance:
        plane_model, inliers = current_pcd.segment_plane(distance_threshold=0.3, ransac_n=5, num_iterations=1000)
        if len(inliers) < tolerance:
            break
        inlier_indices = np.asarray(inliers)
        inlier_cloud = current_pcd.select_by_index(inlier_indices)
        current_pcd = current_pcd.select_by_index(inlier_indices, invert=True)

        normal_vector = plane_model[:3]
        point_in_plane = -normal_vector * plane_model[3] / np.linalg.norm(normal_vector)**2
        endpoint = point_in_plane + normal_vector * 2

        line = o3d.geometry.LineSet()
        line.points = o3d.utility.Vector3dVector([point_in_plane, endpoint])
        line.lines = o3d.utility.Vector2iVector([[0, 1]])

        planes.append(line)
        planes.append(inlier_cloud)

    return current_pcd, planes

def main(plypath):
    pcd = o3d.io.read_point_cloud(plypath)

    remain_pcd, planes = plane_detection(pcd)
    for plane in planes:
        plane.paint_uniform_color(np.random.rand(3))

    mesh_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=5.0, origin=[0,0,0])
    # 可视化结果
    o3d.visualization.draw_geometries([ remain_pcd, *planes,mesh_frame])

main(r"../save.pcd")

在这里插入图片描述

隐藏点剔除

假设您希望从给定的视点渲染点云,但背景中的点会泄漏到前景中,因为它们不会被其他点遮挡。为此,我们可以应用隐藏点移除算法。
在Open3D中,实现了Katz2007的方法,该方法从给定视图近似点云的可见性,无需曲面重建或法线估计。

import open3d as o3d
import numpy as np

print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("../bun000.pcd")
print(pcd)

print("->正在剔除隐藏点...")
diameter = np.linalg.norm(np.asarray(pcd.get_max_bound()) - np.asarray(pcd.get_min_bound()))
print("定义隐藏点去除的参数")
camera = [0, 0, diameter]       # 视点位置
radius = diameter * 100         # 噪声点云半径,The radius of the sperical projection
_, pt_map = pcd.hidden_point_removal(camera, radius)    # 获取视点位置能看到的所有点的索引 pt_map

# 可视点点云
pcd_visible = pcd.select_by_index(pt_map)
pcd_visible.paint_uniform_color([0, 0, 1])# 可视点为蓝色
print("->可视点个数为:", pcd_visible)
# 隐藏点点云
pcd_hidden = pcd.select_by_index(pt_map, invert = True)
pcd_hidden.paint_uniform_color([1, 0, 0])# 隐藏点为红色
print("->隐藏点个数为:", pcd_hidden)
print("->正在可视化可视点和隐藏点点云...")
o3d.visualization.draw_geometries([pcd_visible, pcd_hidden])

在这里插入图片描述

点曲面重建

Alpha shapes

Alpha shapes 是凸壳的推广。正如这里所描述的,我们可以直观地认为 Alpha shapes 如下:想象一个巨大的冰淇淋,其中包含点s作为硬巧克力块。使用其中一个球形冰淇淋勺,我们可以在不撞到巧克力块的情况下雕刻出冰淇淋块的所有部分,从而甚至在内部雕刻出孔(例如,仅从外部移动勺子无法触及的部分)。我们最终将得到一个(不一定是凸的)对象,该对象以帽、弧和点为边界。如果我们现在把所有的面拉直成三角形和线段,我们就可以直观地描述s的 Alpha shapes

Open3D实现了create_from_point_cloud_alpha_shape 方法

import open3d as o3d
import numpy as np

# --------------------------- 加载点云 ---------------------------
print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("../bunny.pcd")
print("原始点云:", pcd)

# ------------------------- Alpha shapes -----------------------
alpha = 0.03
print(f"alpha={alpha:.3f}")
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd, alpha)
mesh.compute_vertex_normals()
o3d.visualization.draw_geometries([mesh], mesh_show_back_face=True)

alpha = 0.03时效果如下:
在这里插入图片描述
alpha = 0.1时效果如下:
在这里插入图片描述


原文地址:https://blog.csdn.net/pengxiang1998/article/details/142485308

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!