☰
双目相机测距原理详解:从视差公式到D435实践
2026/10/3 17:16:05 网站建设 项目流程

我记得第一次在实验室里把两个USB相机架到滑轨上,花了一整个下午做标定、校正、跑立体匹配,最后屏幕上终于打出“目标距离:1.18米”的时候,同事凑过来用卷尺一拉,1.16米。那两厘米的误差,对于很多项目来说已经够用,但也让人清醒:双目相机测距原理听起来像是一道几何题,真正跑起来却是一整套工程题。这篇内容我想从视差公式讲到标定坑位,再讲到实际设备D435怎么用,把“双目为什么能测距”和“双目到底怎么测准”这两件事彻底讲透。不管你是准备选型的学生、想入门立体视觉的开发者,还是已经在产品里踩坑的工程师,都应该能从这里面找到点有用的东西。

1. 人眼是怎么判断远近的:视差与三角测量的底层逻辑

1.1 视觉测距的前置认知

先说一个最容易被忽略的事实:单个相机单张照片是测不出绝对距离的。照片把三维世界压缩成了二维平面,沿着光心射出去的无数条光线都可能落到同一个像素上,深度信息在成像那一刻就丢掉了。人眼之所以能判断远近,靠的是两只眼睛从略微不同的角度观察世界,大脑再根据两幅画面的差异反推距离。这个“差异”就是视差。

双目相机就是模仿这个过程的装置:在一条基线上放置两个相机,同时采集同一场景的两幅图像,通过算法找到两幅图像中对应同一个空间点的像素位置,算出它们的水平坐标差,再利用三角几何关系反推出深度。说到底,视觉测距不是一个“直接测量”的过程,而是一个“根据已知几何关系反解”的过程。

1.2 从相似三角形推导深度公式

两个相机水平放置,光轴平行,这是双目视觉最经典的理想模型。设左相机光心为O_L,右相机光心为O_R,两者之间的距离叫基线B。某个空间点P在左相机坐标系下的深度为Z,它在左图上的成像点水平坐标为x_l,在右图上的成像点水平坐标为x_r。

根据相似三角形关系,可以写出:

x_l = f · X / Z

x_r = f · (X - B) / Z

两式相减得到:

x_l - x_r = f · B / Z

把Z单独提出来:

Z = f · B / (x_l - x_r)

其中:

  • Z是目标点到相机平面的距离,单位与B一致;
  • f是焦距,单位是像素;
  • B是基线长度,单位是毫米或米;
  • (x_l - x_r)就是视差d,单位是像素。

这就是双目测距最核心的公式。深度与视差成反比,视差越大,距离越近;视差越小,距离越远。这个公式理解透了,后面所有误差分析都能踩着它展开。

1.3 基线和焦距怎么影响测距

既然Z = f·B/d,那么在视差不变的情况下,基线和焦距都会直接影响深度输出。这也决定了双目相机的设计取舍。

基线越长,对同样的深度,产生的视差就越大,反推深度时对匹配误差的容忍度就越高,所以长基线适合测远距离。但基线太长会带来另一个问题:近处物体可能只在两个相机中的一个里有完整成像,公共视野变小,近距离匹配直接失效。

焦距也是同理,长焦镜头相当于放大了视差,有利于远距离测距,但视野变窄,近距离大物体放不下。短焦则视野宽,适合近距离大范围场景。

所以你在选双目相机时,看到的基线长度和镜头视场角从来不是随便定的,它们决定了这台设备的工作距离区间。比如Intel RealSense D435的基线大约50mm,适合0.3米到3米左右的中近距离场景;而一些工业级的双目云台会把基线做到200mm以上,就是为了探测几十米外的目标。没有通吃所有距离的双目相机,只有匹配使用场景的几何参数。

2. 双目、单目、超声波、蓝牙与结构光:测距方案选型到底怎么比

很多人搜“双目测距”的时候,其实是在做方案选型,网络上经常把“单目测距”“超声波测距”“蓝牙测距”“结构光”这些关键词放在一起搜。既然你有这个困惑,我就把主流的测距方式一次性拉通对比。

2.1 单目为什么测不出绝对距离

