学习笔记 发布于 更新于

Open3D 学习笔记:点云、KD-Tree、Octree 与基础处理

围绕 Open3D 中常见的数据结构、点云读写与可视化、KD-Tree / Octree、滤波与法线估计做一版更适合复习的整理。

#Open3D#点云#KD-Tree#Octree#3D视觉

这篇内容基于原始 Notion 导出整理,保留原笔记的知识主线,同时清理了导入残留、代码块污染和不自然的标题层级,方便后续复习与继续补充。

一、Open3D 是什么

Open3D 是一个常用的 3D 数据处理库,适合做:

  • 点云读写与可视化
  • 点云/网格/RGB-D 数据结构操作
  • 邻域搜索
  • 滤波、法线估计、配准、重建等基础几何处理

对后续 3D 视觉、点云处理或配准实验而言,Open3D 基本可以看作一个很好用的工具箱。

Open3D 数据结构概览

这里先把最常见的几个基础模块整理清楚:

  1. 点云、网格、RGB-D 这三类核心数据结构
  2. 点云的读写与可视化
  3. KD-Tree 与 Octree
  4. 常见滤波
  5. 法线估计

二、Open3D 中常见的数据结构

1. 点云(Point Cloud)

点云是最常见的 3D 数据形式,本质上是一组三维点的集合。一个点云对象中常见的数据包括:

  • 点坐标 points
  • 颜色 colors
  • 法向量 normals
import open3d as o3d

pcd = o3d.geometry.PointCloud()

pcd.points    # 点的坐标 (x, y, z)
pcd.colors    # 点的颜色 (r, g, b),可选
pcd.normals   # 点的法向量 (nx, ny, nz),可选

2. 网格(Mesh)

网格更适合表达连续表面,通常由顶点和三角面片组成。

import open3d as o3d

mesh = o3d.geometry.TriangleMesh()

mesh.vertices          # 顶点坐标
mesh.triangles         # 三角面片(顶点索引)
mesh.vertex_colors     # 顶点颜色,可选
mesh.vertex_normals    # 顶点法向量,可选
mesh.triangle_normals  # 面片法向量,可选

3. RGB-D 图像

RGB-D 图像可以理解为“彩色图 + 深度图”的组合。每个像素不仅有颜色信息,还有到相机的距离信息。

import open3d as o3d

rgbd = o3d.geometry.RGBDImage()

rgbd.color  # RGB 图像
rgbd.depth  # 深度图像

直观上可以这样理解:

  • RGB 图像对应像素的颜色信息
  • 深度图对应像素到相机的距离信息

这也是很多三维重建流程的起点。

4. 三者之间的关系

在很多任务里,这三类数据并不是孤立的,而是能相互转换:

  • RGB-D → 点云:根据相机内参把像素反投影到三维空间
  • 点云 → 网格:通过曲面重建方法恢复连续表面

也就是说,常见链路可以概括为:

RGB-D 图像 → 点云 → 网格

5. RGB-D 转点云

import open3d as o3d

color = o3d.io.read_image("color.jpg")
depth = o3d.io.read_image("depth.png")
rgbd = o3d.geometry.RGBDImage.create_from_color_and_depth(color, depth)

intrinsic = o3d.camera.PinholeCameraIntrinsic(
    width=640,
    height=480,
    fx=525.0,
    fy=525.0,
    cx=319.5,
    cy=239.5,
)

pcd = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd, intrinsic)

这一步里最关键的是相机内参,因为它决定了二维像素如何被映射到三维坐标系中。

6. 点云转网格

点云转网格本质上属于表面重建问题,常见方法包括:

  • 泊松重建(Poisson Reconstruction)
  • Ball Pivoting
  • Alpha Shapes
# 方法 1:泊松表面重建
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
    pcd, depth=9
)

# 方法 2:Ball Pivoting
radii = [0.005, 0.01, 0.02, 0.04]
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
    pcd, o3d.utility.DoubleVector(radii)
)

# 方法 3:Alpha Shapes
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(
    pcd, alpha=0.03
)

这里要注意:不同重建方法对点云密度、噪声和法线质量都比较敏感,实际使用时通常要先做滤波和法线估计。

三、点云读写

1. 基本读写接口

import open3d as o3d

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

print("-> 正在保存点云...")
o3d.io.write_point_cloud("write.pcd", pcd, write_ascii=True)

常用接口就是:

  • o3d.io.read_point_cloud()
  • o3d.io.write_point_cloud()

2. 常见点云文件格式

PLY

PLY 比较适合存储顶点、颜色、面片等信息。

