
简介这是一份基于Python与OpenCV实现的双目视觉尺寸测量项目面向期末大作业、课程设计或毕业设计等场景解决通过左右图像计算被摄物体尺寸的核心问题涵盖从图像采集到尺寸输出的完整处理链路。压缩包内共23个文件含Python主程序、README开发文档以及21张双目视图与结果样例图整体大小仅2.27MB轻量易于部署。其中Python源码实现主要测量流程开发文档对模块与步骤做了说明样例图片可用于验证和调试算法。目前已有71人学习代码配有详细注释新手也能顺畅理解从图像读取、视差计算到尺寸换算的逻辑清晰可见。项目源自个人高分作业并经过严格调试运行稳定、界面直观可作为课程答辩或毕设演示的完整方案多组样例也能帮助直观展示测量效果。1. 基于PythonOpenCV的双目视觉测量尺寸期末大作业怎么跑通、怎么答辩期末大作业最怕的是一堆公式讲得头头是道演示时刻“跑不起来”。这份基于pythonopencv的双目视觉测量被摄物体尺寸的项目就是用来填这个坑的左右相机各拍一张哈密瓜melonL.jpg/melonR.jpg配合9对棋盘格标定图运行m.py就能输出带尺寸标注的result.jpg。整个方案的链路非常完整——棋盘格标定、立体校正、SGBM视差、重投影三维点云再到尺寸测量每步都有注释新手能顺着代码把双目视觉原理过一遍。对正在赶期末大作业、课程设计或毕设的人来说它既是能直接复现的成品也是写开发文档时的技术底稿。想搞懂“视差到底是怎么变成毫米”的入门工程师也可以拿它当活教材。2. 原理与拆解视差三角测量、m.py和每张测试图的作用2.1 双目测尺寸的底层逻辑视差、基线与三角测量两个相机从不同位置拍同一个场景物体上同一点在两个像平面上的投影位置会有一个水平偏移这个偏移就是视差disparity。理想平行双目模型里深度Z、基线B、焦距f、视差d满足Z (B * f) / d也就是说目标和相机的距离只由四个量决定其中B和f来自标定d来自左右图像的匹配。目标越近左右像点偏移越大d越大Z越小反之Z越大。整套代码的所有计算都建立在这个式子上所以它先花大力气做棋盘格标定目的就是把B和f变成可信的已知量再让后面的“视差→深度→三维尺寸”有真实物理单位。实际搭建双目时两个相机很难保证光轴绝对平行如果直接拿原始左右图做匹配误差会非常大。因此m.py在计算视差前会让两幅图做立体校正也叫极线校正把左右图变换到“行对齐”状态让同名点只存在水平方向的偏移这样匹配搜索从二维降成一维正确率和速度都能上来。这是OpenCV里stereoRectify remap这两步做的事。视差计算之后得到的是视差图不是深度图。视差图要配合重投影矩阵Q才能映射到三维点云也就是用cv2.reprojectImageTo3D这个函数。三维点云的X、Y、Z坐标都以毫米为单位测量尺寸时只要把待测物体区域的点云取出来X方向最大值减最小值就是宽度Y方向最大值减最小值就是高度。注意这时候量的是三维空间里的两点距离不是二维图像里的像素宽度这是整个项目最容易理解偏的地方。二维像素宽度会随距离变化三维点云里的距离才是物体真实尺寸。这里再补一个选型问题为什么不直接用神经网络做单目深度估计原因很简单单目深度估计给的是相对深度缺少绝对尺度要还原成物理尺寸需要一个已知大小的参照物这就偏离了作业“测量被摄物体尺寸”的本意。双目靠基线当物理尺子天生能给绝对尺度所以作业题目本身就是冲着立体视觉去的。2.2 项目文件对照哪些是标定输入、哪些是测量输入、哪些是输出解压文件夹后图片大致分三类melonL.jpg和melonR.jpg是左右相机拍同一个哈密瓜的待测样本left_0.jpg到left_8.jpg和right_0.jpg到right_8.jpg是左右相机同步拍的9组棋盘格标定图result.jpg是程序运行后的输出结果。m.py是整个项目的入口README.md则记录了环境依赖和运行方式。文件作用所属阶段m.py主程序含标定、校正、视差、重投影运行入口melonL.jpg / melonR.jpg待测哈密瓜的左右视图测量输入left_0.jpg ~ left_8.jpg左相机棋盘格标定图标定输入right_0.jpg ~ right_8.jpg右相机棋盘格标定图标定输入result.jpg带尺寸标注的效果图测量输出README.md环境与运行说明文档这套文件结构是典型的“标定测量”双阶段布局。实际跑通后你会发现标定阶段读的是left_/right_测量阶段用的才是melonL/melonR两阶段不能混。特别提醒一下9对标定图属于“勉强够用”的状态一般建议10到20对。数量不够时标定出的畸变参数在外插区域会不准如果复现后发现视差图边缘有畸变优先补拍标定图而不是疯狂调SGBM参数。2.3 m.py主流程拆解标定、校正、视差到三维点云的四个阶段m.py虽然从文件名看是个单文件但内部逻辑是按四个阶段排的找棋盘格角点做双目标定、生成校正映射、SGBM算视差、重投影测尺寸。代码里每个阶段都有注释结构化做得比较清楚。等价的骨架代码如下# m.py主流程骨架实际变量名以压缩包内源码为准 # 阶段一棋盘格角点提取与双目标定 # 对 left_X.jpg / right_X.jpg 逐对执行 retL, cornersL cv2.findChessboardCorners(grayL, (cols, rows), None) # cols、rows是内角点数量例如7x9棋盘写(9, 7) # 收集足够多的角点对后标定左右相机内参和相对位姿 ret, cameraMatrixL, distL, cameraMatrixR, distR, R, T cv2.stereoCalibrate( objpoints, imgpointsL, imgpointsR, cameraMatrixL, distL, cameraMatrixR, distR, (imgW, imgH), R, T, flagscv2.CALIB_USE_INTRINSIC_GUESS) # 阶段二立体校正生成左右图的remap映射表 R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( cameraMatrixL, distL, cameraMatrixR, distR, (imgW, imgH), R, T) map1L, map2L cv2.initUndistortRectifyMap( cameraMatrixL, distL, R1, P1, (imgW, imgH), cv2.CV_16SC2) imgL_rect cv2.remap(imgL, map1L, map2L, cv2.INTER_LINEAR) # 阶段三SGBM立体匹配得到浮点视差图 sgbm cv2.StereoSGBM_create(numDisparities64, blockSize11) disparity sgbm.compute(imgL_rect, imgR_rect).astype(np.float32) / 16.0 # 阶段四重投影为三维点云再在ROI内量尺寸 points_3D cv2.reprojectImageTo3D(disparity, Q) # ROI内X方向极差宽Y方向极差高单位毫米这段骨架里最需要解释的是三处。第一findChessboardCorners的(cols, rows)是内角点数而不是外部棋盘格子数比如视觉上3x3格子的棋盘实际只有2x2个内角点填错一个数角点就找不到。第二stereoRectify返回的Q矩阵是4x4的重投影矩阵它负责把“像素坐标加视差”还原成(X, Y, Z)其中Z正好是深度值这是后面所有尺寸计算的中枢。第三StereoSGBM_create返回的原始视差图是int16定点格式要除以16.0才是真视差代码漏掉这一步会导致后续三维坐标全部放大16倍尺寸数据直接没救。在实际项目里标定耗时比较久所以成熟的写法会判断本地有没有stereoMap.npz这类缓存文件存在就直接加载不存在才重新标定。这样做的好处是后面调整测量逻辑时不用反复跑标定几秒就能出结果。你拿到m.py后也可以按这个思路小改一下能省不少等待时间。2.4 为什么不用单目绝对尺度只有双眼能给单目相机从一张图里能算出相对深度关系比如人和墙谁更远但它算不出具体距离更算不出物理尺寸。要把单目图中的像素高度变成毫米高度必须知道相机高度、俯仰角或者场景中某个物体的真实尺寸而这些条件在作业场景里往往是未知的。双目就不同两相机光心之间的距离本身就是一把已知长度的物理尺子一旦标定完成每个像素对应的空间位置都有了绝对尺度。这也是题目要求“测量被摄物体尺寸”而不是“对图像里的物体做分割”的根本原因。顺便多说一句工业视觉里halcon也提供这类双目测量方案而且精度调校工具更全但商业授权价格不低OpenCV方案免费开源并且底层公式能打印出来写进报告作为课程设计或期末大作业讲清楚原理更加友好。3. 复现与运行环境配置、执行命令和StereoSGBM关键参数3.1 环境准备Python版本、OpenCV安装和模块互相“打架”的问题先说复现环境。这个项目不挑电脑CPU就能跑不需要GPU。Python版本建议3.7到3.9我用3.8跑m.py没有遇到兼容性问题3.10以上也能跑但某些OpenCV版本在Windows下需要对应的编译环境为了省事尽量用3.8。核心依赖只有两个opencv-python提供cv2模块numpy负责数组运算和三维点云操作。最容易翻车的是Python环境不一致。很多人电脑上装了Anaconda又装了官方Python命令行pip install opencv-python装到了一个环境而python m.py用的却是另一个解释器于是出现经典的ModuleNotFoundError: No module named cv2。要避免这种问题最稳的做法是在项目目录下建一个虚拟环境所有东西都装在里面# 在项目根目录创建虚拟环境 python -m venv venv # Windows激活 venv\Scripts\activate # Linux/macOS激活 source venv/bin/activate # 安装依赖 pip install opencv-python numpy # 打印cv2版本确认装进了当前环境 python -c import cv2; print(cv2.__version__)代码块里的venv是Python自带的虚拟环境不需要额外安装。激活成功后命令行会出现(venv)前缀。如果安装速度慢可以在pip命令后面加-i参数指定国内镜像源这里不赘述。版本打印出来只要是4.x都能用并不要求必须和项目开发时完全一致。提示如果电脑上装了多个Pythonpython -m pip install 会比裸pip install更可靠因为它安装到当前python解释器对应的环境里。3.2 运行m.py目录结构、执行命令和预期输出运行前先确认目录结构。项目里图片都放在imgs文件夹时m.py里的路径会写成imgs/melonL.jpg这种相对路径所以必须在项目根目录执行脚本。如果在PyCharm里直接右键运行工作目录可能不在项目根目录m.py会报找不到图片。# 切到项目解压后的根目录 cd your_project_dir # 直接运行 python m.py第一次运行时如果m.py内置了标定流程控制台会陆续出现“Chessboard corners found”之类的提示这是正常的。标定9对图加上SGBM匹配在普通笔记本上大概要十几秒到几十秒。跑完会在当前目录生成result.jpg。打开它应该能看到左右相机原图、视差图或者带尺寸标注的效果图这类项目通常会把三个画面拼在一张画布上方便直接截图放进期末大作业报告。如果运行后只显示黑白噪点、没有数值输出先不要怀疑源码有问题最可能是SGBM参数和图像尺寸不匹配。标准处理流程是先在m.py里找到StereoSGBM_create那一段把参数临时改回保守值再用4.4节提到的方法检查视差图。改完参数记得重新生成result.jpg数值才会更新。3.3 影响测量结果的五个StereoSGBM关键参数SGBM全称是Semi-Global Block Matching它在效率和效果之间取得了一个平衡室内小场景物体测量完全够用。但它的参数直接影响视差质量进而影响最终尺寸。常见的一段初始化代码是sgbm cv2.StereoSGBM_create( minDisparity0, # 起始视差固定场景一般设为0 numDisparities64, # 视差搜索范围必须是16的倍数 blockSize11, # 匹配窗口边长必须是奇数 P18 * 3 * blockSize ** 2, P232 * 3 * blockSize ** 2, disp12MaxDiff1, # 左右一致性检查容差 uniquenessRatio10, # 唯一性比例过小误匹配多 speckleWindowSize100, # 斑点平滑窗口抑制孤立噪点 speckleRange2 )表格把这些参数的含义和调参边界展开参数作用经验值和注意事项numDisparities视差搜索范围必须能被16整除物体越近范围要越大blockSize匹配窗口边长必须为奇数越小细节越丰富但噪点多P1 / P2平滑惩罚系数P2约为P1的4到8倍blockSize变大时惩罚也要相应增大uniquenessRatio匹配唯一性5到20之间越大越严格视差图越干净但也越稀疏speckleWindowSize去孤立噪点100到200视差图黑点多就加大disp12MaxDiff左右一致性校验0到2越大越宽松边缘更容易产生飞点调这些参数有一条铁律一次只动一个。通行的做法是先固定blockSize通过观察视差图的黑色空洞面积来调numDisparities然后把uniquenessRatio从10往上加看边缘是不是变干净最后再用speckleWindowSize把零星噪点抹掉。我这里给出的64和11只是起点不是标准答案。如果待测物体离相机很近视差数值会很大64可能不够用要改成96或128反之物体在2米外视差范围可以减到32以提高匹配速度。3.4 先别急着调SGBM极线校正才是SGBM不吃参数的前提这个点我要单独拿出来讲因为很多人跳进项目第一件事就是调SGBM参数然后把问题越调越乱。SGBM本身的匹配算法有一个隐藏前提输入的两张图必须已经行对齐。只有行对齐的时候匹配搜索才是一维的水平搜索如果左右图还带着畸变和旋转SGBM会在搜索窗口里疯狂找不到同名点视差图出来就是雪花。所以m.py在SGBM之前要先完成stereoRectify和remap这不是过渡步骤而是SGBM能用的前提。验证校正是否有效的方法很直观在左校正图中选一个特征点比如哈密瓜上的斑点再看右校正图的同一个y坐标附近有没有同样纹理。批量验证时可以并排显示两幅校正图在图上画一条水平线如果特征点基本压在同一条线上说明极线已经对齐。没有这一步你花再多时间调blockSize都是白搭。这是我在跑这种项目时必做的一次“体检”。4. 避坑与排错标定、视差图和尺寸误差的典型翻车现场4.1 现象运行直接崩出 cv2.error提示blockSize或numDisparities非法这是最典型的第一课。现象是在StereoSGBM_create或compute处抛出cv2.error报错信息里能看到类似“Block size must be odd”或“numDisparities must be positive and divisible by 16”的文字后面还带一长串OpenCV源码编译路径很多同学看到OpenCV(4.4.0) C:\Users...就懵了以为代码有问题。原因OpenCV底层是C实现的对参数合法性卡得特别严。blockSize是偶数、numDisparities不是16的倍数都会直接抛异常和图像内容无关。解决把blockSize改成奇数numDisparities改成64或96这类16的倍数。如果是从网上抄的参数最好先看一遍opencv官方文档把每个参数的含义和边界搞清楚再加进代码。4.2 现象import cv2报 No module named cv2但pip安装明明成功了现象终端执行pip install opencv-python显示Successfully installed结果运行python m.py时第一行import cv2就报ModuleNotFoundError: No module named cv2。原因最常见的是虚拟环境和全局环境错位。pip装在venv里但运行脚本的python解释器却来自系统全局环境另一种可能是项目目录里恰好有一个cv2.py文件Python会优先导入当前目录下的同名模块于是真实的cv2被“遮蔽”了。解决先执行python -m pip list查看当前环境有没有opencv-python再用python -c import sys; print(sys.executable)确认解释器路径最后检查项目目录下有没有cv2.py或cv2文件夹有就改名。养成用python -m pip而不是裸pip的习惯能从根上避免这类问题。4.3 现象findChessboardCorners找不到角点标定一步都启动不了现象跑m.py时控制台一直提示找不到棋盘格角点或者图片全被跳过标定直接失败退出。原因这个函数要求你传入棋盘格内角点的数量。很多人把视觉上看到的格子数当成参数例如一个7x9的棋盘内角点其实是6x8写错一个数字角点就找不到。另外一个原因是标定图本身质量不行棋盘格在画面里占比太小、曝光过度、运动模糊、拍摄角度过于倾斜都会导致角点提取失败。解决先单独对一张图调用cv2.findChessboardCorners并画出来看看确认参数是内角点数。拍摄时让棋盘格占画面一半以上保持角点清晰锐利多换几个角度但别追求极端角度。打包里那9对图是项目中已经验证过的如果换了自己的标定图就要按这个标准重新拍。4.4 现象视差图大范围黑色只有物体边缘零星亮点现象生成的result.jpg里视差图绝大部分是黑色或者只有边缘轮廓有白点中间物体区域没有任何视差值。原因按经验列三个。第一numDisparities范围不够物体太近时真实视差值超出搜索上限匹配失败第二左右校正图没有行对齐同名点不在同一行SGBM的水平搜索找不到对应关系第三左右图曝光差异过大同一物体在两张图上的亮度差太多相似度计算全部低于阈值。解决先把numDisparities从64提到96或128再检查极线校正结果是否合格最后把左右图分别做cv2.equalizeHist再做直方图均衡化后送入匹配。还有一个隐藏坑值得反复记StereoSGBM_create返回的视差图是int16格式需要除以16.0才是浮点视差如果代码漏了这一步后面三维点云和尺寸数据都是错的可视化的视差图也全是噪点。复现时如果看到结果极其异常先检查这一行。4.5 现象标定和视差都正常但测量尺寸偏大或偏小移动相机后更离谱现象视差图看着很漂亮物体轮廓也很清晰但输出的宽度和真实哈密瓜尺寸对不上误差可能超过20%而且换个距离误差还会变。原因第一层是标定尺度问题棋盘格角点坐标如果默认按1.0填进objpoints而不是按真实格子边长比如30.0mm填那么整个三维点云都会在一个未知的尺度上缩放第二层是测量方式问题如果你在二维图像上选点量像素再乘某个系数这本质上回到了单目估算没有把双目三维信息用起来第三层最隐蔽标定完成后移动过相机R、T、Q矩阵全部失效后面算出来的深度自然全错。解决标定时把棋盘格实际边长以毫米为单位写进代码比如格子是30mm就填30.0测量尺寸时优先在reprojectImageTo3D得到的点云中取ROI范围而不是依赖二维像素相机标定完就固定住标定和测量尽量在同一个会话里完成拆装之后要重新标定。用一张已知宽度的参照物放到被测物体同一深度做一次校验也能快速定位是哪里出了问题。4.6 现象换了一台电脑或换了一个OpenCV版本后结果完全变了现象同一份m.py在别人机器上正常在自己机器上输出尺寸数值不同或者干脆报错。原因不同OpenCV版本对SGBM和标定相关函数的默认行为有细微差异尤其是匹配参数没有显式指定时。另一个原因是图像读取路径不同导致读入了不同分辨率的图标定图分辨率变了内参和畸变参数适用范围也会变。解决把StereoSGBM和stereoRectify的所有关键参数都显式写进代码不要依赖默认值确认左右相机图的分辨率一致并且标定和测量都用同一分辨率。这种“玄学”差异多半不是算法坏了而是输入变了参数没跟上。5. 进阶与验证用参照物把测量误差压进5%的一个实操套路5.1 为什么测量前先放一个已知尺寸的参照物就算标定、校正、视差全都没有报错我也不会把输出的尺寸直接写进开发文档。原因在于整条链路每一步都有误差标定的重投影误差、SGBM在物体边缘的错误匹配、哈密瓜这类曲面物体在深度图上的边界本来就是渐变的这些误差叠加起来尺寸偏个10%太正常了。想跟导师说“误差控制在5%以内”需要一个可验证的校准动作。最简单的办法就是场景里放一个已知真实尺寸的东西和被测物体放在同一个深度平面。比如银行卡真实宽度85.6mm放在哈密瓜旁边用同一套三维点云量银行卡算出测量值和真实值的比值再用这个比值去校正哈密瓜的尺寸。这个比值就是当前拍摄距离下的尺度修正系数它把标定残差和边缘误差一起吸收掉。5.2 点云测量与参照物校验的代码片段这里用一个可复用的函数来说明。注意ROI的选择以及无效深度点的过滤。# 用三维点云测宽度和高度单位毫米 import numpy as np def measure_roi(points_3D, x1, y1, x2, y2): 从重投影后的点云中取ROI返回宽度和高度 roi points_3D[y1:y2, x1:x2].reshape(-1, 3) # 剔除无效深度inf或负值会污染结果 mask np.isfinite(roi[:, 2]) (roi[:, 2] 0) roi roi[mask] x_min, x_max np.min(roi[:, 0]), np.max(roi[:, 0]) y_min, y_max np.min(roi[:, 1]), np.max(roi[:, 1]) return x_max - x_min, y_max - y_min # 假设points_3D由cv2.reprojectImageTo3D得到单位毫米 # 第一步量参照物比如银行卡实际宽85.6mm ref_true_w 85.6 ref_measured_w, _ measure_roi(points_3D, ref_x1, ref_y1, ref_x2, ref_y2) scale ref_true_w / ref_measured_w # 校正系数 # 第二步量哈密瓜并用scale修正 melon_w, melon_h measure_roi(points_3D, mel_x1, mel_y1, mel_x2, mel_y2) real_w, real_h melon_w * scale, melon_h * scale print(f哈密瓜实际尺寸约: {real_w:.1f} x {real_h:.1f} mm)这段代码里最关键的是mask过滤reprojectImageTo3D在图像无纹理区域会给出非有限值或负深度不把它们排除极值会被错误的点带飞。参数说明就一句话ROI的坐标x1,y1,x2,y2要在校正后的左图里框出来并且要框得比物体实际轮廓稍微大一点点给SGBM的边缘误差留出余量。5.3 把误差压进5%的三个读数习惯我踩过几次坑之后总结出来的三条习惯第一测量三次取中位数而不是平均值。SGBM偶尔会在某个位置产生一个离谱的飞点平均值会被这个飞点拉偏中位数能把它挡在门外。第二ROI不要贴着物体边缘裁切至少留出2到3个像素的边界深度图在边缘处是斜坡而不是直角框得太紧反而把斜坡也框一部分进去。第三尽量近距离测量让画面中物体的宽度占到图像宽度的三分之一以上距离越远同一个像素级的视差误差被放大得越厉害。5.4 换物体时的最小改动把ROI框选改成边缘检测如果下次测的不是哈密瓜而是纸盒这种有棱角的物体ROI手工框选就不够用了。一个比较通用的做法是在左校正图上做Canny边缘检测找到最大轮廓取最小外接矩形再把四个角点换算成ROIgray cv2.cvtColor(imgL_rect, cv2.COLOR_BGR2GRAY) edges cv2.Canny(gray, 50, 150) contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnt max(contours, keycv2.contourArea) rect cv2.minAreaRect(cnt) # ((cx, cy), (w, h), angle) print(像素尺寸:, rect[1]) # 还需要乘以尺度才能变成毫米这里的Canny阈值50/150只是起点光照变了阈值也要跟着调。纸盒这类物体边缘锐利外接矩形的稳定性比哈密瓜的ROI点云极差更可靠但曲面物体还是优先用点云极差法因为外接矩形会把球体的弧面边缘也算进去测出来的尺寸偏大。5.5 收尾把误差数字写进开发文档的习惯从那以后我每次交测量类的作业或者帮别人验收这类项目都会强制走一遍这个流程先标定再放参照物再测三次取中位数最后把误差百分比写进开发文档。这套流程看起来多花几分钟但答辩时老师问“误差哪来的”“怎么验证的”你可以直接掏出数字而不是现算。还有一个习惯是标定完就把cameraMatrix、Q矩阵和基线B存成npz文件下次换物体时直接复用不用浪费十分钟重新跑标定。希望帮到你。本文还有配套的精品资源点击获取