☰
双目相机测距全解析:从视差原理到立体匹配与精度控制
2026/10/3 7:33:45 网站建设 项目流程

双目相机测距这话题,估计做视觉的朋友都绕不开。不管是做机器人避障、AGV导航,还是AR交互、工业检测,只要涉及“让机器知道物体离自己有多远”,双目方案都是个绕不过去的选项。这篇东西我会从原理到实操、从选型到踩坑完整过一遍,尽量让刚入门的朋友也能一步步跟着做下来,同时把那些“书上不写但实际特别重要”的经验都抖出来。

这个内容适合谁?适合刚接触视觉测距的初学者、正在做毕设或比赛项目的学生,也适合已经在用双目但总感觉精度不稳、想系统性搞明白原理的工程师。看完之后,你应该能回答三个问题:双目测距为什么能测距离?精度受什么影响?实际项目中怎么把精度做上去?

1. 双目测距的物理基础:为什么要用两只“眼睛”

双目测距的底层逻辑其实特别朴素,就是模拟人眼的立体视觉。人有两只眼睛,左右眼看到的画面略有差异,大脑根据这个差异,也就是视差,来判断物体的远近。双目相机干的事就是把这件事用算法复现出来。

1.1 视差是怎么来的

想象你伸出一根手指放在面前,先闭上左眼只用右眼看,再闭上右眼只用左眼看,你会发现手指相对于背景的位置明显跳了一下。这就叫视差。物体离你越近,这个“跳动”越明显;离得越远,跳动越小。当物体足够远的时候,两只眼睛看到的画面几乎没有区别,所以人眼对远距离物体的深度感知是失效的。

双目相机利用的正是这个物理现象。两个相机同时拍摄同一场景,左图中某个像素点是某个物体上的同一个物理点,这个物理点在右图中也会有对应的像素点。两个像素点在各自图像中的位置不一样,横坐标的差值就是视差(disparity),通常用像素为单位。视差越大,说明物体越近;视差越小,说明物体越远。测距本质上是“由视差反推深度”。

这里有个关键假设:左右相机必须同时拍摄同一场景。如果两个相机不同步,运动物体的位置会不一致,后面的立体匹配就成了瞎配。所以选双目相机时,硬件上要有同步触发能力,这一点后面实操章节会展开。

1.2 从视差到距离:一个几何推导就能看懂

双目测距的数学模型建立在理想状态下:左右相机光轴平行、焦距相同、成像平面共面。这个状态叫理想双目系统。在这个状态下,三维空间中任意一点在两台相机上的成像,只存在水平方向的偏移,不存在垂直方向的偏移,这就是极线约束。

用相似三角形就可以推出深度公式。设基线长度为B,是两个相机光心之间的距离;相机焦距为f;某点在左图中的像素横坐标为xl,在右图中的像素横坐标为xr,则视差d=xl-xr。根据三角形相似关系,深度Z满足:

Z = f × B / d

这个公式是整个双目测距的灵魂。你不需要理解复杂的矩阵变换,先把这条公式刻在脑子里,后面所有关于精度、标定、匹配的讨论,都围绕这条公式展开。

把公式拆开看,影响深度Z的因素有三个:基线B、焦距f、视差d。其中f和B在相机固定安装后基本不变,属于系统参数;d是每帧图像中每个像素点都要计算出来的量,属于逐像素参数。所以双目测距的核心难题,就是准确地算出每个像素的视差d。

1.3 为什么不能用单目直接测距

研究过程中很多人会问:那我用单个相机,能不能通过图像算距离?答案是可以的,但有个前提:必须已知目标物体的实际尺寸。比如一个标准篮球,图像中篮球的直径占了多少像素,结合相机的焦距,就能估算出篮球的距离。这叫单目测距,模型上用的是小孔成像原理。

单目测距的问题在于,它本质上做的是“已知尺寸反推距离”,一旦目标物体换成了未知尺寸的东西,或者目标被部分遮挡、发生形变、旋转,测距就失灵了。更关键的是,单目相机天生无法获得真正的深度信息,同一个画面可能对应无穷多个三维场景,这是一种尺度歧义。

