《计算机双目立体视觉》高宏伟:第5章-三维重建

双目立体视觉终极目标:三维点云生成、配准与表面重建全解

前四章我们一路过关斩将:搞定了相机标定(给相机配好"精准眼镜")、对极几何(让两个相机"协同工作")、立体校正(让极线水平平行)、立体匹配(生成高精度视差图)。现在终于到了双目视觉的最终目标 ------三维重建

如果说前面的步骤都是"准备工作",那三维重建就是"最终成果"。它的任务是:把二维的视差图,转化为三维的点云模型,再进一步生成表面网格,最终还原出真实世界的三维结构

毫不夸张地说,三维重建是双目视觉所有技术的集大成者,也是工业检测、自动驾驶、3D打印、文物修复等领域的核心技术。这一章我们就把高宏伟《计算机双目立体视觉》第五章彻底吃透,从点云生成、预处理、配准到表面重建,一次性讲全讲透。


一、从视差到三维:点云是怎么来的?

先给大家一个灵魂比喻

视差图就像一张"深度地图",每个像素的灰度值代表这个点离我们的距离。而三维点云,就是把这张"深度地图"上的每个像素,都"拉"到它真实的三维位置上,形成一个由无数个三维点组成的"云"。

1.1 视差转三维坐标的核心公式

这是三维重建最核心的公式,所有点云生成都基于它。

XYZW=Quvd1 \begin{bmatrix} X\\ Y\\ Z\\ W \end{bmatrix} = Q \begin{bmatrix} u\\ v\\ d\\ 1 \end{bmatrix} XYZW =Q uvd1

每个符号的通俗解释

  • X,Y,ZX,Y,ZX,Y,Z:空间点的三维世界坐标(单位:毫米)
  • WWW:齐次坐标因子,最终三维坐标为 (X/W,Y/W,Z/W)(X/W, Y/W, Z/W)(X/W,Y/W,Z/W)
  • u,vu,vu,v:像素坐标(左图像素位置)
  • ddd:视差(上一章立体匹配得到)
  • QQQ:重投影矩阵(4×4矩阵,上一章立体校正时生成)

重投影矩阵Q的具体形式:

Q=100−cx010−cy000f00−1/Tx(cx−cx′)/Tx Q = \begin{bmatrix} 1 & 0 & 0 & -c_x\\ 0 & 1 & 0 & -c_y\\ 0 & 0 & 0 & f\\ 0 & 0 & -1/T_x & (c_x - c'_x)/T_x \end{bmatrix} Q= 10000100000−1/Tx−cx−cyf(cx−cx′)/Tx

每个符号解释

  • cx,cyc_x, c_ycx,cy:左相机主点坐标
  • cx′c'_xcx′:右相机主点x坐标
  • fff:相机焦距
  • TxT_xTx:基线长度的负值(Tx=−BT_x = -BTx=−B)

通俗理解

这个公式就像一个"魔法转换器",输入一个像素的坐标和它的视差,就能输出这个像素在真实世界中的三维位置。把图像中所有像素都输入这个转换器,就得到了完整的三维点云。

1.2 点云的基本概念

点云是三维空间中一组离散点的集合,每个点包含以下信息:

  • 几何信息:X,Y,Z三维坐标(必须有)
  • 颜色信息:R,G,B(可选,从原始图像中提取)
  • 法向量信息:nx,ny,nz(可选,用于表面重建)
  • 强度信息:I(可选,激光雷达点云常用)

从视差图到三维点云的转换过程:

转换效果分析

可以看到,点云完美还原了场景的三维结构,近处的物体点云更密集,远处的物体点云更稀疏。


二、点云预处理:给点云"洗澡"

原始点云就像刚从工地回来的工人,浑身都是"泥"------有噪声、离群点、冗余点、无效点。如果直接用原始点云进行后续处理,结果会非常差。

点云预处理的目标就是:去除噪声和离群点,减少点云数量,保留有效信息,提高后续处理的速度和精度

书里第五章重点介绍了三种最常用的预处理方法:

2.1 无效点去除

原始点云中存在大量无效点,主要包括:

  • 视差为0的点(遮挡区域)
  • 深度超过最大测量范围的点
  • 深度小于最小测量范围的点

处理方法

python 复制代码
# 去除无效点
mask = (disparity > min_disp) & (disparity < max_disp)
valid_points = point_cloud[mask]

通俗解释

就像筛沙子,把太小的和太大的沙子都筛掉,只留下我们需要的大小。

2.2 离群点去除

离群点是指那些远离主体点云的孤立点,主要由噪声、匹配错误、反光等原因造成。

2.2.1 统计滤波(最常用)

原理

计算每个点到它最近的k个邻居的平均距离,然后根据高斯分布,去除那些平均距离超过阈值的点。

公式

μ=1N∑i=1Ndiσ=1N∑i=1N(di−μ)2阈值=μ+k⋅σ \mu = \frac{1}{N} \sum_{i=1}^{N} d_i \\ \sigma = \sqrt{\frac{1}{N} \sum_{i=1}^{N} (d_i - \mu)^2} \\ \text{阈值} = \mu + k \cdot \sigma μ=N1i=1∑Ndiσ=N1i=1∑N(di−μ)2 阈值=μ+k⋅σ

每个符号解释

  • did_idi:第i个点到最近k个邻居的平均距离
  • μ\muμ:所有点平均距离的均值
  • σ\sigmaσ:所有点平均距离的标准差
  • kkk:标准差倍数,一般取1~3

通俗解释

如果一个点周围的邻居都离它很远,那它很可能就是一个离群点,应该被去掉。

2.2.2 半径滤波

原理

对于每个点,统计在指定半径范围内的邻居数量,如果数量少于阈值,就去除这个点。

通俗解释

如果一个点在指定范围内没有足够多的邻居,那它就是一个"孤家寡人",应该被去掉。

2.3 点云下采样(降采样)

原始点云的数量非常大,一张640×480的图像就能生成30多万个点。这么多点会导致后续处理速度非常慢,而且很多点是冗余的。

下采样的目标是:在保留点云几何结构的前提下,减少点云的数量

2.3.1 体素滤波(最常用)

原理

把点云空间划分成一个个小立方体(体素),然后用每个体素内所有点的重心来代替这个体素内的所有点。

通俗解释

就像把点云分成一个个小格子,每个格子只保留一个代表点,这样既减少了点的数量,又保留了点云的整体形状。

点云预处理前后对比图:

预处理效果分析

左图是原始点云,有很多噪声和离群点,点云非常密集;右图是预处理后的点云,噪声和离群点都被去除了,点云数量减少了80%以上,但几何结构完全保留。


三、点云配准:把多片点云"粘"在一起

单目相机只能看到场景的一个侧面,要想得到完整的三维模型,需要从多个角度拍摄,生成多片点云,然后把它们"粘"在一起------这就是点云配准

点云配准的目标是:找到一个最优的旋转矩阵R和平移向量T,将两片点云对齐到同一个坐标系中

3.1 ICP算法(迭代最近点):配准界的"常青树"

ICP算法是最经典、最常用的点云配准算法,几乎所有的点云库都实现了它。

3.1.1 ICP算法的基本步骤
  1. 对于源点云中的每个点,在目标点云中找到最近的对应点
  2. 计算使对应点之间均方误差最小的旋转矩阵R和平移向量T
  3. 用R和T变换源点云
  4. 重复步骤1~3,直到误差小于阈值或达到最大迭代次数
3.1.2 ICP算法的核心公式

ICP算法的目标是最小化以下均方误差:

E(R,T)=1N∑i=1N∣∣pi−(R⋅qi+T)∣∣2 E(R,T) = \frac{1}{N} \sum_{i=1}^{N} || p_i - (R \cdot q_i + T) ||^2 E(R,T)=N1i=1∑N∣∣pi−(R⋅qi+T)∣∣2

每个符号解释

  • pip_ipi:目标点云中的点
  • qiq_iqi:源点云中的对应点
  • RRR:旋转矩阵
  • TTT:平移向量
  • NNN:对应点的数量

通俗理解

ICP算法就像一个"自动对齐器",它不断地旋转和平移源点云,直到源点云和目标点云尽可能地重合。

3.1.3 ICP算法的优缺点

优点

  • 简单易懂,容易实现
  • 精度高,在初始对齐较好的情况下,能达到亚毫米级精度

缺点

  • 对初始对齐非常敏感,如果初始对齐不好,很容易陷入局部最优
  • 计算速度慢,尤其是对于大数量的点云

3.2 ICP算法的改进

为了解决原始ICP算法的缺点,研究者们提出了很多改进版本:

  1. 点到面ICP:不是最小化点到点的距离,而是最小化点到面的距离,收敛速度更快,精度更高
  2. 广义ICP:同时考虑点到点和点到面的距离,鲁棒性更好
  3. NDT算法(正态分布变换):把点云转化为正态分布,然后进行配准,对初始对齐不敏感,速度更快

点云配准前后对比图:

配准效果分析

左图是两片未配准的点云,明显错位;右图是配准后的点云,两片点云完美对齐,几乎看不出缝隙。


四、三维表面重建:从点云到实体模型

点云只是三维空间中离散点的集合,看起来像"沙子"一样。要想得到一个完整的实体模型,需要把这些"沙子"粘成一个"面"------这就是三维表面重建

表面重建的目标是:从离散的点云数据中,生成一个连续的三角网格模型

书里第五章重点介绍了两种最常用的表面重建方法:

4.1 贪婪投影三角化

原理

将点云投影到一个局部平面上,然后在这个平面上进行二维三角化,再把三角化结果投影回三维空间。

优点

  • 速度快,适合处理大数量的点云
  • 对噪声有一定的鲁棒性

缺点

  • 只能重建表面,不能处理封闭的模型
  • 对于复杂的几何结构,重建效果较差

4.2 泊松重建(最常用)

原理

通过求解泊松方程,从点云的法向量信息中重建出表面。

优点

  • 重建效果好,表面光滑
  • 可以重建封闭的模型
  • 对噪声和离群点有较好的鲁棒性

缺点

  • 计算速度较慢
  • 需要点云的法向量信息

三维表面重建前后对比图:

重建效果分析

左图是原始点云,右图是泊松重建后的三角网格模型。可以看到,重建后的模型表面光滑,完美还原了物体的三维形状。


五、实验结果与算法对比(原文表格全还原)

5.1 不同点云预处理方法效果对比(表5-1)

方法 点云数量 处理时间(ms) 噪声去除率(%) 结构保留率(%)
原始点云 307200 0 0 100
统计滤波 245760 125 85 98
半径滤波 261120 98 78 97
体素滤波(0.5mm) 61440 45 92 95
统计滤波+体素滤波 49152 152 95 94

结论

  • 体素滤波的降采样效果最好,点云数量减少了80%以上
  • 统计滤波的噪声去除率最高
  • 统计滤波+体素滤波的组合效果最好,兼顾了噪声去除和结构保留

5.2 不同点云配准算法精度与速度对比(表5-2)

算法 配准误差(mm) 迭代次数 运行时间(ms) 对初始对齐的敏感度
原始ICP 0.12 50 2500
点到面ICP 0.08 30 1800
广义ICP 0.07 25 2200
NDT 0.15 10 500

结论

  • 点到面ICP和广义ICP的精度最高
  • NDT算法的速度最快,对初始对齐最不敏感
  • 实际应用中,一般先用NDT进行粗配准,再用点到面ICP进行精配准

六、核心代码:OpenCV+PCL三维重建全流程

python 复制代码
import cv2
import numpy as np
import pcl
import pcl.pcl_visualization

# ====================== 1. 读取数据 ======================
# 读取校正后的左右图像
left_img = cv2.imread('left_rectified.jpg')
left_gray = cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY)
right_gray = cv2.imread('right_rectified.jpg', 0)

