Python双目立体视觉测距源码实战:标定校正与SGBM匹配

发布时间:2026/10/30 5:04:00
Python双目立体视觉测距源码实战:标定校正与SGBM匹配 简介这是一套面向高校学生与计算机视觉初学者的毕业设计级项目源码主题为Python基于双目立体视觉的图像匹配与测距可直接用于毕设、期末大作业或课程设计。项目经过严格调试代码注释完整新手也能看懂部署后即可运行具备较高的实际应用与参考价值。资源包共164个文件约96.22MB包含38个py源码文件、10个ui界面文件、5个xml与1个qrc配置、1个qss样式以及49个bmp、23个jpg、3个png等双目图像素材另附13个caj、5个pdf、2个docx等参考文献与说明文档覆盖代码、界面、数据与资料四类内容。目前已有70人学习下载。读者可获得完整的双目匹配与测距实现方案、可运行的工程目录结构、带注释的核心算法代码以及配套图像数据与参考文档便于快速理解立体视觉流程并完成二次开发。1. 从两张 .bmp 到真实距离这套双目源码到底能跑出什么如果你手头正好有一组img_0001.bmp到img_0015.bmp这样的左右目图像却卡在“怎么把像素差变成毫米”这一步这套基于 Python 的双目立体视觉图像匹配与测距源码就是冲这个场景来的。它把标定、校正、SGBM 匹配、视差转深度、测距输出串成一条能直接跑的链路代码带注释新手照着改路径就能出结果。适合做毕业设计、课程大作业也适合刚接触双目、想先跑通再深挖参数的工程师。它不教你造相机但能让你在半天内看到深度图上的数值跳动。2. 拆开看链路标定、校正、匹配、测距四步怎么落地2.1 为什么必须先做标定和极线校正双目测距的底层公式就一句话Z f * B / d其中f是焦距像素单位B是两相机光心距离基线d是同一个物点在左右图上的横坐标差也就是视差。听起来简单但原始左右图直接拿去匹配匹配点往往不在同一行上搜索空间是二维的又慢又容易错。极线校正就是把两个相机虚拟成完全平行、光轴一致、成像平面共面的状态让同名点只差一个水平位移。这一步不做后面 SGBM 出来的视差图基本是废的。标定要拿到两个相机的内参矩阵、畸变系数以及它们之间的旋转和平移。常见做法是打印一张棋盘格用cv2.findChessboardCorners抓角点再cv2.stereoCalibrate一次性解出所有参数。源码里通常已经带了一份stereo_calib.xml或.npz但你要清楚它是对应哪组分辨率、哪块标定板的换相机或换分辨率就得重标。import cv2 import numpy as np # 棋盘格内角点数量比如 9x6注意是内角点不是格子数 pattern_size (9, 6) objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objpoints [] # 三维世界坐标 imgpoints_l [] # 左图角点 imgpoints_r [] # 右图角点 for fname_l, fname_r in zip(left_list, right_list): img_l cv2.imread(fname_l, 0) img_r cv2.imread(fname_r, 0) ret_l, corners_l cv2.findChessboardCorners(img_l, pattern_size, None) ret_r, corners_r cv2.findChessboardCorners(img_r, pattern_size, None) if ret_l and ret_r: objpoints.append(objp) imgpoints_l.append(corners_l) imgpoints_r.append(corners_r) # 单目标定拿初始内参 ret_l, mtx_l, dist_l, _, _ cv2.calibrateCamera(objpoints, imgpoints_l, img_l.shape[::-1], None, None) ret_r, mtx_r, dist_r, _, _ cv2.calibrateCamera(objpoints, imgpoints_r, img_r.shape[::-1], None, None) # 双目标定flags 固定内参可提高稳定性 ret, mtx_l, dist_l, mtx_r, dist_r, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, mtx_l, dist_l, mtx_r, dist_r, img_l.shape[::-1], flagscv2.CALIB_FIX_INTRINSIC )这段代码的逻辑是先单目标定给双目标定一个靠谱初值再用stereoCalibrate联合优化。CALIB_FIX_INTRINSIC表示单目标定结果够好时不再动内参避免过拟合。T的模长就是基线B单位和你棋盘格方格尺寸一致通常用毫米。标定完记得用cv2.stereoRectify算校正映射再用cv2.remap把每张图拉正这一步的alpha0会裁掉黑边alpha1保留全部像素但边缘有无效区做测距建议用 0。2.2 SGBM 匹配参数怎么调才不糊校正之后左右图同名点在同一行匹配从二维降到一维。源码里大概率用的是cv2.StereoSGBM_create因为它是 OpenCV 里半全局匹配的成熟实现比 BM 稳比深度学习方案轻。核心参数有四个minDisparity、numDisparities、blockSize、uniquenessRatio。numDisparities必须是 16 的整数倍它决定能测多近。视差越大距离越近如果最近处视差超过这个值近处就会出黑洞。blockSize是匹配代价聚合窗口奇数3 到 11 之间。纹理弱、噪声大就调大但边缘会糊纹理强、要细节就调小。uniquenessRatio一般 5 到 15太小误匹配多太大视差图稀疏。window_size 5 min_disp 0 num_disp 16 * 6 # 必须能被 16 整除 stereo cv2.StereoSGBM_create( minDisparitymin_disp, numDisparitiesnum_disp, blockSizewindow_size, P18 * 3 * window_size ** 2, P232 * 3 * window_size ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange2, modecv2.STEREO_SGBM_MODE_SGBM_3WAY ) disparity stereo.compute(img_l_rect, img_r_rect).astype(np.float32) / 16.0P1、P2是平滑惩罚项控制视差图连续性经验值就是上面这种按blockSize平方缩放。disp12MaxDiff做左右一致性检查设 1 比较严能滤掉不少错误。speckleWindowSize和speckleRange去掉小块噪点。最后除以 16 是因为 SGBM 输出的是定点数放大 16 倍存的。这一步出来的是视差图不是深度图别急着量距离。2.3 视差转深度一个除法加两个坑拿到视差后套公式depth f * B / disparity。f和B来自标定disparity是上一步结果。听起来一步到位但有两个坑一是视差为 0 或负数的位置要屏蔽否则除出无穷大二是f的单位。stereoRectify返回的P1里P1[0,0]就是校正后的焦距直接用这个别拿单目标定的mtx[0,0]两者在裁剪后不一样。# P1 是 stereoRectify 输出的左相机投影矩阵 f P1[0, 0] B np.linalg.norm(T) # 基线单位与标定板一致 mask disparity min_disp 0.5 depth np.zeros_like(disparity) depth[mask] f * B / disparity[mask] # 转成毫米并限制合理范围 depth_mm depth valid (depth_mm 100) (depth_mm 10000)min_disp 0.5这个阈值是为了避开无效视差。valid掩码把 10 厘米到 10 米之外的点滤掉具体范围按你场景改。到这一步深度图里每个像素值就是距离取图上某点坐标就能读出该点距离。源码里一般会封装一个get_depth_at(x, y)之类的函数点一下鼠标就出数。3. 把源码跑起来环境、路径、输出三步走3.1 环境依赖与版本对齐这套源码是 Python 的核心依赖就三个opencv-python、numpy、matplotlib。但 OpenCV 版本对 SGBM 参数和stereoRectify返回值有细微影响建议锁 4.x 的某个稳定版别用太新的预览版。安装命令很直接pip install opencv-python4.8.1.78 numpy matplotlib如果你要用cv2.imshow看中间结果opencv-python就够如果跑在无桌面环境换成opencv-python-headless但那样就不能弹窗只能存图。numpy版本别低于 1.20否则某些矩阵运算会报弃用警告。装完先跑一句import cv2; print(cv2.__version__)确认版本别等跑到一半才发现 API 对不上。3.2 图像路径与命名规则项目正文里那串img_0013.bmp、img_0007.bmp就是左右目图像对。通常命名规律是左图img_XXXX.bmp右图img_XXXX_r.bmp或者放在left/、right/两个文件夹。你要做的是把源码里的路径变量改成你实际的目录。常见写法是import os import glob left_dir ./data/left right_dir ./data/right left_files sorted(glob.glob(os.path.join(left_dir, *.bmp))) right_files sorted(glob.glob(os.path.join(right_dir, *.bmp))) assert len(left_files) len(right_files), 左右图数量不一致sorted保证左右按同一顺序配对assert防止数量对不上导致后面索引错位。如果你的图是img_0001.bmp和img_0001_r.bmp混在一个目录那就用字符串替换生成右图路径别用glob一把抓否则左右会混。路径里尽量别有中文和空格OpenCV 在某些平台读中文路径会返回None这是血泪经验。3.3 运行输出与结果验证跑起来后一般会输出三样东西校正后的左右图、视差图、深度伪彩色图。验证是否正常先看校正图里同一物体是否在同一水平线上再看视差图是否连续、边缘是否贴合物体轮廓。如果视差图大片黑多半是numDisparities太小或纹理太弱如果噪点满天飞调大uniquenessRatio和speckleWindowSize。cv2.imwrite(rectified_left.png, img_l_rect) cv2.imwrite(disparity.png, (disparity - disparity.min()) / (disparity.max() - disparity.min()) * 255) cv2.imwrite(depth_color.png, cv2.applyColorMap(cv2.convertScaleAbs(depth_mm, alpha0.03), cv2.COLORMAP_JET))convertScaleAbs里的alpha是缩放系数深度值动辄几千毫米不缩放直接转 8 位会全白。COLORMAP_JET让近处偏红、远处偏蓝肉眼一看就知道深度趋势对不对。验证时拿个已知距离的物体放进去比如一米外的椅子看深度图对应位置数值是否接近 1000 毫米误差在百分之几以内就算标定和匹配都正常。4. 避坑排查双目测距最常见的五类翻车4.1 深度图全黑或全白现象是输出深度图要么一片黑要么一片白看不到任何结构。原因通常是视差全为 0 或全为负除法之后要么无穷大要么无效。先检查numDisparities是否覆盖了实际视差范围近处物体视差大如果numDisparities只有 64 而实际视差到 100近处就全丢。再检查左右图是否真的校正过没校正的图同名点不在同一行SGBM 直接罢工。解决方法是先用cv2.stereoRectify加cv2.remap生成校正图肉眼确认水平对齐再逐步加大numDisparities直到近处出现有效视差。4.2 测距数值随距离线性漂移现象是近距离还行越远误差越大甚至两米外偏出半米。原因多半是基线B或焦距f用错。B必须用stereoCalibrate返回的T的模长单位和你标定板方格尺寸一致如果你标定时填的是 25 毫米但实际打印出来是 24 毫米那所有距离都会按比例偏。f要用stereoRectify后P1[0,0]不是原始内参。解决方法是拿一个已知距离的物体做单点校验反推f * B的实际值和理论值对比偏差超过百分之五就重标定。4.3 纹理弱区域出现空洞现象是白墙、桌面等纯色区域视差图出现大片空洞。原因是 SGBM 依赖局部纹理做匹配代价没纹理就没法唯一确定同名点。这是算法固有边界不是代码 bug。常见做法是调大blockSize增加聚合窗口或者降低uniquenessRatio放宽唯一性约束但会引入误匹配。更稳妥的是给场景打投影纹理或者接受空洞、只对纹理丰富区域测距。如果毕设要求全覆盖可以在深度图上做空洞填充但填出来的值只能算估计别当精确测量用。4.4 左右图读取顺序反了现象是视差图看起来像负片深度值全负或者完全乱套。原因是左图目录和右图目录传反了或者命名排序后左右错位。双目里左图是参考图右图是目标图反了之后视差符号相反。解决方法是先单独显示一张左图和一张右图看同一物体在右图里是偏左还是偏右。正常情况下右图里物体应该比左图偏左因为右相机在右边看同一物体会向左移。确认顺序后再跑匹配。4.5 标定板角度单一导致参数不稳现象是标定重投影误差看着不大但实际测距就是不准。原因是标定图像里棋盘格角度太单一都正对相机导致旋转参数约束不足解出来的R和T在深度方向上有较大不确定性。解决方法是标定时让棋盘格在画面里各个角度都出现倾斜、旋转、远近都覆盖至少 15 到 20 组有效图像。标定完看stereoCalibrate返回的ret值也就是重投影误差一般要小于 0.5 像素大于 1 就重来。5. 进阶技巧用重投影误差和单点校验把精度钉死跑通只是第一步要让这套源码在答辩或实际使用中站得住得会验证精度。我一般会做两件事一是看标定重投影误差二是做单点距离校验。重投影误差在stereoCalibrate的返回值里单位是像素它衡量的是三维点投影回图像后和实际角点的平均偏差。这个值小于 0.5 算合格小于 0.3 算好。如果偏大先检查棋盘格尺寸填对没有再检查角点检测有没有误检。单点校验更直接把棋盘格或一个标记物放在已知距离Z_true处用源码测出Z_meas算相对误差|Z_meas - Z_true| / Z_true。在 0.5 米到 3 米范围内好的双目系统误差能控制在百分之二以内。如果误差随距离线性增大说明f * B整体偏了用Z_true * d_meas反推真实的f * B和理论值比一下。这个反推值可以硬编码回源码做补偿但更规范的做法是重标定。还有一个容易被忽略的点视差图的亚像素精度。SGBM 输出的是整数视差除以 16 后有小数但本质还是离散的。距离越远同样一个像素的视差误差对应越大的深度误差。所以远距离测距时要么用cv2.StereoSGBM的mode开SGBM_3WAY提高精度要么在匹配后对视差图做二次曲线拟合做亚像素细化。源码里如果没做这一步你可以自己加代价是计算量上去。# 简单的亚像素细化在整数视差附近做抛物线拟合 def subpixel_disparity(disp_map, x, y): d disp_map[y, x] if d 0 or d disp_map.shape[1] - 1: return d c0 disp_map[y, x - 1] c1 d c2 disp_map[y, x 1] denom c0 - 2 * c1 c2 if abs(denom) 1e-6: return d return d - 0.5 * (c2 - c0) / denom这段逻辑是在视差图上沿水平方向取三点做抛物线拟合顶点位置就是亚像素视差。denom是二阶差分接近零说明三点共线直接返回原值。这个技巧对远距离测距提升明显但要求视差图局部平滑噪声大的区域反而会引入抖动所以配合speckleWindowSize去噪一起用。从那以后我每次拿到新的双目数据都强制先跑一遍校正图目视检查再拿已知距离物体做单点校验两项都过了才敢往下做测量。这套源码把链路搭好了但参数和验证得你自己盯。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询