双目视觉测量原理与SGBM算法:从相机标定到三维尺寸测量
2026/9/19 8:05:58 网站建设 项目流程

简介:双目视觉测量原理与算法PPT讲义围绕计算机视觉中的三维定位问题,讲解如何基于两台相机与视差原理恢复物体空间坐标,重点面向学习三维视觉、摄影测量与几何算法的学生和工程师。资源共1个pptx文件,整包约1.15MB,以讲义形式系统呈现直线求交与最优解求解过程。内容以空间直线交点问题为切入点:理想情况下通过联立直线方程求交点;实际存在误差时,借助最小二乘算法寻找最佳位置点,并对比公垂线中点作为解的精度。通过AX-B残差判断,最小二乘所得点最大残差不超1.6mm,而公垂线中点最大残差达8.59mm,直观说明最小二乘在不相交直线求交问题上的优势。已有162人浏览/学习,适合希望深入理解双目视觉测量原理、直线拟合误差处理及测量精度评估的读者参考。

1. 双目视觉测量原理与算法:为什么两张图能算出真实尺寸

如果你手里只有一台单目相机,拍到的每个像素只代表一条射线的方向,深度信息在成像瞬间就丢掉了。而双目视觉测量做的事,就是模仿人的双眼——用两台固定相对位置的相机同时拍摄同一场景,利用两幅图像中同名点的像素偏移(即视差),通过三角几何关系反推出物体的三维坐标。它不需要结构光、不需要激光雷达,只要有两张同步图像和一个准确的几何模型,就能在毫米级精度下完成尺寸测量、位姿估计和三维重建。

这篇文章的核心脉络是这样的:先从成像几何讲清楚“视差为什么能换成深度”,再给出相机标定和极线校正的完整落地路径,接着深入立体匹配算法——尤其是工程上最常用的 SGBM 算法的参数原理与调优方法,最后落在测量精度验证与典型排错技巧上。适合刚接手视觉测量项目的工程师,也适合被标定精度或误匹配困扰的熟手。读完你应该能独立搭出一套可用的双目测量流程,并且知道每一个中间结果应该长什么样。

2. 双目测量原理:从成像几何到三角测量公式

2.1 针孔模型与坐标系变换:像素坐标是怎么来的

双目测量的底层数学是射影几何。我们先从单目说起。三维空间中一个点 (P=(X,Y,Z)) 在相机坐标系下,经过针孔投影落到图像平面上,其像素坐标 (p=(u,v)) 满足:

[ Z \begin{bmatrix} u \ v \ 1 \end{bmatrix} = \mathbf{K} \begin{bmatrix} X \ Y \ Z \end{bmatrix} = \begin{bmatrix} f_x & 0 & c_x \ 0 & f_y & c_y \ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} X \ Y \ Z \end{bmatrix} ]

这里的 (\mathbf{K}) 是内参矩阵,(f_x) 和 (f_y) 是焦距(以像素为单位,不是毫米),(c_x) 和 (c_y) 是主点偏移。这个式子说明:投影过程把三维空间的 3 个自由度压缩成了图像的 2 个自由度,深度 Z 被吸收进了分母——这正是单目无法直接测距的原因。

但如果你有两台相机,情况就不同了。假设左右相机已经完成极线校正,两个相机光轴平行、成像平面共面,那么同一点在两个图像上的投影只存在水平方向的偏移,这个偏移量就是视差 (d = u_l - u_r)。此时深度 Z 与视差 d 构成一个漂亮的反比关系:

[ Z = \frac{f \cdot B}{d} ]

其中 B 是基线(两相机光心之间的距离),f 是焦距。这个公式看似简单,但它包含了一个重要结论:视差越大的物体离你越近,视差为 0 意味着物体在无穷远。同时,深度精度与视差精度的关系是 (\Delta Z = -Z^2 / (fB) \cdot \Delta d)——距离越远,同样的视差误差带来的深度误差按平方放大。这就是为什么双目测量适合近距离(通常几米内),而远距离测量需要更大基线或改用激光方案。

2.2 极线约束与本质矩阵:给匹配加上几何枷锁