PLY 文件结构示意

PCD

PCD 是点云处理中非常常见的一种格式,常见头信息包括:

PCD 文件头示意

  • FIELDS:数据字段,例如 x y z rgb
  • SIZE:每个字段所占字节数
  • TYPE:字段数据类型,例如 F 表示 float
  • COUNT:每个字段的通道数
  • WIDTH / HEIGHT:描述点云组织形式
  • VIEWPOINT:采集视角或传感器位姿
  • POINTS:点总数
  • DATA:存储方式,例如 asciibinary

这里有一个值得记一下的问题:

VIEWPOINT 背后会涉及位姿表示,所以后面确实有必要继续补四元数、旋转矩阵、欧拉角之间的关系。

这部分和点云配准、相机位姿估计都很相关。

四、点云可视化

最常见的可视化接口是:

import open3d as o3d

pcd = o3d.io.read_point_cloud("test.pcd")
o3d.visualization.draw_geometries([pcd])

如果要同时显示多个点云,也可以直接放进同一个列表里:

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

下图就是典型的点云显示效果:

点云可视化示意

五、KD-Tree 与 Octree

这两个结构本质上都是为了提高空间查询效率,但适用思路不一样。

5.1 KD-Tree

KD-Tree 是一种面向 k 维空间的二叉树结构。对三维点云来说,它通常会交替地按照 x、y、z 方向切分空间,因此非常适合做:

  • 最近邻搜索
  • 半径搜索
  • 混合邻域搜索

KD-Tree 搜索示意

在 Open3D 里建立 KD-Tree 很直接:

pcd_tree = o3d.geometry.KDTreeFlann(pcd)

1. K 近邻搜索

k = 5000
[num_k, idx_k, _] = pcd_tree.search_knn_vector_3d(pcd.points[1500], k)

返回值含义:

  • num_k:找到的邻域点数量
  • idx_k:邻域点索引
  • _:距离平方列表,很多时候可以忽略

如果想把邻域点染成蓝色,可以这样写:

import numpy as np

np.asarray(pcd.colors)[idx_k[1:], :] = [0, 0, 1]

这里 idx_k[1:] 的作用是跳过查询点本身。

2. 半径邻域与混合邻域

radius = 0.02
[num_radius, idx_radius, _] = pcd_tree.search_radius_vector_3d(
    pcd.points[1500], radius
)

max_nn = 200
[num_hybrid, idx_hybrid, _] = pcd_tree.search_hybrid_vector_3d(
    pcd.points[1500], radius, max_nn
)
  • KNN:返回固定个数的最近邻
  • Radius Search:返回固定半径内的所有点
  • Hybrid Search:返回固定半径内、但不超过最大数量的点

3. 一个完整示例

import open3d as o3d
import numpy as np

pcd = o3d.io.read_point_cloud("bunny.pcd")
pcd.paint_uniform_color([0.5, 0.5, 0.5])

pcd_tree = o3d.geometry.KDTreeFlann(pcd)

# 查询点染成紫色
np.asarray(pcd.colors)[1500] = [0.5, 0.0, 0.5]

# K 近邻染成蓝色
k = 5000
[num_k, idx_k, _] = pcd_tree.search_knn_vector_3d(pcd.points[1500], k)
np.asarray(pcd.colors)[idx_k[1:], :] = [0, 0, 1]

# 半径邻域染成红色
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]

# 混合邻域染成绿色
max_nn = 200
[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]

o3d.visualization.draw_geometries([pcd])

5.2 Octree

Octree 是一种面向三维空间的树结构,每个节点对应一个立方体区域,并继续细分成 8 个子立方体。

它常用于:

  • 空间划分
  • 稀疏表示
  • 体素化相关处理
  • 空间查询
  • 碰撞检测与渲染

Octree 结构示意

1. 从点云创建 Octree

import open3d as o3d

pcd = o3d.io.read_point_cloud("tree.pcd")

octree = o3d.geometry.Octree(max_depth=4)
octree.convert_from_point_cloud(pcd, size_expand=0.01)

o3d.visualization.draw_geometries([octree])

这里 max_depth=4 表示树的最大深度。深度越大,空间划分越细,但开销也会增加。

2. 从体素栅格创建 Octree

import open3d as o3d

pcd = o3d.io.read_point_cloud("tree.pcd")
voxel_grid = o3d.geometry.VoxelGrid.create_from_point_cloud(pcd, voxel_size=0.2)

octree = o3d.geometry.Octree(max_depth=4)
octree.create_from_voxel_grid(voxel_grid)

