立体视觉与深度估计
一台相机把三维世界压成二维图像,深度被丢掉了;两台相机并排放,同一个点在两幅图里的位置差(视差)就把深度还原出来,公式只有一行:\(Z=fB/d\)。真正难的是找到匹配,而物理上的代价是:深度误差随距离的平方增长。本节用随机点立体图(random-dot stereogram)亲手跑块匹配、左右一致性检验和半全局匹配(SGM),看它们在纹理缺失、重复纹理、遮挡处怎样失败;再看误差为什么是 \(Z^2\),以及怎样用一个纯旋转把两台随便摆放的相机"矫正"成可以逐行搜索的标准形式。
1为什么机器人需要它
机器人要抓东西、躲障碍、落脚,需要的不是"图像里有什么",而是"它在哪、有多远"。单目图像做不到这一点:10.1 节的针孔投影 \(u=fX/Z+c_x\) 把 \((X,Z)\) 揉成了一个数 \(u\),沿着同一条视线的所有点成像在同一个像素。人有两只眼睛,机器人也一样:ZED、Bumblebee 这样的双目相机,RealSense D4xx 这样带红外投射器的主动双目,人形机器人头部的一对镜头,无人机前向的双目避障,火星车的导航相机,都是在用两个视角换回丢失的一维。
- 几何是一行公式:并排放的两台相机("矫正"之后),同一点在两幅图里水平错开 \(d\) 个像素,深度 \(Z=fB/d\)(\(B\) 是基线,\(f\) 是以像素计的焦距)。
- 难点是匹配:在右图里找到左图那个点的"同一点"。11.1 节告诉我们,矫正之后对极线就是水平扫描线,匹配退化成一维搜索;但纹理缺失、重复纹理、遮挡和噪声会让这个一维搜索出错。
- 物理给出的代价:深度误差 \(\sigma_Z\approx Z^2\sigma_d/(fB)\),越远越糟,而且糟得很快(4.5 节的误差传播)。基线、焦距、视差精度共同决定一台双目相机能"看多远"。
本节把这三件事各做成一个实验:FIG 1 是几何,FIG 2 是匹配,FIG 3 是误差;FIG 4 说明怎样把两台任意摆放的相机变成 FIG 1 的标准形式;FIG 5 把视差图变回三维点云。
2直觉:视差 = 基线被"缩短"了多少
伸出一根手指,交替闭上左右眼:近处的手指"跳"得很厉害,远处的墙几乎不动。这个"跳动"就是视差,而且越近越大,无穷远为 0。把它画成俯视图:两台相机相距 \(B\),一个点在深度 \(Z\) 处;两条视线在像平面上各留下一个标记,左右标记的水平位置之差就是 \(d\)。下图用相似三角形说明 \(d/f=B/Z\):以两个相机中心和点 \(P\) 为顶点的大三角形,与以两个标记和 \(P\) 为顶点的小三角形相似,所以两个标记之间的距离是 \(B\) 缩小到 \((Z-f')/Z\) 倍(\(f'\) 是画出来的像平面距离),恰好是 \(B\) 少了 \(d\)(同样按绘图比例)。
拖动琥珀色的点,或调基线 \(B\)、焦距 \(f\)、搜索范围 \(D\)(匹配算法最多愿意搜多少像素)。注意三件事:视差与 \(Z\) 成反比(右下曲线);整数视差对应一层一层的深度,越远层越疏;点太近,视差超过 \(D\),算法根本找不到它。(画图为了看清相似三角形,像平面放大绘制:1 像素画成 0.4 mm。)
- 把点从近处拖到离相机至少两倍远的地方:视差变成原来的一半。右下曲线上的点沿着哪条曲线移动?
- 保持点不动,把基线 \(B\) 调到 0.60 m:视差随之变成两倍。双目相机的"眼距"越大,同一深度的视差越大,深度分辨率越高,但近处更容易看不见(想想为什么)。
- 把点拖到足够近,使读数出现"太近:d > D"。最小可测深度 \(Z_{\min}=fB/D\);想看得更近,要减小 \(B\)、减小 \(f\) 或增大搜索范围 \(D\)。
矫正之后,立体视觉有两个量互为倒数:视差 \(d\) 与深度 \(Z\) 成反比。视差在像素上是均匀的(匹配算法一次走一个像素),但深度不均匀:近处一个像素对应几毫米,远处一个像素对应几米。后面所有现象,深度误差随 \(Z^2\) 增长、远处分层、近处丢失,都是这一个反比关系的推论。
3动手玩:随机点立体图与块匹配实验室
怎样"找同一点"?最朴素的做法是块匹配:取左图以 \((x,y)\) 为中心的一小块,在右图的同一行上从 \(x\) 往左一个像素一个像素地滑动,计算每个位置 \(d\) 的差异 \(C(x,y,d)\)(比如绝对差之和 SAD),选差异最小的 \(d\)(赢者通吃,WTA)。所有 \(C(x,y,d)\) 排成的三维数组叫代价体(cost volume)。
为了知道算法对不对,我们需要真值。下图的两幅图是程序合成的随机点立体图(Julesz 1960 年代用它证明了"视差本身就足以产生深度感"):一个倾斜的地面、一个悬浮的方块、一个圆顶,还有两块"困难区"——一面没有纹理的墙和一片周期条纹。右图由左图按真值视差前向变换得到(近处遮住远处),所以每个像素的真视差是已知的,右图里被遮住的左图像素无处可配(半遮挡)。下面跑的是真正的算法,不是预先画好的图:点击左图任一像素,看它的代价曲线 \(C(d)\),看最小值落在哪里。
- 把窗口半径 \(r\) 调到 0(1×1 窗口):视差图满是噪点;再调到 6 以上(13×13):平滑了,但物体边缘向外"胖"了一圈(前景膨胀)。找一个折中。
- 在左图里分别点击纹理缺失的墙(右上)和周期条纹(右下):墙的代价曲线是一条平线(没有信息),条纹的曲线有周期性的多个同样深的谷(歧义)。
- 把优化器切到 SGM:周期条纹区的错误大幅下降,因为平滑项把歧义的谷"拉"向邻居的答案;墙仍然是错的——平滑项不能凭空创造信息。
- 打开左右一致性检验:深度突变处左侧的半遮挡像素(紫色)被拒绝,统计中的错误率下降,代价是密度下降。
- 把"右图垂直错位"调到 1 像素:匹配几乎全军覆没。矫正不准、标定偏一个像素,细纹理就再也配不上(11.1 节的对极线不再是同一行)。
窗口:对每个像素独立地比较两小块。代价体:先算出所有 \(C(x,y,d)\),再在体上做优化(WTA、SGM 乃至 3D 卷积网络都只是"在代价体上选一条路径")。平面扫描:第 \(d\) 层代价 \(C(\cdot,\cdot,d)\) 就是把右图平移 \(d\) 个像素再和左图比,相当于假设整个场景是深度 \(Z=fB/d\) 的一张正对相机的平面;扫过所有 \(d\),每个像素就挑出让它最一致的那张平面。FIG 2 的"代价切片"把一行的代价体直接画了出来。
4另外两个角度:误差为什么是 Z²,以及矫正
深度误差随距离的平方增长
假设匹配算法找到的视差有一个小误差 \(\sigma_d\)(FIG 2 里亚像素之内的抖动)。\(Z=fB/d\) 对 \(d\) 求导得 \(\mathrm dZ/\mathrm dd=-fB/d^2=-Z^2/(fB)\),这就是 4.5 节里的 Jacobian,于是 \(\sigma_Z\approx\frac{Z^2}{fB}\sigma_d\)。同样的 0.25 像素误差,在 2 m 处不到 1.5 cm,在 20 m 处超过 1 m,因为远处一个像素代表的深度间隔大得多。左图画 \(\sigma_Z(Z)\),右图是蒙特卡洛:在每个深度站放一排点,给视差加高斯噪声再反推深度,散开的程度就是误差。数字是示意性的,用来比较趋势,不是某款相机的规格。
- 把深度圆圈(或"深度 Z"滑块)从起点 6 m 拖到两倍远(12 m 以上)或一半近(3 m 以下):误差变成 4 倍(或 1/4)。读数里的相对误差验证了平方律。
- 调基线、焦距和视差精度,让"±5 cm 可用距离"达到 10 m 以上。需要的 \(fB/\sigma_d\) 是多少?哪个旋钮最便宜?(提示:焦距 = 视场角,基线 = 体积和近距盲区,\(\sigma_d\) = 算法与标定。)
矫正:把任意摆放的两台相机变成标准形式
FIG 1 和 FIG 2 假设两台相机光轴平行、共面、像平面在同一平面上。真实的两台相机永远不会这样:装配有微小的转角,镜头也不同。11.1 节说,对极线一般是斜的,每个点要沿一条不同的斜线搜索。矫正(rectification)是在不动相机中心的前提下,把两台相机各自转到同一个新朝向,使新的 \(x\) 轴就是基线方向:这时基线在新坐标系里是 \((B,0,0)\),对极线自动变成同一行。因为只改变朝向,新图像和旧图像之间只差一个由旋转诱导的单应(10.5 节)\(H=K\,R_{\text{new}}^{\top}R_i\,K^{-1}\)。
下图的右相机有可调的偏航、俯仰、滚转。拖动左图中的琥珀点选一个像素,右图里画出它的对极线(它在右图里所有可能的位置),再调"试探深度",对应的点会在这条线上滑动。把"矫正程度" \(t\) 从 0 拉到 1,看两台相机怎样转向同一个朝向,斜线变成水平线,读数里同名点的行差缩小到数值误差 \(10^{-10}\) 像素以下。
- 把"矫正程度"拉到 1(或点"矫正"按钮):对极线变成水平线,行差降到 \(10^{-10}\) 像素以下。
- 矫正后拖动"试探深度":圆环沿水平线移动,视差 \(d=u_L-u_R\) 与 \(fB/Z\) 完全一致。现在匹配只需要在这一行上搜索。
5再看数学
提示:把鼠标悬停在公式中带底色的符号上,FIG 1、FIG 2 或 FIG 3 中对应的元素会被高亮。
矫正后的几何:Z = fB/d,以及由视差图恢复三维点
取左相机坐标系为参考(OpenCV 光学坐标系:\(x\) 右,\(y\) 下,\(z\) 前)。矫正后右相机相对左相机只沿 \(x\) 平移 \(B\),内参相同。点 \(P=(X,Y,Z)\) 在右相机坐标系里是 \((X-B,Y,Z)\),于是
视差永远 \(\ge0\)(左图为参考,右图里的点向左移)。反过来,视差图上每个像素 \((u,v,d)\) 都是一个三维点:
右边的 \(4\times4\) 矩阵 \(Q\) 就是 OpenCV 的 stereoRectify 返回、reprojectImageTo3D 使用的那个重投影矩阵(符号约定略有不同)。注意 \(d\) 的单位是像素,\(f\) 也是像素,\(B\) 才是米,所以 \(Z\) 的单位是米。
误差模型
第一个式子是 4.5 节的 \(\sigma_Z^2=J\sigma_d^2J\) 在标量情形的样子。三个推论:①误差 \(\propto Z^2\);②相对误差等于视差的相对误差,视差只有几个像素时一个像素就是几十个百分点;③横向误差只随 \(Z\) 线性增长,所以双目是"横向准、深度粗"。一阶近似在 \(\sigma_d\ll d\) 时有效;\(d\) 接近 \(\sigma_d\) 时分布严重偏斜(\(\mathbb E[1/d]>1/\mathbb E[d]\),FIG 3 右图的长尾),甚至 \(d+n\le0\) 给出"负深度"。整数视差给出的深度层间隔是 \(\Delta Z\approx Z^2/(fB)\),很粗;OpenCV 的 StereoBM / StereoSGBM 因此输出带 4 位小数的定点视差(要除以 16)。另外 \(Z\propto fB\):标定里 \(f\) 或 \(B\) 的百分之一的误差,就是整个深度图百分之一的系统尺度误差。
匹配代价、聚合与选择
把绝对值换成平方得 SSD;换成 \(1-\mathrm{NCC}\)(减去窗口均值、除以窗口标准差,FIG 2 中的 NCC 就是 \((1-\mathrm{NCC})/2\))就对左右相机的亮度偏置、增益("曝光不一致")不敏感;census 变换把窗口里每个像素与中心比大小,得到一个比特串,用汉明距离作代价,对任何单调的亮度变化都不变,硬件流水线里很常见。窗口大小是偏差—方差权衡:小窗口定位准但噪声大、纹理不足时无信息;大窗口平滑,但窗口跨过深度边缘时两侧的深度互相污染(前景膨胀)。
第一式是过最小值及两个邻居拟合抛物线的顶点(\(|\delta|\le\tfrac12\));它在 SAD 下有"像素锁定"偏差,所以只能当作改进,不是魔法。第二式是左右一致性检验:用同一个代价体,也对右图的像素 \(x'\) 选 \(d_R(x')=\arg\min_dC(x'+d,y,d)\);如果左像素 \(p\) 的匹配像素回指的不是 \(p\),说明这个匹配不可信,半遮挡像素几乎都过不了这一关。
半全局匹配(SGM)
逐像素 WTA 没有"相邻像素视差应当相近"的先验。理想的全局能量是 \(E(D)=\sum_p\big[C(p,D_p)+\sum_{q\in\mathcal N_p}P_1[|D_p-D_q|=1]+P_2[|D_p-D_q|>1]\big]\):小跳变罚 \(P_1\)(倾斜面),大跳变罚 \(P_2\)(深度边缘)。二维网格上它是 NP 难的;Hirschmüller 的办法是沿 \(r\) 个一维方向做精确的动态规划,再把各方向的结果相加:
最后减去 \(\min_iL_r(p-r,i)\) 只是防止数值无限增长。每条路径的代价是 \(O(WHD)\),FIG 2 用上下左右四条路径,原论文用 8 或 16 条,并用互信息代价而不是 SAD。OpenCV 的 StereoSGBM 是它的一个变体。SGM 之后,学习式立体(GC-Net、PSMNet 用 3D 卷积在特征代价体上做同样的事;RAFT-Stereo 用迭代更新;FoundationStereo 一类大模型追求零样本泛化)并没有改变框架:构造代价体,然后选一条路径,只是把代价和平滑先验换成了学出来的。
矫正的构造(纯旋转版)
\(C_L,C_R\) 是相机中心(世界坐标),\(R_i\)(\({}^{w}R_{c}\),相机到世界)是各自的朝向,\(\mathbf k_L\) 是左相机的光轴。新 \(x\) 轴取基线,新 \(y\) 轴取尽量接近原图"向下"的方向,新 \(z\) 轴补成右手系。验证:\(R_{\text{new}}^{\top}(C_R-C_L)=(\|C_R-C_L\|,0,0)^{\top}\),所以两个相机在新坐标系里只差一个沿 \(x\) 的平移 \(B=\|C_R-C_L\|\),于是 \(v_L=v_R\)。这就是 Fusiello–Trucco–Verri 的"紧凑算法"的核心;Bouguet 的做法(OpenCV stereoRectify 采用)把两台相机之间的相对旋转各转一半以减小畸变。FIG 4 的"矫正程度" \(t\) 用 \(R_i\exp(t\log(R_i^{\top}R_{\text{new}}))\) 插值(5.6 节的指数/对数映射)。真实系统中两台相机的 \(K\) 和畸变也不同:先去畸变,再用同一个新的 \(K\) 重采样。
6机器人中的它:从视差图到点云,以及各种深度传感器
视差图本身对机器人没用,要变成三维点才能避障、建图、规划抓取。下图用 FIG 2 当前的视差图,按 5 节的公式 \((X,Y,Z)=\frac{B}{d}(u-c_x,\,v-c_y,\,f)\) 逐像素重投影(假设 \(f=150\) px、\(B=0.12\) m,一个头部尺度的双目)。拖动旋转:地面是一张斜面,方块和圆顶悬在它前面。它直接显示出两件事:整数视差把点云切成一层一层的"纸板"(回到 FIG 1 的深度层),打开 FIG 2 的亚像素拟合并把真值设为亚像素就会变平滑;错误匹配(墙、遮挡处)变成飘在空中的"飞点",下游算法必须过滤它们(左右检验、置信度、离群点滤波)。
深度传感器对比
"主动"的意思是传感器自己发光。FIG 2 的纹理缺失的墙最能说明为什么要有主动双目:RealSense D4xx 的红外投射器向场景打一片伪随机点,它本质上就是 FIG 2 里那种随机点图案,墙上有了纹理,块匹配又能工作了(在 FIG 2 的场景选项里选"+ 红外点投射",墙和条纹区的错误率会立刻降下来)。下表只给通用结论,具体量程与精度因型号而异。
| 传感器 | 原理 | 量程与误差随距离 | 强项 / 典型失败 |
|---|---|---|---|
| 被动双目(ZED、Bumblebee、机器人头部双目) | 两台相机,匹配场景自身纹理 | 量程随基线增长,从几十厘米到几十米;误差 \(\propto Z^2\) | 室外阳光下可用、无需发光、分辨率高;纹理缺失的墙、反光/透明表面、重复纹理、夜间失败 |
| 主动双目(RealSense D415/D435/D455) | 两台红外相机 + 红外点投射器,给场景加纹理 | 室内常见量程约零点几米到数米;误差仍 \(\propto Z^2\) | 白墙也有深度;强日光会淹没投射图案(退回被动双目),黑色/镜面/玻璃仍然困难 |
| 结构光(Kinect v1、PrimeSense、手机人脸识别) | 一台相机 + 已知图案的投射器:投射器相当于"虚拟第二台相机" | 近距离(零点几米到数米);误差随距离同样平方增长 | 室内精度好;阳光、多台设备互相干扰、黑色表面 |
| ToF 相机(Kinect v2 / Azure Kinect 等) | 调制红外光,测每个像素返回光的相位 / 飞行时间 | 室内约数米;误差增长比双目慢,不是 \(Z^2\) | 无需匹配、白墙也行;多径干扰、物体边缘的"飞点"、强日光、深色吸光表面 |
| LiDAR(机械旋转、固态) | 发射激光脉冲,测往返时间 | 远(数十到数百米),厘米级误差基本不随距离变大 | 室外远距离、测距准;点稀疏、昂贵,玻璃/镜面/强吸光表面会漏掉或给出假点,没有颜色纹理 |
硬件:Stereolabs ZED 系列和 FLIR/Point Grey Bumblebee 是被动双目;Intel RealSense D415/D435/D455 是主动红外双目,深度由芯片内的专用处理器计算(Keselman 等,2017);Luxonis OAK-D 在自带的视觉处理器上跑双目匹配;火星车的导航相机与避险相机,以及一些汽车的前视系统(如 Subaru EyeSight)都是双目对。软件:OpenCV 的 stereoRectify、StereoBM、StereoSGBM、reprojectImageTo3D 是本节全部流程的现成版本;ROS 的 stereo_image_proc 把它们接进机器人系统;GPU 上有 libSGM、NVIDIA VPI,以及 Isaac ROS 里的学习式双目视差网络;得到的点云再交给 Open3D / PCL(12.1 节)。机器人用法:无人机前向双目避障、人形机器人头部相机取抓取点云(9.3 节的抓取规划)、移动机器人的局部代价地图;视觉里程计与 SLAM 用双目可以直接得到尺度(Part IV 回来讲)。深度补全(depth completion)把稀疏但可靠的测距(LiDAR、ToF)和稠密的图像融合,补上主动传感器丢失的区域。
7常见误区
本节取左图为参考,\(d=u_L-u_R\ge0\)。有的库以右图为参考,有的把左右图交换,有的(OpenCV 的投影矩阵 \(P_2\))把基线写成 \(T_x=-fB\) 这种带符号的量。结果是 \(Z\) 变成负数,或者"远处的东西比近处的东西近"。另一个经典陷阱:OpenCV 的 StereoBM / StereoSGBM 输出是定点数,要除以 16,无效像素是一个负的特殊值。直接把原始输出当像素视差代进 \(Z=fB/d\),得到的深度只有真值的 1/16。永远用已知距离的物体做一次端到端的核对。
块匹配假设同名点在同一行。直接对没有矫正的图像做 StereoBM 不会报错,只是悄悄变差。标定(10.4 节)的残余误差、镜头热胀冷缩、相机支架被撞歪,都会引入纵向错位。FIG 2 的"垂直错位"滑块说明了后果:对细纹理,1 个像素就足以让匹配从"基本正确"变成"几乎全错";纹理越平滑越能容忍,但精度同样受损。出厂标定好的双目相机也要警惕:机器人摔过一次之后,就该重新检查对极线残差。
"这台相机深度精度 1%"只在某一个距离成立。\(\sigma_Z/Z=Z\sigma_d/(fB)\),距离翻倍则相对误差翻倍、绝对误差翻四倍。FIG 3 里近处几乎看不到的散布,在远处是一个拖长尾的偏斜分布,不是对称的高斯。两个实际后果:①不要对深度做平均,而要对视差(噪声近似高斯的量)做平均或滤波,再取倒数;②给下游(建图、避障)的不确定性必须随深度变化,4.5 节的传播公式给出每个点自己的协方差,用一个固定的 σ 会在远处过度自信。
FIG 2 的墙:代价曲线是平的,WTA 随便挑一个视差;平滑项(SGM)会从邻居那里借一个值,视差图看上去干净、完整,却是编的。重复纹理(栅栏、百叶窗、地砖)有多个同样深的谷。反光表面显示的是镜中的世界,不在表面上;透明物体匹配到背后的东西。对策:使用置信度(谷的深度、最优与次优代价的比值——OpenCV 的 uniquenessRatio、左右检验),丢掉不可信的点而不是填满;改用主动照明;或者让学习式方法利用语义先验。"稠密"和"正确"是两回事。
深度突变的一侧,左图里有一条像素在右图里被前景挡住了(FIG 2 里紫色的半遮挡区),它们没有匹配;WTA 仍然会给出一个值,而且往往是错的,所以要靠左右检验或遮挡检测丢掉,再用邻近的背景值填充。另一个静默的失败:物体近到 \(d>D\)(FIG 1 的红区),匹配器根本不会去搜索真正的位置,只会在范围内挑一个虚假的最小值,输出的是一个看似合理的错误深度,而不是"太近了"的警告。选 \(D\) 要对应机器人真正需要的最近距离 \(Z_{\min}=fB/D\);基线越大,近距盲区越大,也更多遮挡,这是大基线换取远处精度的代价。
8小练习
9延伸阅读
- R. Hartley & A. Zisserman, Multiple View Geometry in Computer Vision, 2nd ed. (Cambridge University Press, 2003), Ch. 11 — 基础矩阵的计算与图像矫正(射影矫正)
- D. Scharstein & R. Szeliski, A taxonomy and evaluation of dense two-frame stereo correspondence algorithms, International Journal of Computer Vision 47(1–3), 2002 — 稠密立体算法的"匹配代价—聚合—优化—精化"四步框架与评测
- H. Hirschmüller, Stereo processing by semiglobal matching and mutual information, IEEE TPAMI 30(2), 2008 — SGM 的原始论文
- R. Szeliski, Computer Vision: Algorithms and Applications, 2nd ed. (Springer, 2022), Ch. 12 — 深度估计:对极几何、稠密对应、全局优化与深度网络
- L. Keselman, J. I. Woodfill, A. Grunnet-Jepsen & A. Bhowmik, Intel RealSense Stereoscopic Depth Cameras, CVPR Workshops, 2017 — 主动红外双目相机的设计与取舍
- A. Fusiello, E. Trucco & A. Verri, A compact algorithm for rectification of stereo pairs, Machine Vision and Applications 12, 2000 — 本节矫正构造的出处
- A. Kendall et al., End-to-End Learning of Geometry and Context for Deep Stereo Regression (GC-Net), ICCV 2017; J.-R. Chang & Y.-S. Chen, Pyramid Stereo Matching Network (PSMNet), CVPR 2018; L. Lipson, Z. Teed & J. Deng, RAFT-Stereo, 3DV 2021 — 学习式立体:在特征代价体上做 3D 卷积,或迭代更新视差
- B. Julesz, Foundations of Cyclopean Perception (University of Chicago Press, 1971) — 随机点立体图与人类立体视觉