ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Python+OpenCV的双目立体视觉测距:原理、标定与SGBM实战

基于Python+OpenCV的双目立体视觉测距:原理、标定与SGBM实战

简介:面向人工智能、通信工程、自动化、电子信息、物联网等专业的在校学生、教师与企业开发者,这份基于Python和OpenCV的双目立体视觉项目,完整实现了图像匹配、视差计算与距离测量流程,可直接支撑毕业设计、课程设计、期末大作业或项目初期立项演示。项目已通过导师指导认可,答辩评审分达95分,源码附带详细注释,新手也能看懂并快速部署。压缩包共164个文件、约96.25MB,主要包含38个Python源码、10个UI界面文件、论文文档与数据库脚本,并配有bmp/jpg等多种格式图像样本用于算法测试,目录结构清晰。目前已有190人学习使用,系统功能完善、界面美观、操作简便,具备较高的实际应用价值。下载后可直接运行调试,也可基于现有代码扩展二次开发,是计算机相关专业学生高分完成项目的不错参考。

1. 双目立体视觉图像匹配与测距:为什么 Python + OpenCV 比深度相机更适合做测距项目

做机器人避障、做物料分拣、做自动驾驶小车,只要牵扯到"让机器知道目标物离自己多远",很多人第一反应是买一台 D435i 深度相机。但深度相机是黑匣子,论文里写不清原理,答辩时讲不透误差来源,换个光照环境还可能直接翻车。基于 Python + OpenCV 开发的双目立体视觉图像匹配与测距,每一步都有公式可查、有中间结果可调、有现成函数能复现,做出来既能在实测里跑通,也能在论文里讲透。

这个方向适合三类人:一是毕设要做视觉测距、需要完整源码和理论支撑的同学;二是想在机器人小车或安防项目里加一个低成本测距模块的工程师;三是被单目测距的平面标定折腾烦了、想换个稳定方案的人。整条链路就四步:标定拿到相机内外参,极线校正让左右图严格行对齐,SGBM 匹配得到视差图,三角测量把视差换算成距离。难点不在最后的除法,而在中间的匹配质量。

下面按我自己的落地顺序讲,每个环节都给能直接跑的 Python 代码和参数说明。跟完你至少能搭出一个在 0.5~5 米范围内误差小于 5% 的双目测距原型,也会知道哪些网上抄来的 SGBM 参数会让视差图彻底花屏。先别急着装包,从原理和选型说起。

2. 双目测距原理与相机选型:先想清楚视差公式,再决定硬件怎么摆

2.1 对极几何与视差:为什么两个摄像头能算出距离

双目测距的原理一句话就能讲清:同一个空间点,在左右两张图里的像素位置不一样,这个横向偏移就是视差(disparity)。视差越大,物体离相机越近;视差越小,物体越远。人眼其实也是这么工作的,只不过大脑把视差换算成了深度感知。超声波的方案一次只能拿到一个点的回波,而双目一次能拿到整幅画面的深度信息,这就是它更适合做视觉感知的原因。

设基线 B 是两个相机光心的水平距离,f 是焦距(像素单位),d 是视差(像素单位),深度 Z 满足 Z = f × B / d。这里 f × B 对一套固定安装的相机来说是常数,所以深度和视差成反比。在 OpenCV 里可以落到两种实现:一种是直接用公式逐点算,另一种是构造 Q 矩阵后调用 reprojectImageTo3D 把整张视差图重投影成三维点云。两者数学本质一样,后者还能顺手拿到每个点的 X、Y 坐标。

理解这个公式还有一个实际价值:它决定了项目的误差分布。视差 d 在分母上,所以近距离视差大、误差小,远距离视差小、误差被放大。很多人到 10 米外测距失败,不是代码错了,而是物理上就不允许。这个边界我会在第 4 章量化,先记在心里:双目测距的黄金范围是基线长度的 20~50 倍。