o3d.visualization.draw_geometries([octree])

如果后面涉及大场景点云组织、稀疏表示或者搜索加速,Octree 是值得继续深入的。

六、点云滤波

点云处理中,滤波的目的通常是:

  • 去掉离群点
  • 做平滑
  • 减少噪声
  • 降低点数规模

原笔记里列了一个总表,核心可以先这样记:

  • 统计滤波:去离群点
  • 半径滤波:去孤立点
  • 体素下采样:降采样
  • 均值/中值滤波:平滑
  • 直通滤波:按空间范围裁剪

1. 统计滤波示例

import open3d as o3d

pcd = o3d.io.read_point_cloud("desk.pcd")

num_neighbors = 20
std_ratio = 2.0

sor_pcd, ind = pcd.remove_statistical_outlier(num_neighbors, std_ratio)
sor_pcd.paint_uniform_color([0, 0, 1])

sor_noise_pcd = pcd.select_by_index(ind, invert=True)
sor_noise_pcd.paint_uniform_color([1, 0, 0])

o3d.visualization.draw_geometries([sor_pcd, sor_noise_pcd])

这里:

  • 蓝色表示保留下来的点
  • 红色表示被识别为噪声的点

这个接口在实际中很常用,因为统计滤波对离群噪声点有比较直观的处理效果。

2. 这一部分后续还可以继续补什么

原笔记里还列到了:

  • 半径滤波
  • 直通滤波
  • 均值滤波
  • 中值滤波
  • 组合滤波

这部分后面很适合单独再扩成一篇“点云滤波专题”,因为它和点云质量、后续配准/重建效果直接相关。

七、点云特征提取:法线估计

法线是点云处理中非常基础的一类局部几何特征。很多任务都离不开它,比如:

  • 表面重建
  • 配准
  • 曲率估计
  • 局部特征描述

法线估计示例

import open3d as o3d
import numpy as np

pcd = o3d.io.read_point_cloud("bunny.pcd")

radius = 0.01
max_nn = 30

pcd.estimate_normals(
    search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=radius, max_nn=max_nn)
)

o3d.visualization.draw_geometries([pcd], point_show_normal=True)

print(np.asarray(pcd.normals)[:10, :])

这段代码本质上做了两件事:

  1. 在每个点附近找一小片邻域
  2. 用邻域几何关系估计该点法向量

这里又能看出 KD-Tree 的重要性:很多点云局部特征计算,本质上都依赖高效邻域搜索。

八、点云分割

点云分割的目标,是把一个大点云拆成更有意义的局部结构,比如:

  • 不同物体
  • 不同平面
  • 不同区域
  • 背景与前景

在 Open3D 的基础流程里,分割通常不是最后一步,而是很多下游任务前的预处理步骤。比如做目标提取、局部重建、局部配准时,先把点云拆开往往更有效。

8.1 DBSCAN 聚类分割

DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类方法。它的优点是:

  • 不需要提前指定簇的数量
  • 能识别噪声点
  • 对形状不规则的簇比较友好

在点云里,它很适合做“把局部连成片的点分成不同簇”这类任务。

Open3D 中常用接口是:

import open3d as o3d
import numpy as np
import matplotlib.pyplot as plt

pcd = o3d.io.read_point_cloud("cluster.pcd")
labels = np.array(pcd.cluster_dbscan(eps=0.02, min_points=10, print_progress=True))

max_label = labels.max()
print(f"点云共有 {max_label + 1} 个聚类")

colors = plt.get_cmap("tab20")(labels / (max_label if max_label > 0 else 1))
colors[labels < 0] = 0
pcd.colors = o3d.utility.Vector3dVector(colors[:, :3])

o3d.visualization.draw_geometries([pcd])

这两个参数最重要:

  • eps:邻域半径,决定多近的点会被看作一个局部密度区域
  • min_points:最少邻域点数,决定一个区域是否足以形成簇

另外,Open3D 文档也提醒过:DBSCAN 在计算前会预计算 epsilon 半径内的邻居,如果 eps 设得太大,内存开销会明显上升。

所以这类参数一般要结合点云尺度来调:

  • 点云尺度大,eps 通常也要更大
  • 点云非常稠密,min_points 可以适当增大
  • 如果噪声特别多,往往要先做滤波,再做聚类

8.2 DBSCAN 适合什么、不适合什么

它适合:

  • 多个物体天然分离的场景
  • 想直接找局部连通结构的场景
  • 需要显式识别噪声点的情况

它不太适合:

  • 不同区域密度差异特别大
  • 大规模超密点云,参数非常难统一
  • 物体本来就紧贴或交叠的场景