双目测距不需要预先知道物体尺寸,它是纯粹用几何关系计算深度,对任意未知物体都能给出逐像素的深度值。这就是双目测距的核心优势。代价是:需要标定、需要立体匹配、计算量大、对硬件同步有要求。说到底,单目是“靠经验”,双目是“靠几何”。

2. 双目相机的选型:从原理到器材的落地

搞懂了原理,下一步就是选设备。市面上的双目相机方案不少,从几百块的模组到几万块的高端设备都有。选型如果不合理,后面算法调得再好也白搭。

2.1 三种主流方案对比

目前市面上常见的双目方案有三类:双USB相机自组方案、双目模组方案、深度相机方案。表面上都叫双目,实际用起来差别很大。

双USB相机自组方案就是把两个普通USB摄像头固定在一块支架上,自己触发同步或者忍受异步误差。这个方案成本最低,灵活性最强,坏处是同步问题很难解决,两个相机各跑各的曝光和采集,运动场景下视差会算错。适合静态场景的算法研究,不适合运动和实时场景。

双目模组方案是厂商做好的硬件,比如常见的双目摄像头模组,内部有硬件同步电路,左右目出厂时大致对齐,有些还出厂标定过。这类设备接上就能跑,适合绝大多数开发场景。

深度相机方案,比如Intel RealSense D435这类设备,集成了红外结构光或主动立体技术,输出的是已经对齐的深度图,不需要自己在CPU上跑立体匹配。这类设备开发效率最高,价格也更高,而且受环境光影响大,在户外强光下表现差。

2.2 D435的定位和边界

提到双目就绕不开D435,这是很多人的入门选择。D435的标称测量范围大概在0.2米到10米左右,实际上精度会随距离急剧下降,1米以内效果尚可,超过3米误差就比较明显了。它用的是主动立体技术,带有红外点阵投射器,在纹理缺乏的白墙等场景下也能工作。

使用D435这类深度相机时有一个常见误区:认为深度图是“准”的。实际上D435输出的深度图在物体边缘、遮挡区域、反光表面、透明表面上都会有大量无效像素和飞点,直接拿来用的话,下游算法会被脏数据带偏。我在项目中通常会把深度图跑一遍中值滤波和空洞填充,同时结合RGB图做边缘膨胀,把边缘误差大的像素屏蔽掉。

2.3 自组双目时如何估算基线

如果选择自组双目,基线和焦距的选择是有讲究的,不能随手定。根据深度公式Z=fB/d,在视差精度相同的情况下,基线越长,可测的距离越远;但基线越长,近处物体的视差越大,可能超出图像搜索范围,导致近处测不了。

用具体数字算一下感受更直观。设某相机焦距f=800像素,期望在10米处还能有1像素的视差分辨率,那么基线B至少要满足B = Z × d / f = 10 × 1 / 800米 = 12.5毫米。这只是刚好能区分1像素视差,实际工程上通常要求视差至少3到5个像素才算可靠,所以基线保守取50毫米以上。

反过来算近处极限。假设图像横向分辨率是1280像素,最大可搜索视差设为200像素,那么最近可测距离Z_min = f × B / d_max。f取800,B取120毫米时,Z_min = 800 × 120 / 200 = 480毫米。意思是小于0.48米的物体直接测不了,因为视差已经超出搜索范围。所以自组双目时,基线要根据“最近测距需求”和“最远测距需求”两头来定。

3. 双目标定的完整流程与角点筛选

公式Z=fB/d看着简单,但前提是所用到参数必须准确。f、B这两项就是标定要算出来的东西,此外还有两个相机之间的相对姿态、各自镜头的畸变系数、主点位置,这些统称为内外参。标定不准确,后面的视差计算就是个纸上谈兵。

3.1 为什么要标定,标定的目标是什么

双目相机出厂时,两个相机的位置是毫米级偏转和旋转的,不可能达到理想双目系统的绝对平行。加上镜头本身的畸变,成像并不是严格的针孔模型。标定要做的,就是计算出这些偏差并建立校正关系,让两张图变成理想平行状态。