2.2 相机选型与硬件摆放:基线、焦距、分辨率的取舍

对毕设和中小型项目来说,最常见做法是用两个普通 USB 摄像头自己固定在铝型材或 3D 打印支架上。省钱、灵活,但要求两个摄像头型号一致,最好是同一批次,否则色彩响应和畸变差异会让后面的匹配很痛苦。另一种是买成品的双目工业模组,出厂做过硬件同步和标定,省事,但价格高不少,而且内参往往锁死,想重新标定反而麻烦。

表格对比三种常见方案:

方案优点缺点适合场景
两个 USB 摄像头自组装成本低、参数可换、原理透明无硬件同步、需自己固定和标定毕设、实验室原型
成品双目模组出厂硬同步、自带标定贵、内参封闭、资料参差产品原型、户外场景
单相机平移拍摄只有一个相机,先天一致只能拍静态场景,无法实时算法验证、标定学习

选型时要搭配着看三个参数。基线 B 越大,相同距离下视差越大、误差越小,但两相机视野交叠区域变小,太近的物体会出画;B 太小,近距离可以,远距离的视差分辨率不够。我的经验是室内桌面场景选 60~100mm,机器人避障选 100~200mm,户外远距离至少要 300mm 以上。焦距决定视场角,广角镜头看得宽,但单位像素对应的视差精度低。分辨率同理,2560×720 的横向分辨率明显比 1280×480 利于匹配,代价是计算量上涨。还有一条硬性要求:摄像头支持手动曝光的话,务必关掉自动曝光和自动白平衡,这是第 5 章要展开的大坑。

2.3 相机标定:棋盘格采图与 calibrateCamera 的完整流程

标定的目的是拿三样东西:左右相机各自的内参和畸变系数,以及两相机之间的旋转矩阵 R 和平移向量 T。没有 T 就不知道基线关系,没有内参就无法去畸变,后面的极线校正和匹配全都无从谈起。标定板最常见的是打印的棋盘格,贴在硬纸板上。打印时选择高对比度、表面尽量平整,格边长一定要用尺子量准,因为它是后续所有距离数值的单位基准。

我的采图习惯是左右各拍 20~25 对,覆盖画面的九个区域,也就是四角、四边中点、中心各来几张,每张里棋盘格要有不同的倾斜角度,避免所有图片都像正面证件照。采集时棋盘格要占画面四分之一以上,并且左右图里都必须完整可见。下面是完整的角点提取和双目标定代码:

import cv2 import numpy as np import glob CHESS_BOARD_SIZE = (8, 6) # 内角点数,对应打印 10x7 的棋盘 SQUARE_SIZE = 25.0 # 格边长,单位 mm,务必用尺子实测 objp = np.zeros((CHESS_BOARD_SIZE[0] * CHESS_BOARD_SIZE[1], 3), np.float32) objp[:, :2] = np.mgrid[0:CHESS_BOARD_SIZE[0], 0:CHESS_BOARD_SIZE[1]].T.reshape(-1, 2) objp *= SQUARE_SIZE obj_points, left_points, right_points = [], [], [] for f in sorted(glob.glob('calib/left_*.jpg')): img_l = cv2.imread(f) img_r = cv2.imread(f.replace('left_', 'right_')) gray_l = cv2.cvtColor(img_l, cv2.COLOR_BGR2GRAY) gray_r = cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY) ret_l, corners_l = cv2.findChessboardCorners(gray_l, CHESS_BOARD_SIZE, None) ret_r, corners_r = cv2.findChessboardCorners(gray_r, CHESS_BOARD_SIZE, None) if not (ret_l and ret_r): print('跳过', f) # 有一张图看不到完整棋盘就整对跳过 continue # 亚像素细化:把角点精度从像素级提升到亚像素级,这是低重投影误差的关键 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_l = cv2.cornerSubPix(gray_l, corners_l, (11, 11), (-1, -1), criteria) corners_r = cv2.cornerSubPix(gray_r, corners_r, (11, 11), (-1, -1), criteria) obj_points.append(objp) left_points.append(corners_l) right_points.append(corners_r) # 单目标定:先单独得到左右相机的内参和畸变 ret_l, mtx_l, dist_l, _, _ = cv2.calibrateCamera( obj_points, left_points, gray_l.shape[::-1], None, None) ret_r, mtx_r, dist_r, _, _ = cv2.calibrateCamera( obj_points, right_points, gray_r.shape[::-1], None, None) # 双目标定:固定内参为单目标定结果,只优化两相机之间的 R 和 T flags = cv2.CALIB_FIX_INTRINSIC ret, mtx_l, dist_l, mtx_r, dist_r, R, T, E, F = cv2.stereoCalibrate( obj_points, left_points, right_points, mtx_l, dist_l, mtx_r, dist_r, gray_l.shape[::-1], criteria=(cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 100, 1e-5), flags=flags) print('双目重投影误差(像素):', ret) print('基线向量 T:', T.ravel())

