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

Posted 没事就要敲代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爆肝5万字❤️Open3D 点云数据处理基础(Python版)相关的知识,希望对你有一定的参考价值。

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

历时一个月,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.

可视化结果:
❤️爆肝万字整理的综合架构web服务之nginx详解❤️,附建议收藏

❤️爆肝万字!一文最全总结之Spring从入门到入土❤️(建议收藏)

Python OpenCV实战画图——这次一定能行!爆肝万字,建议点赞收藏~❤️❤️❤️

❤️爆肝十二万字《python从零到精通教程》,从零教你变大佬❤️(建议收藏)

❤️爆肝四万字的MySQL总结全面整理+详细解释❤️

❤️爆肝十八万字《python从零到精通教程》第二版,贴心保姆教你从零变大佬❤️(建议收藏),学不会找我!