# 读取立体校正参数
stereo_params = np.load('stereo_params.npz')
Q = stereo_params['Q']

# ====================== 2. 立体匹配生成视差图 ======================
sgbm = cv2.StereoSGBM_create(
    minDisparity=0,
    numDisparities=128,
    blockSize=5,
    P1=8 * 3 * 5 ** 2,
    P2=32 * 3 * 5 ** 2,
    disp12MaxDiff=1,
    uniquenessRatio=10,
    speckleWindowSize=100,
    speckleRange=2,
    mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY
)

disparity = sgbm.compute(left_gray, right_gray)
disparity = disparity.astype(np.float32) / 16.0  # 转换为真实视差

# ====================== 3. 视差转三维点云 ======================
# 生成三维点云
points_3d = cv2.reprojectImageTo3D(disparity, Q)

# 提取颜色信息
colors = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)

# 去除无效点
mask = disparity > 0
points_3d = points_3d[mask]
colors = colors[mask]

# 转换为PCL点云格式
cloud = pcl.PointCloud_PointXYZRGB()
cloud.from_array(np.hstack((points_3d, colors)).astype(np.float32))

print(f"原始点云数量: {cloud.size}")

# ====================== 4. 点云预处理 ======================
# 统计滤波去除离群点
sor = cloud.make_statistical_outlier_removal()
sor.set_mean_k(50)
sor.set_std_dev_mul_thresh(1.0)
cloud_filtered = sor.filter()

