网络知识 娱乐 爆肝5万字❤️Open3D 点云数据处理基础(Python版)

爆肝5万字❤️Open3D 点云数据处理基础(Python版)

在这里插入图片描述

Open3D 点云数据处理基础(Python版)

文章目录

    • 1 概述
    • 2 安装
      • 2.1 PyCharm 与 Python 安装
      • 2.3 Anaconda 安装
      • 2.4 Open3D 0.13.0 安装
      • 2.5 新建一个 Python 项目
    • 3 点云读写
    • 4 点云可视化
      • 2.1 可视化单个点云
      • 2.2 同一窗口可视化多个点云
      • 2.3 可视化的属性设置
    • 5 k-d tree 与 Octree
      • 5.1 k-d tree
      • 5.2 Octree
        • 5.2.1 从点云中构建Octree
        • 5.2.2 从体素栅格中构建Octree
    • 6 点云滤波
      • 6.1 体素下采样
      • 6.2 统计滤波
      • 6.3 半径滤波
    • 7 点云特征提取
      • 7.1 法线估计
    • 8 点云分割
      • 8.1 DBSCAN 聚类分割
      • 8.2 RANSAC 平面分割
      • 8.3 隐藏点剔除
    • 9 点云曲面重建
      • 9.1 Alpha shapes
      • 9.2 Ball pivoting
      • 9.3 Poisson surface reconstruction
        • 9.3.1 直接读取点云的方法
        • 9.3.2 mesh方法
    • 10 点云空间变换
      • 10.1 Translate 平移
        • 10.1.1 指定平移行向量,实现点云平移
        • 10.1.2 将点云质心平移到指定位置
      • 10.2 Rotation 旋转
        • 10.2.1 使用欧拉角旋转
          • 10.2.1.1 未指定旋转中心
          • 10.2.1.2 指定旋转中心
        • 10.2.2 使用轴向角旋转
        • 10.2.3 使用四元数旋转
      • 10.3 Scale 缩放
      • 10.4 General transformation 一般变换(平移+旋转)
    • 11 点云配准
    • 12 其他常用算法
      • 12.1 计算点云间的距离
      • 12.2 计算点云最小包围盒
      • 12.3 计算点云凸包
      • 12.4 点云体素化
        • 12.4.1 一种简单的方法
        • 12.1.2 复杂方法
      • 12.5 计算点云质心
      • 12.6 根据索引提取点云
      • 12.7 点云赋色

历时一个月,Open3D 点云数据处理基础(Python版) 终于初具雏形!目前主要是一些基础内容,后续会继续完善,希望互相学习!


1 概述

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

open3D包含了九个模块,如下表所示:

模块功能
Geometry 几何模块数据结构和基本处理算法
Camera 相机模块相机模型和相机轨迹
Odometry 里程计模块RGB-D图像的跟踪与对齐
Registration 配准模块全局和局部配准
Integration 积分模块体积积分
I/O 输入输出模块读写3维数据
Visualization 可视化模块使用OpenGL呈现3D数据的可自定义GUI
Utility 辅助功能模块辅助功能,如控制台输出、文件系统和特征包装器
Python 模块Open3D Python绑定和教程

本文旨在快速入门Python点云数据处理,原理性知识会在后续专题中给出,这里不做过多描述。

2 安装

2.1 PyCharm 与 Python 安装

PyCharm Community 2021.2 安装教程(附汉化教程!)

2.3 Anaconda 安装

Anaconda3 2021 安装教程

2.4 Open3D 0.13.0 安装

在成功安装 Anaconda3 之后,在所有应用中找到 Anaconda3,选择 Anaconda Powershell Prompt

在这里插入图片描述
打开之后如下所示

在这里插入图片描述
手动输入

pip install open3d

开始安装 open3d-0.13.0,需要漫长的等待…

在这里插入图片描述
安装成功!

在这里插入图片描述

2.5 新建一个 Python 项目

文件 > 新建项目,输入(若路径不存在,则自动创建)或选择位置路径,选择已经由Conda配置好的解释器

在这里插入图片描述
新建Python文件

在这里插入图片描述
输入新建的Python文件名,按回车完成新建

在这里插入图片描述
测试代码

import open3d as o3d
import numpy as np

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

print("->正在保存点云")
o3d.io.write_point_cloud("write.pcd", pcd, True)	# 默认false,保存为Binarty;True 保存为ASICC形式
print(pcd)

在这里插入图片描述

3 点云读写

代码:

import open3d as o3d
import numpy as np

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

print("->正在保存点云")
o3d.io.write_point_cloud("write.pcd", pcd, True)	# 默认false,保存为Binarty;True 保存为ASICC形式
print(pcd)

输出结果:

->正在加载点云... 
PointCloud with 356478 points.
->正在保存点云
PointCloud with 356478 points.

