3D 视觉:点云与深度估计

本文系统讲解 3D 视觉中的点云与深度估计,回答单目与双目深度怎么选、点云怎么体素化、ICP 配准为什么不收敛等实战问题。覆盖单目与双目与结构光与 ToF 原理、点云数据结构与体素化、PointNet 与 3D 检测、ICP 与特征配准、NeRF 与 3D Gaussian Splatting 概览、坐标系约定与自动驾驶多传感器融合,并给出深度转点云与 ICP 两段可运行代码。

引言

前面所有任务都建立在「图像是二维投影」这个前提上,但真实世界是三维的。3D 视觉要回答的是「这个像素离我多远」「这个物体的三维形状是什么」「相机移动了多少」。它支撑自动驾驶的障碍物测距、机器人的抓取位姿、AR 的空间锚定、以及工业的三维测量。

难点有三个。第一是深度获取的代价与精度权衡:单目便宜但尺度不可靠,双目在中远距离精度骤降,结构光与 ToF 精度高但受环境光与材质影响。第二是点云的无序与稀疏:点云不是网格,没有固定拓扑,同一个场景两次扫描的点数都可能不同,这决定了它不能用普通卷积处理。第三是坐标系:相机系、激光系、车体系、世界系之间要做标定与变换,任何一处搞错都会让结果整体错位。

本文按「深度获取 → 点云表示 → 3D 检测 → 配准 → 神经渲染 → 坐标系 → 融合」的顺序讲,重点把体素化、PointNet、ICP、坐标系约定讲透。多传感器融合的工程实践可参考 机器人传感器融合 ,几何变换的数学基础见 图形学数学基础 。

目录

  1. 深度估计的四条技术路线
  2. 单目深度估计
  3. 双目立体匹配与视差
  4. 结构光与 ToF
  5. 点云数据结构与体素化
  6. PointNet 系列与 3D 检测
  7. 代码:深度图转点云
  8. 点云配准与 ICP
  9. 代码:ICP 配准
  10. NeRF 与 3D Gaussian Splatting
  11. 坐标系与标定约定
  12. 自动驾驶的多传感器融合

1. 深度估计的四条技术路线

路线原理精度成本典型场景
单目单图预测深度相对深度准,绝对尺度差最低手机人像、视频特效
双目视差三角测量近距离准,随距离平方衰减中机器人、ADAS
结构光投射编码图案近距离高精度中高人脸解锁、工业扫描
ToF光飞行时间中远距离稳定高手机后摄、室内导航
激光雷达激光扫描全局高精度最高自动驾驶、测绘

选型的核心问题是「你要的是相对深度还是绝对尺度」。单目模型能给出很好的相对深度(谁近谁远),但没有绝对尺度;双目与 ToF 有绝对尺度但受基线或调制频率限制。

2. 单目深度估计

单目深度估计从单张图预测每像素深度。早期方法靠几何线索,现代方法靠大规模数据监督或自监督。

  • 监督式:用激光雷达或双目生成的深度图作为标签训练,MiDaS 与 Depth Anything 用多数据集混合训练,泛化性强。
  • 自监督式:用视频中相邻帧的亮度一致性构造损失,不需要深度标签,但需要相机内参。
  • 相对深度与度量深度:MiDaS 输出的是尺度不变的相对深度,Depth Anything V2 与 Metric3D 支持度量深度。

指标常用 AbsRel(绝对相对误差)与 δ<1.25(预测与真值比值在 1.25 倍内的像素占比)。MiDaS 在 NYU-Depth 上 δ<1.25 约 0.95 量级。

单目深度的陷阱是「看起来对但尺度错」:模型可能把所有物体都缩放到合理比例,但整体尺度偏移,直接用于测距会出事故。需要绝对尺度时必须用双目、ToF 或有标定的参考物做尺度对齐。

3. 双目立体匹配与视差

双目用两个已知基线的相机同时拍摄,同一物点在两图中的水平位置差就是视差 d,深度由 Z = f * B / d 得到:f 是焦距,B 是基线。

关键点:

  • 视差精度决定深度精度:视差差 1 个像素,深度误差与 Z² 成正比。基线 0.1 米、焦距 700 像素时,10 米处的深度误差可达米级。
  • 立体校正:先用 stereoRectify 把两图校正到同一极线,让匹配退化成一维搜索。
  • 代价计算与聚合:SGM(半全局匹配)是经典算法,OpenCV 的 StereoSGBM 是其实现。
  • 学习式方法:PSMNet、RAFT-Stereo 用神经网络做代价体与迭代优化,精度显著高于传统方法。
