3、点云基础数据结构:点云的数学表示(Nx3矩阵),Open3D中的PointCloud对象,点云的基本属性(坐标、颜色、法向量)

点云这东西,说白了就是一堆三维空间里的点。你想想看,我们拿激光雷达或者深度相机扫一圈,得到的无非就是成千上万个点的坐标。那这些点在计算机里怎么存?怎么用?这就是今天要聊的核心。

我个人习惯把点云理解成「三维世界的像素」。图像是二维网格上的颜色值,点云就是三维空间里的坐标集合。嗯,这个类比虽然不完全准确,但初学者这么理解,上手会快很多。

3.1 点云的数学表示:Nx3矩阵

先从最基础的数学表示说起。一个点云,在数学上就是一个 N行3列 的矩阵。N代表点的个数,3代表X、Y、Z三个坐标轴。

举个例子,假设我们有3个点:

  • 点A: (1.0, 2.0, 3.0)
  • 点B: (4.0, 5.0, 6.0)
  • 点C: (7.0, 8.0, 9.0)

那这个点云在数学上就是:

[[1.0, 2.0, 3.0],
 [4.0, 5.0, 6.0],
 [7.0, 8.0, 9.0]]

这个矩阵的形状就是 (3, 3)。在Python里,我们通常用NumPy的ndarray来表示它。

核心要点:点云 = Nx3的浮点数矩阵。每一行是一个点,每一列是一个坐标轴。

我在项目中遇到过一个问题:有人把点云存成了Nx4的矩阵,多了一列强度值。这本身没问题,但如果你直接把它喂给Open3D的标准函数,就会报错。所以,搞清楚你的数据格式,是第一步。

3.2 Open3D中的PointCloud对象

Open3D把点云封装成了一个叫 PointCloud 的类。这个类里,最核心的成员变量就是 points

怎么创建一个点云?看代码:

import open3d as o3d
import numpy as np

# 创建一个空的点云对象
pcd = o3d.geometry.PointCloud()

# 生成一些随机点(1000个点)
points = np.random.rand(1000, 3)

# 赋值给点云对象
pcd.points = o3d.utility.Vector3dVector(points)

# 看一眼
print(pcd)
print(f"点的数量: {len(pcd.points)}")

这里有个细节要注意:pcd.points 不能直接赋一个NumPy数组,必须用 o3d.utility.Vector3dVector() 包装一下。为什么?因为Open3D内部有自己的数据结构,它需要这种转换。嗯,刚开始用的时候我也觉得多此一举,但用习惯了就发现,这种设计其实是为了性能优化。

小技巧:如果你从文件读取点云,直接用 o3d.io.read_point_cloud("文件路径.pcd") 就行,它会自动帮你创建好PointCloud对象。

3.3 点云的基本属性:坐标、颜色、法向量

一个完整的点云,通常不止有坐标。还有颜色和法向量。这三个属性,就是点云的「三件套」。

3.3.1 坐标(points)

坐标是点云的基础,没有坐标就不叫点云了。坐标的数据类型是 float64,范围理论上没有限制,但实际应用中,我们经常会对坐标做归一化处理。

3.3.2 颜色(colors)

颜色信息通常来自RGB相机。Open3D中,颜色的取值范围是 [0, 1] 的浮点数,而不是常见的[0, 255]整数。这一点很容易踩坑。

我曾经在项目里从图片读取颜色值,直接用了0-255的整数赋值给点云,结果渲染出来一片惨白。排查了半天才发现是范围问题。

# 正确的颜色赋值方式
colors = np.random.rand(1000, 3)  # 范围[0, 1]
pcd.colors = o3d.utility.Vector3dVector(colors)

# 错误的做法(如果你有0-255的数据,记得除以255)
# colors_255 = np.random.randint(0, 255, (1000, 3))
# pcd.colors = o3d.utility.Vector3dVector(colors_255 / 255.0)

3.3.3 法向量(normals)

法向量是点云里比较「高级」的属性。它表示每个点所在局部表面的朝向。法向量有什么用?

  • 点云渲染:有法向量的点云看起来更立体
  • 点云配准:很多配准算法需要法向量信息
  • 表面重建:法向量是重建三角网格的关键输入

法向量不是天然存在的,需要计算。Open3D提供了现成的函数:

# 估计法向量
pcd.estimate_normals(
    search_param=o3d.geometry.KDTreeSearchParamHybrid(
        radius=0.1, max_nn=30
    )
)

# 查看法向量
print(pcd.normals[0])  # 第一个点的法向量

避坑指南:我曾经在计算法向量时,把搜索半径设得太小,结果法向量方向乱七八糟。后来我总结了一个经验:搜索半径一般取点云平均点间距的3-5倍。你可以先用 pcd.compute_nearest_neighbor_distance() 算一下平均距离。

3.4 知识体系结构图

下面这张图,帮你理清点云数据结构的核心脉络:

点云基础数据结构 数学表示 Nx3 浮点数矩阵 Open3D PointCloud o3d.geometry.PointCloud() 三大属性 坐标 · 颜色 · 法向量 坐标 (points) • 数据类型: float64 • 范围: 无限制 • 赋值: Vector3dVector() • 读取: pcd.points 颜色 (colors) • 取值范围: [0, 1] • RGB顺序: R,G,B • 来源: RGB相机 • 注意: 0-255需归一化 法向量 (normals) • 需要计算获得 • 表示局部朝向 • 用于渲染/配准 • 搜索半径要合适 核心:点云 = 坐标 + 颜色 + 法向量,三者通过PointCloud对象统一管理

3.5 实战中的一些经验

最后,分享几个我在实际项目中积累的经验:

  1. 数据检查很重要:拿到点云后,先打印 pcd.points[0:5] 看看数据范围。有时候文件读进来坐标是毫米单位,但你的算法期望的是米单位,不检查就会出问题。
  2. 颜色和法向量是可选的:不是所有点云都有颜色和法向量。如果你只需要几何信息,只存坐标就够了。但如果你要做可视化,颜色和法向量能帮你省很多事。
  3. 法向量方向一致性:Open3D计算的法向量方向可能不一致(有的朝里有的朝外)。如果你要做表面重建,记得用 orient_normals_consistent_tangent_plane() 统一方向。

一句话总结:点云就是Nx3的矩阵,Open3D把它包装成了PointCloud对象,然后你可以往里面塞坐标、颜色、法向量。就这么简单。

专注资料整理