默认情况下,Open3D尝试通过文件扩展名推断文件类型。支持以下点云文件类型:

格式描述
xyz每一行包含 [x, y, z], 其中的x, y, z 是三维坐标
xyzn每一行包含 [x, y, z, nx, ny, nz], 其中的 nx, ny, nz 是法线向量
xyzrgb每一行包含 [x, y, z, r, g, b], 其中的 r, g, b 是范围在 [0, 1]的float类型
pts第一行是表示点数的整数。每个后续行包含[x, y, z, i, r, g, b], 其中的 r, g, buint8类型
ply查看 Polygon File Format, ply文件能够同时包含点云和网格数据
pcd查看 Point Cloud Data

也可以显式地指定文件类型。在这种情况下,文件扩展名将被忽略。

pcd = o3d.io.read_point_cloud("../../test_data/my_points.txt", format='xyz')

4 点云可视化

2.1 可视化单个点云

代码:

import open3d as o3d
import numpy as np

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

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

输出结果:

->正在加载点云... 
PointCloud with 356478 points.
->正在可视化点云

可视化结果展示:

在这里插入图片描述

可以通过键盘上的 +-,实时调整点的大小。

2.2 同一窗口可视化多个点云

函数描述:

o3d.visualization.draw_geometries([pcd1, pcd2, ... ,pcdn])

代码:

import open3d as o3d
import numpy as np

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

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

print("->正在同时可视化两个点云...")
o3d.visualization.draw_geometries([pcd1, pcd2])

输出结果:

->正在加载点云1... 
PointCloud with 35947 points.
->正在加载点云2...
PointCloud with 35947 points.
->正在同时可视化两个点云...

可视化结果:

在这里插入图片描述

2.3 可视化的属性设置

函数原型1:

draw_geometries(geometry_list, window_name='Open3D', width=1920, height=1080, left=50, top=50, point_show_normal=False, mesh_show_wireframe=False, mesh_show_back_face=False)

参数说明

参数名描述
geometry_list要可视化的几何体列表
window_name(str, optional, default='Open3D'),可视化窗口的显示标题
width(int, optional, default=1920),可视化窗口的宽度
height(int, optional, default=1080),可视化窗口的高度
left(int, optional, default=50),可视化窗口的左边距
top(int, optional, default=50),可视化窗口的顶部边距
point_show_normal(bool, optional, default=False),如果设置为true,则可视化点法线,需要事先计算点云法线
mesh_show_wireframe(bool, optional, default=False),如果设置为true,则可视化网格线框
mesh_show_back_face(bool, optional, default=False),可视化网格三角形的背面

函数原型2:

draw_geometries(geometry_list, window_name='Open3D', width=1920, height=1080, left=50, top=50, point_show_normal=False, mesh_show_wireframe=False, mesh_show_back_face=False, lookat, up, front, zoom)

参数说明

参数名描述
geometry_list要可视化的几何体列表
window_name(str, optional, default='Open3D'),可视化窗口的显示标题
width(int, optional, default=1920),可视化窗口的宽度
height(int, optional, default=1080),可视化窗口的高度
left(int, optional, default=50),可视化窗口的左边距
top(int, optional, default=50),可视化窗口的顶部边距
point_show_normal(bool, optional, default=False),如果设置为true,则可视化点法线
mesh_show_wireframe(bool, optional, default=False),如果设置为true,则可视化网格线框
mesh_show_back_face(bool, optional, default=False),可视化网格三角形的背面
lookat(numpy.ndarray[float64[3, 1]]),相机的注视向量
up(numpy.ndarray[float64[3, 1]]),相机的上方向向量
front(numpy.ndarray[float64[3, 1]]),相机的前矢量
zoom(float),相机的缩放倍数

代码:

import open3d as o3d

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

# 法线估计
radius = 0.01   # 搜索半径
max_nn = 30     # 邻域内用于估算法线的最大点数
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius, max_nn))     # 执行法线估计

# 可视化
o3d.visualization.draw_geometries([pcd], 
                                  window_name = "可视化参数设置",
                                  width = 600,
                                  height = 450,
                                  left = 30,
                                  top = 30,
                                  point_show_normal = True)

可视化结果:

在这里插入图片描述

5 k-d tree 与 Octree

5.1 k-d tree

  • 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 个近邻点

代码:

import open3d as o3d
import numpy as np


