本章仅为个人学习整理。
Open3D: https://www.open3d.org/
Github repo: https://github.com/isl-org/Open3D
1. 概述
Open3D 是一个开源库,旨在为 3D 数据处理提供高效且易用的工具。它由 Intel 开发和维护,支持多种 3D 数据处理任务,如点云处理、3D 重建、几何处理和可视化等。
1.1 主要功能
- 点云处理:
- 支持点云的读取、写入和可视化。
- 提供点云滤波、配准、分割和特征提取等功能。
- 3D 重建:
- 支持从深度图像生成 3D 网格。
- 提供多视图 3D 重建算法。
- 几何处理:
- 支持对三角网格、体素网格和曲面的处理。
- 提供几何变换、简化和布尔运算等功能。
- 可视化:
- 提供交互式的 3D 可视化工具。
- 支持点云、网格和体素的渲染。
- 机器学习:
- 提供与深度学习框架的集成,支持 3D 数据的机器学习任务。
- 提供与深度学习框架的集成,支持 3D 数据的机器学习任务。
2. 安装
2.1 安装 Open3D
方法一:通过 pip 安装
可以直接使用 pip 安装 Open3D:
pip install open3d
方法二:手动安装
你也可以从 PyPI 下载对应版本的 .whl
文件,然后手动安装。例如,对于 Linux x86 系统和 Python 3.9 环境:
pip install open3d-0.18.0-cp39-cp39-manylinux_2_27_x86_64.whl
方法三:安装 CPU 版本
如果不使用 NVIDIA 的 CUDA,可以考虑安装 CPU 版本:
pip install open3d-cpu
方法四:源码安装
你也可以从源码安装 Open3D。具体步骤可以参考 Open3D 的官方文档。
2.2 第三方库管理
Open3D 使用 CMake 来管理第三方库。CMake 是一个跨平台的构建系统,它可以帮助自动化软件构建过程,包括查找和配置第三方库。Open3D 通过 CMake 的 find_package
和 ExternalProject
模块来管理第三方库。
- 第三方库管理步骤
-
查找系统库:
- Open3D 使用
find_package
命令查找系统中已经安装的库。例如,查找 Eigen 库:find_package(Eigen3 REQUIRED) include_directories(${EIGEN3_INCLUDE_DIR})
- Open3D 使用
-
下载和构建外部项目:
- 对于一些没有预安装的库,Open3D 使用
ExternalProject_Add
命令从源代码下载并构建这些库。例如,下载并构建 GLFW:include(ExternalProject) ExternalProject_Add(glfw GIT_REPOSITORY https://github.com/glfw/glfw.git GIT_TAG latest CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/third_party_install )
- 对于一些没有预安装的库,Open3D 使用
-
使用
third_party
目录:- Open3D 在其源代码中包含了一些第三方库的副本,这些库存放在
third_party
目录下。CMakeLists.txt 文件会配置这些库的构建和链接。例如,配置和使用 Filament 库:add_subdirectory(third_party/filament) include_directories(third_party/filament/include)
- Open3D 在其源代码中包含了一些第三方库的副本,这些库存放在
Open3D 通过 CMake 的 find_package
和 ExternalProject_Add
命令来查找和管理第三方库,并使用 third_party
目录包含一些必要的库。这样可以确保在不同平台上都能顺利构建和运行 Open3D。
2.3 编译原理
-
CMake 配置:
- Open3D 使用 CMake 作为构建系统。CMakeLists.txt 文件定义了项目的构建配置,包括源文件、依赖项、编译选项等。
- CMake 会生成适合目标平台的构建文件(如 Makefile 或 Visual Studio 项目文件)。
-
依赖项管理:
- Open3D 依赖多个第三方库,如 Eigen(用于线性代数计算)、GLFW(用于窗口管理)、Pybind11(用于 Python 绑定)等。
- CMake 会自动查找和配置这些依赖项。
-
编译和链接:
- CMake 生成的构建文件会调用编译器(如 GCC 或 Clang)编译源代码,并链接生成目标文件(如库或可执行文件)。
- 编译过程中会根据配置选项启用或禁用某些功能模块。
-
Python 绑定:
- 如果启用了 Python 绑定,Open3D 会使用 Pybind11 生成 Python 模块,使得 Open3D 可以在 Python 中使用。
- 编译过程中会生成
_pybind
模块,并将其安装到 Python 的包目录中。
3. 点云写入、读取、可视化
open3d.io.write_point_cloud
是一个用于将点云数据写入文件的函数。
open3d.io.read_point_cloud
是一个用于从文件中读取点云数据的函数。
open3d.visualization.draw_geometries
是一个用于可视化几何对象列表的函数。
3.1 点云写入文件
open3d.io.write_point_cloud(
filename: os.PathLike,
pointcloud: open3d.geometry.PointCloud,
format: str = 'auto',
write_ascii: bool = False,
compressed: bool = False,
print_progress: bool = False
) -> bool
参数说明
filename (os.PathLike)
:文件路径。
pointcloud (open3d.geometry.PointCloud)
:要写入的 PointCloud 对象。
format (str, optional, default='auto')
:输出文件的格式。当未指定或设置为 auto 时,格式将从文件扩展名推断。
write_ascii (bool, optional, default=False)
:如果为 True,则以 ASCII 格式输出,否则使用二进制格式。
compressed (bool, optional, default=False)
:如果为 True,则以压缩格式写入。
print_progress (bool, optional, default=False)
:如果为 True,在控制台中显示进度条。
3.2 读取点云文件
open3d.io.read_point_cloud(
filename: os.PathLike,
format: str = 'auto',
remove_nan_points: bool = False,
remove_infinite_points: bool = False,
print_progress: bool = False
) -> open3d.geometry.PointCloud
参数说明
filename (os.PathLike)
:文件路径。
format (str, optional, default='auto')
:输入文件的格式。当未指定或设置为 auto 时,格式将从文件扩展名推断。
remove_nan_points (bool, optional, default=False)
:如果为 True,则移除包含 NaN 值的点。
remove_infinite_points (bool, optional, default=False)
:如果为 True,则移除包含无限值的点。
print_progress (bool, optional, default=False)
:如果为 True,在控制台中显示进度条。
3.3 可视化点云
open3d.visualization.draw_geometries(
geometry_list: list[open3d.geometry.Geometry],
window_name: str = 'Open3D',
width: int = 1920,
height: int = 1080,
left: int = 50,
top: int = 50,
point_show_normal: bool = False,
mesh_show_wireframe: bool = False,
mesh_show_back_face: bool = False,
lookat: numpy.ndarray[numpy.float64[3, 1]] | None = None,
up: numpy.ndarray[numpy.float64[3, 1]] | None = None,
front: numpy.ndarray[numpy.float64[3, 1]] | None = None,
zoom: float | None = None
) -> None
参数说明
geometry_list (list[open3d.geometry.Geometry])
:要可视化的几何对象列表。
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)
:如果为 True,则显示网格三角形的背面。
lookat (Optional[numpy.ndarray[numpy.float64[3, 1]]], optional, default=None)
:相机的 lookat 向量。
up (Optional[numpy.ndarray[numpy.float64[3, 1]]], optional, default=None)
:相机的 up 向量。
front (Optional[numpy.ndarray[numpy.float64[3, 1]]], optional, default=None)
:相机的 front 向量。
zoom (Optional[float], optional, default=None)
:相机的缩放。
3.4 使用案例
import open3d as o3d
import numpy as np
# 生成一个简单的点云(例如,一个立方体的顶点)
points = np.array([
[0, 0, 0],
[1, 0, 0],
[1, 1, 0],
[0, 1, 0],
[0, 0, 1],
[1, 0, 1],
[1, 1, 1],
[0, 1, 1],
])
# 创建 PointCloud 对象
pcd = o3d.geometry.PointCloud()
# 将点添加到 PointCloud 对象中
pcd.points = o3d.utility.Vector3dVector(points)
# 保存点云到文件
o3d.io.write_point_cloud("generated_point_cloud.ply", pcd)
# 读取点云文件
load_pcd = o3d.io.read_point_cloud("generated_point_cloud.ply")
# 可视化点云
o3d.visualization.draw_geometries([load_pcd])
4. TriangleMesh 读取、保存
TriangleMeshIO节选代码: https://github.com/isl-org/Open3D/blob/main/cpp/open3d/io/TriangleMeshIO.cpp
static const std::unordered_map<
std::string,
std::function<bool(const std::string &,
geometry::TriangleMesh &,
const ReadTriangleMeshOptions &)>>
file_extension_to_trianglemesh_read_function{
{"ply", ReadTriangleMeshFromPLY},
{"stl", ReadTriangleMeshUsingASSIMP},
{"obj", ReadTriangleMeshUsingASSIMP},
{"off", ReadTriangleMeshFromOFF},
{"gltf", ReadTriangleMeshUsingASSIMP},
{"glb", ReadTriangleMeshUsingASSIMP},
{"fbx", ReadTriangleMeshUsingASSIMP},
};
static const std::unordered_map<
std::string,
std::function<bool(const std::string &,
const geometry::TriangleMesh &,
const bool,
const bool,
const bool,
const bool,
const bool,
const bool)>>
file_extension_to_trianglemesh_write_function{
{"ply", WriteTriangleMeshToPLY},
{"stl", WriteTriangleMeshToSTL},
{"obj", WriteTriangleMeshToOBJ},
{"off", WriteTriangleMeshToOFF},
{"gltf", WriteTriangleMeshToGLTF},
{"glb", WriteTriangleMeshToGLTF},
};
} // unnamed namespace
在这段代码中,open3d
使用 assimp
来读取和写入多种三角网格文件格式。以下是支持的文件格式:
4.1 支持的读取文件格式
ply
(使用ReadTriangleMeshFromPLY
函数)stl
(使用ReadTriangleMeshUsingASSIMP
函数)obj
(使用ReadTriangleMeshUsingASSIMP
函数)off
(使用ReadTriangleMeshFromOFF
函数)gltf
(使用ReadTriangleMeshUsingASSIMP
函数)glb
(使用ReadTriangleMeshUsingASSIMP
函数)fbx
(使用ReadTriangleMeshUsingASSIMP
函数)
4.2 支持的写入文件格式
ply
(使用WriteTriangleMeshToPLY
函数)stl
(使用WriteTriangleMeshToSTL
函数)obj
(使用WriteTriangleMeshToOBJ
函数)off
(使用WriteTriangleMeshToOFF
函数)gltf
(使用WriteTriangleMeshToGLTF
函数)glb
(使用WriteTriangleMeshToGLTF
函数)
这些函数通过文件扩展名与相应的读取和写入函数进行映射,从而支持多种三角网格文件格式的读写操作。
4.3 TriangleMesh 读取
open3d.io.read_triangle_mesh
open3d.io.read_triangle_mesh(
filename: os.PathLike,
enable_post_processing:
bool = False,
print_progress: bool = False
) → open3d.geometry.TriangleMesh
参数说明
filename
:文件路径,类型为 os.PathLike。
enable_post_processing
:是否启用后处理,类型为 bool,默认值为 False。
print_progress
:是否在控制台显示进度条,类型为 bool,默认值为 False。
import open3d as o3d
# 定义文件路径
filename = "doll.stl"
try:
# 尝试读取三角网格
mesh = o3d.io.read_triangle_mesh(filename, enable_post_processing=True, print_progress=True)
# 检查网格是否成功读取
if mesh.is_empty():
print("Failed to read the mesh. The file format may not be supported.")
else:
print("Successfully read the mesh.")
# 可视化三角网格
o3d.visualization.draw_geometries([mesh])
except Exception as e:
print(f"An error occurred: {e}")
此时因为没有计算法线, 可视化出来的模型会涂成统一的灰色
然后我们可以compute_vertex_normals
来计算出法线信息
import open3d as o3d
import numpy as np
# 定义文件路径
filename = "doll.stl"
# 读取三角网格
mesh = o3d.io.read_triangle_mesh(filename, enable_post_processing=True, print_progress=True)
# 检查网格是否成功读取
if mesh.is_empty():
print("Failed to read the mesh. The file format may not be supported.")
else:
print("Successfully read the mesh.")
# 计算法线
mesh.compute_vertex_normals()
# 设置网格的颜色为红色
mesh.paint_uniform_color([1, 0, 0]) # 设置为红色
# 创建一个可视化窗口
vis = o3d.visualization.Visualizer()
vis.create_window()
# 添加网格到可视化窗口
vis.add_geometry(mesh)
# 更新几何体和渲染器
vis.update_geometry(mesh)
vis.poll_events()
vis.update_renderer()
# 渲染
vis.run()
vis.destroy_window()
import open3d as o3d
import numpy as np
# 定义文件路径
filename = "doll.stl"
# 读取三角网格
mesh = o3d.io.read_triangle_mesh(filename)
if mesh.is_empty():
print("Failed to read the mesh. The file format may not be supported.")
else:
print("Successfully read the mesh.")
# 计算法线
mesh.compute_vertex_normals()
# 设置材质
mat_box = o3d.visualization.rendering.MaterialRecord()
mat_box.shader = 'defaultLitSSR'
mat_box.base_color = [0.467, 0.467, 0.467, 0.2] # 设置透明度为0.2
mat_box.base_roughness = 0.0
mat_box.base_reflectance = 0.0
mat_box.base_clearcoat = 1.0
mat_box.thickness = 1.0
mat_box.transmission = 1.0
mat_box.absorption_distance = 10
mat_box.absorption_color = [0.5, 0.5, 0.5]
# 使用draw函数渲染
o3d.visualization.draw(
[{'name': 'box', 'geometry': mesh, 'material': mat_box}],
show_skybox=False,
width=800,
height=600,
bg_color=[0.5, 0.5, 0.5, 0.8] # 设置背景颜色为灰色
)
4.4 从mesh上提取点云
import open3d as o3d
import numpy as np
# 定义文件路径
filename = "doll.stl"
# 读取三角网格
mesh = o3d.io.read_triangle_mesh(filename)
if mesh.is_empty():
print("Failed to read the mesh. The file format may not be supported.")
else:
print("Successfully read the mesh.")
# 计算法线
mesh.compute_vertex_normals()
# 从mesh提取点云
point_cloud = mesh.sample_points_uniformly(number_of_points=10000)
# 设置材质
mat_box = o3d.visualization.rendering.MaterialRecord()
mat_box.shader = 'defaultLitSSR'
mat_box.base_color = [0.467, 0.467, 0.467, 0.2] # 设置透明度为0.2
mat_box.base_roughness = 0.0
mat_box.base_reflectance = 0.0
mat_box.base_clearcoat = 1.0
mat_box.thickness = 1.0
mat_box.transmission = 1.0
mat_box.absorption_distance = 10
mat_box.absorption_color = [0.5, 0.5, 0.5]
# 使用draw函数渲染
o3d.visualization.draw(
[{'name': 'box', 'geometry': mesh, 'material': mat_box},
{'name': 'point_cloud', 'geometry': point_cloud}],
show_skybox=False,
width=800,
height=600,
bg_color=[0.5, 0.5, 0.5, 0.8] # 设置背景颜色为灰色
)
5. KD-Tree
5.1 KD-树 说明与算法原理
5.1.1 KD-树的简介
KD-树(K-Dimension Tree)是一种用于多维空间数据的搜索数据结构,其构建和搜索过程类似于二叉搜索树,但适用于高维场景。通过交替使用各维特征进行划分,KD-树能在 (O(\log N)) 的时间复杂度内实现最近邻搜索。此外,它还支持动态插入新节点,通过一种类似替罪羊树的方法保持一定的结构平衡,确保插入效率。
另外, 可以直接看wiki的说明:
https://en.wikipedia.org/wiki/K-d_tree
5.1.2 KD-树的构建
KD-树的构建过程如下:
- 选择分割维度:从根节点开始,依次选择各维度进行分割。通常选择数据点在该维度上的中位数作为分割点。
- 递归构建子树:将数据点分为两部分,左子树包含小于等于分割点的数据点,右子树包含大于分割点的数据点。递归地对每个子树进行上述操作,直到所有数据点都被处理完。
5.1.3 KD-树的搜索
KD-树的搜索过程如下:
- 递归搜索:从根节点开始,根据查询点在当前分割维度上的值,递归地搜索左子树或右子树。
- 回溯检查:在回溯过程中,检查当前节点是否比已找到的最近邻更接近查询点。如果是,则更新最近邻。
- 检查其他子树:如果查询点与当前分割平面的距离小于已找到的最近邻距离,则需要检查另一个子树。
5.1.4 KD-树的插入
KD-树的插入过程如下:
- 找到插入位置:从根节点开始,递归地找到适合插入新节点的位置。
- 插入新节点:在找到的插入位置插入新节点,并根据需要调整树的结构以保持平衡。
5.1.5 KD-树的应用
KD-树广泛应用于以下场景:
- 最近邻搜索:在点云处理、图像检索等领域,KD-树可以高效地找到距离查询点最近的点。
- 范围查询:在地理信息系统中,KD-树可以用于查找指定范围内的所有点。
- 聚类分析:在机器学习中,KD-树可以用于加速 K-means 聚类算法。
5.1.6 KD-树的C++实现
以下是一个简单的 KD-树的 C++ 实现示例:
#include <iostream>
#include <vector>
#include <algorithm>
struct Point {
std::vector<double> coords;
Point(std::initializer_list<double> init) : coords(init) {}
};
struct KDNode {
Point point;
KDNode* left;
KDNode* right;
KDNode(Point p) : point(p), left(nullptr), right(nullptr) {}
};
class KDTree {
public:
KDTree(const std::vector<Point>& points) {
root = build(points, 0);
}
KDNode* build(const std::vector<Point>& points, int depth) {
if (points.empty()) return nullptr;
int k = points[0].coords.size();
int axis = depth % k;
std::vector<Point> sorted_points = points;
std::sort(sorted_points.begin(), sorted_points.end(), [axis](const Point& a, const Point& b) {
return a.coords[axis] < b.coords[axis];
});
int median = sorted_points.size() / 2;
KDNode* node = new KDNode(sorted_points[median]);
std::vector<Point> left_points(sorted_points.begin(), sorted_points.begin() + median);
std::vector<Point> right_points(sorted_points.begin() + median + 1, sorted_points.end());
node->left = build(left_points, depth + 1);
node->right = build(right_points, depth + 1);
return node;
}
void nearestNeighborSearch(const Point& query, Point& best, double& best_dist, KDNode* node, int depth) {
if (!node) return;
int k = query.coords.size();
int axis = depth % k;
double dist = distance(query, node->point);
if (dist < best_dist) {
best_dist = dist;
best = node->point;
}
double diff = query.coords[axis] - node->point.coords[axis];
KDNode* near = diff <= 0 ? node->left : node->right;
KDNode* far = diff <= 0 ? node->right : node->left;
nearestNeighborSearch(query, best, best_dist, near, depth + 1);
if (std::abs(diff) < best_dist) {
nearestNeighborSearch(query, best, best_dist, far, depth + 1);
}
}
Point nearestNeighbor(const Point& query) {
Point best = root->point;
double best_dist = distance(query, best);
nearestNeighborSearch(query, best, best_dist, root, 0);
return best;
}
private:
KDNode* root;
double distance(const Point& a, const Point& b) {
double dist = 0;
for (size_t i = 0; i < a.coords.size(); ++i) {
dist += (a.coords[i] - b.coords[i]) * (a.coords[i] - b.coords[i]);
}
return dist;
}
};
int main() {
std::vector<Point> points = {{2.0, 3.0}, {5.0, 4.0}, {9.0, 6.0}, {4.0, 7.0}, {8.0, 1.0}, {7.0, 2.0}};
KDTree tree(points);
Point query = {9.0, 2.0};
Point nearest = tree.nearestNeighbor(query);
std::cout << "最近邻点: (" << nearest.coords[0] << ", " << nearest.coords[1] << ")\n";
return 0;
}
KD-树是一种高效的多维空间数据搜索结构,适用于最近邻搜索、范围查询和聚类分析等场景。通过交替使用各维特征进行划分,KD-树能在 (O(\log N)) 的时间复杂度内实现高效搜索。
5.2 KDTreeFlann接口
Open3D 提供了 KDTreeFlann
类,用于高效的空间查询。主要的接口包括:
-
search_knn_vector_3d:最近邻搜索
[k, idx, dist] = kdtree.search_knn_vector_3d(query_point, k)
query_point
:查询点k
:返回最近邻的数量- 返回值:
k
为找到的邻居数量,idx
为邻居的索引,dist
为邻居的距离
-
search_radius_vector_3d:半径搜索
[k, idx, dist] = kdtree.search_radius_vector_3d(query_point, radius)
query_point
:查询点radius
:搜索半径- 返回值:
k
为找到的邻居数量,idx
为邻居的索引,dist
为邻居的距离
-
search_hybrid_vector_3d:固定距离搜索
[k, idx, dist] = kdtree.search_hybrid_vector_3d(query_point, radius, max_nn)
query_point
:查询点radius
:搜索半径max_nn
:返回的最大邻居数量- 返回值:
k
为找到的邻居数量,idx
为邻居的索引,dist
为邻居的距离
5.3 Open3D 中 k-d 树的接口案例
以下是使用 Open3D 构建和查询 k-d 树的示例代码:
import open3d as o3d
import numpy as np
# 创建一个随机点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(np.random.rand(5000, 3))
# 为点云设置颜色
colors = np.random.rand(5000, 3) # 随机颜色
pcd.colors = o3d.utility.Vector3dVector(colors)
# 构建k-d tree
kdtree = o3d.geometry.KDTreeFlann(pcd)
# 查询k-d tree中的最近邻
query_point = np.random.rand(3)
[k, idx, dist] = kdtree.search_knn_vector_3d(query_point, 10)
print("查询点:", query_point)
print("k-d tree最近邻索引:", idx)
print("k-d tree最近邻距离:", dist)
# 提取最近邻点
nearest_points = np.asarray(pcd.points)[idx, :]
# 创建查询点和最近邻点的点云
query_pcd = o3d.geometry.PointCloud()
query_pcd.points = o3d.utility.Vector3dVector([query_point])
query_pcd.paint_uniform_color([1, 0, 0]) # 将查询点设置为红色
nearest_pcd = o3d.geometry.PointCloud()
nearest_pcd.points = o3d.utility.Vector3dVector(nearest_points)
nearest_pcd.paint_uniform_color([0, 1, 0]) # 将最近邻点设置为绿色
# 可视化点云、查询点和最近邻点
vis = o3d.visualization.Visualizer()
vis.create_window()
vis.add_geometry(pcd)
vis.add_geometry(query_pcd)
vis.add_geometry(nearest_pcd)
# 调整点云大小
opt = vis.get_render_option()
opt.point_size = 2.0 # 设置原始点云大小
opt.background_color = np.asarray([0.8, 0.8, 0.8]) # 设置背景颜色
# 放大最近邻点的大小
for i in range(len(nearest_pcd.points)):
sphere = o3d.geometry.TriangleMesh.create_sphere(radius=0.02)
sphere.translate(nearest_pcd.points[i])
sphere.paint_uniform_color([0, 1, 0])
vis.add_geometry(sphere)
# 更新可视化
vis.poll_events()
vis.update_renderer()
vis.run()
vis.destroy_window()
- 创建点云:生成一个包含 1000 个随机点的点云。
- 构建 k-d 树:使用 o3d.geometry.KDTreeFlann 构建 k-d 树。
- 查询最近邻:使用 search_knn_vector_3d 方法查询给定点的 5 个最近邻。
- 提取最近邻点:从点云中提取最近邻点。
- 设置颜色:将原始点云设置为灰色,查询点设置为红色,最近邻点设置为绿色。
- 可视化:将点云、查询点和最近邻点一起可视化。
6. Octree 八叉树
Octree 八叉树是一种用于描述三维空间的树状数据结构。它的基本思想是递归地将三维空间划分成更小的体积单元,每个节点表示一个正方体的体积元素,每个节点有八个子节点,将八个子节点所表示的体积元素加在一起就等于父节点的体积。
另外, 可以直接看wiki的说明:
https://en.wikipedia.org/wiki/Octree
6.1 基本原理
6.1.1 构建八叉树
- 根节点:八叉树的根节点表示整个三维空间或一个较大的正方体。
- 划分空间:将空间划分为八个相等的子空间,每个子空间对应一个子节点。
- 递归划分:对于每个子节点,如果其包含的元素数量超过预设阈值,则继续递归地将该子节点对应的空间再划分为八个更小的子空间,直到每个子节点包含的元素数量小于或等于阈值,或者达到设定的最大深度。
6.1.2 节点结构
每个八叉树节点包含以下信息:
- 边界(Boundary):定义了节点所代表的空间区域。
- 子节点(Children):指向八个子节点的指针。
- 元素(Elements):节点所包含的元素列表,通常是点、物体或其他空间实体。
6.1.3 空间划分
在三维空间中,每个节点代表一个正方体,可以通过中心点和边长来定义。将正方体沿三个坐标轴(x、y、z)各切一刀,就可以得到八个子正方体。
6.2 应用
6.2.1 空间划分
八叉树常用于三维空间的分层表示和管理,例如在计算机图形学中用于加速光线追踪和碰撞检测。通过将复杂的三维场景划分成更小的区域,可以大大减少需要处理的元素数量,从而提高计算效率。
6.2.2 最近邻搜索
在三维空间中查找某个点的最近邻居时,可以利用八叉树快速缩小搜索范围。通过递归地检查包含目标点的节点及其相邻节点,可以高效地找到最近邻居。
6.2.3 碰撞检测
在物理引擎中,八叉树被广泛用于碰撞检测。通过将物体划分到不同的节点中,可以快速确定哪些物体可能发生碰撞,从而减少不必要的碰撞检测计算。
6.2.4 空间索引
八叉树也可以用于空间数据库中的空间索引,支持快速的空间查询操作,如范围查询和K近邻查询。
6.3 实现细节
6.3.1 插入元素
将一个元素插入八叉树时,首先找到包含该元素的节点,然后递归地检查该节点是否需要进一步划分,直到找到最适合的叶子节点,将元素插入其中。
6.3.2 查找元素
查找元素时,从根节点开始,根据元素的位置递归地进入对应的子节点,直到找到包含该元素的节点。
6.3.3 删除元素
删除元素时,首先找到包含该元素的节点,然后从节点的元素列表中删除该元素。如果删除后节点的元素数量小于阈值,则可以考虑合并该节点的子节点以减少树的深度。
6.4 优缺点
6.4.1 优点
- 高效的空间划分:八叉树可以高效地划分三维空间,适用于处理大规模三维数据。
- 快速查询:支持快速的空间查询操作,如最近邻搜索和碰撞检测。
- 灵活性:可以自适应地划分空间,根据需要调整树的深度和节点容量。
6.4.2 缺点
- 内存消耗:在处理大规模数据时,八叉树的节点数量可能非常庞大,导致较高的内存消耗。
- 复杂性:实现和维护八叉树的数据结构相对复杂,特别是在处理动态数据时。
八叉树是一种强大的数据结构,广泛应用于三维空间的划分和管理。通过递归地将三维空间划分为更小的体积单元,八叉树可以高效地支持各种空间查询操作,如最近邻搜索和碰撞检测。然而,在实际应用中,需要权衡其内存消耗和实现复杂性,以确保其高效性和实用性。
6.4 Open3D Octree
Octree
类是 Open3D 中用于三维空间分割的主要类。它提供了构建、插入、查询等功能。
6.4.1 构建 Octree
要构建一个 Octree,可以使用 Octree
类并指定最大深度:
import open3d as o3d
# 创建一个 Octree,指定最大深度
max_depth = 4
octree = o3d.geometry.Octree(max_depth)
6.4.2 从点云构建 Octree
可以从一个点云构建 Octree:
# 创建一个随机点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(np.random.rand(5000, 3))
# 从点云构建 Octree
octree.convert_from_point_cloud(pcd, size_expand=0.01)
6.4.3 插入点
可以向 Octree 中插入单个点:
point = [0.5, 0.5, 0.5]
octree.insert_point(point)
6.4.4 查询点
可以查询一个点是否在 Octree 中,并获取其所在的叶节点信息:
query_point = [0.5, 0.5, 0.5]
success, node_info = octree.locate_leaf_node(query_point)
print("查询成功:", success)
print("节点信息:", node_info)
6.4.5 可视化 Octree
可以使用 Open3D 的可视化工具来显示 Octree:
o3d.visualization.draw_geometries([octree])
6.4.6 完整示例代码
以下是一个完整的示例代码,展示了如何构建、插入、查询和可视化 Octree:
import open3d as o3d
import numpy as np
# 创建一个随机点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(np.random.rand(5000, 3))
# 构建 Octree
max_depth = 4
octree = o3d.geometry.Octree(max_depth)
octree.convert_from_point_cloud(pcd, size_expand=0.01)
# 插入多个点
points = np.random.rand(100, 3) # 生成 100 个随机点
def leaf_node_init():
return o3d.geometry.OctreeColorLeafNode()
def leaf_node_update(node):
pass
def internal_node_init():
return o3d.geometry.OctreeInternalNode()
def internal_node_update(node):
pass
for point in points:
octree.insert_point(point, leaf_node_init, leaf_node_update, internal_node_init, internal_node_update)
# 查询 Octree 中的点
query_point = np.array([0.5, 0.5, 0.5])
success, node_info = octree.locate_leaf_node(query_point)
print("查询点:", query_point)
print("查询成功:", success)
print("节点信息:", node_info)
# 可视化 Octree 节点
def create_pointcloud_from_octree(octree):
points = []
colors = []
def traverse(node, node_info):
if isinstance(node, o3d.geometry.OctreeColorLeafNode):
origin = node_info.origin
size = node_info.size
depth = node_info.depth
# 根据深度设置颜色
if depth == 0:
color = [1, 0, 0] # 红色
elif depth == 1:
color = [0, 1, 0] # 绿色
elif depth == 2:
color = [0, 0, 1] # 蓝色
elif depth == 3:
color = [1, 1, 0] # 黄色
else:
color = [0, 1, 1] # 青色
points.append(origin + size / 2)
colors.append(color)
return False
octree.traverse(traverse)
pointcloud = o3d.geometry.PointCloud()
pointcloud.points = o3d.utility.Vector3dVector(np.array(points))
pointcloud.colors = o3d.utility.Vector3dVector(np.array(colors))
return pointcloud
octree_pointcloud = create_pointcloud_from_octree(octree)
# 创建 Octree 的线框表示
def create_lineset_from_octree(octree):
lines = []
colors = []
points = []
def traverse(node, node_info):
if isinstance(node, o3d.geometry.OctreeColorLeafNode) or isinstance(node, o3d.geometry.OctreeInternalNode):
origin = node_info.origin
size = node_info.size
# 添加立方体的 12 条边
cube_lines = [
[0, 1], [1, 3], [3, 2], [2, 0], # 底面
[4, 5], [5, 7], [7, 6], [6, 4], # 顶面
[0, 4], [1, 5], [2, 6], [3, 7] # 侧面
]
cube_points = [
origin,
origin + [size, 0, 0],
origin + [0, size, 0],
origin + [size, size, 0],
origin + [0, 0, size],
origin + [size, 0, size],
origin + [0, size, size],
origin + [size, size, size]
]
base_index = len(points)
points.extend(cube_points)
lines.extend([[base_index + start, base_index + end] for start, end in cube_lines])
colors.extend([[0, 0, 0] for _ in range(len(cube_lines))]) # 黑色
return False
octree.traverse(traverse)
lineset = o3d.geometry.LineSet()
lineset.points = o3d.utility.Vector3dVector(np.array(points))
lineset.lines = o3d.utility.Vector2iVector(np.array(lines))
lineset.colors = o3d.utility.Vector3dVector(np.array(colors))
return lineset
octree_lineset = create_lineset_from_octree(octree)
# 创建找到的立方体
if success:
origin = node_info.origin
size = node_info.size
cube = o3d.geometry.TriangleMesh.create_box(width=size, height=size, depth=size)
cube.translate(origin)
cube.paint_uniform_color([1, 0, 0]) # 红色
# 使用默认的绘制函数来显示点云和 Octree
geometries = [octree_pointcloud, octree_lineset]
if success:
geometries.append(cube)
o3d.visualization.draw_geometries(geometries)
这段代码展示了如何使用 Open3D 库创建一个 Octree,插入点,查询节点,并可视化 Octree 结构及其节点。
-
创建随机点云:
pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(np.random.rand(5000, 3))
-
构建 Octree:
octree = o3d.geometry.Octree(max_depth=4) octree.convert_from_point_cloud(pcd, size_expand=0.01)
-
插入多个点:
points = np.random.rand(100, 3) for point in points: octree.insert_point(point, leaf_node_init, leaf_node_update, internal_node_init, internal_node_update)
-
查询 Octree 中的点:
query_point = np.array([0.5, 0.5, 0.5]) success, node_info = octree.locate_leaf_node(query_point)
-
可视化 Octree 和查询结果:
octree_pointcloud = create_pointcloud_from_octree(octree) octree_lineset = create_lineset_from_octree(octree) if success: cube = o3d.geometry.TriangleMesh.create_box(width=node_info.size, height=node_info.size, depth=node_info.size) cube.translate(node_info.origin) cube.paint_uniform_color([1, 0, 0]) geometries = [octree_pointcloud, octree_lineset, cube] if success else [octree_pointcloud, octree_lineset] o3d.visualization.draw_geometries(geometries)
6.4.7 点云分割
在 Open3D 中使用 Octree 进行点云分割可以通过以下步骤实现:
-
创建点云并构建 Octree:
- 创建一个点云对象并填充点数据。
- 使用点云数据构建 Octree。
-
遍历 Octree 并分割点云:
- 遍历 Octree 的叶节点。
- 根据叶节点的信息将点云分割成不同的部分。
import open3d as o3d
import numpy as np
# 创建多个点云簇
def create_clustered_point_cloud(num_clusters=5, points_per_cluster=2000, cluster_radius=0.05):
points = []
for i in range(num_clusters):
cluster_center = np.random.rand(3)
cluster_points = cluster_center + cluster_radius * np.random.randn(points_per_cluster, 3)
points.append(cluster_points)
return np.vstack(points)
# 生成点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(create_clustered_point_cloud())
# 构建 Octree
max_depth = 4 # 调整 Octree 的深度
octree = o3d.geometry.Octree(max_depth)
octree.convert_from_point_cloud(pcd, size_expand=0.01)
# 分割点云并为每个部分赋予不同的颜色
def segment_point_cloud(octree):
segments = []
colors = np.random.rand(100, 3) # 生成随机颜色
color_index = 0
def traverse(node, node_info):
nonlocal color_index
if isinstance(node, o3d.geometry.OctreeLeafNode):
# 获取叶节点中的点
segment = pcd.select_by_index(node.indices)
segment.paint_uniform_color(colors[color_index % len(colors)])
segments.append(segment)
color_index += 1
return False
octree.traverse(traverse)
return segments
# 获取分割后的点云部分
segments = segment_point_cloud(octree)
# 检查是否有分割后的点云部分
if len(segments) == 0:
print("没有分割后的点云部分,请检查 Octree 构建和遍历逻辑。")
else:
# 可视化分割后的点云
o3d.visualization.draw_geometries(segments)
6.4.8 点云过滤
在 Open3D 中使用 Octree 进行点云滤波可以通过以下步骤实现:
- 创建点云并构建 Octree。
- 定义滤波条件。
- 遍历 Octree 并应用滤波条件。
- 生成滤波后的点云。
import open3d as o3d
import numpy as np
# 创建一个随机点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(np.random.rand(10000, 3))
# 构建 Octree
max_depth = 4 # 调整 Octree 的深度
octree = o3d.geometry.Octree(max_depth)
octree.convert_from_point_cloud(pcd, size_expand=0.01)
# 定义体素滤波函数
def voxel_filter(octree, voxel_size):
filtered_points = []
def traverse(node, node_info):
if isinstance(node, o3d.geometry.OctreeLeafNode):
# 计算叶节点的中心点
voxel_center = node_info.origin + node_info.size / 2
filtered_points.append(voxel_center)
return False
octree.traverse(traverse)
return filtered_points
# 设置体素大小
voxel_size = 0.05
# 获取滤波后的点云
filtered_points = voxel_filter(octree, voxel_size)
filtered_pcd = o3d.geometry.PointCloud()
filtered_pcd.points = o3d.utility.Vector3dVector(filtered_points)
# 可视化原始点云和滤波后的点云
print("原始点云点数:", len(pcd.points))
print("滤波后点云点数:", len(filtered_pcd.points))
o3d.visualization.draw_geometries([pcd], window_name="原始点云")
o3d.visualization.draw_geometries([filtered_pcd], window_name="滤波后点云")
过滤前 | 过滤后 |
---|---|
![]() | ![]() |
7. 点云过滤
Open3D 提供了以下几种常用的点云滤波方法:
-
统计滤波 (Statistical Outlier Removal):
- 方法:
remove_statistical_outlier
- 参数:
nb_neighbors
:用于计算平均距离的邻居点数。std_ratio
:距离的标准差乘数。
- 方法:
-
半径滤波 (Radius Outlier Removal):
- 方法:
remove_radius_outlier
- 参数:
nb_points
:在指定半径内的最小点数。radius
:搜索半径。
- 方法:
-
体素下采样 (Voxel Downsampling):
- 方法:
voxel_down_sample
- 参数:
voxel_size
:体素的大小。
- 方法:
-
Uniform Downsampling:
- 方法:
uniform_down_sample
- 参数:
every_k_points
:每隔多少个点采样一个点。
- 方法:
以下是这些方法的示例代码:
import open3d as o3d
import numpy as np
# 创建一个随机点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(np.random.rand(10000, 3))
# 统计滤波
pcd_statistical, ind_statistical = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=1.0)
filtered_pcd_statistical = pcd.select_by_index(ind_statistical)
# 半径滤波
pcd_radius, ind_radius = pcd.remove_radius_outlier(nb_points=10, radius=0.1)
filtered_pcd_radius = pcd.select_by_index(ind_radius)
# 体素下采样
voxel_size = 0.05
downsampled_pcd_voxel = pcd.voxel_down_sample(voxel_size)
# Uniform 下采样
every_k_points = 10
downsampled_pcd_uniform = pcd.uniform_down_sample(every_k_points)
# 可视化原始点云和过滤后的点云
print("原始点云点数:", len(pcd.points))
print("统计滤波后的点云点数:", len(filtered_pcd_statistical.points))
print("半径滤波后的点云点数:", len(filtered_pcd_radius.points))
print("体素下采样后的点云点数:", len(downsampled_pcd_voxel.points))
print("Uniform下采样后的点云点数:", len(downsampled_pcd_uniform.points))
o3d.visualization.draw_geometries([pcd], window_name="原始点云")
o3d.visualization.draw_geometries([filtered_pcd_statistical], window_name="统计滤波后的点云")
o3d.visualization.draw_geometries([filtered_pcd_radius], window_name="半径滤波后的点云")
o3d.visualization.draw_geometries([downsampled_pcd_voxel], window_name="体素下采样后的点云")
o3d.visualization.draw_geometries([downsampled_pcd_uniform], window_name="Uniform下采样后的点云")
- 代码说明:
- 统计滤波:使用
remove_statistical_outlier
方法去除离群点。该方法通过计算每个点的邻居点的平均距离,并将距离超过标准差乘数的点视为离群点。参数nb_neighbors
指定用于计算平均距离的邻居点数,std_ratio
指定距离的标准差乘数。 - 半径滤波:使用
remove_radius_outlier
方法去除孤立点。该方法通过检查每个点在指定半径内的邻居点数,并将邻居点数少于指定值的点视为孤立点。参数nb_points
指定在指定半径内的最小点数,radius
指定搜索半径。 - 体素下采样:使用
voxel_down_sample
方法通过体素网格下采样点云。该方法将点云划分为体素网格,并用每个体素内的点的重心来代表该体素。参数voxel_size
指定体素的大小。 - Uniform 下采样:使用
uniform_down_sample
方法均匀下采样点云。该方法通过按固定间隔选择点来下采样点云。参数every_k_points
指定每隔多少个点采样一个点。
这些方法可以帮助你在不同的场景下对点云进行预处理和优化,以提高点云处理的效率和效果。
8. 点云转换
8.1 transform:应用变换矩阵到点云
transform
方法用于将一个 4x4 的变换矩阵应用到点云上。该矩阵可以包含平移、旋转和缩放。
import open3d as o3d
import numpy as np
# 创建一个随机点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(np.random.rand(1000, 3))
# 定义一个变换矩阵
transformation_matrix = np.array([[1, 0, 0, 1],
[0, 1, 0, 2],
[0, 0, 1, 3],
[0, 0, 0, 1]])
# 应用变换矩阵到点云
pcd.transform(transformation_matrix)
# 可视化变换后的点云
o3d.visualization.draw_geometries([pcd], window_name="Transformed Point Cloud")
8.2 translate:平移点云
translate
方法用于将点云沿指定的方向平移。
# 平移向量
translation_vector = np.array([1, 2, 3])
# 平移点云
pcd.translate(translation_vector)
# 可视化平移后的点云
o3d.visualization.draw_geometries([pcd], window_name="Translated Point Cloud")
8.3 rotate:旋转点云
rotate
方法用于将点云绕指定的轴旋转。旋转矩阵可以通过欧拉角或四元数生成。
# 定义一个旋转矩阵(绕 Z 轴旋转 45 度)
rotation_matrix = pcd.get_rotation_matrix_from_xyz((0, 0, np.pi / 4))
# 旋转点云
pcd.rotate(rotation_matrix, center=(0, 0, 0))
# 可视化旋转后的点云
o3d.visualization.draw_geometries([pcd], window_name="Rotated Point Cloud")
8.4 scale:缩放点云
scale
方法用于将点云按指定的比例缩放。
# 缩放比例
scale_factor = 2.0
# 缩放点云
pcd.scale(scale_factor, center=pcd.get_center())
# 可视化缩放后的点云
o3d.visualization.draw_geometries([pcd], window_name="Scaled Point Cloud")
9. 点云法线估计
9.1 estimate_normals:估计点云法线
estimate_normals
方法用于估计点云的法线。该方法通过计算每个点的邻域点的协方差矩阵,并求解其特征向量来确定法线方向。
- 参数说明:
search_param
:搜索参数,定义了用于法线估计的邻域搜索方法和半径。search_param=o3d.geometry.KDTreeSearchParamKNN(knn)
:使用 K 近邻搜索,knn
为邻居点的数量。search_param=o3d.geometry.KDTreeSearchParamRadius(radius)
:使用半径搜索,radius
为搜索半径。
9.2 orient_normals_consistent_tangent_plane:使法线方向一致
orient_normals_consistent_tangent_plane
方法用于使点云的法线方向一致。该方法通过构建一致的切平面来调整法线方向。
- 参数说明:
k
:用于一致性调整的邻居点数量。
9.3 详细案例
以下是一个完整的案例,展示了如何读取点云、估计法线并使法线方向一致:
import open3d as o3d
import numpy as np
# 生成点云数据
def generate_point_cloud():
# 生成一个简单的平面点云
mesh = o3d.geometry.TriangleMesh.create_sphere(radius=1.0)
pcd = mesh.sample_points_poisson_disk(number_of_points=500)
return pcd
# 生成点云
pcd = generate_point_cloud()
# 打印点云信息
print("Point cloud before normal estimation:")
print(pcd)
# 估计法线
print("Estimating normals...")
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamKNN(knn=30))
# 打印估计法线后的点云信息
print("Point cloud after normal estimation:")
print(pcd)
# 可视化带法线的点云
o3d.visualization.draw_geometries([pcd], point_show_normal=True, window_name="Point Cloud with Normals")
# 使法线方向一致
print("Orienting normals consistently...")
pcd.orient_normals_consistent_tangent_plane(k=30)
# 打印调整法线方向后的点云信息
print("Point cloud after orienting normals:")
print(pcd)
# 可视化带一致法线的点云
o3d.visualization.draw_geometries([pcd], point_show_normal=True, window_name="Point Cloud with Oriented Normals")
法向生成 | 法向统一 |
---|---|
![]() | ![]() |
通过 estimate_normals
和 orient_normals_consistent_tangent_plane
方法,你可以估计点云的法线并使其方向一致。这对于后续的点云处理和分析(如表面重建、配准等)非常重要。
10. 点云配准
Open3D 提供了多种点云配准方法,主要包括以下几种:
-
ICP (Iterative Closest Point) 配准:这是最常用的点云配准方法之一,通过迭代地最小化两组点云之间的距离来实现配准。
-
Colored ICP 配准:这是对传统 ICP 的改进,除了几何距离外,还考虑了颜色信息来进行配准。
-
Global Registration (全局配准):用于初始配准,通常在没有初始对齐的情况下使用。包括 RANSAC-based 和 Fast Global Registration (FGR) 方法。
-
Multiway Registration (多路配准):用于将多个点云配准到一个共同的参考框架中。
下面是每种方法的说明和案例:
10.1 ICP 配准
说明:通过迭代地最小化两组点云之间的距离来实现配准。
案例:
import open3d as o3d
import numpy as np
def create_colored_point_cloud(color):
"""创建并上色的球体点云"""
pcd = o3d.geometry.TriangleMesh.create_sphere(radius=1.0).sample_points_uniformly(number_of_points=1000)
pcd.paint_uniform_color(color)
return pcd
def visualize_point_clouds(pcd1, pcd2, window_name):
"""可视化点云"""
o3d.visualization.draw_geometries([pcd1, pcd2], window_name=window_name)
# 生成并上色两个相似的点云
pcd1 = create_colored_point_cloud([1, 0, 0]) # 红色
pcd2 = create_colored_point_cloud([0, 1, 0]) # 绿色
# 对第二个点云进行初始变换
initial_transformation = np.array([[0.862, 0.011, -0.507, 0.5],
[-0.139, 0.967, -0.215, 0.7],
[0.487, 0.255, 0.835, -1.4],
[0.0, 0.0, 0.0, 1.0]])
pcd2.transform(initial_transformation)
# 可视化初始点云
visualize_point_clouds(pcd1, pcd2, "Initial Point Clouds")
# 使用 ICP 进行配准
threshold = 0.7 # 增加阈值
trans_init = np.eye(4)
reg_p2p = o3d.pipelines.registration.registration_icp(
pcd1, pcd2, threshold, trans_init,
o3d.pipelines.registration.TransformationEstimationPointToPoint()
)
# 打印配准信息
print("ICP converged:", reg_p2p.inlier_rmse < threshold)
print("Fitness:", reg_p2p.fitness)
print("Inlier RMSE:", reg_p2p.inlier_rmse)
print("Transformation matrix:")
print(reg_p2p.transformation)
# 计算逆矩阵
inverse_transformation = np.linalg.inv(reg_p2p.transformation)
# 应用逆矩阵到第二个点云
pcd2.transform(inverse_transformation)
# 可视化配准后的点云
visualize_point_clouds(pcd1, pcd2, "Aligned Point Clouds")
原始点云 | 配准点云 |
---|---|
![]() | ![]() |
10.2 Colored ICP 配准
说明:在传统 ICP 的基础上,考虑了颜色信息来进行配准。
import open3d as o3d
import numpy as np
def create_colored_sphere(radius, color, density=1000):
mesh = o3d.geometry.TriangleMesh.create_sphere(radius=radius)
pcd = mesh.sample_points_poisson_disk(number_of_points=density)
pcd.paint_uniform_color(color)
return pcd
def estimate_normals(pcd, radius=0.1, max_nn=30):
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=radius, max_nn=max_nn))
def execute_colored_icp(source, target, max_correspondence_distance, initial_transformation):
criteria = o3d.pipelines.registration.ICPConvergenceCriteria(
relative_fitness=1e-3,
relative_rmse=1e-3,
max_iteration=500)
result = o3d.pipelines.registration.registration_colored_icp(
source, target, max_correspondence_distance, initial_transformation,
o3d.pipelines.registration.TransformationEstimationForColoredICP(),
criteria)
return result
# 创建两个绿色的球体
sphere1 = create_colored_sphere(1.0, [0, 1, 0], density=1000)
sphere2 = create_colored_sphere(1.0, [0, 1, 0], density=1000)
# 对第二个球体进行变换
transformation = np.array([[0.862, 0.011, -0.507, 0.5],
[-0.139, 0.967, -0.215, 0.7],
[0.487, 0.255, 0.835, -1.4],
[0.0, 0.0, 0.0, 1.0]])
sphere2.transform(transformation)
# 估算法线
estimate_normals(sphere1)
estimate_normals(sphere2)
# 可视化配准前的点云
print("配准前的点云:")
o3d.visualization.draw_geometries([sphere1, sphere2], window_name="Before Registration")
# 使用彩色ICP进行配准
max_correspondence_distance = 5.0 # 增大最大对应点距离
initial_transformation = np.identity(4)
result_colored_icp = execute_colored_icp(sphere1, sphere2, max_correspondence_distance, initial_transformation)
# 应用变换到第一个球体点云
sphere1.transform(result_colored_icp.transformation)
# 可视化配准后的点云
print("配准后的点云:")
o3d.visualization.draw_geometries([sphere1, sphere2], window_name="After Registration")
- 代码说明:
- 创建彩色球体:使用
create_colored_sphere
函数创建两个绿色的球体。 - 对第二个球体进行变换:对第二个球体进行随机变换。
- 估算法线:计算球体点云的法线。
- 可视化配准前的点云:在配准前显示两个球体点云。
- 使用彩色ICP进行配准:使用彩色ICP算法进行配准。
- 应用变换到第一个球体点云:将配准结果应用到第一个球体点云。
- 可视化配准后的点云:在配准后显示两个球体点云。
原始点云 | 配准点云 |
---|---|
![]() | ![]() |
10.3 全局配准
说明:用于初始配准,通常在没有初始对齐的情况下使用。
好的,以下是包含配准前后可视化的完整代码:
import open3d as o3d
import numpy as np
# 生成点云数据
def generate_point_cloud():
# 创建一个球体点云
mesh = o3d.geometry.TriangleMesh.create_sphere(radius=1.0)
pcd = mesh.sample_points_poisson_disk(number_of_points=500)
return pcd
# 生成源点云和目标点云
source = generate_point_cloud()
target = generate_point_cloud()
# 对目标点云进行随机变换
transformation = np.array([[0.862, 0.011, -0.507, 0.5],
[-0.139, 0.967, -0.215, 0.7],
[0.487, 0.255, 0.835, -1.4],
[0.0, 0.0, 0.0, 1.0]])
target.transform(transformation)
# 下采样点云
voxel_size = 0.05
source_down = source.voxel_down_sample(voxel_size)
target_down = target.voxel_down_sample(voxel_size)
# 估计法线
source_down.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
target_down.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
# 计算FPFH特征
source_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
source_down,
o3d.geometry.KDTreeSearchParamHybrid(radius=0.25, max_nn=100))
target_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
target_down,
o3d.geometry.KDTreeSearchParamHybrid(radius=0.25, max_nn=100))
# 使用RANSAC进行全局配准
result_ransac = o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
source_down, target_down, source_fpfh, target_fpfh,
mutual_filter=True,
max_correspondence_distance=0.15,
estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPoint(False),
ransac_n=4,
checkers=[o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(0.9),
o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(0.15)],
criteria=o3d.pipelines.registration.RANSACConvergenceCriteria(4000000, 500))
print(result_ransac)
# 可视化配准前的点云
source_temp = source_down.transform(np.identity(4)) # 恢复原始位置
o3d.visualization.draw_geometries([source_temp, target_down], window_name="配准前")
# 可视化配准后的点云
source_temp = source_down.transform(result_ransac.transformation)
o3d.visualization.draw_geometries([source_temp, target_down], window_name="配准后")
- 代码说明:
- 生成点云数据:创建一个球体点云,并对目标点云进行随机变换。
- 下采样点云:对点云进行体素下采样,以减少计算量。
- 估计法线:计算点云的法线。
- 计算FPFH特征:计算快速点特征直方图(FPFH)特征。
- 使用RANSAC进行全局配准:使用 RANSAC 算法基于特征匹配进行全局配准。
- 可视化配准前的点云:在配准前显示源点云和目标点云。
- 可视化配准后的点云:在配准后显示源点云和目标点云。
初始点云 | 配准点云 |
---|---|
![]() | ![]() |
10.4 多路配准
说明:用于将多个点云配准到一个共同的参考框架中。
import open3d as o3d
import numpy as np
def create_colored_sphere(radius, color, density=1000):
mesh = o3d.geometry.TriangleMesh.create_sphere(radius=radius)
pcd = mesh.sample_points_poisson_disk(number_of_points=density)
pcd.paint_uniform_color(color)
return pcd
def preprocess_point_cloud(pcd, voxel_size):
pcd_down = pcd.voxel_down_sample(voxel_size)
radius_normal = voxel_size * 2
pcd_down.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30))
radius_feature = voxel_size * 5
pcd_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
pcd_down,
o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100))
return pcd_down, pcd_fpfh
def pairwise_registration(source, target, voxel_size):
source_down, source_fpfh = preprocess_point_cloud(source, voxel_size)
target_down, target_fpfh = preprocess_point_cloud(target, voxel_size)
distance_threshold = voxel_size * 1.5
result = o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
source_down, target_down, source_fpfh, target_fpfh, True,
distance_threshold,
o3d.pipelines.registration.TransformationEstimationPointToPoint(False),
4, [
o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(0.9),
o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(distance_threshold)
], o3d.pipelines.registration.RANSACConvergenceCriteria(4000000, 500))
return result
def full_registration(pcds, voxel_size):
pose_graph = o3d.pipelines.registration.PoseGraph()
odometry = np.identity(4)
pose_graph.nodes.append(o3d.pipelines.registration.PoseGraphNode(odometry))
for source_id in range(len(pcds)):
for target_id in range(source_id + 1, len(pcds)):
result = pairwise_registration(pcds[source_id], pcds[target_id], voxel_size)
trans = result.transformation
information = o3d.pipelines.registration.get_information_matrix_from_point_clouds(
pcds[source_id], pcds[target_id], voxel_size * 1.5, result.transformation)
if target_id == source_id + 1:
odometry = np.dot(trans, odometry)
pose_graph.nodes.append(o3d.pipelines.registration.PoseGraphNode(np.linalg.inv(odometry)))
pose_graph.edges.append(o3d.pipelines.registration.PoseGraphEdge(source_id, target_id, trans, information, uncertain=False))
else:
pose_graph.edges.append(o3d.pipelines.registration.PoseGraphEdge(source_id, target_id, trans, information, uncertain=True))
return pose_graph
def run_global_optimization(pose_graph):
option = o3d.pipelines.registration.GlobalOptimizationOption(
max_correspondence_distance=0.02,
edge_prune_threshold=0.25,
reference_node=0)
o3d.pipelines.registration.global_optimization(
pose_graph,
o3d.pipelines.registration.GlobalOptimizationLevenbergMarquardt(),
o3d.pipelines.registration.GlobalOptimizationConvergenceCriteria(),
option)
def merge_point_clouds(pcds, pose_graph):
pcd_combined = o3d.geometry.PointCloud()
for point_id in range(len(pcds)):
pcd_transformed = pcds[point_id].transform(pose_graph.nodes[point_id].pose)
pcd_combined += pcd_transformed
return pcd_combined
# 创建不同颜色和大小的球体
sphere1 = create_colored_sphere(1.0, [1, 0, 0], density=1000) # 红色球体
sphere2 = create_colored_sphere(0.8, [0, 1, 0], density=1000) # 绿色球体
sphere3 = create_colored_sphere(0.6, [0, 0, 1], density=1000) # 蓝色球体
# 对球体进行随机变换
transformation1 = np.array([[0.862, 0.011, -0.507, 0.5],
[-0.139, 0.967, -0.215, 0.7],
[0.487, 0.255, 0.835, -1.4],
[0.0, 0.0, 0.0, 1.0]])
sphere2.transform(transformation1)
transformation2 = np.array([[0.707, -0.707, 0.0, 1.0],
[0.707, 0.707, 0.0, 0.5],
[0.0, 0.0, 1.0, -0.5],
[0.0, 0.0, 0.0, 1.0]])
sphere3.transform(transformation2)
pcds = [sphere1, sphere2, sphere3]
# 设置体素大小
voxel_size = 0.05
# 可视化配准前的点云
print("配准前的点云")
o3d.visualization.draw_geometries(pcds, window_name="Before Registration")
# 进行多路配准
pose_graph = full_registration(pcds, voxel_size)
# 运行全局优化
run_global_optimization(pose_graph)
# 合并点云
pcd_combined = merge_point_clouds(pcds, pose_graph)
# 可视化配准后的点云
print("配准后的点云")
o3d.visualization.draw_geometries([pcd_combined], window_name="After Registration")
- 代码说明:
- 创建彩色球体:使用
create_colored_sphere
函数创建不同颜色和大小的球体。 - 对球体进行变换:对第二个和第三个球体进行随机变换。
- 预处理点云:使用
preprocess_point_cloud
函数对点云进行下采样和特征提取。 - 配对配准:使用
pairwise_registration
函数对两个点云进行配对配准。 - 全局配准:使用
full_registration
函数对所有点云进行全局配准,构建位姿图。 - 全局优化:使用
run_global_optimization
函数对位姿图进行全局优化。 - 合并点云:使用
merge_point_clouds
函数将所有点云合并到一个全局坐标系中。 - 可视化配准前的点云:使用 Open3D 的可视化工具显示配准前的点云。
- 可视化配准后的点云:使用 Open3D 的可视化工具显示配准后的点云。
原始点云 | 配准点云 |
---|---|
![]() | ![]() |
这些案例展示了 Open3D 中不同点云配准方法的基本用法。
11. 点云表面重建
11.1 Alpha形状重建
Alpha形状重建是一种用于从点云数据生成三角网格的方法。它基于计算几何中的Alpha形状理论。Alpha形状是由Edelsbrunner等人在1983年提出的,它是Delaunay三角剖分的一个子集,用于描述点集的形状。
11.1.1 原理
-
Delaunay三角剖分:
- 首先,对点云进行Delaunay三角剖分。Delaunay三角剖分是一种将点集划分为一系列三角形的算法,具有最大化最小角的性质,避免了瘦长三角形。
-
Alpha球:
- 对于给定的参数α,定义一个半径为α的球(称为Alpha球)。Alpha球用于筛选Delaunay三角剖分中的三角形。
-
筛选三角形:
- 对于每个Delaunay三角剖分中的三角形,检查其外接圆的半径。如果外接圆的半径小于或等于α,则保留该三角形;否则,丢弃该三角形。
-
生成Alpha形状:
- 保留的三角形构成了Alpha形状。通过调整α的值,可以控制生成的形状的细节程度。较小的α值会生成更细致的形状,而较大的α值会生成更平滑的形状。
11.1.2 代码示例
以下是使用Open3D库进行Alpha形状重建的代码示例:
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("doll_1.ply")
# 估计法线
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
# Alpha形状重建
alpha = 0.03 # 调整alpha值
mesh_alpha = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd, alpha)
# 可视化Alpha形状重建结果
mesh_alpha.compute_vertex_normals()
o3d.visualization.draw_geometries([mesh_alpha], window_name=f"Alpha Shape Reconstruction with alpha={alpha}")
11.1.3 调整Alpha值
通过调整alpha值,可以生成不同细节程度的形状:
- 较小的alpha值:生成的形状更细致,保留更多的细节。
- 较大的alpha值:生成的形状更平滑,去除了更多的细节。
11.1.4 总结
Alpha形状重建是一种有效的从点云数据生成三角网格的方法,通过调整alpha值,可以控制生成形状的细节程度。它在计算几何和计算机图形学中有广泛的应用。
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("doll_1.ply")
# 估计法线
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
# 调整Alpha形状重建参数
print("调整Alpha形状重建参数...")
alphas = [0.01, 0.03, 0.05]
for alpha in alphas:
mesh_alpha = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(pcd, alpha)
mesh_alpha.compute_vertex_normals()
title = f"Alpha Shape Reconstruction with alpha={alpha}"
print(title)
o3d.visualization.draw_geometries([mesh_alpha], window_name=title)
11.2 泊松重建
泊松重建(Poisson Surface Reconstruction)是一种从点云数据生成平滑三角网格的方法。它基于泊松方程,通过全局优化的方法生成表面,能够有效处理噪声和不完整的点云数据。
11.2.1 原理
-
法线估计:
- 首先,从点云数据中估计每个点的法线方向。这一步通常使用邻域搜索算法,如KD树搜索。
-
泊松方程:
泊松重建的核心是求解泊松方程。泊松方程是一种偏微分方程,形式为:
∇ ⋅ V = ρ \nabla \cdot \mathbf{V} = \rho ∇⋅V=ρ
其中, V \mathbf{V} V 是一个向量场, ρ \rho ρ 是一个标量场。在泊松重建中, V \mathbf{V} V 是由点云的法线生成的向量场, ρ \rho ρ 是点云的散度。
-
构建八叉树:
- 为了高效地求解泊松方程,使用八叉树(Octree)对点云进行分层表示。八叉树将空间递归地划分为八个子空间,直到达到指定的深度。
-
求解泊松方程:
- 在八叉树的每个节点上,求解泊松方程。通过最小二乘法或其他数值方法,计算出每个节点的标量场值。
-
提取等值面:
- 使用Marching Cubes算法从标量场中提取等值面,生成三角网格。等值面是标量场中具有相同值的点的集合。
-
平滑和优化:
- 对生成的三角网格进行平滑和优化,以去除噪声和不规则性,生成最终的平滑表面。
11.2.2 代码示例
以下是使用Open3D库进行泊松重建的代码示例:
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("doll_1.ply")
# 估计法线
pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
# 泊松重建
depth = 9 # 调整深度参数
mesh_poisson, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=depth)
# 可视化泊松重建结果
mesh_poisson.compute_vertex_normals()
o3d.visualization.draw_geometries([mesh_poisson], window_name=f"Poisson Reconstruction with depth={depth}")
11.2.3 调整深度参数
通过调整深度参数,可以控制生成的网格的细节程度:
- 较小的深度值:生成的网格较粗糙,计算速度较快。
- 较大的深度值:生成的网格较细致,计算速度较慢。
11.2.4 总结
泊松重建是一种从点云数据生成平滑三角网格的有效方法。它通过求解泊松方程,能够处理噪声和不完整的点云数据,生成高质量的表面。通过调整深度参数,可以控制生成网格的细节程度。
12. 最小包围盒
使用Open3D计算点云的包围盒可以通过以下两种方式:轴对齐包围盒(Axis-Aligned Bounding Box, AABB)和有向包围盒(Oriented Bounding Box, OBB)。
下面是一个示例代码,展示如何计算和可视化这两种包围盒。
- 示例代码
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("doll_1.ply")
# 计算轴对齐包围盒(AABB)
aabb = pcd.get_axis_aligned_bounding_box()
# 计算有向包围盒(OBB)
obb = pcd.get_oriented_bounding_box()
# 设置包围盒的颜色
aabb.color = (1, 0, 0) # 红色
obb.color = (0, 1, 0) # 绿色
# 可视化点云和包围盒
o3d.visualization.draw_geometries([pcd, aabb, obb], window_name="Bounding Boxes")
- 说明
-
读取点云:
- 使用
o3d.io.read_point_cloud
函数读取点云数据。
- 使用
-
计算轴对齐包围盒(AABB):
- 使用
pcd.get_axis_aligned_bounding_box()
方法计算点云的轴对齐包围盒。AABB是一个与坐标轴对齐的最小包围盒。
- 使用
-
计算有向包围盒(OBB):
- 使用
pcd.get_oriented_bounding_box()
方法计算点云的有向包围盒。OBB是一个最小体积的包围盒,可以任意旋转。
- 使用
-
设置包围盒的颜色:
- 通过设置
color
属性来改变包围盒的颜色,以便在可视化时区分不同的包围盒。
- 通过设置
-
可视化点云和包围盒:
- 使用
o3d.visualization.draw_geometries
函数同时可视化点云和包围盒。
- 使用
- 总结
通过上述代码,可以使用Open3D计算点云的轴对齐包围盒和有向包围盒,并进行可视化。这对于点云数据的分析和处理非常有用。
13. 凸包
使用Open3D计算点云的凸包可以通过 compute_convex_hull
方法来实现。
以下是一个示例代码,展示如何计算和可视化点云的凸包。
- 示例代码
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("doll_1.ply")
# 计算凸包
hull, _ = pcd.compute_convex_hull()
# 设置凸包的颜色
hull.paint_uniform_color([1, 0, 0]) # 红色
# 可视化点云和凸包
o3d.visualization.draw_geometries([pcd, hull], window_name="Convex Hull")
- 说明
-
读取点云:
- 使用
o3d.io.read_point_cloud
函数读取点云数据。
- 使用
-
计算凸包:
- 使用
pcd.compute_convex_hull()
方法计算点云的凸包。该方法返回一个三角网格表示的凸包和一个索引数组(这里我们只关心凸包)。
- 使用
-
设置凸包的颜色:
- 使用
hull.paint_uniform_color([1, 0, 0])
方法将凸包的颜色设置为红色,以便在可视化时区分凸包和点云。
- 使用
-
可视化点云和凸包:
- 使用
o3d.visualization.draw_geometries
函数同时可视化点云和凸包。
- 使用
-
总结
- 通过上述代码,可以使用Open3D计算点云的凸包,并进行可视化。这对于点云数据的分析和处理非常有用,特别是在需要了解点云的外部形状时。
14. 体素化
使用Open3D计算点云的体素化可以通过 voxel_down_sample
方法来实现。体素化是将点云划分为固定大小的立方体(体素),并用每个体素内的点的中心点来代表该体素。
以下是一个示例代码,展示如何对点云进行体素化处理并进行可视化。
- 示例代码
import open3d as o3d
# 读取点云
pcd = o3d.io.read_point_cloud("doll_1.ply")
# 设置体素大小
voxel_size = 0.05
# 进行体素化
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=voxel_size)
# 可视化原始点云和体素化后的点云
o3d.visualization.draw_geometries([pcd], window_name="Original Point Cloud")
o3d.visualization.draw_geometries([voxel_grid], window_name="Voxelized Point Cloud")
- 说明
-
读取点云:
- 使用
o3d.io.read_point_cloud
函数读取点云数据。
- 使用
-
设置体素大小:
- 定义体素的大小(例如
voxel_size = 0.05
),该值决定了每个体素的边长。
- 定义体素的大小(例如
-
进行体素化:
- 使用
o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=voxel_size)
方法对点云进行体素化处理。该方法返回一个VoxelGrid
对象,表示体素化后的点云。
- 使用
-
可视化原始点云和体素化后的点云:
- 使用
o3d.visualization.draw_geometries
函数分别可视化原始点云和体素化后的点云。
- 使用
-
总结
- 通过上述代码,可以使用Open3D对点云进行体素化处理,并进行可视化。体素化处理可以有效地减少点云数据的数量,同时保留点云的整体结构,对于点云数据的分析和处理非常有用。