简介这份资源面向计算机视觉初学者与需要完成课程设计、毕业设计的开发者提供一套基于OpenCV、VS2019与MATLAB的双目视觉测距完整实现。代码覆盖从相机标定到利用BM局部匹配算法输出视差图的全流程可帮助读者理解双目测距的标定、校正与匹配环节并对照运行结果排查问题。压缩包共42个文件约1.16MB以30张jpg标定与校正图像、6个txt标定参数与结果文件为主另含sln解决方案、cpp源码、vcxproj工程文件及项目说明md目录结构清晰便于按标定、校正、测距模块检索学习。目前已有661人学习下载适合希望快速搭建双目测距实验环境、对照参数文件复现视差图并掌握BM算法调参思路的读者参考。1. 从一组左右图到真实距离这套双目测距源码到底能跑出什么手头有一对左右相机拍的同场景照片想知道画面里某个物体离镜头到底几米这件事听起来简单真做起来会卡在三个地方相机内参从哪来、畸变怎么消、视差图怎么算成深度。这套基于 OpenCV VS2019 MATLAB 的双目视觉测距源码把这三个环节串成了一条能跑通的链路——MATLAB 负责标定出内外参VS2019 里的 C 工程用 OpenCV 做立体校正和 BM 局部匹配输出视差图最后按三角测量公式换算成距离。它适合正在做课程设计、毕设或者刚接触双目视觉测距的 C 开发者也适合已经会调 OpenCV 但没完整走过标定到测距全流程的人。资源包里带了 13 组左右标定图、标定结果文本、校正前后对比图和一个可直接打开的 sln 解决方案省掉了自己凑数据集和搭工程骨架的时间。2. 标定先行MATLAB 工具箱怎么把左右相机参数抠出来双目标定的本质是求两组东西每台相机自己的内参和畸变系数以及两台相机之间的旋转平移关系。内参决定像素坐标和相机坐标怎么换算外参决定左相机坐标系到右相机坐标系怎么变换。没有这两组参数后面的立体校正和深度计算全是空中楼阁。这套资源用 MATLAB 的 Camera Calibrator 工具箱做标定原因是它可视化好、能直接导出 OpenCV 格式的参数矩阵比手写标定代码省事得多。2.1 标定图的采集与筛选逻辑资源包里 left01 到 left13、right01 到 right13 是成对的棋盘格图。标定时左右相机各导入自己那一组MATLAB 会自动检测棋盘角点。这里有个容易被忽略的点不是所有图都能用。角点检测失败的图、棋盘格倾斜过大导致透视畸变严重的图、左右图棋盘格数量不一致的图都要剔掉。我一般会先跑一遍检测把 MATLAB 提示 Failed to detect checkerboard 的图删掉再重新导入。棋盘格的实际方格尺寸要填对比如你打印的是 25mm 方格就在设置里填 25单位保持一致这个值直接影响平移向量的物理尺度。2.2 导出参数并转成 OpenCV 能读的格式MATLAB 标定完成后导出的是它自己的结构体需要转成 OpenCV 的矩阵格式。资源里已经给了 calibrationresults_L.txt、calibrationresults_R.txt、stereocalibrateresult.txt 这几个文本里面存的就是标定结果。核心参数对应关系如下参数含义在 OpenCV 中的变量内参矩阵 3x3焦距和主点cameraMatrix畸变系数 1x5径向和切向畸变distCoeffs旋转矩阵 3x3左右相机相对姿态R平移向量 3x1左右相机基线T如果你要自己重新标定导出后在 MATLAB 命令窗用下面这段把矩阵按行打印出来方便粘进 C 代码% 假设标定结果存在 stereoParams 里 disp(左相机内参); disp(stereoParams.CameraParameters1.IntrinsicMatrix); % 注意转置 disp(右相机内参); disp(stereoParams.CameraParameters2.IntrinsicMatrix); disp(旋转矩阵); disp(stereoParams.RotationOfCamera2); disp(平移向量); disp(stereoParams.TranslationOfCamera2);逻辑说明MATLAB 的内参矩阵默认是行向量乘以矩阵的形式和 OpenCV 的列向量约定相反所以导出时要转置。平移向量的单位跟你填的棋盘格尺寸一致如果填的是毫米T 就是毫米后面算出来的距离也是毫米别忘了换算。这一步参数填错后面视差图看着正常但距离会整体偏属于典型的玄学翻车点。3. VS2019 工程拆解从 sln 到 BM 视差图的完整链路拿到标定参数后重头戏在 VS2019 的 C 工程里。资源里的 CameraCalibration.sln 可以直接双击打开工程结构不复杂核心逻辑集中在 CameraCalibration.cpp。整个流程分四步读入标定参数、对左右图做立体校正、用 BM 算法算视差、把视差转成深度。下面按这四步拆。3.1 立体校正让左右图的极线对齐立体校正的目的是把左右图像重投影使得同一个物点在两幅图里处于同一行这样匹配时只需要在同一行水平搜索计算量大幅下降。OpenCV 提供 stereoRectify 和 initUndistortRectifyMap 两个函数配合完成。常见做法是先算校正映射表再用 remap 把原图映射过去。// 读入标定参数示例实际从 txt 或硬编码读取 Mat K1 (Mat_double(3,3) fx1, 0, cx1, 0, fy1, cy1, 0, 0, 1); Mat D1 (Mat_double(1,5) k1, k2, p1, p2, k3); Mat K2 (Mat_double(3,3) fx2, 0, cx2, 0, fy2, cy2, 0, 0, 1); Mat D2 (Mat_double(1,5) k1_, k2_, p1_, p2_, k3_); Mat R, T; // 从 stereocalibrateresult.txt 读入 Size imgSize leftImg.size(); Mat R1, R2, P1, P2, Q; stereoRectify(K1, D1, K2, D2, imgSize, R, T, R1, R2, P1, P2, Q, CALIB_ZERO_DISPARITY, 0, imgSize); Mat map1x, map1y, map2x, map2y; initUndistortRectifyMap(K1, D1, R1, P1, imgSize, CV_32FC1, map1x, map1y); initUndistortRectifyMap(K2, D2, R2, P2, imgSize, CV_32FC1, map2x, map2y); Mat leftRect, rightRect; remap(leftImg, leftRect, map1x, map1y, INTER_LINEAR); remap(rightImg, rightRect, map2x, map2y, INTER_LINEAR);参数说明CALIB_ZERO_DISPARITY 表示让主点在左右图中水平对齐通常都开。Q 是后面用来做 reprojectImageTo3D 的 4x4 视差转深度矩阵别丢。initUndistortRectifyMap 生成的映射表可以缓存如果处理视频流没必要每帧重算这是性能优化的关键点。校正效果好不好直接看资源里那张「校正后左右相机图像.jpg」如果左右图同一行能对上说明校正成功。3.2 BM 局部匹配参数怎么调视差图才不花BMBlock Matching是局部立体匹配里最经典的一种原理是对左图每个像素在右图同一行的某个范围内找最相似的块相似度用 SAD 算。它的优点是快缺点是参数敏感、遮挡区域和弱纹理区域容易出空洞。资源里用的就是 BM核心参数在 StereoSGBM 或 StereoBM 的初始化里。PtrStereoBM bm StereoBM::create(16*5, 21); bm-setPreFilterCap(31); bm-setBlockSize(21); // 匹配块大小奇数 bm-setMinDisparity(0); bm-setNumDisparities(16*5); // 视差搜索范围16 的倍数 bm-setTextureThreshold(10); bm-setUniquenessRatio(15); bm-setSpeckleWindowSize(100); bm-setSpeckleRange(32); bm-setDisp12MaxDiff(1); Mat disp, disp8; bm-compute(leftRect, rightRect, disp); disp.convertTo(disp8, CV_8U, 255/(16*5.0));逻辑说明numDisparities 决定搜索范围物体越近视差越大如果近处物体测不出来把这个值调大但计算量也上去。blockSize 越大越平滑但边缘越糊越小细节多但噪点多一般 15 到 25 之间试。uniquenessRatio 是唯一性检验防止误匹配值太小会引入错误视差太大又会让视差图变稀疏。speckleWindowSize 用来滤掉小块噪点弱纹理场景可以适当加大。调参没有万能值得对着你的场景反复试这就是双目测距里最像玄学的部分。3.3 视差转深度三角测量公式和 Q 矩阵拿到视差图后深度计算就是三角测量。公式很简单Z f * B / df 是焦距B 是基线d 是视差。OpenCV 里用 reprojectImageTo3D 一步到位它内部用的就是 stereoRectify 输出的 Q 矩阵。Mat xyz; reprojectImageTo3D(disp, xyz, Q, true); // 取图像中心点看看深度 Point p(disp.cols/2, disp.rows/2); Vec3f point3D xyz.atVec3f(p); float distance point3D[2]; // Z 值就是距离 cout 中心点距离 distance mm endl;参数说明reprojectImageTo3D 输出的 xyz 是三通道浮点图每个像素存 (X, Y, Z)。Z 就是相机坐标系下的深度。注意 handleMissingValues 设为 true 时视差为 0 或无效的点会被置成很大的值取点前最好判断一下视差是否有效。距离单位跟标定时棋盘格尺寸一致如果标定填的是毫米这里就是毫米。资源里 stereocalibrateresult.txt 存的就是 Q 矩阵相关参数可以直接读进来用。4. 避坑与排查双目测距跑不通时先看这几条这套流程我前后搭过几次每次翻车的地方都不太一样但高频问题就那么几个。下面按「现象 → 原因 → 解决」列出来遇到问题先对照排查。现象一视差图全黑或者大片空洞。原因通常是 numDisparities 设得太小近处物体视差超出搜索范围或者左右图根本没校正对齐。解决先把 numDisparities 调到 168 甚至 1610 试再检查校正后的左右图同一行是否对齐没对齐就回头查标定参数和 stereoRectify 的输入顺序。现象二距离数值明显偏大或偏小但视差图看着正常。原因基本是标定参数单位不一致比如棋盘格实际是 25mm 但填了 25cm或者平移向量 T 读入时没按正确顺序。解决核对标定时填的方格尺寸和实际打印尺寸检查 T 向量的三个分量是否和 stereocalibrateresult.txt 一致单位统一后再算。现象三BM 匹配结果噪点极多像雪花屏。原因可能是 preFilterCap 和 textureThreshold 没调好弱纹理区域被强行匹配。解决适当提高 textureThreshold 让弱纹理区域不参与匹配同时加大 speckleWindowSize 滤掉孤立噪点。如果场景本身纹理就少BM 效果有限可以考虑换 SGBM但资源里给的是 BM先把 BM 参数吃透。现象四VS2019 编译报错找不到 opencv_world 或头文件。原因是 OpenCV 环境没配好包含目录、库目录、附加依赖项三处都要设。解决在项目属性里把 OpenCV 的 include 目录加到 C/C 常规lib 目录加到链接器常规opencv_world4xx.lib 加到附加依赖项。注意 Debug 和 Release 用的 lib 不一样别混。现象五MATLAB 标定导出的参数粘进 C 后结果全乱。原因是 MATLAB 矩阵是列主序还是行主序没搞清内参矩阵忘了转置。解决按 2.2 节的方式先转置再导出旋转矩阵 R 也要确认方向是从左到右还是从右到左搞反了校正图会明显错位。5. 进阶技巧把单帧测距改成能落地的验证流程单帧跑通只是第一步真正要验证这套系统靠不靠谱得做几件事。第一是拿已知距离的物体做标定验证比如把棋盘格放在离相机 1 米处看算出来的 Z 值偏差多少偏差在 2% 以内算合格。第二是换不同场景的左右图测试资源里那 13 组图可以轮流当测试集看参数泛化能力。第三是把 Q 矩阵和标定参数存成配置文件别硬编码在 cpp 里换相机时只改配置不动代码。// 把关键参数存成 YAML方便复用 FileStorage fs(stereo_config.yml, FileStorage::WRITE); fs K1 K1 D1 D1; fs K2 K2 D2 D2; fs R R T T Q Q; fs.release(); // 读取时 FileStorage fs2(stereo_config.yml, FileStorage::READ); fs2[K1] K1;参数说明YAML 存 Mat 时 OpenCV 会自动处理格式读回来直接就是 Mat比手写 txt 解析省事。注意 FileStorage 写中文路径可能出问题路径用英文。还有一个实用技巧是给视差图做后处理。BM 出来的视差图边缘毛刺多可以用中值滤波先平滑再用双边滤波保边。另外视差图里无效值视差为 0的区域深度值不可信取距离时最好在有效视差区域内取中位数而不是随便取一个点这样抗噪能力会好很多。Mat dispFiltered; medianBlur(disp8, dispFiltered, 5); // 只统计有效视差区域的深度中位数 vectorfloat validDepths; for (int i 0; i disp.rows; i) { for (int j 0; j disp.cols; j) { if (disp.atshort(i, j) 0) { Vec3f pt xyz.atVec3f(i, j); if (pt[2] 0 pt[2] 10000) // 过滤异常值 validDepths.push_back(pt[2]); } } } sort(validDepths.begin(), validDepths.end()); float medianDepth validDepths[validDepths.size()/2];逻辑说明先判断视差大于 0 才认为是有效匹配再过滤掉深度异常大或异常小的点最后取中位数。这样比单点取值稳得多尤其在纹理不均匀的场景里。我一般还会把有效视差的比例打印出来低于 30% 就说明这组图匹配质量不行得回头调 BM 参数或者换图。从那以后我每次拿到新的双目图都强制先跑一遍校正对齐检查再跑视差有效比例统计两个都过了才看距离数值。这套源码把标定、校正、匹配、测距的骨架都搭好了剩下的就是拿你自己的图去磨参数。希望帮到你。本文还有配套的精品资源点击获取