import cv2
import numpy as np

left = cv2.imread("left.png", cv2.IMREAD_GRAYSCALE)
right = cv2.imread("right.png", cv2.IMREAD_GRAYSCALE)

# numDisparities 必须是 16 的倍数,blockSize 通常取奇数
sgbm = cv2.StereoSGBM_create(
    minDisparity=0, numDisparities=128, blockSize=5,
    P1=8 * 5 * 5, P2=32 * 5 * 5, uniquenessRatio=10,
    speckleWindowSize=100, speckleRange=2, disp12MaxDiff=1,
    mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY,
)
disp = sgbm.compute(left, right).astype(np.float32) / 16.0
print("valid ratio:", float((disp > 0).mean()))

numDisparities 决定可测的最小深度:视差搜索范围越大能测越近,但计算量上升。无效视差(遮挡区与无纹理区)会被置 0,必须显式处理,否则深度图会出现大片空洞。

4. 结构光与 ToF

结构光投射已知图案(条纹或散斑),通过图案变形反推深度。优点是近距离精度可达亚毫米,缺点是受环境光影响大、有效距离短(通常 0.3 到 3 米)、对强反光与透明材质失效。经典产品是 Kinect v1 与 Face ID。

**ToF(Time of Flight)**分两类:

  • iToF(间接):发射调制光,测相位差。距离范围大但多径干扰严重,室外强光下噪声大。
  • dToF(直接):直接测光子飞行时间,用 SPAD 阵列。抗干扰强,iPhone 的 LiDAR 属于此类。

两者的共同问题是多径干扰:光在角落或反光面多次反射后进入传感器,导致深度偏大。工程上要限制场景(避免镜面与玻璃)或用算法补偿。

5. 点云数据结构与体素化

点云是 (N, 3) 或 (N, 6)(加颜色或强度)的无序点集合。直接输入神经网络有三个问题:无序性、点数不定、旋转不变性难保证。

体素化把空间切成规则网格(如 0.1 米边长),每个体素内有点则置 1 或存统计量,转成 3D 张量后就能用 3D 卷积。VoxelNet 与 SECOND 是代表。

  • 优点:结构规整,可直接用卷积。
  • 缺点:分辨率与内存矛盾。0.05 米体素下,100 米 × 100 米 × 8 米的场景有 6400 万个体素,绝大多数为空。
  • 优化:稀疏卷积(只计算非空体素)是 SECOND 的关键,能大幅降低计算量。

其他表示:投影到 BEV(鸟瞰图)用 2D 卷积,PointPillars 把点云组织成柱体,速度很快,是工业首选之一。

import numpy as np

def voxelize(points, voxel_size=0.1, pc_range=(-50, -50, -5, 50, 50, 3)):
    # points: (N, 3+),返回体素坐标与每点归属
    xmin, ymin, zmin, xmax, ymax, zmax = pc_range
    grid = np.floor((points[:, :3] - [xmin, ymin, zmin]) / voxel_size).astype(np.int32)
    dims = np.ceil([xmax - xmin, ymax - ymin, zmax - zmin]) / voxel_size
    valid = np.all((grid >= 0) & (grid < dims), axis=1)
    return grid[valid], valid.sum()

pts = np.random.uniform(-50, 50, (10000, 3))
coords, n = voxelize(pts)
print("valid points:", n, "unique voxels:", len(np.unique(coords, axis=0)))

体素大小是最重要的超参:小体素保细节但内存爆炸,大体素省内存但小目标(行人、自行车)被抹掉。自动驾驶常用 0.1 米。

6. PointNet 系列与 3D 检测

PointNet(2017)是点云深度学习的开山之作。它的关键设计是:

  • 每个点独立过共享的 MLP 升维(如 3 → 64 → 128 → 1024)。
  • 用一个对称函数(最大池化)聚合所有点的特征,保证输出与点的输入顺序无关。这就是它处理无序性的方式。
  • 用 T-Net 学习一个变换矩阵,对齐输入点云,缓解旋转敏感问题。

PointNet++ 引入层次结构:用最远点采样(FPS)选中心点,用球查询(ball query)分组邻域,逐层提取局部特征,弥补 PointNet 只看全局的缺陷。

3D 检测的代表:

方法表示特点
VoxelNet体素端到端,慢
SECOND稀疏体素稀疏卷积加速,实用
PointPillars柱体极快,工业常用
CenterPointBEV 中心点无锚框,精度高
PV-RCNN体素 + 点精度高,两阶段