两个参数值得说明。第一个是 CALIB_FIX_INTRINSIC,意思是固定单目标定得到的内参,只优化 R 和 T,这样收敛快、结果稳定;如果两个摄像头差异较大,可以去掉这个 flag,让双目标定顺手把内参微调一遍。第二个是 cornerSubPix 的 (11, 11) 窗口,窗口越大角点越平滑,但靠近棋盘边缘时容易被邻居干扰,11 是折中值。

标定完一定要看 stereoCalibrate 返回的重投影误差 ret。我的经验:小于 0.3 像素是优秀,0.3~0.5 可用,超过 0.5 像素就建议重拍。重拍时优先补两类图:画面边缘的斜放棋盘,以及左右图里棋盘倾斜方向相反的组。误差大的另一个常见原因是标定板本身不平,打印纸贴在弯曲的纸箱上,角点坐标就是歪的,后面怎么优化都救不回来。

3. 立体校正与 SGBM 匹配:把左右图变成一张可靠的视差图

3.1 为什么选 SGBM:BM、SGBM 和深度学习的取舍

拿到内外参之后,下一步是把左右图校正成行对齐的状态,再用匹配算法逐像素找对应点。OpenCV 里的匹配器大致分三个档次,选型理由很实际。

BM(Block Matching)是固定窗口的 SAD(绝对差值和)搜索,速度快,但只在纹理丰富的区域靠谱,低纹理区域经常出现条状噪声,适合实时性要求极高、精度不敏感的场景。SGBM(Semi-Global Block Matching,半全局块匹配)在 BM 基础上引入了多个方向的代价聚合,利用相邻像素的平滑性约束,精度和速度最均衡,是 CPU 上做实时或近实时测距的第一选择。再往上就是深度学习立体匹配网络,精度高,但需要 GPU 或较强算力,且训练数据多来自合成数据集,换成自己的摄像头后效果常常打折。工业上常见的 Halcon 块匹配和 OpenCV 的 SGBM 思路同源,SGBM 免费、可控、参数透明,对毕设来说足够把原理讲清楚。

3.2 立体校正:stereoRectify 与 remap 完整代码

立体校正是整套流程里最容易被跳过的环节。不校正也能算视差,但匹配要在二维平面上搜索,既慢又容易错;校正之后,同一个点在左右图上严格落在同一行,匹配变成一维搜索,SGBM 才能高效工作,这也是"极线约束"的实际含义。OpenCV 的做法是用 stereoRectify 得到校正映射表,再用 remap 重映射每一帧:

import cv2 import numpy as np # mtx_l / dist_l / mtx_r / dist_r / R / T 来自双目标定 image_size = (640, 480) # 图像的 (宽, 高),和你实际分辨率一致 # 计算左右相机的校正旋转矩阵 R1/R2 和投影矩阵 P1/P2,Q 用于后续转三维坐标 R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify( mtx_l, dist_l, mtx_r, dist_r, image_size, R, T, flags=cv2.CALIB_ZERO_DISPARITY, alpha=0) # 生成重映射表;CV_32FC1 保留亚像素坐标精度,别用 CV_16SC2 省这点内存 map1_l, map2_l = cv2.initUndistortRectifyMap( mtx_l, dist_l, R1, P1, image_size, cv2.CV_32FC1) map1_r, map2_r = cv2.initUndistortRectifyMap( mtx_r, dist_r, R2, P2, image_size, cv2.CV_32FC1) # 每帧执行重映射就是极线校正,之后左右图行对齐 rect_l = cv2.remap(frame_l, map1_l, map2_l, cv2.INTER_LINEAR) rect_r = cv2.remap(frame_r, map1_r, map2_r, cv2.INTER_LINEAR)

这里三个参数要知道在干什么。alpha=0 表示对校正后的图像做最大程度裁剪,去掉无效区域,画面最干净;alpha=1 会保留全部原始像素,但四周出现黑边,调试时容易干扰判断。Q 矩阵是 4×4 的视差到三维坐标转换矩阵,第 4 章测距会用到。remap 的插值选 INTER_LINEAR 就够,换成更贵的插值对匹配结果几乎没有提升。

校正效果怎么验证?把左右图水平拼接后人工目检,找一条明显的竖直边缘,比如门框、桌沿,看它在左右图里是不是落在同一行。更定量的做法是画一条水平扫描线,比较同一行左右图的灰度跳变位置。如果明显错位,先回标定环节检查,别急着调 SGBM。

3.3 SGBM 参数逐个拆解:numDisparities、blockSize、P1/P2 的调优逻辑

SGBM 参数看起来多,真正影响结果的就五个。先看一段能直接用的创建代码:

# OpenCV 4.x 的 SGBM 创建方式 stereo = cv2.StereoSGBM_create( minDisparity=0, numDisparities=64, # 必须是 16 的倍数 blockSize=11, # 必须是奇数,建议 3~11 P1=8 * 3 * 11 * 11, # 相邻像素视差变化 1 个单位的惩罚 P2=32 * 3 * 11 * 11, # 相邻像素视差变化超过 1 的惩罚 disp12MaxDiff=1, # 左右一致性检查阈值 uniquenessRatio=10, # 匹配唯一性比例 speckleWindowSize=100, # 滤除小斑点噪声 speckleRange=2, mode=cv2.STEREO_SGBM_MODE_SGBM ) disparity = stereo.compute(rect_l, rect_r).astype(np.float32) / 16.0

注意最后一行:SGBM 输出的是定点数,真实视差值必须除以 16.0。这一步最容易被忽略,直接拿原始值算距离会系统性偏大 16 倍,是新手翻车排行榜第一名。

逐项拆解参数。numDisparities 决定视差搜索范围,范围越大能测越近的物体,一般从 64 起步,如果近处目标区域出现匹配不上的黑色空洞,就加大到 96、128,但计算量同步上涨,远处的无效匹配也会变多。blockSize 是匹配窗口边长,窗口越大低纹理区域的空洞越少,但边缘越糊、细节越差,建议在 3~11 之间试,不要超过 15。P1 和 P2 是平滑惩罚项,P2 一般取 P1 的 4 倍;它们越大视差图越平滑,同时会把细小物体的边缘抹掉。我一般从 P1=83blockSize^2、P2=323blockSize^2 起步,噪声明显就两倍两倍往上加。uniquenessRatio 是匹配唯一性门槛,太低会产生大量误匹配,太高会让低纹理区域变成黑洞,10~15 是常见区间。