单目测距论文、单目测距方案在网上热度一直不低,但必须先认清楚一个事实:单张单目图像没有尺度信息。同样一个物体,在1米处拍和在2米处拍,如果在图像里像素尺寸一样,那只能说明物体实际大小差了一倍,但算法并不知道哪个距离是对的。

单目测距想要拿到绝对距离,必须引入额外假设:

  • 假设目标物体尺寸已知,用像素大小反推距离;
  • 假设地面平面已知,用透视图中的消隐点配合相机高度推算;
  • 假设相机在运动,用多帧之间的运动视差恢复深度(视觉里程计、SLAM)。

前两种本质上都是在“猜”物理尺度,第三种已经成为另一套复杂的系统。所以单目的优势是硬件成本低、结构简单,但它测距的可靠性和普适性,跟双目的几何确定性相比有明显差距。

2.2 超声波和蓝牙的测距逻辑

超声波测距的原理很简单:发射超声波脉冲,碰到物体反弹回来,根据“声速×往返时间/2”算出距离。这就是热搜里提到的“超声波测距报警系统”背后那套逻辑。它的优点是便宜、实现简单、对透明物体和纯色物体不挑剔;缺点是波束角大、分辨率低,只能给出一个扇区范围内的平均距离,测不准具体是哪个物体,受温度影响也比较明显。

蓝牙测距则完全是另一回事。它利用的是RSSI(接收信号强度)随距离衰减的特性,通过信号强度反推距离。但无线信号在室内会经过折射、反射、遮挡,强度波动非常大,所以它一般只用于室内定位里的粗略距离估计,根本做不到厘米级测量,跟视觉测距不是一个量级的东西。

2.3 结构光与TOF,和双目有什么互补关系

结构光和TOF属于主动光方案。结构光是把编码好的光斑投射到物体表面,另一个相机拍摄光斑的变形图案,根据变形量计算深度。TOF则是直接测量光脉冲的往返飞行时间。这两者都有一个共同短板:强光环境下容易失效,而且多台设备同时工作会互相干扰。

双目相机是纯被动方案(如果加上红外投射器就成了主动双目),不需要主动发射信号,所以不存在多设备互相干扰的问题,功耗也更低。它的弱点是遇到纯色墙面、白墙这类没有纹理的区域时,左右图找不出匹配点,容易“瞎”。我后面讲D435的时候会提到,消费级设备怎么用一个红外投射器弥补这个短板。

2.4 选型建议表

方案类型典型范围精度优势主要局限
双目视觉被动光0.2m~10m中高无主动光源、成本可控、纹理信息丰富弱纹理失效、计算量大、需要标定
单目视觉被动光依赖先验中低硬件简单、功耗低单帧缺乏尺度信息
超声波主动声波0.02m~5m中便宜、对透明物体有效波束角大、空间分辨率低
结构光主动光0.2m~5m高弱纹理场景也能工作强光下易失效、设备间会干扰
TOF主动光0.1m~10m中高帧率高、实时性好分辨率偏低、多机干扰
蓝牙RSSI无线信号1m~20m低穿透性好、覆盖广精度极低、环境敏感

这张表可以当作一个粗向导:如果你要做的项目在室内、弱纹理较多、预算有限,可以考虑结构光或者主动双目;如果在室外强光环境,纯被动双目更有优势;如果只是做个避障报警,超声波就够了,没必要上视觉。

3. 让两个相机“看见同一个世界”:从标定到立体校正的完整链路

3.1 相机成像的坐标系变换链路

要把空间中的三维点映射到图像像素,中间要经过四个坐标系的变换:世界坐标系、相机坐标系、图像坐标系、像素坐标系。这个过程可以拆成三步:

  1. 世界坐标系到相机坐标系:刚体变换,换算是外参R(旋转矩阵)和t(平移向量)负责的;
  2. 相机坐标系到图像坐标系:针孔投影,核心是焦距f;
  3. 图像坐标系到像素坐标系:把物理尺寸离散成像素,核心是主点(cx, cy)和像素物理尺寸。

