文章目录
- 点云数据介绍
- 点云
- Open3D
- 点云基础操作
- RGBD转换为PCD
- PLY转PCD
- 点云空间搜索
- K-DTree
- 原理
- 搜索算法
- OCTree
- 点云滤波
- 体素下采样
- 点云体素化
- 复杂点云体素化
- 统计滤波
- 半径滤波
- 点云特征提取
- 法线估计
- 点云分割
- DBSCAN 聚类分割(运行时间较长)
- PANSNC平面分割
- 隐藏点剔除
- 点云曲面重建
- Alpha shapes
点云数据介绍
点云与三维图像的关系:三维图像是一种特殊的信息表达形式,其特征是表达的空间中三个维度的数据,表现形式包括:
- 深度图(以灰度表达物体与相机的距离),
- 几何模型(由CAD软件建立),
- 点云模型(所有逆向工程设备都将物体采样成点云)。
和二维图像相比,三维图像借助第三个维度的信息,可以实现天然的物体——背景解耦。
点云数据是最为常见也是最基础的三维模型。点云模型往往由测量直接得到,每个点对应一个测量点,未经过其他处理手段,故包含了最大的信息量。
这些信息隐藏在点云中需要以其他提取手段将其萃取出来,提取点云中信息的过程则为三维图像处理。
点云
点云是某个坐标系下的点的数据集。点包含了丰富的信息,包括三维坐标 X,Y,Z、颜色、分类值、强度值、时间等等。点云在组成特点上分为两种,一种是有序点云,一种是无序点云。
- 有序点云:
一般由深度图还原的点云,有序点云按照图方阵一行一行的,从左上角到右下角排列,当然其中有一些无效点。
有序点云按顺序排列,可以很容易的找到它的相邻点信息(邻近搜索),如果某些点数据丢失或无效,通常会被标记为“NaN”或其他标识符。
有序点云在某些处理的时候还是很便利的,但是很多情况下是无法获取有序点云的。 - 无序点云:
无序点云就是其中的点的集合,点排列之间没有任何顺序,点的顺序交换后没有任何影响。
是比较普遍的点云形式,有序点云也可看做无序点云来处理。
点的位置是基于其在物理空间中的实际位置,而不是在某种预定义的网格或数组中的位置。
误区扫盲
从视觉上看,有序点云和无序点云可能看起来非常相似,特别是当它们表示相同的物体或场景时。关键的区别在于它们的数据结构和生成方式,而不仅仅是它们的外观。
点云的有序性或无序性与其颜色无关。有序性和无序性描述的是点云数据的结构和组织方式。而颜色是点云的一个额外属性,可以独立于点云的结构存在。
相较于2维图像,其点的值代表的是色彩,点云数据的点不是像素信息,而是位置信息。
Open3D
Open3D是一个开源库,支持快速开发处理3D数据的软件。0pen3D后端是用C++实现的,经过高度优化并通过Pvthon的前端接口公开。Open3D提供了三种数据结构:点云Q(point coud)、网格(mesh)和RGB-D图像。对于每个表示,open3D都实现了一整套基本处理算法,如I/0、采样、可视化和数据转换。此外,还包括一些常用的算法,如法线估计、ICP配准等。
下面,将以Open3D
为工具,进行对点云数据的相关操作。
点云基础操作
RGBD转换为PCD
import open3d as o3dcolor_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 o3ddef 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-tree是一个二叉树,每个节点表示一个空间范围:
- Node-data:数据矢量,数据集中具体的某个数据点,是k维矢量
- Range:空间矢量,该节点所代表的空间范围
- split:整数,垂直于分割超平面的方向轴序号
- Left:kd-tree,由位于该节点分割超平面左子空间内所有数据点所构成的kd-tree
- Right:kd-tree,由位于该节点分割超平面右子空间内所有数据点所构成的kd-tree
- parent:kd-tree,父节点
先以一个简单直观的实例来介绍kd-tree算法。假设有6个二维数据点{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},数据点位于二维空间内。kd-tree算法就是要确定图1中这些分割空间的分割线(多维空间即为分割平面,一般为超平面)。下面就要通过一步步展示kd-tree是如何确定这些分割线的。
由于此例简单,数据维度只有2维,所以可以简单地给x,y两个方向轴编号为0,1,也即split={0,1}。
(1)确定split域的首先该取的值。分别计算x,y方向上数据的方差得知x方向上的方差最大,所以split域值首先取0,也就是x轴方向;
(2)确定Node-data的域值。根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以Node-data = (7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于split = 0(x轴)的直线x = 7;
(3)确定左子空间和右子空间。分割超平面x = 7将整个空间分为两部分,如图2所示。x < = 7的部分为左子空间,包含3个节点{(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点{(9,6),(8,1)}。
(4)kd-tree的构建是一个递归的过程。然后对左子空间和右子空间内的数据重复根节点的过程就可以得到下一级子节点(5,4)和(9,6)(也就是左右子空间的根节点),同时将空间和数据集进一步细分。如此反复直到空间中只包含一个数据点,最后生成的kd-tree如图3所示。
搜索算法
在kd-tree中进行数据的查找也是特征匹配的重要环节,其目的是检索在kd-tree中与查询点距离最近的数据点。这里先以一个简单的实例来描述最邻近查找的基本思路。
星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近。为了找到真正的最近邻,还需要进行回溯操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。
此例中先从(7,2)点开始进行二叉查找,然后到达(5,4),最后到达(2,3),此时搜索路径中的节点为<(7,2),(5,4),(2,3)>,首先以(2,3)作为当前最近邻点,计算其到查询点(2.1,3.1)的距离为0.1414,然后回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点。以(2.1,3.1)为圆心,以0.1414为半径画圆,如图4所示。发现该圆并不和超平面y=4交割,因此不用进入(5,4)节点右子空间中去搜索。
应用:利用kd
树评估点云分辨率
用点云分辨率(也有叫点间距、密度的)来评价点云的疏密程度。常用的计算方法就是针对点云中的每个点P,首先计算P的最近点Q到它的欧式距离。然后把所有点的计算结果取均值作为点间距。
import open3d as o3d
import numpy as npprint("->正在加载点云... ")
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])
体素下采样使用常规体素网格从输入点云创建统一下采样的点云。 它通常用作许多点云处理任务的预处理步骤。 该算法分两个步骤运行:
- 将点云进行进行体素划分,点被存储到体素中;
- 每个占用的体素通过平均内部的所有点来生成精确的一个点。一般而言取体素内点云的质心作为该体素的点的位置。
import open3d as o3d
import numpy as npprint("->正在加载点云... ")
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 npprint("->正在加载点云... ")
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 npprint("->正在加载点云... ")
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.1
和max_nn = 30
指定搜索半径和最大最近邻居。 它的搜索半径为10厘米
,最多可考虑30个邻居
,以节省计算时间。
import open3d as o3d
import numpy as npprint("->正在加载点云... ")
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 pltprint("->正在加载点云... ")
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 o3dprint("->正在加载点云... ")
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 randomdef plane_detection(pcd, tolerance = 50):current_pcd = pcdplanes = []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:breakinlier_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)**2endpoint = point_in_plane + normal_vector * 2line = 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, planesdef 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 npprint("->正在加载点云... ")
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])
点云曲面重建
应用 :曲面重建技术在逆向工程、数据可视化、机器视觉、虚拟现实、医疗技术等领域中得到了广泛的应用 。 例如,在汽车、航空等工业领域中,复杂外形产品的设计仍需要根据手工模型,采用逆向工程的手段建立产品的数字化模型,根据测量数据建立人体以及骨骼和器官的计算机模型,在医学、定制生产等方面都有重要意义 。
在许多情况下,我们希望生成密集的三维几何体,即三角形网格。然而,从多视图立体方法或深度传感器中,我们只能获得非结构化点云。要从非结构化输入中获得三角形网格,我们需要执行曲面重建。文献中有几种方法,Open3D目前实现了以下功能:
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
时效果如下: