跳转至

第 5 章 三维表达与点云处理

从相机出来的是像素,机器人需要的是米。介于两者之间的是一组互有血缘的三维场景表达:点云、深度图、体素与 TSDF、mesh、高斯泼溅、隐式场——它们各自回答不同的问题,又通过固定的转换边彼此派生。本章先立起这张表达谱系,再进入点云这一表达的处理基本功(降采样、滤波、法向、配准、分割与聚类)。

六种表达与固定的几条转换边:

flowchart LR
    img["多视图图像"] --> sfm["SfM / MVS"]
    sfm --> pc["点云"]
    depth["深度图"] --> pc
    pc --> vox["体素 / TSDF"]
    vox --> mesh["mesh"]
    pc --> splat["高斯泼溅"]
    imp["隐式场(NeRF 类)"] -.-> mesh

1. 表达谱系与转换关系

1.1 六种表达的定义与数据结构

点云(point cloud):无序的三维坐标集合,核心数据结构为 \(N \times 3\) 坐标矩阵(可扩展法向、颜色、强度通道)。无拓扑、无连接关系,是传感器最直接的输出形态——激光雷达与 RGB-D 相机的原始产物都是它。优点是采集直接、算子简单;缺点是稀疏且无序,同一场景密度随距离衰减,做表面级操作(渲染、求交、面积)需先补拓扑。

深度图(depth image / depth map):\(H \times W\) 的图像网格,每个像素存相机坐标系下沿视线方向的距离值,核心数据结构为单通道浮点或 16 位整型数组(单位毫米是常见约定)。它与 RGB 图逐像素对齐,因此能直接借用全部 2D 算子(卷积、ROI、可视化);缺点是表达绑定相机位姿——换一台相机、换一个视角,同一场景的深度图完全不同,不天然具备场景级累积能力。

体素与 TSDF(voxel grid / truncated signed distance function):把空间切成规则网格,每格存占据概率或符号距离。TSDF(截断符号距离函数,truncated signed distance function)在每个体素存「到最近表面的带符号距离」并截断到一个窄带内,多次观测按权重融合,零等值面即表面。核心数据结构为 \(X \times Y \times Z\) 三维数组(Open3D 的 VoxelBlockGrid 一类稀疏实现控制内存)。体素是「可累积、可融合」的表达:不同位姿的观测在同一世界坐标系下加权平均,噪声被融合过程摊薄。代价是内存随分辨率立方增长、表面精度被体素尺寸量化。占据栅格是它在 2D 平面的降维版,与导航 costmap 同构(衔接第 8 章)。

mesh(三角网格,triangle mesh):顶点表 + 面索引表,核心数据结构为 \(V \times 3\) 浮点数组加 \(F \times 3\) 整点索引数组,可附法向、UV 与纹理。它是工程侧的终点表达:碰撞检测、有限元、渲染管线、URDF/USD 导入都以 mesh 为输入。缺点是没有一种传感器直接输出 mesh——它必须经重建得到,重建失败(空洞、自交、噪声面片)的清理成本落在后处理。

高斯泼溅(3D Gaussian Splatting,3DGS):一池各向同性/各向异性的三维高斯核,每个高斯存中心、协方差(旋转 + 缩放)、不透明度与球谐颜色系数,渲染时按深度排序做 alpha 混合光栅化。核心数据结构为 \(G \times (3 + 6 + 1 + \text{SH 系数})\) 的参数张量——本质是可优化的点云扩展。优点是训练快、渲染达到实时帧率、外观保真度高(照片级新视角合成);缺点是几何是半透明的「雾」,直接精度不如 mesh,导出干净 mesh 需额外一步提取,且无表面拓扑。工具链以原始实现、gsplat、nerfstudio 为主干,具体可用版本与许可标「待核实」。

隐式场(neural implicit field):用一个网络(典型为 MLP)把连续坐标映到属性值——神经辐射场(neural radiance field,NeRF)存「密度 + 颜色」,占用场(occupancy field)与 SDF 场存几何。核心数据结构不是显式阵列而是网络权重,查询靠光线步进(ray marching)。优点是表达连续、内存与场景分辨率解耦、适合不完整观测下的补全;缺点是训练与推理都慢于泼溅,几何提取同样要 Marching Cubes 一类等值面步骤。本章只把隐式场立为谱系中的参照项,工程主线走 mesh 与泼溅。

1.2 对照速查