上面假设了理想平行结构,但真实的双目系统几乎不可能手工装到像素级对齐。左右相机之间存在一个旋转矩阵 (\mathbf{R}) 和平移向量 (\mathbf{t}),这个关系用本质矩阵 (\mathbf{E} = [\mathbf{t}]_\times \mathbf{R}) 描述。本质矩阵的核心约束是极线约束:左图上一点 (\mathbf{p}_l),它在右图上的匹配点 (\mathbf{p}_r) 不可能是全图任意位置,而必须落在一条直线上——这条线叫极线。

数学上写为:

[ \mathbf{p}_r^T \mathbf{F} \mathbf{p}_l = 0 ]

其中 (\mathbf{F} = \mathbf{K}_r^{-T} \mathbf{E} \mathbf{K}_l^{-1}) 是基础矩阵。这个约束的价值在于:它把二维搜索变成了一维搜索。不做极线约束,匹配需要在全图范围内找同名点,计算量是 (O(W \cdot H)) 的搜索窗口;做了极线约束,只需要沿极线方向扫描,计算量降到 (O(W))。这也是立体匹配算法能实时运行的前提。

但实际工程里,沿斜极线做匹配仍然麻烦——你需要在图像上做插值采样。所以有了立体校正(Stereo Rectification):对左右图分别做一个单应变换,让两幅图的极线变成水平的,且行对齐。这样同名点必然落在同一行上,视差退化为纯水平位移。OpenCV 里的stereoRectifyinitUndistortRectifyMap就是干这件事的。校正之后,左右图像的同一行对应的就是空间中的同一个极平面,匹配搜索从二维降到一维,速度和可靠性都有本质提升。

2.3 三角测量:视差到三维坐标的最终一步

匹配做完之后,每个像素有了一个视差值 d,接下来就是把视差图反投影回三维空间。在完成校正的理想坐标系下,左右相机的投影矩阵分别为:

[ P_l = \begin{bmatrix} f & 0 & c_x & 0 \ 0 & f & c_y & 0 \ 0 & 0 & 1 & 0 \end{bmatrix}, \quad P_r = \begin{bmatrix} f & 0 & c_x' & f \cdot B \ 0 & f & c_y & 0 \ 0 & 0 & 1 & 0 \end{bmatrix} ]

给定左右匹配点 ((u_l, v_l)) 和 ((u_r, v_r)),可以通过线性最小二乘解得三维坐标。OpenCV 中cv::triangulatePoints接受两个投影矩阵和匹配点对,输出齐次坐标后再除以第四个分量得到 ((X, Y, Z))。

这一步看起来只是数学代换,但工程上有两个关键细节。第一,左右投影矩阵必须来自同一套标定结果,混用不同标定板次数的参数会导致三维坐标系统性偏移。第二,齐次坐标除法对数值精度敏感,如果匹配点坐标不是浮点型,三角测量结果会明显抖动。我一般会把匹配点坐标先转成float,并用cv::undistortPoints做一次亚像素精炼,再送进三角测量。

提示:不要用reprojectImageTo3D的返回值直接做高精度测量。这个函数假设校正后的 Q 矩阵完全精确,但实际标定总有残差,用它做定性展示可以,做定量测量需要逐点三角测量或至少做一次系统误差补偿。

3. 相机标定与极线校正:高精度测量的前置条件

3.1 标定板拍摄规范与内参求解

双目视觉测量的精度上限由标定决定——匹配算法做得再好,标定误差也会直接写进三维坐标。所以这一步值得花最多时间。常见做法是用棋盘格或圆点标定板,通过 OpenCV 的cv::calibrateCamera分别求解左右相机内参和畸变系数,再用cv::stereoCalibrate求解双目的相对位姿。

拍摄标定板时有一个容易被忽视的规则:标定板必须占据画面约 1/4 到 1/2 的面积,并且覆盖不同的空间姿态。只拍正对相机的平面姿态,会在求解焦距时产生退化,得到的 (f_x) 可能偏差几个像素——这直接导致深度误差。我一般要求采集 20 到 30 组图像对,覆盖以下姿态:

  • 标定板平面与相机光轴夹角在 (15^\circ) 到 (60^\circ) 之间变化
  • 标定板出现在画面的四个角落和中心区域
  • 距离从最近工作距离到最远工作距离均匀分布
  • 绕光轴旋转标定板,避免所有角点落在同一行

采集时使用cv::findChessboardCorners检测角点,再调用cv::cornerSubPix获取亚像素坐标。注意亚像素精炼的窗口尺寸和迭代次数要设得足够:窗口Size(11, 11)Size(15, 15),终止条件用TermCriteria::EPS + TermCriteria::COUNTmaxCount设 30。

3.2 stereoCalibrate 与立体校正:参数列表及含义

单目标定完成后,用cv::stereoCalibrate求解右相机相对左相机的旋转 (\mathbf{R}) 和平移 (\mathbf{t})。这个函数的输入要传单目内参和畸变系数作为初始值,标志位设为CALIB_FIX_INTRINSIC,这样它只优化外参,内参不再被扰动。完整的批处理脚本如下:

import cv2 import numpy as np # 假设 objpoints_l/r, imgpoints_l/r 已经通过 findChessboardCorners 收集完毕 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 1e-6) flags = cv2.CALIB_FIX_INTRINSIC ret, K1, D1, K2, D2, R, T, E, F = cv2.stereoCalibrate( objpoints_l, imgpoints_l, imgpoints_r, K1_init, D1_init, K2_init, D2_init, (width, height), criteria=criteria, flags=flags ) # 立体校正:得到左右校正映射表 R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify( K1, D1, K2, D2, (width, height), R, T, alpha=0 ) map1_l, map2_l = cv2.initUndistortRectifyMap( K1, D1, R1, P1, (width, height), cv2.CV_32FC1 ) map1_r, map2_r = cv2.initUndistortRectifyMap( K2, D2, R2, P2, (width, height), cv2.CV_32FC1 )

参数说明:alpha=0表示校正后图像只保留有效像素区域,边缘会被裁掉,适合测量用途;如果希望保留全视野做可视化,可以调大到 0.5 或 1。P1P2是校正后的投影矩阵,直接用P1[0][2]P2[0][2]计算新的主点偏移,也可以算出校正后基线的精确值。

校正结果必须做质量验证,而不是看一眼重投影误差就收工。我用三个指标判断校正是否合格:

指标接受标准说明
stereoCalibrate 返回的 RMS< 0.25 像素整体重投影残差
行对齐误差< 0.5 像素取多对校正图同名点的 y 坐标差
视差范围合理性与实际场景深度匹配最近/最远物体视差在预期区间

3.3 标定常见失败现象与补救方法

标定过程中最典型的失败是 RMS 降到 0.1 像素以下,但实际测量仍然偏得离谱。造成这种情况的原因通常是:参与立体标定的图像对数量不足,或姿态过于集中在某一区域。RMS 只能反映参与标定的图像的重投影误差,不能反映外参推广到新视角时的精度。解决办法是增加 10 组以上不同距离和角度的图对,重跑一次 stereoCalibrate。

另一个高发问题是校正后图像行对齐误差在图像两侧明显偏大,中间几乎为 0。这往往说明镜头畸变模型没有完全拟合——尤其是大广角镜头,径向畸变的高阶项((k_3))没有参与优化。把标志位从CALIB_FIX_INTRINSIC改为移除CALIB_RATIONAL_MODEL的限制,让k_3参与求解,通常能改善两侧误差。

提示:每次标定后保存K1, D1, K2, D2, R, T为独立文件,并在文件名中标注拍摄日期和相机编号。双目系统的机械结构即使微小松动,标定参数也会失效。如果项目运行几周后测量偏差增大,重新标定前先检查机械固定的稳定性。

4. 立体匹配与 SGBM 算法:从匹配代价到视差图的完整链路

4.1 匹配代价计算:像素相似性怎么度量

立体匹配的目标是为左图每个像素在右图同一行上找到最相似的点。相似性的度量方式多种多样,工程上常用的有 SAD(绝对差之和)、SSD(平方差之和)和 Census 变换。SAD 和 SSD 对光照变化敏感——左右相机即使做了白平衡校准,也可能因为曝光差异导致同一物体表面灰度不同。Census 变换对这类单调灰度变化有天然的鲁棒性:它把窗口内的每个像素与中心像素比较,生成一个二进制串,然后用汉明距离度量相似度。

SGBM(Semi-Global Block Matching)算法在 OpenCV 中的实现,可以在StereoSGBMCreate中通过mode参数选择匹配代价方式:StereoSGBM::MODE_SGBM使用 Birchfield-Tomasi 代价,它对亚像素采样导致的灰度差异不敏感,是首选;MODE_HH则使用更完整的全局能量优化,效果更好但耗时大幅增加。对实时测量系统,MODE_SGBM是性能和质量的平衡点。

4.2 全局能量最小化与代价聚合:SGM 的根本思路

SGBM 的核心是 SGM(Semi-Global Matching)的能量函数。它不把每个像素看成孤立的匹配问题,而是认为视差图应该满足平滑性约束——相邻像素的视差通常接近,只有在物体边缘才会发生跳变。这个先验写成能量函数:

[ E(D) = \sum_p C(p, D_p) + \sum_q P_1 \cdot [|D_p - D_q| = 1] + \sum_q P_2 \cdot [|D_p - D_q| > 1] ]

第一项是匹配代价,第二项惩罚相邻像素视差相差 1 的情况,第三项惩罚视差跳变(对应物体边界)。(P_2 > P_1) 意味着算法容忍小的视差渐变,但强烈抑制深度不连续。二维能量最小化是 NP-hard 问题,SGM 的做法是把问题分解为沿 8 个或 16 个方向的动态规划扫描线,每个方向独立求解,最后取平均。这就是“半全局”的含义——不是全局优化,但比纯局部窗口方法远远更鲁棒。

每个方向的代价递推公式是:

[ L_r(p, d) = C(p, d) + \min \left( L_r(p-r, d), \quad L_r(p-r, d-1) + P_1, \quad L_r(p-r, d+1) + P_1, \quad \min_i L_r(p-r, i) + P_2 \right) - \min_k L_r(p-r, k) ]

最后一项减掉当前方向最小代价,是为了防止代价累积无限增大。理解这个公式对调参的帮助极大——P1P2不是玄学,它们直接控制视差图的平滑程度和对边缘的保持能力。

4.3 OpenCV SGBM 参数详解与调参路径

OpenCV 中创建 SGBM 的代码以及每个参数的落地经验如下:

cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create( minDisparity, // 最小视差,通常为 0 numDisparities, // 视差范围,必须是 16 的倍数 blockSize, // 匹配窗口大小,建议 5~11 之间的奇数 P1, // 视差变化 1 的惩罚项 P2, // 视差跳变惩罚项 disp12MaxDiff, // 左右一致性检查的最大允许差异 preFilterCap, // 代价计算前的截断值 uniquenessRatio, // 唯一性比例 speckleWindowSize, // 去除斑点噪声的窗口大小 speckleRange, // 斑点内允许的视差变化 mode // MODE_SGBM 或 MODE_HH );

参数设置的核心原则是先粗后细。第一步把numDisparities设大(比如工作距离内物体视差范围的两倍),blockSize设 9,跑通流程后再逐步收紧。P1P2的经验公式是:

  • P1 = 8 * channels * blockSize^2
  • P2 = 32 * channels * blockSize^2

P2可以按需调大(比如 4 倍),这样能进一步抑制纹理稀疏区域的视差噪声,代价是物体边缘的轮廓会被磨平。uniquenessRatio是一个关键参数:它要求最小代价比次小代价小至少该比例才接受当前视差,默认 5 到 10 即可,太低会产生大量误匹配,太高会导致大面积空洞。

disp12MaxDiff设置左右一致性检查的阈值,这是去除遮挡区域错误匹配的最有效手段。speckleWindowSizespeckleRange用于滤除孤立的小块噪声区域,前者设为 50 到 200,后者设 1 或 2。一次常用的参数组合是:

cv::Ptr<cv::StereoSGBM> sgbm = cv::StereoSGBM::create( 0, 96, 9, 8 * 1 * 9 * 9, 32 * 1 * 9 * 9, 1, 63, 10, 100, 2, cv::StereoSGBM::MODE_SGBM );

4.4 视差后处理:一致性检查与亚像素插值

直接输出的视差图往往包含三类问题:遮挡区域的错误匹配、纹理稀疏区域的噪声、以及视差量化误差。遮挡区域因为一个像素在另一幅图中不可见,任何匹配算法都会给出错误结果,只能靠左右一致性检查剔除——即从左图算一次视差,再从右图算一次,两者的差异超过阈值就置为无效。

亚像素精度对测量至关重要。SGBM 输出的视差是整像素,直接代入 (Z = fB/d) 计算,在 Z = 1000 mm 处一个像素的视差误差可能带来数毫米的深度误差。常见做法是对最优视差附近的匹配代价做抛物线拟合,得到亚像素偏移:

# 对每个像素,取 d-1, d, d+1 处的代价做抛物线拟合 # 亚像素偏移 = (C(d-1) - C(d+1)) / (2 * (C(d-1) - 2*C(d) + C(d+1)))

这个公式来自抛物线顶点求解,工程上可以直接用。注意它只在代价曲线接近抛物线时有效,preFilterCap太小会导致代价曲线出现平台区,拟合偏差大。

提示:OpenCV 还提供cv::createDisparityWLSFilter做加权最小二乘后处理,利用左图的边缘信息引导视差平滑,能明显改善物体边缘的视差质量,是 QUALITY 优先场景下值得加的一步。它需要左图的置信度图和右视差图作为输入,代码量不大,但计算耗时大约会翻倍。

5. 三维重建与尺寸测量:从视差图到毫米级坐标

5.1 视差到深度映射:reprojectImageTo3D 与 Q 矩阵的局限

OpenCV 提供了一条快捷路径把视差图转成三维点云:cv::reprojectImageTo3D(disparity, xyz, Q, handleMissingValues=true)。这里的 Q 矩阵来自stereoRectify,形式为:

[ Q = \begin{bmatrix} 1 & 0 & 0 & -c_x \ 0 & 1 & 0 & -c_y \ 0 & 0 & 0 & f \ 0 & 0 & -1/T_x & (c_x - c_x')/T_x \end{bmatrix} ]

已知某点像素坐标 (u, v) 和视差 d,三维坐标通过 (Q \cdot [u, v, d, 1]^T) 得到。这条路径适合密度优先的场景,比如生成点云做可视化或初步的平面分割。但对高精度尺寸测量,它有明显局限:Q 矩阵把视差到深度的映射固化为线性模型,而实际镜头畸变和校正残差会引入系统性偏差。做亚毫米级测量时,我习惯用逐点三角测量——对每个像素匹配到浮点坐标后,用cv::triangulatePoints求三维点。

对应代码片段如下:

std::vector<cv::Point2f> pts_l, pts_r; // 收集匹配点对(亚像素坐标) cv::Mat pts4D; cv::triangulatePoints(P1, P2, pts_l, pts_r, pts4D); // 齐次坐标转三维 for (int i = 0; i < pts4D.cols; ++i) { cv::Mat col = pts4D.col(i); col /= col.at<double>(3, 0); double X = col.at<double>(0, 0); double Y = col.at<double>(1, 0); double Z = col.at<double>(2, 0); }

三角测量把左右投影矩阵直接作为输入,对校正残差更透明,也方便逐点评估每个点的测量置信度。

5.2 测量场景中的视差空洞修复与边缘处理

实际测量中最让人头疼的不是算法本身,而是视差图的空洞——黑色像素出现在高光表面、透明物体和遮挡边界。测量一个工件的长度时,如果边缘有空洞,边缘提取就不可靠。

常见的补救顺序是:先做形态学闭运算填补细小空洞,再做中值滤波去除孤立噪点,最后用最近邻插值处理剩余空洞。但我要提醒一个反直觉的点:闭运算会同时磨掉边缘的真实位置,导致尺寸测量偏小。如果测量目标是物体长度或直径,不要急着填洞,先在原视差图上提取边缘点,再用边缘点拟合直线或圆。这样即使边缘附近有几个像素的空洞,拟合结果仍然稳健。

如果空洞规模大(比如大面积高光),就该回到采集端解决:调整光源角度或偏振片,比任何后处理算法都有效。算法永远补不回来采集阶段丢失的信息。

5.3 测量精度验证方法:用标准量块做端到端评估

完成整个流程后,必须做端到端的精度验证。方法是用已知尺寸的标准量块(比如 50.000 mm 的量块)放在视野内不同位置拍摄,跑完整流程测量长度,统计偏差和重复性。这比单独看视差图正确率更有说服力,因为它覆盖了标定、校正、匹配、后处理全链路。

验证时我按以下步骤操作。第一步,固定量块位置拍摄 10 帧,测量结果的标准差代表系统的随机误差,应小于 0.02 mm。第二步,把量块移动到视野角落重复测量,与中心结果的差异代表系统的场曲误差,这个数通常比随机误差大一个数量级,是最需要关注的指标。第三步,改变量块距离(在标定工作范围内),测量误差随距离的变化趋势——如果误差随距离线性增长,说明基线或焦距标定存在系统性偏差。

这组数据应当写入测量系统的验收文档,任何算法参数的修改(比如调大P2)后都要重新跑一遍,确保没有牺牲精度去换视觉效果。

6. 边界条件与排错技巧:让测量系统在真实场景稳定工作

在实际项目中,双目测量的失效模式比成功模式更多。把最常见的场景约束和对应的解决技巧列在这里,能让你少走几个月的弯路。

光照强度与纹理不足。SGBM 依赖图像中的灰度纹理。纯色平面(比如白墙、未喷砂的金属件)在左右相机中几乎没有任何可区分的特征,匹配代价在所有视差上都很接近,输出的视差图会充满随机噪声。常见处理手段是投影随机纹理(speckle pattern),或使用辅助光源制造阴影纹理。如果只能在自然光下工作,把uniquenessRatio调高到 15 以上能减少部分误匹配,但代价是空洞变多——两害相权取其轻。

曝光与增益差异。左右相机如果使用自动曝光,同一物体的灰度在左右图中会有显著差异。这不仅影响 SAD/SSD 代价,也会让 Birchfield-Tomasi 代价的边缘响应退化。强制将左右相机设为相同的固定曝光和增益参数,比任何算法层的光照归一化都可靠。如果固定曝光后整体偏暗,优先增加光源亮度而不是提高 ISO。

基线选择与测量精度匹配。有很多项目只在算法层下功夫却没有意识到:基线 B 已经决定了测量精度上限。在 Z = 500 mm、f = 1200 像素、视差精度 0.1 像素的条件下,基线 60 mm 的深度精度约为 0.35 mm,基线 120 mm 则能达到 0.17 mm。增大基线能直接提升精度,但代价是近处物体的共同视野变窄。选基线时,要同时满足最近工作距离下的视野重叠和最远工作距离下的精度需求,并在采集前用三角公式算一笔账。

温度漂移与机械热胀冷缩。双目系统的机械结构在温度变化下会产生微米级的形变。对工业环境(-5℃ 到 45℃ 日温差)中的测量系统,铝合金支架的热胀冷缩足以让标定参数在一个工作日内失效。若项目对精度要求高,使用殷钢等低膨胀系数的材料做相机支架,或者在高低温环境下分别做一次标定,运行时按温度插值使用两套参数。

遮挡边界的处理策略。物体边缘在左图可见但在右图中被物体自身遮挡,这是双目测量的固有盲区。不要试图用后处理填平,而是在测量算法中只采样视差图内部的可靠区域,用几何拟合代替逐点提取。测量圆形工件的直径时,取圆心附近一个圆环区域内的三维点做圆拟合,比直接提取边缘点稳健得多。

验证系统是否稳定的一个实用技巧:用同一组标定参数在连续一周内每天拍摄同一量块,记录测量均值和极差。如果日均值漂移超过随机误差的三倍,说明系统存在机械或热稳定性问题,算法调参无法解决。做一次这样简单的长期稳定性测试,能帮你在部署前发现大多数靠临时调参解决不了的隐患。

本文还有配套的精品资源,点击获取

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

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

立即咨询