KITTI 上 PointPillars 的汽车类 3D mAP 约 60(中等难度),CenterPoint 可达 70 以上。选型看延迟预算:实时用 PointPillars,精度优先用 CenterPoint 或 PV-RCNN。

7. 代码:深度图转点云

有了深度图与相机内参,就能把每个像素反投影成三维点。

import numpy as np

def depth_to_pointcloud(depth, K, rgb=None, depth_scale=1000.0):
    # depth: (H, W),单位毫米;K: 3x3 内参
    h, w = depth.shape
    fx, fy, cx, cy = K[0, 0], K[1, 1], K[0, 2], K[1, 2]
    u, v = np.meshgrid(np.arange(w), np.arange(h))
    z = depth / depth_scale
    valid = z > 0
    x = (u - cx) * z / fx
    y = (v - cy) * z / fy
    pts = np.stack([x, y, z], axis=-1)[valid]           # (N, 3)
    if rgb is not None:
        pts = np.concatenate([pts, rgb[valid]], axis=-1)  # 加颜色
    return pts

K = np.array([[600.0, 0, 320.0], [0, 600.0, 240.0], [0, 0, 1.0]])
depth = (np.random.rand(480, 640) * 3000).astype(np.float32)
print("points:", depth_to_pointcloud(depth, K).shape)

注意深度的单位:Kinect 与 RealSense 常以毫米为单位输出,忘了除以 depth_scale 会让点云整体放大 1000 倍。反投影后还要按 ROI 裁剪,去掉过远与过近的噪声点。

8. 点云配准与 ICP

配准是求两个点云之间的刚体变换(旋转 R 加平移 t),让它们对齐。ICP(Iterative Closest Point)是最经典的迭代方法:

  1. 对源点云中每个点,在目标点云中找最近邻。
  2. 用这些对应点对估计最优刚体变换(SVD 闭式解)。
  3. 应用变换,重复直到误差收敛。

ICP 的三个致命弱点:

  • 依赖初值:初值差会收敛到局部最优。实践中用 GPS、IMU 或特征匹配给初值。
  • 对离群点敏感:错误对应会拉偏估计,需要加距离阈值与鲁棒核(如 Huber)。
  • 点到点 vs 点到面:点到面(point-to-plane)在平面场景收敛快得多,PCL 与 Open3D 都支持。

改进方案:用 FPFH 等特征做粗配准再 ICP 精配(Fast Global Registration),或用深度学习方案(DCP、PCRNet)直接回归变换。

9. 代码:ICP 配准

Open3D 提供了简洁的配准接口,下面演示点到面 ICP。

import numpy as np
import open3d as o3d

def make_cloud(n=2000, seed=0):
    rng = np.random.RandomState(seed)
    pts = rng.rand(n, 3) * 2 - 1
    return o3d.geometry.PointCloud(o3d.utility.Vector3dVector(pts))

def icp_demo():
    src = make_cloud(seed=0)
    # 人为构造一个刚体变换作为目标
    theta = np.deg2rad(15)
    T = np.array([[np.cos(theta), -np.sin(theta), 0, 0.2],
                  [np.sin(theta), np.cos(theta), 0, 0.1],
                  [0, 0, 1, 0.05], [0, 0, 0, 1]])
    tgt = make_cloud(seed=0).transform(T)

    # 先做粗配准(这里用已知初值),再用点到面 ICP 精配
    result = o3d.pipelines.registration.registration_icp(
        src, tgt, max_correspondence_distance=0.2,
        init=np.eye(4),
        estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPlane(),
    )
    print("fitness:", round(result.fitness, 4),
          "rmse:", round(result.inlier_rmse, 5))

icp_demo()

max_correspondence_distance 是最关键参数:太大会引入错误对应,太小会找不到足够对应。常用做法是「由粗到细」多轮 ICP,逐轮缩小这个距离。fitness 是内点比例,低于 0.3 基本说明配准失败。

10. NeRF 与 3D Gaussian Splatting

神经渲染是 3D 视觉的另一条路线:不显式重建几何,而是学习「从任意视角看这个场景长什么样」。

NeRF(Neural Radiance Fields,2020)用 MLP 表示场景:输入一个点的三维坐标与视线方向,输出该点的颜色与体密度,再用体渲染积分出像素颜色。它需要多视角图像训练,渲染质量极高,但训练要数小时、渲染慢。

3D Gaussian Splatting(2023)用大量三维高斯椭球表示场景,每个高斯有位置、协方差、不透明度与球谐系数。渲染时把高斯投影到屏幕做 alpha 混合,速度可达实时(数十到上百 FPS),训练时间从数小时降到几十分钟。它已成为实时新视角合成的首选。