具体来说,标定分两步:先对每个相机分别标定内参和畸变,获取焦距、主点、径向畸变和切向畸变参数;再标定两个相机之间的外参,也就是旋转矩阵R和平移向量T。之后用这些参数对图像做立体校正,让左右图的极线对齐成水平线。这样立体匹配时只需要在水平方向搜索对应点,计算量会骤减,匹配准确率也大幅提升。

3.2 标定板的选择和采集姿势

标定板建议用棋盘格或者不对称圆点板,尺寸要覆盖相机画面的四分之一以上。棋盘格角点特征简单,算法成熟,圆点板在自动检测时更鲁棒,但对光照要求高一些。我在实际项目中偏爱不对称圆点板,因为它在标定中对极几何的约束更稳定,不容易出现镜子翻转问题。

采集标定图片时,最关键的不是拍得多,而是姿势分布广。我见过很多新手拿标定板在画面正中间拍十几张就完事了,结果标定完图像校正效果奇差,误差特别大。正确的做法是:从近到远、从左到右、从正对到倾斜,尽量让标定板覆盖图像的四个角落和中心区域,姿态差异大一点。我通常采集30到50张图,其中保证有大量边缘和倾斜位置,用Opencv自带的stereoCalibrate函数批量处理。

3.3 为什么要剔除不合格角点

这是热词里专门提到的一个点,也是实际标定中特别容易被忽略的环节。采集的图像虽然量多,但其中一部分质量是不过关的。比如标定板反光、边缘被剪切、模糊、角点被遮挡,这类图像的角点检测结果虽然也能出来,但亚像素精度很差。如果这些不合格的数据混进标定流程,会直接拉低整个标定结果的精度。

所以标定前一定要做筛选。我的习惯是三步走:第一步肉眼检查每张图,删除标定板不完整、严重反光、运动模糊的图像;第二步跑一遍角点检测,观察重投影误差,把误差远超平均水平的图单独挑出来;第三步看校正后的图像,如果某张图的左右极线明显没对齐,就把那张图删掉重标。

重投影误差是对标定质量最直观的判断指标,它表示三维标定板上的角点坐标,经过标定参数投影回图像坐标系后,与原始检测到的角点像素坐标的平均偏差。一般高质量标定,这个值应该小于0.1像素,最多到0.15像素。如果超过0.3甚至0.5,那说明数据里混了太多坏图,或者标定板安装不平、发生了弯曲。

3.4 标定的完整流程

我用OpenCV做标定时的流程,整理出来可以直接抄。环境方面需要OpenCV和一块合适的标定板,我用的是OpenCV Python接口,整个流程在Ubuntu和Windows上都跑过,兼容性没问题。

先把左右目各自的内参和畸变系数标出来。对每一张采集到的棋盘格图像,调用cv2.findChessboardCorners检测角点,然后用cv2.cornerSubPix做亚像素细化,传入所有图像的点集,调用cv2.calibrateCamera得到单目内参。这里需要注意,如果不做亚像素细化,角点位置会偏差好几分之一像素,对长距离测距来说影响非常大。

得到左右目内参之后,将左右目图像中的对应角点对一起交给cv2.stereoCalibrate,传入左目和右目的内参矩阵,它会输出双目外参R和T,以及本征矩阵E和基础矩阵F。这一步是双目标定的核心,算完之后用cv2.stereoRectify计算立体校正映射表,再用cv2.initUndistortRectifyMap生成左右图的校正映射,最终调用cv2.remap完成图像校正。

校正完成后,左右图极线应当对齐到同一水平线。验证方法是把左右校正图并排显示,用鼠标取左图某个角点,右图对应角点应该在同一行。更量化的方式是直接画几条水平直线叠加在左右图上,检查角点是否都落在对应的水平线上。

4. 立体匹配:从极线约束到视差图生成