把这三步写成一个矩阵,就是相机的内参矩阵K和外参[R|t]的组合。内参描述的是“相机自己长什么样”,外参描述的是“相机在世界里的位置和朝向”。我们做标定,本质上就是要把这两个东西都测出来。

3.2 标定到底在标什么

回到双目的场景。双目系统里有两个相机,所以需要标定的内容有三部分:

  • 左相机内参;
  • 右相机内参;
  • 左右相机之间的相对位姿(R和T)。

内参里最值得关注的是焦距和畸变系数。几乎所有镜头都存在畸变,尤其是鱼眼镜头和广角镜头,画面边缘的直线会变成曲线。如果畸变不校正,左右图像上的像素位置本身就带误差,后面算视差就是错上加错。

标定常用的方法是张正友标定法。你打印一张棋盘格标定板,从不同角度、不同距离、不同位置拍摄十几到几十张照片,算法自动提取棋盘格角点,然后根据角点的空间共面约束,迭代求解相机参数。

你可能听说过“双目相机标定剔除不合格角点”这个说法。这说的是标定过程中,有些图像上的角点提取得不够准,如果硬塞给优化算法,会拉低整个标定结果的精度。判断角点是否合格,最直接的指标是重投影误差:把标定得到的相机参数代回去,把角点的三维位置重新投影到图像上,算投影点和实际提取角点之间的距离差。这个误差超过0.5像素甚至更大的图像,就应该丢掉重拍,而不是留下硬凑。

3.3 立体校正:把二维匹配降成一维

标定完成之后,两个相机之间的相对位姿是已知的,但这还不够。想让左右图像直接做水平方向的像素匹配,必须经过一步“立体校正”。

立体校正的目的是让左右两幅图像经过重投影后,同一个空间点在两幅图像中的像点处于同一水平扫描线上。这样才能把二维的图像搜索问题降成一维的极线搜索,极大地降低匹配难度和计算量。

OpenCV里对应的是stereoRectify函数,输入两个相机的内参、畸变系数和相对外参,输出左右相机的校正旋转矩阵、投影矩阵和重投影矩阵。校正完之后,左右图里的对应点就在同一行了,匹配算法只需要沿着水平方向搜索。

这一步经常被初学者忽略,但不做立体校正就去算视差图,匹配结果会非常混乱,因为对应点不在同一行,搜索范围从一行变成整个二维平面,计算量和误匹配率都失控。

3.4 标定质量怎么检验

标定做得好不好,不能只看算法有没有跑完。我常用的检验方法有三个:

  • 看每张标定图的平均重投影误差,合格情况下大多数角点的误差应该控制在0.1到0.3像素以内;
  • 把左右图校正结果可视化,画上水平参考线,观察对应特征点是否落在同一条线上,这是最直观的检查方式;
  • 摆一个已知长度的物体,比如一块100mm的标定块,放到不同距离测几次,看深度输出是否和实际一致。

标定本身不是目的,标定服务于后续的立体匹配。如果标定有偏差,后面所有深度计算都会系统性地偏移,这个误差不会因为你匹配算法写得好而消失。

4. 算准视差才是关键:立体匹配算法背后的平衡艺术

拿到校正后的左右图像,接下来的核心任务就是为每一个像素找到它在另一幅图像中对应的像素,然后算出视差。这个环节叫立体匹配,是整个双目测距里最复杂、最影响精度的一环。

4.1 匹配代价:怎么判断“长得像”

立体匹配的第一步,是定义“长得像”的标准。给定左图中的一个像素,要判断右图中哪个像素和它对应,得先定义一个代价函数。常见的方案有三种:

  • SAD(绝对差和):比较两个小窗口内像素灰度差的绝对值之和,窗口越小越敏感,越大越平滑,但代价是丢失细节;
  • SSD(平方差和):类似SAD,但使用差的平方,对大差异更敏感;
  • Census变换:把窗口内每个像素与中心像素比较大小,编码成二进制串,然后比较汉明距离,对光照变化更鲁棒。

实际工程里,纯SAD已经很少用了。因为左右相机即便型号完全相同,也会因为曝光差异、传感器响应差异导致相同物体的灰度不一致,灰度层面的代价函数很容易失配。Census这类基于局部序关系的代价函数,抗光照变化能力明显更强。这也是为什么SGM算法在工业界被广泛使用的原因之一。