调参时建议固定一个调试界面:左边显示原图,右边用热力图显示视差图。每次只改一个参数,跑同一段测试视频对比,别凭感觉一次改三个。顺序上先调 numDisparities 到目标距离内没有大块黑洞,再调 blockSize 平衡空洞和边缘,最后动 P1/P2 处理噪声。

4. 从视差图到距离:三角测量代码、后处理细节与误差边界

4.1 视差图后处理:WLS 滤波与空洞填充

SGBM 直接输出的视差图通常有两方面毛病:一是低纹理区域出现黑斑,也就是视差为 0 的无效像素;二是物体边缘有椒盐式噪声。对测距来说,单帧的坑点位置不稳定,直接取均值也会被噪声带偏,所以要先做后处理。OpenCV contrib 模块的 WLS 滤波是常见做法,它用左图做引导图,把彩色图像的边缘信息传递到视差图,能在保留物体边缘的同时抹平大片低频噪声:

# 注意:要装 opencv-contrib-python,不能和 opencv-python 并存 left_matcher = cv2.StereoSGBM_create( numDisparities=64, blockSize=11, P1=8 * 3 * 11 * 11, P2=32 * 3 * 11 * 11) # WLS 需要左右两张视差图,先创建与左匹配器参数完全一致的右匹配器 right_matcher = cv2.ximgproc.createRightMatcher(left_matcher) wls = cv2.ximgproc.createDisparityWLSFilter(left_matcher) wls.setLambda(8000) # 平滑强度,越大越平滑 wls.setSigmaColor(1.5) # 引导图颜色权重 disp_left = left_matcher.compute(rect_l, rect_r) disp_right = right_matcher.compute(rect_r, rect_l) disp_wls = wls.filter(disp_left, rect_l, disp_right) # 空洞填充:对仍为 0 的无效像素做形态学修补 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) disp_filled = cv2.morphologyEx(disp_wls, cv2.MORPH_CLOSE, kernel)

setLambda 是平滑力度,8000 是常见起点:纹理丰富的场景降到 3000 保留更多细节,墙面占比大的场景要加到 15000。有一点必须说清楚:MORPH_CLOSE 对空洞的填充只是视觉补偿,填补出来的值不代表真实匹配。如果你的测距目标恰好落在空洞区域,正确的做法是判定为"当前不可测",而不是拿修补值硬算一个距离出来,这个原则在写论文结论时也要讲,反而加分。

4.2 三角测量:鼠标点击取视差算出距离

后处理完就能测距了。常见做法有两种:用 reprojectImageTo3D 把整张视差图重投影成三维点云,或者用鼠标事件取单点视差直接套公式。前者适合批量测距,后者适合交互调试,两个都写:

import cv2 import numpy as np # 方法一:Q 矩阵重投影,得到每个像素的三维坐标(X, Y, Z) points_3d = cv2.reprojectImageTo3D(disp_filled, Q, handleMissingValues=True) Z_map = points_3d[:, :, 2] # 单位由标定板格边长决定,毫米就全程毫米 # 方法二:鼠标点击取单点距离,用于交互验证 def on_mouse(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: d = disp_filled[y, x] / 16.0 # 转成真实视差,单位像素 if d <= 0: print('该点无有效视差') return # BASELINE 用实际量出的光心水平距离,单位 mm # FOCAL_PX 取左相机内参 mtx_l[0][0],单位像素 Z = BASELINE * FOCAL_PX / d print(f'({x}, {y}) 视差={d:.2f}px 距离={Z:.0f}mm') cv2.setMouseCallback('disp', on_mouse)

两个方法的坑位不同。reprojectImageTo3D 的坐标单位完全取决于标定时的 SQUARE_SIZE,如果用的是毫米,输出就是毫米;如果偷懒把格边长写成 1.0,输出就变成"格边长的倍数",常见事故是把单位写错导致所有距离系统性偏大若干倍。方法二公式里的 BASELINE,我建议用尺子实际量两镜头光心之间的水平距离,而不是取标定出的 T 分量。标定 T 的单位和标定板一致,但光心位置本身有装配误差,物理量测往往更接近真实基线。

