3D视觉基础:点云、深度图、视差图与坐标系转换

各位同学,欢迎来到第三章。前两章我们聊了位姿估计的整体框架和相机模型,今天要进入真正的3D数据世界了。

说实话,我刚接触3D视觉时,被一堆概念搞得晕头转向——点云、深度图、视差图,还有那四个坐标系来回转换。后来做项目踩了不少坑,才慢慢理清楚。今天我把这些经验整理出来,希望能帮你少走弯路。

3.1 四种3D数据形式

我们先搞清楚一个核心问题:3D视觉到底在处理什么数据?

我个人习惯把3D数据分为四种形态,它们本质上是同一份深度信息的不同表达方式。

数据形式 维度 存储结构 典型应用
点云 无序点集 (N×3) (x, y, z) 坐标列表 物体重建、SLAM
深度图 2D图像 (H×W) 每个像素存深度值 抓取定位、避障
视差图 2D图像 (H×W) 每个像素存视差值 双目立体匹配
彩色图 2D图像 (H×W×3) RGB三通道 纹理映射、识别

3.1.1 点云——最原始的3D数据

点云说白了就是一堆三维坐标点的集合。每个点包含 (x, y, z) 信息,有些还带RGB颜色或法向量。

我在项目中遇到过一个问题:用激光扫描仪采集的点云动辄几百万个点,直接处理根本跑不动。后来学会了体素滤波,才把数据量降下来。

关键概念:点云是无序的,没有像素之间的邻接关系。这意味着你不能像处理图像那样用卷积直接操作点云。

3.1.2 深度图——图像化的深度信息

深度图就是把深度值映射到一张灰度图上。每个像素的值代表该点到相机的距离。

举个例子:一张640×480的深度图,其实就是30多万个深度值排成的矩阵。值越小,表示物体离相机越近。

我的经验:深度图里的空洞(无效像素)很常见,尤其是反光表面或黑色物体。处理前一定要做填充或滤波,否则后续位姿估计会出大问题。

3.1.3 视差图——双目视觉的产物

视差图来自双目相机。左右两个相机看到同一个点,在图像上的位置会有偏移,这个偏移量就是视差。

为什么会有视差?你想想看,用手指放在眼前,闭上左眼和右眼,手指的位置是不是在跳?这个跳动的距离就是视差。

视差和深度是反比关系:

深度 Z = (基线长度 B × 焦距 f) / 视差 d

基线越长,同样距离下的视差越大,深度精度越高。但基线太长,两个相机共同视野会变小——这是个trade-off。

3.2 四种坐标系与转换关系

好了,数据形式搞清楚了。接下来是本章最核心的内容——坐标系转换。

我记得第一次做手眼标定时,被这四个坐标系绕得晕头转向。后来画了一张图,才彻底搞明白。

四大坐标系转换关系 世界坐标系 (Xw, Yw, Zw) 相机坐标系 (Xc, Yc, Zc) 图像坐标系 (x, y) 像素坐标系 (u, v) 外参 (R, t) 内参投影 仿射变换 💡 转换流程: 世界坐标 → (外参R,t) → 相机坐标 → (内参投影) → 图像坐标 → (仿射变换) → 像素坐标 反向过程:像素坐标 → 图像坐标 → (内参逆投影) → 相机坐标 → (外参逆变换) → 世界坐标

3.2.1 世界坐标系

世界坐标系是参考系。你可以把它理解为整个场景的"大地基准"。在机器人应用中,通常把机器人基座或工作台面设为世界坐标系原点。

为什么要引入世界坐标系?因为相机在动,物体也在动,我们需要一个不变的参考系来描述所有物体的位置。

3.2.2 相机坐标系

相机坐标系以相机光心为原点,Z轴指向相机前方,X轴向右,Y轴向下(遵循右手系)。

从世界坐标到相机坐标,靠的是外参矩阵——一个3×3的旋转矩阵R和一个3×1的平移向量t。

P_cam = R * P_world + t

⚠️ 避坑指南:我曾经在标定时把旋转矩阵和平移向量的顺序搞反了,结果投影出来的点全跑到相机后面去了。记住:先旋转,后平移。