表达 信息单元 存储随场景的规模 采集直接性 几何精度 渲染能力 典型用途
点云 离散坐标 \(O(N)\),线性 传感器直出 无表面概念 弱(需先成面) 配准、分割、测距
深度图 像素距离 一张图,恒定 传感器直出 单视角精度 无 单帧测量、2.5D 算子
体素 / TSDF 格子距离值 \(O(R^3)\) 或稀疏块 需融合 受体素尺寸量化 无(先出面) 多观测累积、建图
mesh 顶点 + 面 \(O(V + F)\) 需重建 表面级,最高 原生 碰撞、仿真、导出
高斯泼溅 高斯核池 \(O(G)\),十万到百万 需训练 半透明、非确定表面 实时照片级 新视角合成、数字孪生
隐式场 网络权重 权重恒定 需训练 连续但提取后量化 光线步进、慢 补全、生成、研究

1.3 转换关系图

各表达之间靠固定的转换边派生;每条边标注方法与代表库。图中各条边在后续各节与第 6 章逐一展开,此处先立地图。

                     特征匹配 + 三角化 + MVS 稠密化
                     (COLMAP → OpenMVS)
  多视图图像 ────────────────────────────────▶ 稠密点云
      │                                            │
      │ 立体匹配 / 传感器直出                        │ 体素降采样 · 滤波 · 配准
      ▼                                            ▼ (Open3D,本章后续节)
  深度图 ──── 反投影 unprojection ────────────▶ 点云
 (RGB-D / 双目)   (K⁻¹ 内参逆 + 深度,Open3D  `create_point_cloud_from_depth_image`)
      │                                            │        │
      │ 沿视线重投影                                │        │ Marching Cubes 等值面
      ▼                                            │        ▼ (TSDF 融合 → 网格,Open3D)
    体素 / TSDF ◀─────────────────────────────────┘      mesh
   (多观测融合,Open3D 一类集成器)                          │  ▲
                                                           │  │ Poisson / SPH 重建
                                                           │  └───────── 点云
                                                      简化 · 清理 · UV · 导出
                                                      (trimesh / PyVista / pymeshlab)

  点云 ──── 可微光栅化优化(初始化 + 训练,gsplat / nerfstudio)────▶ 高斯泼溅
  泼溅 ──── mesh 提取(等值面类方法,名目待核实)──▶ mesh
  泼溅 / 隐式场 ── 渲染新视角 ──▶ 图像(训练的监督来源,构成闭环)

边的读法:箭头方向即「谁产出谁」。三点结构性规律——

  1. 传感器只直出两种表达:深度图与点云。其余表达全部经由转换边得到,因此转换方法的质量(配准误差、融合权重、提取阈值)决定下游精度。
  2. 多视图图像是隐式源头:COLMAP 一类 SfM/MVS 管线先产出稀疏/稠密点云,再作为泼溅训练的初始化——「图像 → 点云 → 泼溅」是当前离线重建链的标准路径,展开归第 6 章。
  3. mesh 与泼溅互为竞争终点:要从场景导出可碰撞、可仿真的资产走 mesh 边;要照片级回看与渲染走泼溅边。两条边不互斥,泼溅提 mesh 的转换边正在成为新惯例(方法与工具名目标「待核实」)。

1.4 选型判据

  • 做测量、配准、分割 → 点云(本章后续节的工具面以 Open3D 为主线)。
  • 单帧距物判断、借用 2D 算子 → 深度图,用完即转点云,不长期持有。
  • 多帧累积建图 → 体素 / TSDF,与导航占据栅格同一族。
  • 进仿真、做碰撞、交付资产 → mesh。
  • 照片级回看、数字孪生、不要求拓扑 → 高斯泼溅。
  • 隐式场作为谱系参照,机器人工程路径上暂不作为一等交付物。

上述库(Open3D、trimesh、COLMAP + OpenMVS、gsplat / nerfstudio)的可用版本与默认行为属外部生态,标「待核实」;本节只作名目与关系陈述。

表达谱系的全貌(每种表达回答不同的问题):

mindmap
  root((三维表达))
    点云
      降采样与滤波
      空间索引
    深度图
      视差反解
    体素与 TSDF
      截断符号距离
    网格 mesh
      泊松 / 体素场
    高斯泼溅
      照片级回看
    隐式场
      NeRF 类

2. 格式与读写

2.1 点云三格式与 Open3D 读写