4.3 测距误差边界:为什么 5 米外精度急剧下降

这一节是很多人不愿意面对的现实。以焦距 f=800px、基线 B=100mm 的典型配置为例,1 米处视差 d = 800×100/1000 = 80px,3 米处约 26.7px,5 米处只剩 16px。SGBM 的视差分辨率理论上是 1/16 像素,但实际受噪声和纹理影响很难稳定优于 1 像素。对公式 Z = fB/d 求导可得 δZ ≈ Z²/(fB) × δd,按 δd=1px 估算:

目标距离理论视差1px 误差对应距离误差相对误差
0.5m160px约 3mm0.6%
1m80px约 12.5mm1.3%
2m40px约 50mm2.5%
3m26.7px约 112mm3.7%
5m16px约 312mm6.2%

这张表本身就够写论文里的一小节了。它说明两件事:一是在有效范围内调好参数,双目测距确实能比超声波、单目测距稳得多;二是超出 3~5 米后误差随距离平方增长,任何参数调优都救不了物理极限。想测更远,只能加长基线、提高分辨率,或者换主动光方案,这是方案选型阶段就要想清楚的事。

5. 双目测距踩坑排查手册:标定翻车、纹理稀疏、环境报错的 5 个真实案例

5.1 视差图整张扭曲:重投影误差超过 0.5 像素

现象:校正后的左右图行不对齐,视差图出现系统性梯度,近处物体边缘有双层影子,远处距离整体偏大偏小。

原因:标定图片数量不够、拍摄角度太单一,或者棋盘格贴在不平的表面上。最常见的翻车是只拍了十张出头,而且全是从正面拍的,棋盘格在画面里占的面积还不到四分之一,角点提取本身就带误差。

解决:重新采图,左右各 20 对起步,覆盖画面的四角、四边、中心九个区域,每张棋盘格要有明显倾斜。重拍前先检查标定板是否平整,打印纸贴在硬纸板上最稳妥。每次标定后打印 stereoCalibrate 返回的 ret,低于 0.5 像素才继续往下走,否则后面所有环节都是在错误地基上盖楼。

5.2 左右图亮度不一致:匹配率骤降,视差图全是雪花

现象:同一物体左右图一明一暗,视差图出现大片错误匹配,像电视雪花,而且随时间闪烁。

原因:USB 摄像头的自动曝光和自动白平衡是各自独立工作的,两个镜头对场景的反应不同,导致同一时刻左右图的亮度和色温不一致。SGBM 的代价计算依赖灰度一致性,亮度差异一上去,匹配结果立刻崩。

解决:优先关闭两个摄像头的自动曝光、自动白平衡和自动增益,改为手动固定参数。可以在 OpenCV 里用 CAP_PROP_AUTO_EXPOSURE、CAP_PROP_EXPOSURE 设置,也可以直接通过摄像头自带的 SDK 或工厂模式软件锁死。如果摄像头不支持手动,退而求其次是在采集后用 CLAHE 做直方图均衡,让左右图灰度分布尽量接近,但这是治标不治本。

5.3 白墙、天空等低纹理区域出现黑洞

现象:视差图里目标区域是纯黑的无效像素,或者视差值在 0 附近来回跳,测出来的距离忽远忽近。

原因:SGBM 本质上是找左右图的相似纹理块,墙面、天空这类区域在局部窗口里几乎没有任何梯度信息,匹配代价函数找不到唯一最小值,自然给不出可靠视差。

解决:三个层面处理。场景层面,给被测目标增加纹理,比如贴二维码、贴标志纸,这是效果最直接的。算法层面,对纹理极差的区域不强行给视差,返回"不可测"状态,让上层逻辑自己处理。工程层面,如果测距范围固定,就把 SGBM 的计算限制在包含目标的 ROI 内,减少整体误匹配对目标区域的干扰。