标定解决了图像校正的问题,下一步就是立体匹配,也就是在左右图中找到同一个空间点的对应像素对。这是双目测距中计算量最大、算法最多的环节,也是精度好坏的分水岭。

4.1 立体匹配的约束条件

在校正后的理想双目系统中,一个关键性质是:左图上任意像素点,对应的右图匹配点一定位于同一水平线上。这个性质就是极线约束。因为搜索范围从二维平面降到了一维水平线,匹配算法的效率和准确率都有了质的提升。

但极线约束只解决了“在哪一维上找”的问题,没解决“如何确定哪个点是同一个点”的问题。这就需要相似性度量。最朴素的思路是:在某一点的周围取一个窗口,在右图对应水平线上滑动这个窗口,找相似度最高的位置作为匹配点。窗口大小、相似度算法、全局约束策略,就构成了立体匹配算法的不同流派。

4.2 局部匹配算法与SAD示例

局部匹配是工程中最常用的思路,核心就是按窗口计算相似度。SAD(绝对差值和)是其中最直观的一种,把窗口内所有对应像素差的绝对值加起来,值越小说明越相似。我用一段简化代码说明这个思路,实际工程中会用更高效的方式实现:

def sad_matching(left_img, right_img, max_disp=64, win_size=5): h, w = left_img.shape[:2] disparity = np.zeros((h, w), dtype=np.float32) half = win_size // 2 # 对每个像素计算不同视差下的SAD代价 for y in range(half, h - half): for x in range(half + max_disp, w - half): best_disp = 0 best_cost = float('inf') for d in range(0, max_disp): cost = 0 for wy in range(-half, half + 1): for wx in range(-half, half + 1): left_val = left_img[y + wy, x + wx].astype(int) right_val = right_img[y + wy, x + wx - d].astype(int) cost += abs(left_val - right_val) if cost < best_cost: best_cost = cost best_disp = d disparity[y, x] = best_disp return disparity

这段代码是纯教学演示,真实场景不可能这样逐像素三重循环。它想表达的意思是:对左图的每个像素,在右图同一水平线上从左到右搜索一段距离,把每个候选位置的窗口差异算出来,取差异最小的那个作为匹配结果。窗口大小对匹配效果影响很大,窗口太小噪声大、误匹配多,窗口太大则物体边缘会被过度平滑,深度突变处丢失细节。一般5x5到11x11是比较常用的区间。

实际工程中,OpenCV的cv2.StereoSGBM_create是目前最实用的选择。它实现了SGBM算法,本质上是在局部匹配的基础上加入了平滑约束,要求相邻像素的视差尽量连续,同时允许在物体边缘处发生突变。这种“全局平滑+局部边缘保持”的思路,比纯局部匹配在弱纹理区域的表现好很多。

4.3 光照差异与纹理缺乏问题

立体匹配最怕两件事:左右图亮不一致、场景没纹理。

左右图亮度不一致,根源是两颗摄像头即使型号相同,感光元件和镜头的响应也存在差异,再加上光照角度不同,左右图相同区域亮度就是不一样。此时用SAD这类基于亮度的代价会大规模误匹配。解决办法通常是预处理,用cv2.equalizeHist做直方图均衡化,或者用图像梯度替代原始像素做匹配,对光照变化就鲁棒得多。项目里我会把原始图转成灰度后做一次归一化滤波,再去跑匹配。

纹理缺乏是另一个大坑。白墙、纯色地板这类区域,窗口里所有像素亮度都相同,无论怎么滑窗匹配,代价都差不多,算法只能靠猜。这就是深度相机在无纹理场景效果差的原因。工程上有几个应对手段:一是把窗口调大,增加匹配的“观察范围”,缺点是边缘被侵蚀;二是结合多个尺度做匹配;三是引入平滑约束让相邻像素互相“拉一把”。

4.4 视差图的后处理流程

SGBM输出的初始视差图通常很粗糙,直接转深度图会发现大片空洞和噪声,必须在后续流程中做清洗。我习惯的处理链路是:左右一致性检查、亚像素插值、中值滤波、空洞填充。