4.2 SGM:用一种折中的方式逼近全局最优

有了每个像素的匹配代价,下一步是想办法从所有可能的视差中选出最合理的一个。最朴素的做法是WTA(赢者通吃),也就是对每个像素选择代价最小的视差值。但单独看每个像素做决策,结果会有很多噪声点,因为单个像素的信息太少了。

更理想的方案是全局优化:定义一个包含数据项和平滑项的能量函数,数据项衡量匹配代价,平滑项鼓励相邻像素有相近的视差,然后求全局最优。但全局最优在二维图像上是一个NP-hard问题,直接求解的代价不可接受。

SGM(半全局匹配)的核心思想是:不在二维平面上全局搜索,而是沿着多个方向(比如8个或16个方向)分别做一维动态规划,再把各方向的代价累加起来,看作对二维全局优化的近似。这个办法在精度和计算量之间取得了非常好的平衡,因此在OpenCV的StereoSGBM、工业双目相机的深度引擎里都是主流方案。

SGM有几个关键参数需要根据场景调:

  • 视差范围(numDisparities):决定了算法搜索的深度区间;
  • 惩罚参数P1、P2:控制平滑度,P2越小视差图越平滑,但太大又会抹掉物体边缘;
  • 窗口大小:blockSize越大,深度图越平滑,但边缘越模糊。

这些参数没有绝对正确的值,通常需要对着你的实际场景反复试。我自己的经验是:先固定一个场景,调整视差范围到目标物体的深度区间,然后微调P2参数,观察物体边缘和弱纹理区域的变化。

4.3 遮挡与无纹理区域的特殊处理

立体匹配里最头疼的两个问题是遮挡和无纹理。

遮挡是指一个物体在左图中可见,但在右图中被其他物体挡住,找不到对应点。这种情况下算法只能输出一个错误的视差。常见的处理手段是左右一致性检查(LRC):用左图做基准算一遍视差图,再用右图做基准算一遍,两个结果不一致的像素被判定为遮挡或错误点,然后丢弃或填充。

无纹理区域是指像白墙、天空、地面这类灰度几乎不变的区域,窗口内的代价函数处处相同,匹配不出唯一的视差值。算法通常会输出一片噪声,或者把远处背景的视差值蔓延过来。对于纯被动双目,这个问题几乎无解,只能在应用层做滤波。这也是为什么很多消费级产品要用主动光辅助,后面讲D435时会提到。

4.4 亚像素视差与深度误差的关系

原生的视差图是整数像素级别的,这意味着离得越远的物体,深度越粗糙。为了提升精度,算法会在最优视差附近做抛物线拟合,得到亚像素精度的视差值。这一步非常重要,因为视差每提升0.1像素的精度,深度值就能平滑很多。

但就算做了亚像素插值,视差误差依然是深度误差的根源。回到Z = f·B/d这个公式,深度误差可以近似写成:

ΔZ ≈ (Z² / (f·B)) · Δd

看到Z²没有?深度对误差的敏感度是平方关系。同样1个像素的匹配误差,在1米处可能只产生几厘米的偏差,到5米处就可能变成几十厘米甚至接近一米。这不是算法写错了,而是双目几何的天然特性:距离越远,测距精度越差,误差按平方放大。理解了这一点,你就能明白为什么双目相机的标称精度通常只在某个范围内有效,超过那个范围,所有双目系统都会力不从心。

5. 实测误差从哪来:双目项目里踩过的几个坑

5.1 标定板角点为什么会不合格

回到热搜词里那条“双目相机标定剔除不合格角点”,我在这里多说几句。标定板角点提取得准不准,直接决定了内参和外参的精度,但实际拍摄时,角点提取经常出问题。

最常见的原因是光照不均匀。标定板一半亮一半暗时,暗处的棋盘格角点对比度太低,角点响应弱,亚像素细化会跑偏。第二个原因是畸变严重的镜头在画面边缘会把棋盘格压得很厉害,角点会连成一片,OpenCV的findChessboardCorners可能报错或给出错误的角点顺序。第三个原因是标定板本身不平整,比如把A4纸打印的棋盘格贴在弯曲的纸箱上,角点的空间位置已经不符合平面假设。