5.4 import cv2 报错与 cv2.error 版本冲突

现象:pip 安装 opencv 后,import cv2 提示 ModuleNotFoundError: No module named 'cv2';或者运行到某个函数时抛出 cv2.error,报错信息里出现类似 OpenCV(4.4.0) 的版本字样;还有人会遇到"opencv 安装成功却找不到 cv2"的问题。

原因:绝大多数是环境混用,最常见的是同时安装了 opencv-python 和 opencv-contrib-python,两个包互相覆盖文件,或者 pip 装到了系统 Python,而代码跑在 conda 虚拟环境里。cv2.error 这类运行时错误大多是函数在新旧版本里的 API 签名变了,网上抄的代码版本和你安装的版本对不上。

解决:先统一环境,把两个包都卸掉再装一个:

pip uninstall opencv-python opencv-contrib-python -y pip install opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simple

然后确认运行环境,在跑代码的那个解释器里执行 python -c "import cv2; print(cv2.version)" 验证。项目从头到尾固定在一个虚拟环境里,不要今天用系统 Python 明天用 conda。版本选择上,推荐装当前 stable 版本而不是追新,网上能找到的 SGBM、WLS 示例代码大多基于 OpenCV 3.x 或 4.x,先让示例跑通再换新版本。

5.5 实时性不够:树莓派或笔记本上跑不到目标帧率

现象:SGBM 在一帧上耗时几百毫秒,画面明显卡顿,树莓派这种低算力平台上尤其明显。

原因:numDisparities 和图像分辨率双重拉高了计算量。匹配代价的计算量是 分辨率 × 视差搜索范围,两个参数同时放大,耗时成倍增长。

解决:按顺序做三件事。先把图像分辨率降到 640×360,测距精度损失有限但速度可能翻倍;再把 numDisparities 从 128 降到 64,如果目标距离本来就不近,这一步影响很小;最后把 mode 换成 cv2.STEREO_SGBM_MODE_SGBM_3WAY,这是 OpenCV 提供的三路路径快速模式,精度略降但速度明显提升。如果还不够,就把 SGBM 限定在 ROI 区域内计算,这是最直接有效的手段。

6. 把测距结果写到论文里:精度验证方法与三个稳定测距技巧

6.1 精度验证方法

实测验证是让这个项目从"能跑"变成"可信"的关键一步,也是论文里最好用的素材。准备一把卷尺或激光测距仪,在固定场景里从 0.5 米到 4 米每隔 0.5 米放置一个带纹理的目标板,每个距离采集 30 帧,取视差的中值作为该点的测量结果,再统计 30 帧里的最大值、最小值和标准差。最后把测量距离和真实距离画成误差曲线,计算平均绝对误差和均方根误差。这张图和这组数据,比任何原理推导都更能说服答辩评委。

6.2 三个稳定测距技巧

第一个技巧是多帧中值。静止目标做连续 5~10 帧测距,取中值而不是均值,能干净地滤掉 SGBM 的单帧跳变。第二个技巧是 ROI 约束。先框定目标所在区域,只对该区域计算视差和距离,既提速又避免区域外噪声干扰。第三个技巧是亚像素抛物线插值,对某个像素及其左右相邻视差做二次插值,能把视差精度从整像素提到亚像素级别,公式是 d_sub = d - 0.5(d+ - d-)/(d+ - 2d + d-),对近距离精度提升明显。

我最开始做双目测距时,把网上的 SGBM 参数原样抄进代码,结果在一台普通笔记本上视差图直接花屏,折腾了一周才发现问题不在匹配器,而在标定。从那以后我养成了一个习惯:每换一组摄像头或支架,先花半天重新标定,再看重投影误差,最后才调 SGBM。这套顺序看起来慢,实际上是最快的路径。希望这个方向能帮你少走几次弯路,也希望你的论文实测数据一次就好看。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进