第一章:Open3D 基础入门 第一章:Open3D 基础入门 1.1 Open3D 简介:3D 数据处理的瑞士军刀 在 3D 数据处理领域,我们常常面临各种挑战:复杂的数据结构、繁琐的算法实现、以及对性能的苛刻要求。Open3D 的出现,正是为了解决这些痛点。它集成了多种功能,包括但不限于: 3D 数据结构: 高效地表示和操作点云 (Point Cloud)、网格 (Mesh)、图像 (Image)、RGBD 图像 (RGBD Image) 和体素网格 (Voxel Grid) 等。 几何处理: 提供丰富的几何算法,例如表面重建、点云配准、网格简化、特征提取等。 可视化: 内置强大的 3D 可视化工具,方便用户直观地观察和分析 3D 数据。
在 3D 数据处理领域,我们常常面临各种挑战:复杂的数据结构、繁琐的算法实现、以及对性能的苛刻要求。Open3D 的出现,正是为了解决这些痛点。它集成了多种功能,包括但不限于:
3D 数据结构: 高效地表示和操作点云 (Point Cloud)、网格 (Mesh)、图像 (Image)、RGBD 图像 (RGBD Image) 和体素网格 (Voxel Grid) 等。
几何处理: 提供丰富的几何算法,例如表面重建、点云配准、网格简化、特征提取等。
可视化: 内置强大的 3D 可视化工具,方便用户直观地观察和分析 3D 数据。
场景理解: 支持场景重建、语义分割、目标检测等高级功能。
机器人学集成: 提供与机器人学相关的模块,例如运动规划、碰撞检测等。
Open3D 的设计哲学是 简洁、高效、易用。它提供了友好的 Python 接口和高性能的 C++ 后端,使得用户既能快速原型开发,又能兼顾性能需求。无论你是初学者还是经验丰富的专家,Open3D 都能成为你 3D 数据处理工具箱中的得力助手。
在开始 Open3D 的旅程之前,我们需要先安装好 Open3D 库。Open3D 支持多种操作系统,包括 Windows、macOS 和 Linux。推荐使用 Python 环境进行开发,因为 Open3D 提供了完善的 Python 接口,并且拥有丰富的示例代码和文档。
1.2.1 前提条件
Python: 建议使用 Python 3.7 或更高版本。你可以从 Python 官网 下载并安装。
pip: Python 的包管理器,通常在安装 Python 时会一同安装。
1.2.2 安装 Open3D (Python)
最简单快捷的安装方式是通过 pip 命令:
pip install open3d
在终端或命令提示符中执行上述命令,pip 会自动下载并安装 Open3D 及其依赖项。
1.2.3 验证安装
安装完成后,我们可以通过一个简单的 Python 脚本来验证 Open3D 是否安装成功:
import open3d as o3d import numpy as np # 创建一个简单的点云 points = np.random.rand(100, 3) pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) # 可视化点云 o3d.visualization.draw_geometries([pcd])
运行这段代码,如果能够弹出一个窗口,显示一个由随机点组成的点云,则说明 Open3D 安装成功。
1.2.4 其他安装方式 (可选)
除了 pip 安装,Open3D 还提供了其他安装方式,例如:
conda: 如果你使用 conda 管理 Python 环境,可以使用 conda 命令安装: conda install -c open3d-admin open3d
源码编译: 对于高级用户或需要定制化编译选项的情况,可以选择从源码编译安装。具体步骤可以参考 Open3D 官方文档。
本教程主要使用 pip 安装方式,因为它简单易用,适合初学者快速上手。
Open3D 提供了多种核心数据结构,用于表示不同类型的 3D 数据。理解这些数据结构是使用 Open3D 的基础。本节将介绍最常用的几种数据结构:点云 (PointCloud)、网格 (TriangleMesh) 和图像 (Image)。
1.3.1 点云 (PointCloud)
点云是 3D 空间中点的集合,是表示 3D 几何形状最基本的数据结构之一。它可以由激光扫描仪、深度相机等设备直接获取,也可以从其他 3D 模型转换而来。
在 Open3D 中,点云由 o3d.geometry.PointCloud 类表示。它主要包含以下几个关键属性:
points: 存储点云中所有点的坐标,类型为 o3d.utility.Vector3dVector,本质上是一个存储 (x, y, z) 坐标的 NumPy 数组。
colors: 可选属性,存储每个点的颜色信息,类型为 o3d.utility.Vector3dVector,存储 (r, g, b) 颜色值,范围通常在 [0, 1] 或 [0, 255]。
normals: 可选属性,存储每个点的法向量,类型为 o3d.utility.Vector3dVector,表示点云表面的局部方向。
代码实践 1:创建和可视化点云
import open3d as o3d import numpy as np # 1. 创建一个空的点云对象 pcd = o3d.geometry.PointCloud() # 2. 生成随机点坐标 (NumPy 数组) points = np.random.rand(1000, 3) # 1000 个点,每个点 3 个坐标 (x, y, z) # 3. 将 NumPy 数组转换为 Open3D 的 Vector3dVector pcd.points = o3d.utility.Vector3dVector(points) # 4. 生成随机颜色 (NumPy 数组) colors = np.random.rand(1000, 3) # 5. 将 NumPy 数组转换为 Open3D 的 Vector3dVector pcd.colors = o3d.utility.Vector3dVector(colors) # 6. 可视化点云 o3d.visualization.draw_geometries([pcd])
代码详解 1:
pcd = o3d.geometry.PointCloud(): 创建一个空的 PointCloud 对象。
points = np.random.rand(1000, 3): 使用 NumPy 生成一个 1000x3 的随机数组,作为点的坐标。np.random.rand() 生成 [0, 1) 之间的均匀分布随机数。
pcd.points = o3d.utility.Vector3dVector(points): 将 NumPy 数组 points 转换为 Open3D 的 Vector3dVector 类型,并赋值给 pcd.points 属性。这是将 NumPy 数据导入 Open3D 数据结构的关键步骤。
colors = np.random.rand(1000, 3): 类似地,生成随机颜色。
pcd.colors = o3d.utility.Vector3dVector(colors): 将颜色数据赋值给 pcd.colors 属性。
o3d.visualization.draw_geometries([pcd]): 使用 draw_geometries 函数可视化点云。该函数接受一个几何对象列表作为输入,并将其显示在一个窗口中。
mermaid 图 1:点云创建与可视化流程
代码实践 2:从文件加载点云
Open3D 支持多种点云文件格式,例如 PLY, PCD, XYZ 等。这里以 PLY 文件为例:
import open3d as o3d # 文件路径 (请替换为你的 PLY 文件路径) file_path = "path/to/your/point_cloud.ply" # 从文件加载点云 pcd_load = o3d.io.read_point_cloud(file_path) # 可视化加载的点云 o3d.visualization.draw_geometries([pcd_load])
代码详解 2:
file_path = "path/to/your/point_cloud.ply": 你需要将 "path/to/your/point_cloud.ply" 替换为你实际的点云文件路径。
pcd_load = o3d.io.read_point_cloud(file_path): 使用 o3d.io.read_point_cloud() 函数从指定路径加载点云数据,返回一个 PointCloud 对象。
o3d.visualization.draw_geometries([pcd_load]): 可视化加载的点云。
1.3.2 网格 (TriangleMesh)
网格是由三角形面片组成的 3D 模型,常用于表示物体的表面形状。网格比点云更紧凑,并且包含了拓扑信息(面片之间的连接关系)。
在 Open3D 中,网格由 o3d.geometry.TriangleMesh 类表示。它主要包含以下关键属性:
vertices: 存储网格顶点的坐标,类型为 o3d.utility.Vector3dVector。
triangles: 存储网格三角形面片的顶点索引,类型为 o3d.utility.Vector3iVector。每个三角形由三个顶点索引组成,索引指向 vertices 中的顶点。
vertex_normals: 可选属性,存储每个顶点的法向量,类型为 o3d.utility.Vector3dVector。
vertex_colors: 可选属性,存储每个顶点的颜色信息,类型为 o3d.utility.Vector3dVector。
triangle_normals: 可选属性,存储每个三角形面片的法向量,类型为 o3d.utility.Vector3dVector。
triangle_material_ids: 可选属性,存储每个三角形面片的材质 ID,类型为 o3d.utility.IntVector。
代码实践 3:创建和可视化网格
import open3d as o3d import numpy as np # 1. 创建一个空的网格对象 mesh = o3d.geometry.TriangleMesh() # 2. 定义顶点坐标 (立方体的 8 个顶点) vertices = np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1] ]) # 3. 定义三角形面片 (立方体的 12 个三角形) triangles = np.array([ [0, 1, 2], [1, 3, 2], # 底面 [4, 5, 6], [5, 7, 6], # 顶面 [0, 4, 1], [1, 5, 4], # 前面 [2, 6, 3], [3, 7, 6], # 后面 [0, 2, 4], [2, 6, 4], # 左面 [1, 3, 5], [3, 7, 5] # 右面 ]) # 4. 将 NumPy 数组转换为 Open3D 的 Vector 类型 mesh.vertices = o3d.utility.Vector3dVector(vertices) mesh.triangles = o3d.utility.Vector3iVector(triangles) # 5. 计算顶点法向量 (用于平滑着色) mesh.compute_vertex_normals() # 6. 可视化网格 o3d.visualization.draw_geometries([mesh])
代码详解 3:
mesh = o3d.geometry.TriangleMesh(): 创建一个空的 TriangleMesh 对象。
vertices = ...: 定义立方体的 8 个顶点坐标。
triangles = ...: 定义立方体的 12 个三角形面片,每个三角形由三个顶点索引组成,索引对应 vertices 数组中的顶点。例如 [0, 1, 2] 表示第一个三角形由 vertices 中索引为 0, 1, 2 的顶点组成。
mesh.vertices = ... 和 mesh.triangles = ...: 将顶点坐标和三角形索引赋值给网格对象。
mesh.compute_vertex_normals(): 计算网格的顶点法向量。法向量对于光照和着色效果非常重要,尤其是在进行平滑着色时。
o3d.visualization.draw_geometries([mesh]): 可视化网格。
代码实践 4:从文件加载网格
Open3D 支持多种网格文件格式,例如 OBJ, STL, OFF 等。这里以 OBJ 文件为例:
import open3d as o3d # 文件路径 (请替换为你的 OBJ 文件路径) file_path = "path/to/your/mesh.obj" # 从文件加载网格 mesh_load = o3d.io.read_triangle_mesh(file_path) # 可视化加载的网格 o3d.visualization.draw_geometries([mesh_load])
代码详解 4:
file_path = "path/to/your/mesh.obj": 你需要将 "path/to/your/mesh.obj" 替换为你实际的网格文件路径。
mesh_load = o3d.io.read_triangle_mesh(file_path): 使用 o3d.io.read_triangle_mesh() 函数从指定路径加载网格数据,返回一个 TriangleMesh 对象。
o3d.visualization.draw_geometries([mesh_load]): 可视化加载的网格。
1.3.3 图像 (Image)
Open3D 也支持处理图像数据,例如 RGB 图像、灰度图像和深度图像。图像数据可以用于纹理映射、图像处理等任务。
在 Open3D 中,图像由 o3d.geometry.Image 类表示。它主要包含以下属性:
data: 存储图像像素数据,类型为 NumPy 数组。
width: 图像宽度 (像素)。
height: 图像高度 (像素)。
bytes_per_channel: 每个通道的字节数 (例如,1 表示 8 位图像,2 表示 16 位图像)。
channels: 图像通道数 (例如,1 表示灰度图像,3 表示 RGB 图像)。
代码实践 5:创建和可视化图像
import open3d as o3d import numpy as np # 1. 创建一个空的图像对象 image = o3d.geometry.Image() # 2. 生成随机灰度图像数据 (NumPy 数组) image_data = np.random.randint(0, 256, size=(200, 300), dtype=np.uint8) # 200x300 灰度图像 # 3. 将 NumPy 数组设置为图像数据 image.set_data(image_data) # 4. 可视化图像 o3d.visualization.draw_geometries([image])
代码详解 5:
image = o3d.geometry.Image(): 创建一个空的 Image 对象。
image_data = np.random.randint(0, 256, size=(200, 300), dtype=np.uint8): 生成一个 200x300 的 NumPy 数组,作为灰度图像数据。np.random.randint(0, 256) 生成 [0, 255] 之间的随机整数,dtype=np.uint8 指定数据类型为 8 位无符号整数,适合表示灰度图像。
image.set_data(image_data): 将 NumPy 数组 image_data 设置为图像数据。
o3d.visualization.draw_geometries([image]): 可视化图像。
代码实践 6:从文件加载图像
Open3D 支持多种图像文件格式,例如 PNG, JPG, JPEG 等。这里以 PNG 文件为例:
import open3d as o3d # 文件路径 (请替换为你的 PNG 文件路径) file_path = "path/to/your/image.png" # 从文件加载图像 image_load = o3d.io.read_image(file_path) # 可视化加载的图像 o3d.visualization.draw_geometries([image_load])
代码详解 6:
file_path = "path/to/your/image.png": 你需要将 "path/to/your/image.png" 替换为你实际的图像文件路径。
image_load = o3d.io.read_image(file_path): 使用 o3d.io.read_image() 函数从指定路径加载图像数据,返回一个 Image 对象。
o3d.visualization.draw_geometries([image_load]): 可视化加载的图像。
1.3.4 RGBD 图像 (RGBDImage)
RGBD 图像是指同时包含彩色图像 (RGB) 和深度图像 (Depth) 的图像数据。深度图像记录了场景中每个像素到相机的距离信息。RGBD 图像常用于 3D 重建、场景理解等应用。
在 Open3D 中,RGBD 图像由 o3d.geometry.RGBDImage 类表示。它由两个 Image 对象组成:
color: 存储彩色图像,类型为 o3d.geometry.Image。
depth: 存储深度图像,类型为 o3d.geometry.Image。
代码实践 7:创建 RGBD 图像
import open3d as o3d import numpy as np # 1. 创建随机 RGB 图像数据 color_data = np.random.randint(0, 256, size=(200, 300, 3), dtype=np.uint8) # 200x300 RGB 图像 # 2. 创建随机深度图像数据 (假设深度值范围为 0-10 米) depth_data = np.random.rand(200, 300).astype(np.float32) * 10 # 200x300 深度图像 # 3. 创建 Open3D Image 对象 color_image = o3d.geometry.Image(color_data) depth_image = o3d.geometry.Image(depth_data) # 4. 创建 RGBDImage 对象 rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth( color_image, depth_image ) # 5. 可视化 RGBD 图像 (默认显示彩色图像) o3d.visualization.draw_geometries([rgbd_image]) # 6. 可视化深度图像 (单独显示深度图像) o3d.visualization.draw_geometries([depth_image])
代码详解 7:
color_data = ... 和 depth_data = ...: 分别生成随机 RGB 图像数据和深度图像数据。深度图像数据通常使用浮点数类型 np.float32 表示。
color_image = ... 和 depth_image = ...: 将 NumPy 数组转换为 Open3D Image 对象。
rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(...): 使用静态方法 create_from_color_and_depth() 从彩色图像和深度图像创建 RGBDImage 对象。
o3d.visualization.draw_geometries([rgbd_image]): 可视化 RGBD 图像,默认显示彩色图像。
o3d.visualization.draw_geometries([depth_image]): 单独可视化深度图像。
1.3.5 体素网格 (VoxelGrid)
体素网格是一种将 3D 空间划分为规则网格的数据结构,每个网格单元称为体素 (Voxel)。体素网格常用于空间离散化、占用栅格地图、3D 体积渲染等应用。
在 Open3D 中,体素网格由 o3d.geometry.VoxelGrid 类表示。它主要包含以下属性:
voxels: 存储体素信息,类型为 std::unordered_map<Eigen::Vector3i, Voxel>,使用哈希表存储体素索引和体素对象。
voxel_size: 体素大小 (立方体边长)。
origin: 体素网格的原点坐标。
代码实践 8:从点云创建体素网格
import open3d as o3d import numpy as np # 1. 创建一个随机点云 points = np.random.rand(2000, 3) pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) # 2. 设置体素大小 voxel_size = 0.1 # 3. 从点云创建体素网格 voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=voxel_size) # 4. 可视化体素网格 o3d.visualization.draw_geometries([voxel_grid])
代码详解 8:
points = ... 和 pcd = ...: 创建一个随机点云。
voxel_size = 0.1: 设置体素大小为 0.1。
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(...): 使用静态方法 create_from_point_cloud() 从点云创建体素网格。该方法会将点云中的点离散化到体素网格中。
o3d.visualization.draw_geometries([voxel_grid]): 可视化体素网格。
掌握了 Open3D 的核心数据结构之后,我们需要学习如何进行基本的操作,例如文件 I/O 和数据转换。
1.4.1 文件 I/O
Open3D 提供了方便的文件 I/O 接口,可以加载和保存各种 3D 数据文件格式。我们在前面的代码实践中已经接触了 o3d.io.read_point_cloud(), o3d.io.read_triangle_mesh(), o3d.io.read_image() 等读取函数。
代码实践 9:保存点云和网格到文件
import open3d as o3d import numpy as np # 创建一个点云 points = np.random.rand(100, 3) pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) # 创建一个网格 (这里使用立方体网格) mesh = o3d.geometry.TriangleMesh() vertices = np.array([ [0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1] ]) triangles = np.array([ [0, 1, 2], [1, 3, 2], [4, 5, 6], [5, 7, 6], [0, 4, 1], [1, 5, 4], [2, 6, 3], [3, 7, 6], [0, 2, 4], [2, 6, 4], [1, 3, 5], [3, 7, 5] ]) mesh.vertices = o3d.utility.Vector3dVector(vertices) mesh.triangles = o3d.utility.Vector3iVector(triangles) # 保存点云到 PLY 文件 o3d.io.write_point_cloud("output_point_cloud.ply", pcd) # 保存网格到 OBJ 文件 o3d.io.write_triangle_mesh("output_mesh.obj", mesh) print("点云和网格已保存到 output_point_cloud.ply 和 output_mesh.obj 文件")
代码详解 9:
o3d.io.write_point_cloud("output_point_cloud.ply", pcd): 使用 o3d.io.write_point_cloud() 函数将点云 pcd 保存到名为 "output_point_cloud.ply" 的 PLY 文件中。
o3d.io.write_triangle_mesh("output_mesh.obj", mesh): 使用 o3d.io.write_triangle_mesh() 函数将网格 mesh 保存到名为 "output_mesh.obj" 的 OBJ 文件中。
Open3D 还提供了 o3d.io.write_image() 函数用于保存图像到文件。
1.4.2 数据转换
Open3D 允许在不同的数据结构之间进行转换,例如点云和网格之间的转换。
代码实践 10:点云转换为网格 (Ball Pivoting 算法)
import open3d as o3d import numpy as np # 1. 加载点云 (例如,从 bunny.ply 文件加载) pcd = o3d.io.read_point_cloud("path/to/bunny.ply") # 请替换为你的 bunny.ply 文件路径 # 2. 使用 Ball Pivoting 算法进行表面重建,将点云转换为网格 radii = [0.01, 0.02, 0.04, 0.08] # 多尺度半径 bp_mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting( pcd, o3d.utility.DoubleVector(radii) ) # 3. 可视化重建的网格 o3d.visualization.draw_geometries([bp_mesh])
代码详解 10:
pcd = o3d.io.read_point_cloud("path/to/bunny.ply"): 加载点云数据。你需要将 "path/to/bunny.ply" 替换为你实际的 bunny.ply 文件路径 (Open3D 示例数据中通常包含 bunny.ply)。
bp_mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(...): 使用 create_from_point_cloud_ball_pivoting() 静态方法,基于 Ball Pivoting 算法将点云 pcd 转换为网格 bp_mesh。radii 参数指定了多尺度球的半径,用于控制重建的细节程度。
o3d.visualization.draw_geometries([bp_mesh]): 可视化重建的网格。