如果后面涉及桌面场景点云、物体分离或局部区域提取,DBSCAN 是一个值得优先上手的基础方法。

九、点云曲面重建

曲面重建的目标,是把离散点云恢复成连续曲面,也就是从“散点”变成“可渲染、可分析的表面网格”。

这一步在很多任务里都很重要,比如:

  • 3D 建模
  • 逆向工程
  • 可视化展示
  • 几何分析
  • 后续网格处理

9.1 为什么重建前通常要先估计法线

很多曲面重建方法不仅需要点的位置,还依赖点的局部方向信息,也就是法线。尤其是:

  • Poisson Surface Reconstruction
  • Ball Pivoting

如果法线估计不准,或者方向不一致,生成的网格很容易:

  • 破碎
  • 过度平滑
  • 出现错误连接
  • 局部翻面

所以比较标准的流程通常是:

点云 → 滤波 / 下采样 → 法线估计 → 法线方向一致化 → 曲面重建

9.2 Alpha Shapes

Alpha Shapes 可以理解成凸包的一个推广,通过 alpha 参数控制最终保留几何细节的程度。

mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(
    pcd, alpha=0.03
)

特点是:

  • 参数直观
  • 适合快速试验
  • 对局部细节有一定表达能力

但它对参数比较敏感,alpha 不同,结果可能差很多。

9.3 Ball Pivoting

Ball Pivoting Algorithm(BPA)可以直观理解成:

拿一个给定半径的小球在点云表面“滚动”,当球同时接触 3 个点时就形成一个三角形。

radii = [0.005, 0.01, 0.02, 0.04]
mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(
    pcd, o3d.utility.DoubleVector(radii)
)

它比较适合:

  • 点分布相对均匀
  • 法线质量较好
  • 希望表面细节尽量被保留

9.4 Poisson 重建

Poisson Surface Reconstruction 是很常见的曲面重建方法。它通常能生成比较平滑、整体性较强的网格。

mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
    pcd, depth=9
)

其中:

  • depth 可以理解成控制重建分辨率的重要参数
  • 数值越大,结果可能越细,但计算量和内存开销也越大

Poisson 的优点是:

  • 结果通常比较完整和平滑
  • 对整体表面恢复效果较好

缺点是:

  • 容易“补洞”过头
  • 对噪声、法线和边界条件比较敏感

如果目标是“展示一个完整表面”,Poisson 常常是很实用的起点;如果更在意局部几何边界,BPA 或其他方法有时会更合适。

十、点云空间变换

点云空间变换本质上就是把点从一个坐标系映射到另一个坐标系。这在 3D 视觉里几乎到处都会遇到,比如:

  • 相机坐标系 → 世界坐标系
  • 局部坐标系 → 全局坐标系
  • 单帧点云 → 配准到另一帧点云

10.1 常见变换类型

最常见的是刚体变换,包括:

  • 旋转(Rotation)
  • 平移(Translation)

如果用齐次矩阵表示,一般写成 4×4 形式:

[ T = \begin{bmatrix} R & t \ 0 & 1 \end{bmatrix} ]

其中:

  • R 是 3×3 旋转矩阵
  • t 是 3×1 平移向量

10.2 Open3D 中的基本变换操作

import open3d as o3d
import numpy as np

pcd = o3d.io.read_point_cloud("test.pcd")

T = np.array([
    [1, 0, 0, 0.1],
    [0, 1, 0, 0.0],
    [0, 0, 1, 0.2],
    [0, 0, 0, 1.0],
])

pcd.transform(T)

除此之外,还会常见到:

  • translate():平移
  • rotate():旋转
  • scale():缩放
  • transform():应用完整齐次变换矩阵

10.3 为什么这一节重要

很多刚接触点云的人会把“配准”和“变换”分开看,但实际上:

  • 配准的输出,本质上就是一个空间变换矩阵
  • 配准算法的目标,就是求出把 source 对齐到 target 的那组变换参数

如果后面要认真做配准,旋转矩阵、平移、齐次变换、坐标系切换这些内容都需要真正吃透。

十一、点云配准

点云配准的目标,是求出两个点云之间的空间变换,使它们在同一坐标系下尽可能对齐。

这是点云处理中最核心的任务之一,尤其在下面这些场景里特别常见:

  • 多视角拼接
  • SLAM / 建图
  • 3D 重建
  • 工业测量
  • 扫描数据融合

11.1 配准一般分成两步