点云落盘常见三种格式,定位不同:PLY(Stanford polygon format)面向顶点 + 可选属性 + 可带面片,ascii 与二进制两种编码,是点云与 mesh 共用的载体;PCD(point cloud data,PCL 发起)面向纯点云,二进制 float32 常见,头部存 WIDTH/HEIGHT/VIEWPOINT 元信息;XYZ 是无头的纯文本三列,任何工具可读但无属性、无元信息。

Open3D 一个包覆盖三类读写,读写接口对称:

import open3d as o3d
cloud = o3d.io.read_point_cloud("scene.ply")   # 按扩展名自动选解码器
o3d.io.write_point_cloud("scene.pcd", cloud)   # 换格式只需改扩展名
back  = o3d.io.read_point_cloud("scene.pcd")

三种格式的取舍落在体积与精度上:PCD 二进制按 float32 存坐标,同一点云的体积约为 PLY 二进制的一半,代价是坐标精度降到约 7 位有效数字;PLY 二进制默认 float64,往返不损失坐标;XYZ ascii 体积随字面精度增长,且不存法向与颜色。工程上常见的坑是 float32 累积误差:单帧无所谓,经多级 float32 中转(消息、二进制缓存)反复变换后坐标漂移,排查时先查中转格式再查算法。

OBJ / glTF 是 mesh 侧格式(顶点 + 面 + UV),SPLAT / .ply-of-gaussians 是泼溅侧格式,它们的读写与互转在第 6 章展开。

2.2 网格侧:trimesh 的操作面

trimesh 补的是 Open3D 之外的网格操作面:全程 numpy 顶点/面数组进出,trimesh.load 按扩展名分派并自动把多体资产装成 Scene,Trimesh.convex_hull 直接给凸包,.section(plane_origin, plane_normal) 在给定平面切出截面路径,.apply_transform(T) 做刚体变换,export 写回 glb/ply/obj。布尔与简化(.boolean()、.simplify_quadric_decimation() 等)依赖外部后端(manifold3d 一类)——能做什么取决于装没装对应 extras,交付前要确认后端在位,不能只看方法名存在。

与 Open3D 的分工判据:点云读写、法向估计、配准、TSDF 融合走 Open3D(上文与本笔记后文的主线);网格的凸包、切片、多体装配、轻量格式转换走 trimesh。两边靠顶点/面数组互转:o3d.geometry.TriangleMesh(o3d.utility.Vector3iVector(f), o3d.utility.Vector3dVector(v)) 与 np.asarray(mesh.vertices)/np.asarray(mesh.triangles) 互相喂。选 trimesh 的场景特征鲜明——手上的数据已经是 numpy 数组、或需要在一个脚本里做“切片看剖面 + 凸包拿去当碰撞体 + 导出 glb”这类串并联操作。

3. 降采样与去噪

体素降采样(voxel downsampling):把空间切成边长 \(v\) 的格,每格内的点聚为其质心。它同时完成两件事——控制点数(计算量上限由 \(v\) 决定)与均匀化密度(消除近处密、远处疏的采样偏差)。\(v\) 的选择就是精度预算:格内质心对表面的量化误差量级为 \(v/2\)。

统计滤波(statistical outlier removal):对每点取 \(k\) 近邻,用「到近邻平均距离」在全云上的分布做 \(\mu + n\sigma\) 阈值,超阈即离群。适合传感器飞点与孤立噪点。

半径滤波(radius outlier removal):保留「半径 \(r\) 内邻居数 ≥ \(n\)」的点。密度先验明确时用,参数物理意义直观(\(r\)=最小特征尺度)。

最小流水线(场景取 1 m × 1 m 地面加一个 20 cm 立方体,噪声在毫米量级,另布若干场景外离群点):

## 5 mm 体素统一密度后做统计滤波;返回值是(滤波后点云,保留点索引列表)
work = cloud.voxel_down_sample(voxel_size=0.005)
clean, kept = work.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0)

两个判读点:其一,体素降采样对表面型点云的压缩率远低于对体积型点云——原始采样密度已接近体素网格时,放大一格只能去掉少量点;其二,半径滤波会误剔边界与棱角处的合法点(那里邻居数天然不足),统计滤波几乎不误伤——棱角分明的工业场景优先统计滤波,半径滤波的参数必须对着最小特征尺度调。

