“dToF 转深度图”听起来像一个格式转换,实际上至少有三个问题:传感器给的是什么距离?每个测量对应哪条光线?最终要生成哪个相机坐标系下的图?
本文接着 小孔成像与相机内参 往下讲。没有绑定某一款硬件;具体设备的单位、有效状态、光线方向和 SDK 输出定义,必须以该设备文档为准,不能根据 depth、distance_mm 这样的变量名猜。
1. dToF 测的是飞行时间,但 SDK 不一定输出原始斜距
dToF 是 direct Time of Flight,即直接飞行时间测距。在发射与接收中心近似重合、往返路径相同,并已扣除电子延迟等偏置的简化模型下:
其中 为光速, 为往返传播时间, 为到反射点的距离。例如往返约 10 ns 对应约 1.5 m。发射与接收中心明显分离时,传播时间对应两段路径之和,应依赖设备的标定模型,而不是无条件把一半路径当作接收中心到点的距离。ST 官方 dToF 介绍第 11 页 给出了基本往返时间关系。
具体实现可能对光子到达时间建立直方图、估计峰值并输出置信度。测量还受到反射率、环境光、多径、串扰与混合目标等影响,并不是每个输出数值都天然可靠。多区域 dToF 的一个实例是 ams OSRAM TMF8821。
这里要把三个维度分开:
| 维度 | 可能的情况 | 为什么重要 |
|---|---|---|
| 测量原理 | dToF、间接 ToF、双目视差 | 决定原始观测与误差来源 |
| 输出布局 | 多区域阵列、扫描点、二维图像、XYZ 点云 | 决定如何找到每个测量的方向 |
| 数值语义 | 径向 range、光轴 Z、已标定 XYZ | 决定能否直接反投影 |
ToF 是原理,LiDAR 常指主动光学探测系统,不能简单把两者当成互斥类别。反过来,名字叫“深度相机”也不代表它是 ToF;例如 RealSense D435 的官方技术类型为立体视觉,本站 D435i 笔记中的双目原理不能直接搬来解释 dToF。
一个真实反例:distance_mm 已经可能是 Z
ST 员工对 VL53L5CX 的说明指出,该器件内部已经做了 radial-to-perpendicular 转换:正对一个垂直于光轴的平墙时,各区域输出应近似对应相同的垂直距离,而非边缘更长的原始斜距。见 ST 关于 VL53L5CX 输出含义的说明。
因此,对这类已转换输出再乘一次 ,反而会把正确平墙“压弯”。这是具体型号的反例,不是所有 dToF 都输出 Z 的保证。
2. range 与 Z:同一个点的两种距离
仍使用相机 optical 坐标: 右、 下、 前。空间点为 ,则:
是光线与正光轴的夹角。光轴上二者相同,边缘则一般有 。
考虑一面位于 m 的正对平墙:
- 光轴上的点: m, m。
- 水平离轴 45° 的点:, m,但 仍是 2 m。
如果把第二个点的 当成 Z 反投影,会得到 ,而不是 。越偏离光轴,几何越被拉长。
反过来,若 SDK 已经输出 Z=2 m,却误当成 range 再乘余弦,就得到 Z≈1.414 m。没有转换、重复转换,都会错。
正对平墙是一个很有用的排错实验,但要避开墙边、无效状态、多径和低信噪比区域;它是验证文档理解的辅助证据,不是替代型号文档的万能判据。
3. 先选输入分支,再恢复三维
3.1 已知单位光线方向时,公式最通用
设厂家标定得到某个区域或测量点的单位方向:
则输入不同,公式也不同:
| 已确认的输入 | 恢复的源坐标系三维点 |
|---|---|
| 径向距离 | |
| 沿源坐标系正 Z 轴的深度 | ,要求 |
| 已标定 XYZ | 直接使用,不再做一次深度反投影 |
注意这里 与 必须针对同一条轴、同一个坐标系定义。如果设备坐标不是本文 optical 约定,要先明确它的坐标映射。
3.2 无畸变针孔图像是一个特殊情况
若输出确实对应已知 的无畸变针孔像素网格:
于是:
千万不要把 当单位向量直接写成 。此外,原始畸变像素必须先经过适合该设备的去畸变/反投影模型。
3.3 8×8 阵列不等于一张普通相机的小图
多区域 dToF 的一个格子通常覆盖一束有面积的角度范围,而不是无限细的一根理想光线。区域内存在前后两个物体时,输出还受峰值选择、回波和固件处理影响;把数值放在区域中心射线上,只是一种代表点近似。
不要仅凭“8×8、总 FOV 多少度”就断言每格等角度,或反推出一套准确的针孔 K。优先使用厂家的每区方向表、畸变模型或 SDK 反投影。
同理,按方位角/仰角排列的球面 range image 与按 排列的透视图不同。若某系统将方位角 从正 Z 轴向右计,仰角 向上计,那么在本文右、下、前坐标中,一种明确约定的单位射线为:
实际设备的零角、正方向和通道角度可能不同,不能直接套用。更不能把球面图 resize 一下就称作透视深度图;应恢复三维后重新投影。PCL 的 RangeImagePlanar 文档 也区分了平面投影与球面投影。
4. XYZ 投到目标深度图:不只是改一个矩阵尺寸
假设已有源坐标系点 ,目标是某个相机 C 的深度图。正确顺序为:
- 根据设备的状态、置信度、量程和单位筛掉无效观测。
- 通过外参变换到目标相机:。
- 丢弃非有限值及 的点,避免投影到身后或除以接近零的数。
- 使用与目标图像模式匹配的 K 和畸变模型,得到像素 。
- 做目标图像边界判断,再离散化成像素索引。
- 同一像素有多个候选点时,保留最近的正 ,同时保留有效性掩码。
这里输出的是目标相机的 Z,不一定等于源设备的 range,也不一定等于源坐标系的 Z。ROS 标准浮点深度采用沿相机 Z 轴的米制深度,参见 REP 118。具体 SDK 的其他编码仍要单独确认。
4.1 带坐标轴变换的完整例题
源坐标为“前、左、上”,目标相机坐标为“右、下、前”,暂设两者原点重合、安装朝向没有额外旋转:
这个 R 只是例题的轴约定变换,不是可用于真实硬件的通用外参。
对源点 m:
使用 ,得到:
该像素应写入 Z=5 m,而非 m。
若另一点为 (目标相机系),它也投到 。两者发生碰撞时留下 Z=5,而不是平均成 7.5。较小深度更新也是 Open3D 点云投影内核 中采用的基本处理方式。
4.2 像素离散化与无效值必须有明确约定
本文规定像素中心为整数,采用最近像素投影:
这样先检查边界,再取整,输出索引一定合法。右、下边界不包含在内。若某库使用 floor(u)、不同的采样中心或边界规则,应整体匹配该库,而不是混用本文的边界与另一种取整方式。
没有点落入的像素用 NaN 或显式 invalid mask 表示。不能把“无观测”当成“距离 0 米”;展示时可以着色为黑色,但数值语义仍要区分。浮点 NaN 与某些 16 位格式中的 0 无效值,转换时也要显式处理。
4.3 Z-buffer 解决不了所有遮挡
取最小 Z 只解决已采到的点之间的投影碰撞。如果前景没有采到点,背景仍可能穿过空隙;不同传感器存在基线时,还有源视角看得到、目标相机却被遮挡的区域。
因此“已投影”“真实观测”“插值补洞”“遮挡推断”应分开记录。把前景 1 m 与背景 5 m 双线性平均成 3 m,会制造一个本不存在的表面。最近邻不会混合数值,但也可能复制错误表面;没有任何简单缩放方法能自动解决深度边界问题。
5. FOV 到底由什么决定
FOV 是视场角,不是分辨率。对于一个已经明确的无畸变针孔图像,它由像素范围和 K 一起决定。
在本文像素中心约定下,横向外边缘对应的方向角为:
同理,将 换成 得到垂直 FOV。这里的角度来自像平面横/纵截面;目标点的矩形透视范围由 、 判断,不能用球面仰角 的固定上下界直接替代。
若主点严格位于几何中心 ,左右对称,才简化为:
FOV 输入三角函数前要从度转成弧度。仅有一个对角 FOV,通常不足以确定 ;还需要像素尺度、主点、投影模型等假设。真实畸变镜头、多区域 dToF 或扫描传感器的覆盖范围,也不能只靠这组针孔公式描述。
同一视场将 640 列重采样成 320 列,K 也相应更新,视场可以不变;从 640 列只保留中间 320 列,视场则缩小。两者虽然输出尺寸相同,几何含义完全不同。
6. ROI 裁剪:主点平移,焦距不变
从原图取整数 ROI:横向索引 ,纵向索引 。例如 NumPy 中:
cropped = image[y0:y0 + h, x0:x0 + w] |
原像素中心在新图的坐标为 。代入投影式可得:
裁剪没有改镜头,只是换了像素原点与保留范围。主点可能偏到一侧,甚至落在裁剪图外;这本身并不意味着内参错误。
对 640×480 图像,取 ,只裁左右、保持高度:
| 图像 | x0 | 宽度 | 新 cx | 左边缘角 | 右边缘角 | 水平 FOV |
|---|---|---|---|---|---|---|
| 完整图 | 0 | 640 | 320 | −28.11° | 28.04° | 56.14° |
| 近中心 ROI | 160 | 320 | 160 | −14.98° | 14.89° | 29.86° |
| 偏右 ROI | 240 | 320 | 80 | −7.64° | 21.76° | 29.40° |
微小不对称来自例题主点 320 与图像几何中心 319.5 的差别。这里不是用视场的像素比例硬乘角度,而是对裁剪后的左右边缘分别取反正切。
为什么“裁一半宽度”不是“FOV 减半”?对严格居中模型,宽度保留比例为 时:
它是正切与反正切关系,而非角度线性关系。越广角,线性近似越容易产生明显偏差。
还有两种名字相近但含义不同的裁剪:
- 图像 ROI:从已有图像里去掉部分像素,按上式更新主点。
- 三维点云按目标相机视场筛选:先将点变到目标相机系,再投影并判断边界。若 dToF 与目标相机有平移,不能只裁源传感器的角度来代替,近距离视差会使结果不同。
7. Resize 与 letterbox:除了比例,还有半像素约定
7.1 先写坐标映射,再更新 K
设先裁剪,再按比例 缩放,最后左右/上下补边,其中左侧 padding 为 、顶部为 。如果使用简单坐标映射:
则:
其本质是 ,其中 A 是同一个像素坐标仿射变换。不能用完整 padded 宽度除原宽度来误算缩放倍率;缩放比例与补边是两步。
7.2 常见的中心对齐重采样需要额外 0.5
很多缩放实现以像素中心对齐采样。例如 OpenCV resize 实现 中常见的采样位置关系,对应如下前向坐标变换:
因此应使用:
纵向同理,焦距仍为 。若使用 align_corners、不同采样模式或厂家图像管线,映射可能不同;这不是必须一律手工加减 0.5 的命令,而是要匹配真实的像素映射。
这里改用相机基础篇的点 m,其原图投影为 ,不是本篇第 4 节的点。原图先裁 ,再缩小到 160×120,不补边:
| 阶段 | fx, fy | cx, cy | 该点的投影 |
|---|---|---|---|
| 原图 | 600, 600 | 320, 240 | 380, 270 |
| 裁剪后 | 600, 600 | 160, 120 | 220, 150 |
| 缩放:简单坐标比例 | 300, 300 | 80, 60 | 110, 75 |
| 缩放:中心对齐 | 300, 300 | 79.75, 59.75 | 109.75, 74.75 |
两种结果的差别来自明确的坐标约定,不是空间点变了。对中心对齐缩放,用本文像素外边缘计算的 FOV 保持不变。
再举 letterbox:640×480 等比例缩到 320×240,放入 320×320 画布,上下各补 40 像素。中心对齐时 。上下 padding 没有真实测量,必须保留 invalid mask,不能因为 K 能描述那些方向就认为传感器看到了更多区域。
7.3 图像尺寸缩放,不等于深度数值缩放
像素从 640×480 变成 320×240,某个点的真实深度 2 m 仍然是 2 m。要变化的是该点的位置和内参,不是米制深度乘 0.5。
还需注意:
- 对深度图做普通双线性插值可能混合前后景;应根据任务选择带有效性判断、边界意识的处理,并标记估计值。
- 从低分辨率 dToF 放大到高分辨率不会创造真实测量或提升原始角分辨率。
undistort、rectify、align 到 RGB 可能涉及非线性重映射或不同相机视点,不能只用一个 crop/resize 仿射 A 代替。- 在 ROS 中,原图 K、矫正图 P、ROI、binning 的约定应与驱动和
image_geometry的处理一致,避免重复扣 ROI 或重复缩放。参见 CameraInfo 定义。
8. 一份不用硬件也能运行的参考实现
下载 camera_geometry_demo.py。它只依赖 Python 3.9+ 标准库,包含 range/Z 反投影、FOV、crop/resize 内参更新、坐标变换和最近 Z 投影。
python3 camera_geometry_demo.py --self-test |
文件中的投影核心是:
# 已经先检查设备状态、置信度和单位;目标是无畸变针孔图。 |
以下例子可以在下载文件所在目录运行:
from camera_geometry_demo import CameraModel, rasterize_z |
这是教学实现,不是某款 dToF 驱动:没有替你估计外参、读取置信度、处理镜头畸变或做运动补偿,也没有进行深度补洞。输入点必须先完成相应设备的数据校验。大规模实时点云可换成向量化或并行实现,但应保留这里可验证的坐标、单位和边界约定。
9. 真正接硬件时的排错顺序
| 现象 | 优先检查 |
|---|---|
| 正对平墙却向边缘鼓起或凹下 | range/Z 混淆、重复余弦修正、畸变与光线方向表 |
| 整体左右镜像、上下颠倒 | optical 轴与机体轴、区域索引顺序、图像是否翻转 |
| 裁剪后整体偏移 | 是否更新主点、是否重复扣 ROI |
| 缩放后偏一点,边缘更明显 | fx/fy 比例、半像素约定、实际工作分辨率 |
| RGB 与深度在近处错位尤其明显 | 外参平移、目标相机 K、跨视角遮挡 |
| 静止正常,运动时错位 | 采集时间戳、设备时钟偏差、扫描点时间与运动补偿 |
| 远近差三个数量级 | 毫米/米、raw depth scale、整数编码解释 |
| 物体边缘出现不存在的中间深度 | 插值跨前后景、区域混合回波、无效值参与平均 |
时间同步值得单独强调:匹配相近时间戳不等于修正了设备时钟偏差,也不等于消除了曝光或扫描期间的运动。若设备是扫描式输出,需要确认点级采集时刻;非扫描多区域器件则按其实际采集方式处理,不应无条件套上扫描雷达的 deskew 流程。
接入前建议把以下信息写进配置或数据说明:型号与 SDK 版本、输出是 range 还是 Z、单位与无效状态、区域/像素到射线的标定、坐标轴、外参方向、目标图像模式、时间戳来源、crop/resize/padding 顺序。这样后续换设备、改分辨率时,不用重新猜一遍。
10. 用四个问题检查是否真的理解
- 某 dToF SDK 已输出垂直深度,边缘像素还要乘 吗?
- 将宽度从 640 缩到 320,深度 5 m 应变成 2.5 m 吗?
- 偏心 ROI 宽度相同,FOV 一定与中心 ROI 相同吗?
- 一张深度图的黑色区域能直接认定为近距离障碍物吗?
答案依次是:不应重复转换;米制深度不跟图像尺寸缩放;不一定,应该从左右边缘方向重新算;不能,先区分无效值、无观测和显示配色。
把这篇压缩成一条实践主线就是:确认距离语义 → 用正确光线恢复三维 → 变换到目标坐标系 → 投影与有效性处理 → 图像预处理时同步更新内参。
回到 计算机视觉基础分类,或阅读 相机内参与小孔成像 和 RealSense D435i 工程入门,对照不同测量原理下相同的投影几何。