3.2.3 图像坐标系

图像坐标系是物理单位(毫米),原点在图像中心。这一步是把3D相机坐标投影到2D成像平面上。

投影公式很简单,就是相似三角形:

x = f * Xc / Zc
y = f * Yc / Zc

这里f是焦距。注意,Zc是深度值,除以Zc这一步实现了"近大远小"的透视效果。

3.2.4 像素坐标系

像素坐标系就是我们最终看到的图像坐标,原点在左上角,单位是像素。

从图像坐标到像素坐标,需要做一次仿射变换:

u = x / dx + u0
v = y / dy + v0

其中dx、dy是每个像素的物理尺寸,u0、v0是主点坐标(光心在像素坐标系中的位置)。

3.3 HALCON中的坐标系操作

在HALCON里,坐标系转换有现成的算子,不用自己手写矩阵运算。但理解背后的原理,才能用好这些算子。

3.3.1 相机标定与内参获取

首先需要标定相机,获取内参矩阵:

* 标定板描述
create_calib_data ('calibration_object', 'calplate', 1, CalibDataID)
set_calib_data_calib_object (CalibDataID, 0, 'caltab_30mm.descr')

* 添加相机
set_calib_data_cam_param (CalibDataID, 0, 'area_scan_division', [0.016, 0, 0.000008, 0, 0, 640, 480, 1280, 960])

* 执行标定
calibrate_cameras (CalibDataID, Errors)
get_calib_data (CalibDataID, 'camera', 0, 'params', CameraParam)

标定完成后,CameraParam里就包含了焦距、主点、畸变系数等信息。

3.3.2 世界坐标转像素坐标

有了内外参,就可以把3D世界点投影到2D图像上:

* 世界坐标点
WorldPoints := [[100, 200, 500], [150, 250, 600]]

* 投影到像素坐标
project_3d_point (WorldPoints, CameraParam, Pose, ImagePoints)

这个Pose就是外参,描述了相机在世界坐标系中的位置和姿态。

3.3.3 像素坐标转3D射线

反过来,从2D像素点反推3D方向,在抓取定位中非常常用:

* 像素坐标转3D方向射线
image_points_to_world_plane (CameraParam, Pose, Row, Col, 'mm', X, Y)

* 或者直接生成3D点云
xyz_to_object_model_3d (X, Y, Z, ObjectModel3D)

注意:单目相机从2D像素只能得到3D射线,无法确定深度。要得到精确的3D坐标,要么用双目视觉,要么用深度相机,要么已知物体尺寸。

3.4 点云与深度图的相互转换

在实际项目中,我们经常需要在点云和深度图之间来回转换。

3.4.1 深度图转点云

每个像素 (u, v) 对应一个深度值 d,通过内参可以反算出3D坐标:

Z = d
X = (u - u0) * Z / fx
Y = (v - v0) * Z / fy

在HALCON中,一行代码搞定:

depth_to_point_cloud (DepthImage, CameraParam, X, Y, Z)

3.4.2 点云转深度图

反过来,把点云投影到成像平面上:

project_object_model_3d (ObjectModel3D, CameraParam, Pose, X, Y, Z)
gen_image_from_points (X, Y, Z, 'real', Width, Height, DepthImage)

核心要点:深度图转点云是一对一的,每个像素对应一个3D点。但点云转深度图时,多个3D点可能投影到同一个像素,需要做Z-buffer处理——保留最近的那个点。

3.5 本章小结

这一章我们覆盖了3D视觉的四个核心概念:

  • 点云:无序的3D点集,最原始的数据形式
  • 深度图:图像化的深度信息,每个像素存距离值
  • 视差图:双目视觉的中间产物,与深度成反比
  • 坐标系转换:世界→相机→图像→像素,四步走

这些概念是后续所有位姿估计算法的基础。下一章我们会深入HALCON的3D对象模型操作,到时候这些知识都会用上。

嗯,今天就到这里。建议你把坐标系转换的公式自己推导一遍,光看是记不住的。