接口形态:remove_statistical_outlier / remove_radius_outlier 返回 (PointCloud, list[int])——第一个返回值是滤波后的点云,索引列表是保留点在输入云中的下标,可直接喂给 select_by_index。平面分割接口为 segment_plane,返回 (平面系数, 内点下标)。

4. 法向估计与朝向一致性

法向是点云从「坐标集」升级为「几何体」的关键属性:点到平面(point-to-plane)配准、TSDF 融合、网格重建、光照渲染都吃它。估计方法是在每点的局部邻域(KNN 或半径邻域)做 PCA,最小特征值对应的特征向量即法向——表面附近的邻域点近似落在切平面上,方差最小的方向就是平面的法线方向。搜索尺度的选择就是表面平滑度的选择:半径小于特征尺寸会把噪声当细节,大于特征尺寸会把棱角抹平。

clean.estimate_normals(
    search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.02, max_nn=30))
## 形参名为 search_param;省略时按 KDTreeSearchParamKNN(knn=30) 取 30 近邻

对毫米级噪声的地面,2 cm 左右的估计半径足以把法向偏差压到几度以内;半径取到噪声尺度以下,法向会被逐点噪声带偏。

法向有一个容易被忽略的自由度:符号不定。PCA 给不出「哪面是外侧」,单点法向的正负随邻域布阵偶然决定。默认的协方差特征分解不做全局协调,地面上会出现成批朝下的点;对朝向敏感的下游(点面配准、网格化、渲染)应先做朝向一致化(orientation consistency):在建好的邻接图上用最小生成树传播,使相邻点法向逐步对齐:

oriented = copy.deepcopy(clean)
oriented.orient_normals_consistent_tangent_plane(k=20)  # 以邻接图传播朝向,O(N log N)

一致化后的地面点会整片朝同一侧,且这一侧往往是下侧:该算法只保证一致,不保证方向符合语义(朝外、朝上、朝相机)。因此拿到一致法向后仍需一步符号定向——按相机光心翻转(法向应指向传感器)、按重力先验、或按已知包围盒外法向——再交给下游。

5. 配准:ICP 及变体

配准(registration)回答「两个视角的点云相对位姿是什么」,是点云作为表达进入多帧累积(第 6 章 TSDF 融合)之前的前置能力。迭代最近点(iterative closest point,ICP)的骨架同构于状态估计里的量测更新:给定初值,交替进行「最近点对应 → 解最优刚体变换 → 更新位姿」直到收敛。变体的差别只在对应与残差定义:

  • 点到点(point-to-point):残差为对应点间距离,无需法向;
  • 点到平面(point-to-plane):残差为源点到目标局部切平面的有符号距离,收敛半径更大、精度更高,但依赖第 4 节的法向质量;
  • 广义 ICP(generalized ICP,GICP):把两点/两平面升为两个局部高斯分布间的马氏距离,对噪声更鲁棒(一句名目,完整推导超出本章)。

评价配准结果看两个量:fitness = 阈值内匹配上的源点比例,inlier_rmse = 内点对应残差的均方根。两者都要报:只看 fitness 会漏掉「匹配上了但滑到了错误位置」的退化。

最小组装(立方体目标云 + 平移 10 cm 后的可见面源云,5 mm 降采样,对应距离阈值 10 cm):

result = o3d.pipelines.registration.registration_icp(
    source, target, max_correspondence_distance=0.1, init=np.eye(4),
    estimation=o3d.pipelines.registration.TransformationEstimationPointToPlane(),
    criteria=o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=30))
## result.transformation / result.fitness / result.inlier_rmse

三条判据:

  1. 收敛半径由对应距离阈值决定:ICP 每一步只在阈值内找最近点,初值偏差超出阈值后对应关系全错,迭代在错误局部最优里收敛——此时 fitness 仍可能很高,高 fitness 并不代表位姿正确。

  2. 平面退化场景慎用纯平移 ICP:若配准对象主要是平面(地面、墙面),沿法向平移、绕法向旋转都产生零残差解,单帧 ICP 无法同辨六个自由度中的三个(实际工程中靠多帧约束或先验消歧)。

  3. 点云配准不止 ICP:ICP 是局部优化器,初值依赖是结构性的而非实现缺陷。完整套路是分两步:先粗配准给初值(特征匹配 + RANSAC:FPFH 描述子能把初值偏差在几十厘米量级的两片云对齐,随后 ICP 收尾到毫米级),再精配准。粗配准另两路常用手段——全局搜索类方法(多面重建基准叠加 NMS,SuperPOS 3D Reg 一类)与学习类特征(3DMatch / FCGF 类)——属第 8 章边界,本章不展开。