print("->正在加载点云... ")
pcd = o3d.io.read_point_cloud("bunny.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 = 5000    # 设置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])

输出结果:

->正在加载点云... 
PointCloud with 35947 points.
使用K近邻,将第1500个点最近的5000个点设置为蓝色
k邻域内的点数为: 5000
使用半径R近邻,将第1500个点半径(0.02)范围内的点设置为红色
半径r邻域内的点数为: 751
使用混合邻域,将第1500个点半径R邻域内不超过max_num个点设置为绿色
混合邻域内的点数为: 200
->正在可视化点云...

结果展示:
在这里插入图片描述

5.2 Octree

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

5.2.1 从点云中构建Octree

可以使用convert_from_point_cloud方法从点云构造八叉树。通过遵循从根节点到“最大深度”(depth max_depth)处的相应叶节点的路径,将每个点插入到树中。随着树深度的增加,内部(最终是叶子)节点表示三维空间的较小分区。

如果点云具有颜色,则对应的叶节点将采用上次插入点的颜色。size_expand参数会增加根八叉树节点的大小,使其略大于原始点云边界以容纳所有点。

代码:

import open3d as o3d
import numpy as np

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

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

输出结果:

->正在加载点云... 
原始点云: PointCloud with 5746 points.
octree 分割
->正在可视化Octree...

结果展示:
在这里插入图片描述

5.2.2 从体素栅格中构建Octree

还可以使用create_from_voxel_grid的方法,从Open3D体素网格VoxelGrid几何体构建八叉树。每个体素被视为三维空间中的一个点,坐标对应于体素的原点。每个叶节点采用其相应体素的颜色。

代码:

import open3d as o3d
import numpy as np

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

# ------------------------- 构建Octree --------------------------
print('体素化')
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=0.2)
print("体素:", voxel_grid)
print("正在可视化体素...")
o3d.visualization.draw_geometries([voxel_grid])

print('Octree 分割')
octree = o3d.geometry.Octree(max_depth=4)
octree.create_from_voxel_grid(voxel_grid)
print("Octree:", octree)
print("正在可视化Octree...")
o3d.visualization.draw_geometries([octree])
# ==============================================================

输出结果:

->正在加载点云... 
原始点云: PointCloud with 5746 points.
体素化
体素: VoxelGrid with 861 voxels.
正在可视化体素...
Octree 分割
Octree: Octree with origin: [64.647, -54.2659, -20.2166], size: 7, max_depth: 4
正在可视化Octree...

可视化结果:

6 点云滤波

6.1 体素下采样

体素下采样使用规则体素栅格从输入点云创建均匀下采样点云。该算法分两步操作:

  • 将点云进行进行体素划分
  • 对所有非空体素,取体素内点云的质心作为该体素的点的位置。

代码:

import open3d as o3d
import numpy as np

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

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

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

print("->正在可视化下采样点云")
o3d.visualization.draw_geometries([downpcd])

输出结果:

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

可视化展示:

6.2 统计滤波

statistical_outlier_removal 会移除距离相邻点更远的点。它需要两个输入参数:

  • num_neighbors,指定为了计算给定点的平均距离,需要考虑多少个邻居。即K邻域的点数
  • std_ratio,允许根据点云平均距离的标准偏差设置阈值水平。该数值越低,滤除的点数就越多

代码:

import open3d as o3d
import numpy as np

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

# ------------------------- 统计滤波 --------------------------
print("->正在进行统计滤波...")
num_neighbors = 20 # 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 41049 points.
->正在进行统计滤波...
统计滤波后的点云: PointCloud with 40061 points.
噪声点云: PointCloud with 988 points.

可视化结果:

在这里插入图片描述

6.3 半径滤波

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

num_points,邻域球内的最少点数,低于该值的点为噪声点

radius ,邻域半径的大小

代码:

import open3d as o3d
import numpy as np


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

# ------------------------- 半径滤波 --------------------------
print("->正在进行半径滤波...")
num_points = 20  # 邻域球内的最少点数,低于该值的点为噪声点
radius = 0.05    # 邻域半径大小
# 执行半径滤波,返回滤波后的点云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 41049 points.
->正在进行半径滤波...
半径滤波后的点云: PointCloud with 40146 points.
噪声点云: PointCloud with 903 points.

可视化结果:
在这里插入图片描述

7 点云特征提取

7.1 法线估计

代码:

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 35947 points.
->正在估计法线并可视化...
->正在打印前10个点的法向量...
[[-0.22344398 -0.96962557  0.09949394]
 [-0.30282456 -0.91827757  0.25507564]
 [-0.0930339  -0.77633579 -0.62341594]
 [ 0.06452443 -0.96881599 -0.23923249]
 [ 0.24771039 -0.96349484  0.10157387]
 [ 0.1890532  -0.97541781  0.11322096]
 [-0.26920394 -0.95010988  0.15754506]
 [ 0.72941317  0.51298568  0.45255067]
 [ 0.83949302  0.5402317   0.05831961]
 [-0.32325253  0.62920765  0.7068278 ]]

可视化结果:
在这里插入图片描述

8 点云分割

8.1 DBSCAN 聚类分割

Open3D实现了DBSCAN[1996],这是一种基于密度的聚类算法。该算法在cluster_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