左右一致性检查是处理遮挡的关键一步。如果左图某个像素匹配到了右图的点,那么以右图那个点为起点,应该能在左图匹配回原来的像素。如果匹配不回去,说明这个像素是遮挡区域或者误匹配,直接把视差置为无效。这个检查能把边缘遮挡造成的大片错误视差干掉大半。

亚像素插值是为了提升深度精度。SGBM输出的是整像素视差,直接带入Z=fB/d,量化误差会非常大。BM算法里会自动计算亚像素值,SGBM里我在使用时会通过二次曲线插值,也就是对最优视差相邻的几个代价拟合一条抛物线,取极小值位置作为亚像素视差,这一下就能把深度精度提升不少。

5. 从视差到三维坐标:重建与精度控制

得到干净的视差图之后,测距只剩最后一步:把视差转成深度。根据公式Z=fB/d,逐像素计算深度值。这里有一个容易被忽略的点:深度和视差不是线性关系,同样的视差误差,在近距离和远距离造成的深度绝对误差完全不同。这也是双目测距“远距离精度差”的数学根源。

5.1 深度精度公式与误差传导

对Z=fB/d求微分,可以得到深度误差ΔZ与视差误差Δd的关系:

ΔZ = Z² / (fB) × Δd

这个公式解释了所有双目测距的精度之谜。深度误差和距离的平方成正比,距离越远,误差增长越快。举个例子,设f=800像素,B=120毫米,视差误差Δd=0.5像素。当Z=1米时,ΔZ = 1×1 / (800×0.12) × 0.5 ≈ 0.005米,即5毫米左右,这个精度相当好。当Z=5米时,ΔZ = 25 / 96 × 0.5 ≈ 0.13米,已经超过10厘米。当Z=10米时,ΔZ = 100 / 96 × 0.5 ≈ 0.52米,超过了半米。这就是为什么说双目适合中近距离测量,远距离误差会指数级膨胀。

理解了这条公式,也就明白了增益精度的基本思路。想提升远距离精度,可以增加基线B、提高焦距f、降低视差误差Δd。其中增加基线和焦距会改变视场角,需要综合考虑;降低视差误差主要靠算法和标定。工程上经常通过加大基线来提升远距离精度,但基线加大的代价是设备体积变大、近处重叠视场变小,在小型机器人上往往不可行。

5.2 利用Q矩阵实现三维重建

除了逐像素深度,很多场景需要的是物体的三维坐标。OpenCV在立体校正时生成了重投影矩阵Q,通过齐次变换可以直接把像素坐标和视差映射到三维空间:

# Q矩阵从 reprojectImageTo3D 直接生成三维点云 points_3d = cv2.reprojectImageTo3D(disparity, Q)

使用cv2.reprojectImageTo3D函数,传入视差图和Q矩阵,输出的是每个像素对应的三维坐标(X,Y,Z),单位与标定板时使用的标定板方格尺寸一致。如果你标定时以毫米为单位,输出的三维坐标就是毫米。这个函数会做齐次除法,是经过了规范化的实际三维坐标。我用它做点云生成和障碍物检测时,流程是同一套,效果稳定。

5.3 测量误差的来源和标定后的验证

搞清楚了误差公式,还需要理解误差来源究竟有哪些。我总结了一下,双目测距的误差来自四个层面:标定误差、匹配误差、几何误差、量化误差。

标定误差是指内外参和畸变参数不准确带来的系统性偏差,解决手段是做好标定数据筛选。匹配误差来自算法误匹配和遮挡,解决手段是立体匹配算法调参和后处理。几何误差来自镜头畸变校正残留和左右相机不平行,可以通过高精度标定来压制。量化误差是视差整像素离散化造成的,解决手段是亚像素插值。

标定完之后,一定要做距离验证实验。在1米、2米、3米、5米的位置放上已知尺寸的标定板或者特征物,实际测一遍深度值,画出误差曲线。我一般会多次测量取平均,比较均值和真实距离的偏差,把整个系统的实际精度摸清楚。如果实际精度和理论估算相差太大,就得回头查标定数据和匹配参数。