6. 分割与聚类

分割回答「哪些点属于同一个东西」,分两路:已知几何模型用参数估计(RANSAC 平面、RANSAC 圆柱),无模型用密度/连通性(欧式聚类、区域生长、DBSCAN)。

RANSAC 地面分割:随机取 3 点定一个候选平面,统计阈值内内点,迭代取最优。对离群点鲁棒,代价是只能提出「平面」这一种模型:

plane_model, inlier_idx = oriented.segment_plane(
    distance_threshold=0.01, ransac_n=3, num_iterations=1000)
rest = oriented.select_by_index(inlier_idx, invert=True)  # 移去地面后的剩余点

RANSAC 的阈值要留噪声裕量:取到噪声量级(毫米级)会把斜入射处的点大量判为外点,取得过宽(厘米以上)则把低矮物体并进地面。

欧式聚类(DBSCAN 型):距离小于 eps 且邻居数达标的点归同簇,无需预设模型与簇数:

labels = np.asarray(rest.cluster_dbscan(eps=0.02, min_points=30))
## -1 标记噪声点,同值非负整数为一簇

立方体被整块提出;包围盒会因体素量化与噪声略大于真实物体,提示估计物体尺寸后应再做一次拟合包围盒(get_axis_aligned_bounding_box / OBB)而不是直接读点云极值。

eps 的调法与半径滤波同理:以最小可分物体间距为下限、物体内部点间距为上限,两物体间距小于 eps 就会被并进同一簇——这是密集成排货架、堆叠箱体场景的经典失效模式。区域生长(按法向/曲率一致性从种子扩展)适合平面片分割,学习式分割(第 8 章)语义层面,均不展开。

7. 一条最小处理链

把各节串成一条可复用的流水线(各步参数取法见对应小节):

## 原始云(传感器直出/合成)→ 可用几何
cloud = o3d.io.read_point_cloud("scene.ply")            # 格式与读写节
cloud = cloud.voxel_down_sample(0.005)                  # 降采样与去噪节:均匀化
cloud, _ = cloud.remove_statistical_outlier(20, 2.0)    # 去飞点
cloud.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(0.02, 30))  # 法向节
cloud.orient_normals_consistent_tangent_plane(20)       # 朝向一致
## 再按语义定向符号(朝相机/朝上)→ 配准节 → 分割聚类节 → 第 6 章融合重建

8. 要点

  • 传感器直出的只有深度图与点云;体素 / TSDF 靠融合,mesh 靠重建,泼溅与隐式场靠训练,表达之间由反投影、融合、等值面提取、SfM/MVS、光栅化优化等固定转换边派生。
  • 格式选型看精度预算:PCD 二进制 float32 体积减半,PLY 二进制 float64 往返无损,多级中转的坐标漂移先查 float32 环节。
  • 体素降采样同时控制点数与均匀密度,量化误差量级 \(v/2\);棱角场景优先统计滤波,半径滤波误伤边界点。
  • 法向靠邻域 PCA,符号不定;一致化只保证一致不保证语义方向,交给点面配准/网格化前还需一步符号定向。
  • ICP 是局部优化器:收敛半径由对应距离阈值决定,fitness 高不等于位姿对;平面主导场景存在结构性退化;标准套路是粗配准(FPFH+RANSAC)给初值、精配准收尾。
  • 分割两路:参数模型用 RANSAC(阈值留噪声裕量),无模型用密度聚类(eps 卡在最小时距与内部点距之间);读尺寸用拟合包围盒而非点云极值。
  • 接口形态:remove_*_outlier 返回 (PointCloud, 保留下标),segment_plane 返回 (平面系数, 内点下标),estimate_normals 的搜索参数形参为 search_param。

参考文献与延伸阅读

  • Rusu, R. B., & Cousins, S. 3D Is Here: Point Cloud Library (PCL). ICRA, 2011. DOI
  • Besl, P. J., & McKay, N. D. A Method for Registration of 3-D Shapes. IEEE TPAMI, 1992. DOI
  • Zhou, Q.-Y., Park, J., & Koltun, V. Open3D: A Modern Library for 3D Data Processing. arXiv:1801.09847, 2018. arXiv
  • Open3D. 文档首页. 文档