# 体素滤波下采样
vox = cloud_filtered.make_voxel_grid_filter()
vox.set_leaf_size(0.5, 0.5, 0.5)  # 体素大小0.5mm
cloud_downsampled = vox.filter()

print(f"预处理后点云数量: {cloud_downsampled.size}")

# ====================== 5. 点云配准(示例:两片点云配准) ======================
# 假设我们有两片点云:cloud_source和cloud_target
# 这里为了演示,我们把cloud_downsampled旋转平移一下作为源点云
cloud_source = cloud_downsampled.copy()
transform = np.eye(4, dtype=np.float32)
transform[0:3, 0:3] = cv2.Rodrigues(np.array([0.1, 0.2, 0.3]))[0]  # 旋转
transform[0:3, 3] = np.array([10.0, 5.0, 3.0])  # 平移
cloud_source.transform(transform)

# ICP配准
icp = cloud_source.make_iterative_closest_point()
icp.set_input_target(cloud_downsampled)
icp.set_max_iterations(50)
icp.set_transformation_epsilon(1e-6)
icp.set_max_correspondence_distance(1.0)

result = icp.align(cloud_source)
print(f"ICP配准误差: {icp.get_fitness_score()}")
print(f"配准变换矩阵:\n{icp.get_final_transformation()}")

# ====================== 6. 三维表面重建(泊松重建) ======================
# 计算法向量
ne = cloud_downsampled.make_NormalEstimation()
tree = cloud_downsampled.make_kdtree()
ne.set_SearchMethod(tree)
ne.set_KSearch(20)
normals = ne.compute()

# 泊松重建
poisson = cloud_downsampled.make_PoissonReconstruction()
poisson.set_depth(8)
poisson.set_scale(1.1)
mesh = poisson.reconstruct(normals)

# ====================== 7. 可视化结果 ======================
visual = pcl.pcl_visualization.PCLVisualizering()
visual.SetBackgroundColor(0, 0, 0)

# 显示原始点云
visual.AddPointCloud(cloud_downsampled, b'cloud', 0)
visual.SetPointCloudRenderingProperties(pcl.pcl_visualization.PCLVISUALIZER_POINT_SIZE, 2, b'cloud')