我的处理习惯是这样的:每拍一组图,就用calibrateCamera返回的perViewErrors检查每张图的平均重投影误差,把误差明显偏大的图像直接剔除,重新补拍。另外用cornerSubPix做亚像素细化时,要选择合适的搜索窗口,窗口太小收敛不到精确位置,窗口太大又会被邻近角点干扰。标定板不是越多越好,而是要覆盖画面的四个角和中心、远近都有、倾斜角度多样,保证约束完整。

5.2 曝光不同步与反光材质

第二个大坑是左右相机的曝光不同步。很多USB摄像头默认开启自动曝光,两个相机各自根据视野里的平均亮度调整曝光时间,导致同一时刻左右图亮度不一致。亮度不一致会破坏灰度匹配代价函数,让匹配错误率直线上升。

解决方法是关闭自动曝光,把左右相机的曝光时间、增益、白平衡都设置成固定值,并且尽量让两个相机朝向相似的场景。如果相机支持硬件外触发同步采集,那是更好的方案,能避免运动物体在左右帧之间的时间差。

反光材质是另一个麻烦。玻璃、镜面、抛光金属这类物体表面,左右相机看到的亮度差异极大,甚至出现高光区域在一边存在、另一边完全消失的情况。Census变换能缓解一部分问题,但无法根治。遇到这种场景,只能接受深度图里会出现空洞,并在后处理阶段做补洞。

5.3 温度、机械结构和时间带来的误差

双目相机的精度依赖两个相机之间的相对位姿保持出厂状态。但实际项目中,温度变化会引起外壳和PCB板的热胀冷缩,机械振动可能让镜头发生微小位移,这些都会悄悄改变基线和外参。

我做过一个户外项目,刚开始精度还行,设备在太阳底下晒了一个小时之后,测距结果出现了系统性偏移。后来检查发现是固定支架在高温下发生了形变,造成两个相机的光轴不再平行。这类问题很难通过算法弥补,只能从结构上解决:使用低膨胀系数的材料、加强机械固定的刚性,或者定期重新标定。

另外还要留意一个时间维度的问题:左右相机成像时间不同步的话,快速移动的物体在左右图里处于不同空间位置,匹配结果会偏得非常离谱。如果项目里有高速运动目标,尽量选支持硬件同步的双目相机,而不是随意抓两个普通摄像头拼起来。

5.4 距离越远误差越大的数学直觉

前面公式推导过,深度误差按Z²放大,这里用一个具体例子让你产生直觉。

假设相机焦距f=600像素,基线B=50mm,这是一个常见的参数组合。在1米处,目标的视差为:

d = f·B/Z = 600×0.05/1 = 30像素

如果匹配误差是1个像素,相对视差误差是1/30,深度误差大约为:

ΔZ ≈ Z²/(f·B) = 1/(600×0.05) ≈ 0.033米 = 3.3厘米

同一套系统在5米处,视差变为6像素,1个像素的匹配误差意味着大约16.7%的深度误差,对应到实际距离就是0.83米左右的偏差。你看,同一个算法、同一套硬件,从1米到5米,误差从厘米级跳到了米级。

所以做项目时,不要只看标称精度,一定要结合自己的目标测量距离来评估需求。如果需要在10米外精确测距,双目视觉可能不是最佳方案,长距离测距就要考虑激光雷达或者其他主动式测距方案。

6. 把双目测距落到实践:用Intel RealSense D435跑通一次深度读取

6.1 D435的主动双目原理与参数选择

D435是很多人入门双目视觉的第一台设备,它之所以好用,是因为它把前面几章讲的标定、立体校正、立体匹配全都封装好了。你拿到手打开深度流,就能直接读每个像素的距离。

