ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

双目立体视觉测距从原理到工程实战:OpenCV标定、SGBM与深度图计算

双目立体视觉测距从原理到工程实战:OpenCV标定、SGBM与深度图计算 简介一套基于C#的双目立体视觉测距项目源码面向计算机视觉初学者和需要实现空间距离测量的开发者完整演示了图像采集、特征匹配、立体匹配、视差图计算、三角测量到距离测量的闭环流程。压缩包共49个文件约2.79MB包含C#源文件、解决方案、配置文件、资源文件、左右相机示例图及可执行程序既可直接编译运行也便于按模块研读。已有670人学习浏览项目以双目视差与三角测量为核心结合相机内外参数实现特定点测距。学习该项目可以掌握特征点匹配、半全局匹配等关键思路并借鉴其工程目录结构来扩展双目视觉应用是理解三维重建与视觉测距的实用参考。 如果说你手里现在有一个压缩包文件名字叫“双目立体视觉测距.zip”那你大概率不是在做毕业设计就是在搞机器人、智能车之类的项目。这类资源包在GitHub、CSDN和各种网盘里流传非常广里面通常塞着代码、标定图片、PDF文档甚至直接是打包好的Python环境。但根据我这些年跑过的各种“开源项目大礼包”的经验真正能解压完就一键跑通的比例其实很低大多数时候你是在替原作者补坑。这篇内容就是围绕这个zip展开的。我会从拿到压缩包之后最容易被忽略的一步说起把双目视觉测距从原理到工程实现、再到我踩过的那些坑完整地过一遍。不管你是刚接触OpenCV的初学者还是打算把双目测距落到实际项目里的开发者这篇文章应该都能给你省下不少时间。1. 拿到zip之后先别急着双击解压很多人的第一反应是双击解压然后打开文件夹找README或者main.py恨不得三分钟内看到画面。但我想说的是这个习惯在处理这种工程类压缩包时非常危险因为双目测距项目对环境和硬件的要求都很挑剔它不是简单的“写个脚本跑起来”就完事的东西。1.1 先看文件清单判断这个包“成色”如何拿到zip之后我建议你用命令行工具先列表看看内容而不是直接解压。Windows下可以用tar -tf或者用7-Zip打开预览Linux/macOS下直接unzip -lunzip -l 双目立体视觉测距.zip这一步能让你在解压之前就弄清楚包里大概有哪些东西。一个结构完整的双目测距项目通常应该有这几类内容源代码目录Python/C文件关键会有相机的采集脚本、标定脚本、匹配和测距脚本requirements.txt 或 environment.yml依赖清单标定板图片或录制好的标定视频通常棋盘格或者圆点格相机参数文件XML/YAML存内参、畸变系数、外参等README说明文档如果解压列表出来只有孤零零几个.py文件连依赖清单和样例数据都没有那你就要做好心理准备了。这种包往往是从某个完整项目里抽出的一小部分要么缺数据要么缺文档跑通全靠自己补全。1.2 解压出的文件乱码问题可能出在编码上不知道你有没有遇到过这样的情况解压出来的文件列表里中文文件名全部变成了一堆乱码甚至直接显示成类似“鍙岀洰”这种可怕的文本。这不是你电脑出问题了而是压缩包创建时使用了非UTF-8的文件名编码。国内很多资源包是在Windows上用老版本WinRAR、好压或者360压缩制作的这些工具默认使用GBK/GB18030编码文件名。而Linux、macOS以及新版Windows的压缩工具默认按UTF-8解码两边对不上自然就是乱码。解决办法Windows下用7-Zip打开压缩包在“选项”里切换文件名编码或者直接用Bandizip这类对编码兼容更好的工具。如果已经解压出一堆乱码文件在Windows下可以装一个叫“乱码修复器”的小工具本质是重命名把GBK字节流重新按UTF-8解码回来。我给个建议如果包里的文件名涉及中文且数量很多宁可花五分钟理顺编码也别强行用后面路径问题会折磨死你。2. 双目测距到底是怎么把距离算出来的在跑代码之前觉得还是有必要把原理捋一下。因为双目测距跟单目测距的本质区别会直接影响你对后面参数的调试。很多人在项目里改了半天参数发现精度还是上不去根子就在于根本没理解这个深度是怎么来的。2.1 视差人眼和双目相机共通的语言你伸出一根手指放在眼前轮流闭左眼和右眼你会发现手指相对于背景的位置在跳动。这个跳动量就是视差。手指离眼睛越近跳动量越大离眼睛越远跳动量越小。你两只眼睛之间的距离在视觉领域叫基线baseline也就是左右相机光心的距离。双目相机干的事情就是模拟这个过程。它用两个固定间距的摄像头同时拍摄同一场景然后在左右两张图中找到同一个物理点的两个像素坐标这两个像素坐标之间的差值就是该点的视差disparity。有了视差深度就能算出来。2.2 深度公式这就是整个项目最核心的一行数学表达双目测距的深度计算公式是depth (focal_length * baseline) / disparity其中depth是目标点到相机基线的垂直距离单位通常为毫米focal_length是相机焦距单位是像素不是毫米是像素baseline是左右相机光心之间的距离单位是毫米disparity是视差值单位是像素这三个变量只要有一个不准算出来的距离就偏。这就是为什么每个双目项目里都一定会有标定这一步——标定就是为了尽可能准确地拿到焦距和基线。2.3 公式看似简单但它有三个潜台词这个公式能成立隐含了三个前提条件第一是左右相机光轴尽量平行如果夹角太大极线校正都救不回来第二是左右图像必须同步采集如果左右相机存在时间差运动物体就会产生伪视差第三是左右图像中确实找到了正确的同名点也就是同一个物理点。这三条决定了工程实现中你绕不开三件事相机标定、图像校正、立体匹配。在zip包里的代码不管写得多么花哨最终跑的其实就是这三步。3. 解包后从0到1跑通项目的四个环节假设你已经把zip解压出来了环境也装好了Python 3.8左右、OpenCV 4.x、NumPy下面就是完整的跑通链路。这个顺序我自己验证过很多次绝对可靠。3.1 相机标定别跳过的第一步很多初学者会跳过标定直接跑测距脚本结果测出来的距离跟实际差十万八千里。我之前见过一个项目代码里写死了相机参数那是作者自己的摄像头参数换一个摄像头完全不适用。所以哪怕zip里已经提供了标定好的参数文件我也建议你用自己手头的相机重新标定一次除非你的设备和原作者完全一致。标定用的是棋盘格标定板OpenCV里经典的流程是打印一张棋盘格图案贴在硬纸板上左右相机同时拍摄15到20张不同角度、不同位置的棋盘格照片用findChessboardCorners提取角点用stereoCalibrate计算内参、畸变系数和左右相机之间的旋转矩阵R、平移向量Timport cv2 import numpy as np # 假设你已经提取了左右图像中的棋盘格角点 # objpoints 是世界坐标点imgpoints_l/imgpoints_r 是左右图像坐标点 ret, K1, D1, K2, D2, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, K1, D1, K2, D2, image_size, # (宽, 高) flagscv2.CALIB_FIX_INTRINSIC )标定结果里重点看重投影误差如果大于0.5像素说明标定板图像质量不佳或者拍摄张数不够需要重新拍。3.2 极线校正让左右图的搜索从二维变成一维标定完之后左右图像还不能直接用来匹配因为两个相机光轴可能不严格平行同一个点在左右图中的位置差异就不是单纯的水平方向差异。极线校正的目的就是通过重投影把左右图像变换成“行对齐”的状态让同一个点的左右像点落在同一水平线上。这样立体匹配时只需要在水平方向搜索同名点计算量大幅下降匹配精度也更高。R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( K1, D1, K2, D2, image_size, R, T, flagscv2.CALIB_ZERO_DISPARITY, alpha0 ) map1_x, map1_y cv2.initUndistortRectifyMap(K1, D1, R1, P1, image_size, cv2.CV_32FC1) map2_x, map2_y cv2.initUndistortRectifyMap(K2, D2, R2, P2, image_size, cv2.CV_32FC1) rectified_l cv2.remap(img_l, map1_x, map1_y, cv2.INTER_LINEAR) rectified_r cv2.remap(img_r, map2_x, map2_y, cv2.INTER_LINEAR)我建议你把校正后的左右图并排显示看看同一物体边缘是否在同一水平线上这是检验校正效果最直观的方法。3.3 立体匹配SGBM以及参数调优立体匹配是双目测距里最耗时的环节也是参数最玄学的地方。OpenCV里最常用的就是SGBMSemi-Global Block Matching。这个算法的核心思路是计算每个像素的匹配代价然后沿着多个方向做能量最小化最后得到一张视差图。参考参数如下sgbm cv2.StereoSGBM_create( minDisparity0, numDisparities128, blockSize11, P18 * 3 * 11 ** 2, P232 * 3 * 11 ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32, modecv2.StereoSGBM_MODE_SGBM ) disparity sgbm.compute(rectified_l, rectified_r).astype(np.float32) / 16.0这几个参数我提一下实际经验numDisparities必须能被16整除它决定了能测量的深度范围数值越大近距离物体越容易匹配但计算越慢blockSize必须是奇数越大越平滑但边缘越容易糊P2一般设为P1的4倍以上它控制视差平滑度太大会把细节抹平disparity里有负值表示无效点计算深度前要过滤掉3.4 由视差图换算深度图视差图拿到后深度图可以逐像素计算# 从重投影矩阵Q中取焦距和基线信息 # depth Q[2, 3] / (Q[2, 2] * disparity Q[3, 2]) 的简化形式 # 更常用的方式是直接用 Q 矩阵做 reprojectImageTo3D points_3d cv2.reprojectImageTo3D(disparity, Q) depth_map points_3d[:, :, 2]Q矩阵是stereoRectify返回的重投影矩阵它包含了基线长度、焦距、主点位置等所有信息。利用reprojectImageTo3D能直接得到每个像素对应的三维坐标取第三维就是深度值。这里要注意depth_map中存在无效值对应之前视差图里的负值区域使用前必须用掩膜剔除。4. 跑通后真正让人头大的几个坑代码跑通只是开始距离准确才是目标。这个环节我把遇到的典型问题连同排查思路一起写出来你自己跑的时候可以照着参照。4.1 解压报错“could not find eocd”和分卷压缩问题如果你的压缩包下载自网盘或者传到一半中断过很可能会碰到这样一个报错invalid zip archive: could not find eocdEOCD是zip格式的“中央目录结尾记录”相当于压缩包的索引表。如果文件里找不到EOCD要么是文件下载不完整要么是文件压根不是有效的zip格式。我在Windows下用命令行解压遇到这个问题时会做两手排查先看压缩包大小是否和源文件一致不一致就重新下载再尝试用7-Zip打开7-Zip的容错比系统自带解压好不少有时能强行恢复出大部分文件。另外网上下载的电影资源和工程包经常被分卷压缩成z01、z02这种后缀。你如果只下载了主zip解压时会提示“必须有下列压缩分卷z01”。这种情况没什么巧办法找到所有分卷放到同一目录确保文件名一致比如xxx.zip、xxx.z01再解压就好了。4.2 深度图全是噪声距离根本读不出来如果校正后的左右图看起来正常但SGBM算出来的视差图跟雪花一样第一个要查的是左右相机是否同步。USB双摄像头通常使用UVC协议如果代码里只是简单交替读取两个设备的帧完全没有硬件同步两个相机拍到的画面可能差了几十毫秒任何一个运动物体都会产生巨大误差。解决办法有三个层次如果只是静态场景实验把物体放稳慢点移动如果需要动态测距建议用带硬件同步功能的双目相机模组如果已经买了两个独立摄像头可以接受牺牲一点实时性用软件同步也就是同时按下采集然后取时间戳最接近的两帧。另外还有一个非常容易被忽略的问题左右图像亮度不一致也会导致匹配失败。两个摄像头因为自动曝光参数不一致画面亮度差很大SGBM匹配时会把亮度差异当成噪声。处理方式是在代码里手动固定曝光和增益或者对左右图分别做直方图均衡。4.3 测出来的距离整体偏大或偏小距离整体偏移无非就是focal_length * baseline这个乘积分母偏大或偏小了。但具体是哪个偏可以做个简单区分。如果你想测1米处的物体测出来是1.2米说明分子偏大或分母偏小。分子里包含焦距和基线。焦距取决于标定是否准确基线如果来自测量——比如你用尺子量两个摄像头光心间距——通常有毫米级误差这个误差会直接传导到结果里。最稳妥的办法是把基线值也放到标定里去估计也就是不固定T向量让stereoCalibrate把基线一起算出来。4.4 深度图在物体边缘出现大片黑洞这个现象其实是视差图里的无效区域属于SGBM的正常现象。物体边缘存在遮挡右侧相机看不到左侧相机能看到的部分匹配代价无解自然就出了空洞。处理办法不外乎两种一种是用中值滤波或者空洞填充算法修补但只能是视觉上好看对测量准确度并无增益另一种是把这些区域直接标记为不可信在应用层做判断时排除它们。我做过一些需要精确测量的项目更倾向于后者宁可少一个点也不给错误点。5. 手头没有双目相机两台手机一样能做实验一个很现实的问题很多读者并没有那种工业级双目相机只有一台甚至两台智能手机。其实OpenCV算力完全跑得动手机拍下来的图片所以用两台手机做双目测距实验是可行的。5.1 实验搭建的基本思路你需要两台手机固定在一个平面上尽量让它们的摄像头光轴平行。最好的办法是用支架把两台手机并排固定间距固定好大概5到10厘米都可以。固定好了之后不要动它们整个实验过程中相对位置不能有任何变化。然后用两台手机同时拍摄同一组标定板的照片。注意两个手机需要保持完全相同的拍摄时刻。最方便的做法是录制视频两台手机同步开始录制然后从视频里抽帧。虽然做不到精确同步但只要场景是静态的抽出来的帧基本可用。5.2 手机实验的标定方案标定流程跟工业双目一样只不过左右相机的型号不同内参完全不一样所以必须分开标定两个单目相机再合起来算外参。# 先每个相机单独 calibrateCamera ret_l, K_l, D_l cv2.calibrateCamera(objpoints, imgpoints_l, image_size, None, None) ret_r, K_r, D_r cv2.calibrateCamera(objpoints, imgpoints_r, image_size, None, None) # 再联合做双目标定得到R和T ret, K_l, D_l, K_r, D_r, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_l, imgpoints_r, K_l, D_l, K_r, D_r, image_size )如果两台手机固定比较稳标定结果可信度会高很多。我建议标定照片至少20对角度尽量丰富特别是左右偏转的角度这对求外参至关重要。5.3 手机方案能做到什么程度坦率地讲手机方案的精度不会特别理想。首先是两台手机无法硬件同步运动物体基本测不了其次是手机镜头自带畸变和自动对焦即使标定了也会存在一定漂移再次是两台手机的光轴很难做到严格平行距离越远误差越大。但话又说回来用来理解双目立体视觉的原理、验证视差与深度的关系、跑通OpenCV的整个流程这个方案完全够用。如果你是在做课程设计或者入门学习没必要一上来就买几千块的双目模组两台手机就可以让你把原理吃透。6. 判断一个双目zip值不值得你花时间的经验最后分享一个我自己的判断标准。拿到任何这种项目压缩包我会先花十分钟做一个“合理性检查”再决定要不要深入跑。找三个东西README里有没有写清楚测试环境Python版本、OpenCV版本、系统有没有提供相机参数文件且标注了适用设备有没有示例图片或录制的样本数据。如果三者一个都没有大概率就是一个“半成品”。你可以拿来参考代码思路但别指望能一键复现。选型方面如果你以后要认真做双目测距项目直接考虑下面几个方向入门学习OpenCV 普通USB双目模组注意区分“双摄像头”和“双目模组”科研实验用公开数据集KITTI、Middlebury跑算法不需要真实相机工业应用购买带硬件同步、能输出IR结构光的深度相机模组真正常用的双目视觉库也不只是OpenCV。SGBM在OpenCV里默认有但如果你追求精度可以试试LibELAS或者基于深度学习的立体匹配模型比如PSMNet效果会好非常多代价是要N卡速度也没那么实时。这个就看你的场景需求来权衡了。本文还有配套的精品资源点击获取
返回列表