# 显示重建后的网格
visual.AddPolygonMesh(mesh, b'mesh', 0)

visual.SetWindowName("三维重建结果")
visual.SetPosition(0, 0)
visual.SetSize(800, 600)

while not visual.WasStopped():
    visual.SpinOnce(100)

关键参数调优指南

  • set_leaf_size:体素大小,根据场景的精度要求调整,一般取0.1~1mm
  • set_mean_k:统计滤波的邻居数量,一般取20~100
  • set_std_dev_mul_thresh:统计滤波的标准差倍数,一般取1~3
  • set_depth:泊松重建的深度,值越大,重建的模型越精细,但速度越慢,一般取8~10

七、趣味案例:三维重建改变世界

  1. 文物修复与数字化

    很多珍贵的文物因为年代久远而损坏,通过三维扫描和重建,可以生成文物的数字模型,然后用3D打印技术修复损坏的部分。比如兵马俑的修复,就大量使用了三维重建技术。

  2. 工业零件检测

    在工业生产中,通过双目视觉对零件进行三维重建,然后和CAD模型进行对比,可以快速检测出零件的尺寸误差和缺陷,检测精度可达0.01毫米。

  3. 自动驾驶环境感知

    自动驾驶汽车通过双目视觉对周围环境进行三维重建,生成高精度的三维地图,然后根据地图进行路径规划和障碍物避让。

  4. 3D打印与个性化定制

    通过三维扫描和重建,可以生成人体或物体的三维模型,然后用3D打印机打印出来,实现个性化定制。比如定制假肢、牙套、鞋子等。

  5. 电影与游戏特效

    很多电影和游戏中的特效都是通过三维重建技术制作的。比如《阿凡达》中的潘多拉星球,就是通过三维重建技术生成的。


八、本章总结

  1. 三维重建核心:从视差图生成三维点云,再通过配准和表面重建得到完整的三维模型
  2. 视差转点云公式X,Y,Z,WT=Qu,v,d,1TX,Y,Z,W^T = Qu,v,d,1^TX,Y,Z,WT=Qu,v,d,1T,Q是重投影矩阵
  3. 点云预处理:无效点去除、离群点去除(统计滤波、半径滤波)、下采样(体素滤波)
  4. 点云配准:ICP算法是经典,NDT+ICP是工业界标准流程(粗配准+精配准)
  5. 表面重建:泊松重建效果最好,适合大多数场景
  6. 工业级标准流程:视差生成 → 点云转换 → 预处理 → 配准 → 表面重建

掌握了这一章,你就掌握了双目视觉的完整技术栈,可以独立开发从图像采集到三维模型输出的完整双目视觉系统。

相关推荐
AOwhisky13 小时前
Ceph系列第二期:Ceph集群部署实战(cephadm)
linux·运维·笔记·分布式·ceph·云计算·存储
还是瓜瓜好14 小时前
CRC(循环冗余校验)笔记
笔记
问心无愧051314 小时前
ctf show web入门259
android·前端·笔记
咸甜适中14 小时前
rust语言学习笔记Trait(十三)Borrow、BorrowMut(借用)
笔记·学习·rust
星轨初途14 小时前
【C++ 进阶】list 核心机制解析及 vector 巅峰对决
开发语言·数据结构·c++·经验分享·笔记·list
鹏北海-RemHusband14 小时前
Go 包管理笔记 — 面向 JS/TS 前端开发者
笔记·golang
Chloeis Syntax14 小时前
JavaEE初阶学习日记(3)---网络初认识
java·网络·笔记·学习
智者知已应修善业14 小时前
【proteus仿真CD4511抢答器4路】2024-5-13
驱动开发·经验分享·笔记·硬件架构·proteus·硬件工程
kinl201814 小时前
cs236_note1 (lec5-lec6) VAEs
笔记