6. 双目测距的实际项目经验与避坑清单

前几节讲的都是原理和推导,这一节分享一些我在实际项目里踩坑踩出来的经验。这些经验往往不会写在论文里,但对工程成败影响极大。

6.1 硬件安装的三个细节

安装双目相机时,三个细节特别容易被忽略。一个是左右相机的朝向调整,安装时一定要把两个镜头的光轴尽量调平行,不要有很大的俯仰角或者偏转角。光轴不平行意味着极线偏差大,即使标定能校正一部分,残余误差会比理想状态大很多。

第二个细节是固定必须牢固。相机支架的微小松动,会直接导致外参漂移,标定完几周后就发现深度不准,回头检查往往是相机被碰歪了。我建议安装完成后用记号笔在固定件上画好对齐标记,每次开机前检查一遍位置有没有明显位移。

第三个细节是左右目图像的同步。如果相机支持硬件触发,务必用硬件触发对齐曝光时间。如果用的是两个独立的USB摄像头,无法硬件同步,项目范围就应限定在低速或静态场景中,不要让算法背同步问题的锅。这个判断对项目成败非常关键。

6.2 测距过程中被问最多的5个问题

遇到最多的一个问题是“为什么我标定完了,测出来的距离还是偏的”。多数情况是标定板图像质量不过关,或者拍摄姿态分布不合理,极线校正效果差。处理方法就是回到标定环节,严格筛选角点数据,尤其是把重投影误差大的图片清除。

第二个常见问题是“白墙和纯色地面测不出深度”。本质是无纹理区域匹配代价没有区分度,推荐方案是先做多尺度匹配,或者调整光照让场景产生更多纹理,实在不行就接受这个短板,靠后续算法对深度孔洞做填补。

第三个问题是“物体边缘深度值忽远忽近”。物体边缘视差计算时窗口跨越了前景和背景,导致匹配结果在两者之间跳动。解决思路是对深度图做边缘检测,深度突变处像素置为无效,或者使用带权重的窗口匹配让中心像素的权重更大。

第四个问题是“光线变化时深度图剧烈抖动”。这说明匹配代价对光照太敏感,建议改用梯度特征匹配,或者在预处理环节做好光线归一化,再做一次时序滤波让深度值平滑。

第五个问题是“SGBM跑起来帧率太低”。SGBM计算量很大,优化思路一般是缩小图像分辨率、限制最大视差范围、用CUDA加速。分辨率从1080p降到720p,速度能提升两倍以上,视差范围从128缩减到64,速度又能提升近一倍,这对实时性要求高的项目特别有效。

6.3 从算法到应用的完整链路

最后把整个双目测距链路串一遍,方便直接落地。拿一个机器人避障的小项目举例,完整的软件流程是:读取左右目图像流,如果相机支持硬件同步就直接读取;然后对图像做校正,这一步用标定阶段生成的映射表执行remap;接着预处理,必要时做直方图均衡化和滤波;然后跑立体匹配,输入校正后左右图,输出视差图;紧接着是视差后处理,包括一致性检查、中值滤波、空洞填充;最后用Q矩阵把视差转成三维点云,交给导航模块做障碍物检测和路径规划。

链路跑通之后,我通常会做一个可视化调试界面,把左右原图、校正图、视差图、深度图、点云都显示出来,再加上鼠标取点显示具体距离值。这个调试界面看着土,但在实际项目中救了我无数次,无论是标定参数调优还是算法问题排查,都比闷头看日志快得多。

关于双目测距,我最后的实操体会是:这套技术真正难的不是“跑通”,而是“跑稳”。跑通只需要搭好环境,调用几个OpenCV函数就行;跑稳需要你对每个环节的原理都门清,哪一步出了问题都能定位到根因。很多人觉得标定麻烦、匹配难调,其实这些都是可以系统性解决的,关键是要理解每一步背后的几何意义,不要盲调参数。如果这篇文章能帮你把双目测距的原理链条完整串起来,那我觉得这些字就没白写。

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

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

立即咨询