这里有个概念需要澄清:D435不是严格的被动双目,而是一台“主动双目立体视觉”设备。它有左右两个红外相机,基线大约50mm,中间还有一个红外点阵投射器。这个投射器会在物体表面投射不可见的红外纹理,相当于在没有天然纹理的地方“人为制造”纹理,帮助左右相机完成匹配。所以它在白墙、夜晚这些被动双目会失效的场景里也能勉强输出深度。

选择D435这样的设备,最大的好处是省掉了自己标定的复杂度。出厂前厂家已经完成了双目标定,深度引擎内部已经跑好了立体校正和匹配。你要做的只是调用SDK,把深度数据读出来。

6.2 编写程序读取深度值

D435的官方SDK是Intel RealSense SDK,Python接口是pyrealsense2。安装好SDK和Python环境之后,直接看下面的代码:

import pyrealsense2 as rs import numpy as np import cv2 # 创建pipeline pipeline = rs.pipeline() config = rs.config() # 配置深度流和彩色流 config.enable_stream(rs.stream.depth, 1280, 720, rs.format.z16, 30) config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30) # 启动pipeline profile = pipeline.start(config) # 获取深度传感器的深度缩放系数 depth_sensor = profile.get_device().first_depth_sensor() depth_scale = depth_sensor.get_depth_scale() print("深度缩放系数:", depth_scale) try: while True: frames = pipeline.wait_for_frames() depth_frame = frames.get_depth_frame() color_frame = frames.get_color_frame() if not depth_frame or not color_frame: continue # 转成numpy数组 depth_image = np.asanyarray(depth_frame.get_data()) color_image = np.asanyarray(color_frame.get_data()) # 取图像中心点的深度值 h, w = depth_image.shape center_depth = depth_image[h // 2, w // 2] * depth_scale # 可视化输出 cv2.putText(color_image, f"depth: {center_depth:.3f} m", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow("D435 Color", color_image) if cv2.waitKey(1) & 0xFF == ord('q'): break finally: pipeline.stop() cv2.destroyAllWindows()

有一个细节需要特别说明:D435的深度帧格式是Z16,也就是每个像素用一个16位无符号整数表示深度值。但这个整数并不是直接以米为单位的,必须乘以depth_scale才是真实的米制距离。你打印一下depth_scale,通常是一个0.001左右的小数,意思是原始值乘以0.001才是米。

这个单位坑我见过很多人踩过:有人直接把原始整数当距离输出,发现读数大了1000倍,还以为设备坏了。以后凡是处理深度图,第一件事就是确认单位换算关系。

6.3 动手验证精度:卷尺就是最好的调试工具

代码跑通之后,建议不要急着认为“能出数就是对的”,花点时间做一次精度验证。

最简单的方法是把D435放在桌面上,正前方摆一个纸箱,用卷尺量好实际距离,然后对比深度图读数。我建议从0.5米开始,每0.5米一个点,一路测到3米,记录数据。下面是我在正常室内光照下用D435测过的一组大致数据,仅供参考:

实际距离(m)D435读数(m)误差(m)
0.50.490.01
1.01.020.02
1.51.540.04
2.02.050.05
2.52.600.10
3.03.150.15

可以看到,距离越远误差越大,这跟前面数学推导的趋势完全一致。D435在1米附近表现不错,到了3米误差已经来到厘米级甚至更高。你的实际数据会因为光照、物体纹理和距离范围而有变化,但误差随距离放大的趋势不会变。

测完数据之后可以配合深度图像的彩色化显示,把深度值映射为伪彩色图。这样能直观地看到物体边缘、遮挡区域的深度是否可靠。如果物体边缘出现大量噪声,试着调整深度引擎的置信度参数,或者运行官方的“On-Chip Calibration”做一次机内标定,往往能改善不少。

最后再说一个个人体会:如果你是想系统学习双目测距的开发流程,建议先用D435这类完整设备跑通全流程,在真实数据里建立对“深度图”“视差图”“误差趋势”的体感,然后再自己搭一个纯被动双目系统,亲手走一遍标定、校正、匹配的过程。这两件事的顺序不要反了,先有全局体感,再扣细节,踩坑时才能分得清是标定问题、匹配问题还是场景问题。双目测距的整套东西,不怕你不会公式,就怕你只背了公式却没摸过真实数据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询