一个很经典的思路是:

  1. 粗配准(Global Registration)
    • 不依赖很好的初始位姿
    • 目标是先找到一个大致正确的对齐关系
  2. 精配准(Local Registration)
    • 在粗配准结果基础上进一步优化
    • 常见代表就是 ICP

Open3D 文档里也明确区分了这一点:

  • Global registration 用来给初始化
  • ICP 这类 local method 用来做精细对齐

11.2 FPFH + RANSAC 粗配准

如果 source 和 target 一开始差得比较远,通常不能直接上 ICP,因为它容易陷入局部最优。

所以常见做法是:

  • 先下采样
  • 再估计法线
  • 再计算 FPFH 特征
  • 然后用 RANSAC 找匹配并估计初始位姿

这里 FPFH(Fast Point Feature Histogram)本质上是一种局部几何特征描述子,能把一个点附近的几何形状编码成特征向量,方便做点之间的粗匹配。

11.3 ICP 精配准

ICP(Iterative Closest Point)是最经典的精配准方法之一。它的基本思路是反复执行两步:

  1. 建立当前最近邻对应关系
  2. 更新变换矩阵,使对应误差最小

Open3D 里常见两种版本:

  • Point-to-Point ICP
  • Point-to-Plane ICP

Point-to-Point ICP

目标函数可以理解成:

让对应点之间的欧氏距离平方和尽量小

它比较直观,也容易理解。

Point-to-Plane ICP

它不是直接最小化点到点距离,而是最小化:

source 点到 target 对应点切平面的距离

这种方法通常收敛更快,也常常更稳定,但前提是法线估计要靠谱。

Open3D 文档里也提到,point-to-plane ICP 在很多情况下比 point-to-point ICP 收敛更快。

11.4 配准结果怎么评估

Open3D 提供了比较实用的评估接口 evaluate_registration(),常见两个指标是:

  • fitness:内点对应比例,可以粗略理解成重叠程度,越高越好
  • inlier_rmse:内点对应的均方根误差,越低越好

如果后面要做实验,不能只看“图上好像对齐了”,还应该结合:

  • fitness
  • inlier RMSE
  • 可视化重叠效果
  • 局部区域误差

一起判断配准质量。

11.5 一条最常见的 Open3D 配准路线

一个非常经典的流程就是:

  1. voxel_down_sample
  2. estimate_normals
  3. compute_fpfh_feature
  4. registration_ransac_based_on_feature_matching
  5. registration_icp
  6. evaluate_registration

如果后面涉及多视角点云拼接,这条路线非常值得先彻底跑通。

十二、常用算法

如果从“点云基础处理”这个角度整理,Open3D 里最常见、也最值得优先掌握的一批算法可以按下面分:

12.1 预处理类

  • Voxel Down Sampling:体素下采样,减少点数、统一密度
  • Statistical Outlier Removal:统计滤波,去除离群点
  • Radius Outlier Removal:半径滤波,去除局部孤立点
  • Crop / Bounding Box:裁剪指定区域

12.2 邻域与空间组织类

  • KD-Tree:高效最近邻搜索
  • Octree:空间层次划分与稀疏组织
  • Voxel Grid:体素网格表达

12.3 分割类

  • DBSCAN:基于密度的聚类分割
  • RANSAC Plane Segmentation:平面分割

12.4 特征与几何分析类

  • 法线估计
  • FPFH 特征
  • 点云距离计算
  • 凸包 / 包围盒

12.5 重建类

  • Alpha Shapes
  • Ball Pivoting
  • Poisson Surface Reconstruction

12.6 配准类

  • RANSAC 全局配准
  • Fast Global Registration (FGR)
  • Point-to-Point ICP
  • Point-to-Plane ICP

如果是为了后续实验和毕设,最值得优先掌握的大致是这几项:

  1. 下采样与滤波
  2. 法线估计
  3. KD-Tree 邻域搜索
  4. FPFH 特征
  5. RANSAC + ICP 配准流程
  6. 配准结果评估

十三、这篇笔记里最该记住的几点

如果只快速回顾,这篇至少记住下面这些:

  1. Open3D 的核心对象:点云、网格、RGB-D
  2. RGB-D 可以反投影成点云,点云可以进一步重建成网格
  3. KD-Tree 适合邻域搜索,Octree 适合空间分割与稀疏组织
  4. 滤波、分割和法线估计是很多下游任务前的基础步骤
  5. 点云配准通常不是一步完成,而是粗配准 + 精配准的组合
  6. 点云处理不是一招解决问题,而是一条流程链:读入 → 清洗 → 分割 / 邻域 → 特征 → 配准 / 重建