工程价值:

  • 数字孪生与场景重建:用照片生成可自由漫游的三维场景。
  • 数据合成:为新视角生成训练图,做数据增强。
  • 局限:需要多视角覆盖,动态场景与反光材质仍难处理。

11. 坐标系与标定约定

3D 视觉最容易出错的地方是坐标系。常见的有:

坐标系定义约定
相机系原点在光心z 向前,x 向右,y 向下(OpenCV)
激光系原点在雷达x 向前,y 向左,z 向上(ROS)
车体系原点在后轴中心x 向前,y 向左,z 向上
世界系全局参考通常是初始位姿

注意 OpenCV 的相机系是「z 向前、y 向下」,而 ROS 是「x 向前、z 向上」,两者差一个固定的旋转。跨库使用时必须显式转换,否则点云会整体旋转 90 度。

标定包括内参(焦距、主点、畸变)与外参(相机到雷达的 R 与 t)。外参标定常用标定板或互信息法,标定误差会随距离放大。

12. 自动驾驶的多传感器融合

自动驾驶同时用相机、激光雷达、毫米波雷达,融合的层次有三种:

  • 前融合:把原始数据在特征层拼接(如相机特征投影到 BEV 与激光特征融合)。信息损失小但标定敏感,代表是 BEVFusion。
  • 中融合:各传感器独立出检测结果,在目标层关联(如相机框与激光簇匹配)。工程稳健,易调试。
  • 后融合:各自出轨迹,在跟踪层融合。最松耦合,但对单传感器错误纠正能力弱。

工程上的现实选择多是「中融合 + 时序跟踪」,因为标定误差与时间同步问题会严重伤害前融合。时间同步(硬触发 vs 软同步)与标定精度是融合质量的两个决定因素。SLAM 中的位姿估计与融合方法可参考 机器人 SLAM 。

权衡取舍

维度倾向代价
单目与双目只要相对深度用单目无绝对尺度,测距不可用
体素大小小体素保小目标内存与算力爆炸
体素与柱体实时用 PointPillars精度略低于体素方案
ICP 与学习式配准初值可靠用 ICP初值差时收敛到局部最优
NeRF 与 3DGS实时用 3DGS,质量用 NeRF3DGS 显存占用大
前融合与中融合稳健用中融合信息损失,精度上限低

几条实用原则:

  • 需要绝对距离时不要用单目深度,双目与 ToF 才有真实尺度。
  • 体素大小按最小目标尺寸定,通常取目标尺寸的 1/3 到 1/5。
  • ICP 一定要给初值,并做多轮由粗到细。
  • 跨库使用坐标系前先写一个已知点的转换测试,别靠记忆。

常见坑清单

  • 深度单位搞错:毫米当米用,点云整体放大千倍,测距全废。
  • 视差未做校正:直接匹配未校正的双目图,匹配质量极差。
  • 无视差无效值:遮挡与无纹理区视差为 0,被当成真实深度。
  • 体素过大:行人等小目标被体素平均抹掉,检测直接漏掉。
  • 点云未裁剪 ROI:远处噪声点进入网络,拖慢推理并引入误检。
  • 坐标系混用:OpenCV 与 ROS 的相机系差一个旋转,结果整体歪。
  • 外参未标定或标定过期:车辆震动后外参漂移,融合精度下降。
  • ICP 无初值:收敛到局部最优,配准结果看起来「差不多」但错。
  • ICP 无鲁棒核:离群点拉偏变换,误差集中在边缘。
  • 把相对深度当度量深度:单目模型输出尺度不定,测距不可信。
  • 3DGS 显存估算不足:百万级高斯在显存小的卡上直接 OOM。
  • 忽略时间同步:相机与雷达时间戳差几十毫秒,高速场景目标错位。

小结

3D 视觉工程的主线是:先定深度获取路线(相对深度用单目,绝对尺度用双目或 ToF)→ 点云做体素化或柱体化 → 用 PointNet 系列或体素检测器做 3D 检测 → 配准用特征粗配加 ICP 精配 → 神经渲染按实时性选 3DGS 或 NeRF。记住三个判断点:视差精度决定深度精度、体素大小决定小目标命运、坐标系必须显式验证。

融合层面,中融合加时序跟踪是最稳健的工程起点,前融合要先把标定与时间同步做到位。当业务需要场景级三维重建时,3D Gaussian Splatting 已经是实时方案的首选,但要提前评估显存与数据采集成本。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机视觉」更多文章

  1. 检测与分割的评估指标
  2. 多模态视觉语言模型
  3. 视频理解与多目标跟踪