资讯动态

OpenCV双目测距实战:标定、BM匹配与深度计算全流程

发布时间:2026/10/1 17:24:24 来源:尧图企业网站定制
简介这是一套面向计算机视觉初学者与嵌入式视觉开发者的双目测距完整工程基于OpenCV、VS2019与MATLAB联合实现重点解决从相机标定到视差图输出的全流程问题。资源包共42个文件约1.16MB包含30张左右相机标定与校正图像、6个标定参数与图像列表文本、CameraCalibration.sln解决方案、CameraCalibration.cpp核心源码及vcxproj工程文件另附项目说明文档便于直接编译运行与对照理解。代码中实现了相机标定、立体校正以及基于BM局部匹配算法输出视差图的功能并给出校正前后左右相机图像与标定结果文件方便读者验证每一步中间结果。目前已有661人学习下载适合需要快速搭建双目视觉测距实验环境、理解标定与立体匹配参数含义、并在此基础上继续扩展SGBM或深度计算的学习者参考。1. 从一组左右图到真实距离这套双目测距源码到底能跑出什么很多人第一次接触双目视觉测距脑子里想的是「两个摄像头拍同一场景算一下像素差就能出深度」真上手才发现卡在第一步——标定。棋盘格拍了一堆角点检测要么漏检要么错位标定出来的重投影误差大得离谱后面视差图全是噪点测出来的距离跟实际差出好几倍。这套基于 OpenCV VS2019 MATLAB 的双目视觉测距系统源码解决的正是从标定到视差输出的完整链路问题。它用 C 在 VS2019 里跑 OpenCV 的标定和 BM 局部匹配算法MATLAB 负责标定结果的可视化验证压缩包里带了 13 组左右相机标定图、标定结果文本、校正前后对比图还有完整的 sln 解决方案。适合正在做双目测距课程设计、毕业设计或者想把双目测距模块集成进自己项目的 C 开发者。你拿到手不是一堆散代码而是一个能直接编译、能对照中间结果排查问题的工程。2. 标定链路拆解从棋盘格到 stereoRectifyParams.txt2.1 为什么标定结果要落成文本文件这套源码有个很实在的设计标定不是跑完就完事而是把左右相机的内参、畸变系数、立体标定参数分别写进calibrationresults_L.txt、calibrationresults_R.txt、stereocalibrateresult.txt和stereoRectifyParams.txt。很多人做双目测距时标定和测距写在一个 main 里标定一改就得重跑整个流程调试起来非常痛苦。把标定结果落盘之后测距阶段直接读文件标定和测距解耦改测距参数不用重新标定。常见做法是先用calibrateCamera分别标左右相机再用stereoCalibrate做立体标定最后stereoRectify算校正参数。这套源码的 CameraCalibration.cpp 就是按这个顺序走的。标定结果文本里一般包含相机矩阵M、畸变向量D、旋转矩阵R、平移向量T、本质矩阵E、基础矩阵F以及校正后的投影矩阵P1、P2和重投影矩阵Q。Q矩阵是后面reprojectImageTo3D出三维坐标的关键很多人只存了M和D到测距那步发现算不出真实尺度就是漏了Q。2.2 标定代码的关键参数与调用顺序下面这段是标定流程的核心骨架参数按源码里的实际调用整理// 左右相机图像路径列表源码里用 caliberationpics_L.txt / caliberationpics_R.txt 存 std::vectorstd::string leftPaths readList(caliberationpics_L.txt); std::vectorstd::string rightPaths readList(caliberationpics_R.txt); // 棋盘格内角点数源码用的 9x6注意是内角点不是格子数 cv::Size boardSize(9, 6); std::vectorstd::vectorcv::Point2f imgPointsL, imgPointsR; std::vectorstd::vectorcv::Point3f objPoints; // 世界坐标系下的棋盘格角点Z0 std::vectorcv::Point3f obj; for (int i 0; i boardSize.height; i) for (int j 0; j boardSize.width; j) obj.push_back(cv::Point3f(j * squareSize, i * squareSize, 0)); for (size_t i 0; i leftPaths.size(); i) { cv::Mat imgL cv::imread(leftPaths[i], cv::IMREAD_GRAYSCALE); cv::Mat imgR cv::imread(rightPaths[i], cv::IMREAD_GRAYSCALE); std::vectorcv::Point2f cornersL, cornersR; // 亚像素角点检测winSize 和零区域参数直接影响角点精度 bool foundL cv::findChessboardCorners(imgL, boardSize, cornersL, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE); bool foundR cv::findChessboardCorners(imgR, boardSize, cornersR, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE); if (foundL foundR) { cv::cornerSubPix(imgL, cornersL, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::EPS cv::TermCriteria::MAX_ITER, 30, 0.001)); cv::cornerSubPix(imgR, cornersR, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::EPS cv::TermCriteria::MAX_ITER, 30, 0.001)); imgPointsL.push_back(cornersL); imgPointsR.push_back(cornersR); objPoints.push_back(obj); } } // 单目标定 cv::Mat M1, D1, M2, D2; cv::calibrateCamera(objPoints, imgPointsL, imgL.size(), M1, D1, cv::noArray(), cv::noArray()); cv::calibrateCamera(objPoints, imgPointsR, imgR.size(), M2, D2, cv::noArray(), cv::noArray()); // 立体标定 cv::Mat R, T, E, F; cv::stereoCalibrate(objPoints, imgPointsL, imgPointsR, M1, D1, M2, D2, imgL.size(), R, T, E, F, cv::CALIB_FIX_INTRINSIC); // 立体校正 cv::Mat R1, R2, P1, P2, Q; cv::stereoRectify(M1, D1, M2, D2, imgL.size(), R, T, R1, R2, P1, P2, Q);逻辑说明findChessboardCorners的boardSize是内角点数9x6 对应 10x7 的格子这个搞反了角点检测直接失败。cornerSubPix的winSize(11,11)是搜索窗口图像分辨率高的时候可以适当加大但太大反而会把角点拉偏。stereoCalibrate里用了CALIB_FIX_INTRINSIC意思是单目标定已经算好了内参立体标定阶段不再优化内参只算R和T。这个标志在标定图数量不多的时候很关键放开内参一起优化容易过拟合。参数说明squareSize是棋盘格实际方格边长单位毫米这个值直接决定后面测距的物理尺度。源码里没写死需要你按自己打印的棋盘格改。calibrateCamera返回的重投影误差要盯着看一般控制在 0.5 像素以内算合格超过 1 像素说明标定图质量有问题后面视差图基本没法用。2.3 MATLAB 在校正验证里的角色压缩包里带了校正前左右相机图像.jpg、校正后左右相机图像.jpg、左相机校正图像.jpg、右相机校正图像.jpg这些是 MATLAB 脚本跑出来的可视化结果。MATLAB 在这套流程里不参与实时测距它的价值是快速验证标定参数对不对。常见做法是把stereoRectifyParams.txt里的R1、R2、P1、P2读进 MATLAB用rectifyStereoImages或者手动做映射看校正后左右图的极线是不是水平对齐。如果校正后左右图的对应点还在同一行上对不齐说明标定参数有问题这时候回去查标定图比在 C 里盲调效率高得多。MATLAB 验证还有一个好处可以叠加棋盘格角点的重投影位置直观看到哪个角点偏了。C 里只能看误差数值MATLAB 能画出来。这套源码把 MATLAB 定位成离线验证工具不掺和实时链路这个分工是合理的。3. BM 局部匹配出视差图参数怎么调才不翻车3.1 BM 算法的参数敏感点BMBlock Matching是 OpenCV 里最直接的立体匹配算法速度快但参数敏感。源码里用 BM 输出视差图核心参数就那么几个但每个都能让结果天差地别。cv::Ptrcv::StereoBM bm cv::StereoBM::create(16 * 5, 21); // numDisparities 必须是 16 的整数倍源码用 80 // blockSize 必须是奇数源码用 21 bm-setPreFilterCap(31); // 预处理滤波截断值默认 31 bm-setBlockSize(21); // 匹配块大小奇数越大越平滑但细节丢失 bm-setMinDisparity(0); // 最小视差一般从 0 开始 bm-setNumDisparities(80); // 视差搜索范围16 的倍数 bm-setTextureThreshold(10); // 纹理阈值低于此值的区域不计算视差 bm-setUniquenessRatio(15); // 唯一性比率过滤误匹配 bm-setSpeckleWindowSize(100); // 斑点窗口大小过滤小连通区域 bm-setSpeckleRange(32); // 斑点范围 bm-setDisp12MaxDiff(1); // 左右视差一致性检查最大差值 cv::Mat disp, disp8; bm-compute(imgL_rect, imgR_rect, disp); disp.convertTo(disp8, CV_8U, 255 / (16 * 5)); // 归一化到 0-255 显示逻辑说明numDisparities决定搜索范围设小了近处物体视差超出范围会丢设大了远处匹配噪声变多。源码用 80对应大概能测到 1 米以内的场景具体要看基线距离和焦距。blockSize越大视差图越平滑但边缘越糊21 是个偏大的值适合纹理弱的场景纹理丰富的话可以降到 11 或 15。textureThreshold是很多人忽略的参数纹理弱的区域比如白墙算出来的视差全是错的设个阈值直接跳过这些区域比后面滤波管用。参数说明uniquenessRatio控制唯一性检查值越大过滤越狠误匹配少但有效视差也少15 是常用起点。speckleWindowSize和speckleRange是一对用来去掉视差图里的小斑点窗口设 100 左右能去掉大部分孤立噪点。disp12MaxDiff做左右一致性检查设 1 表示左右视差差超过 1 像素就标记为无效这个对去除遮挡区域的误匹配很有效。3.2 从视差图到深度Q 矩阵和 reprojectImageTo3D视差图出来只是中间结果要得到真实距离还得用reprojectImageTo3D配合Q矩阵。cv::Mat xyz; cv::reprojectImageTo3D(disp, xyz, Q, true); // xyz 是三通道浮点图每个像素存 (X, Y, Z)Z 就是深度 // 取图像中心点的深度值 cv::Point center(disp.cols / 2, disp.rows / 2); cv::Vec3f point3D xyz.atcv::Vec3f(center); float depth point3D[2]; // 单位与标定时 squareSize 一致通常是毫米逻辑说明reprojectImageTo3D内部就是用Q矩阵做齐次变换disp里无效视差值为 0 或负对应的三维点会是很大的值取深度前要先判断视差是否有效。handleMissingValues参数设true会把无效视差对应的点设成很大的数方便过滤。参数说明Q矩阵来自stereoRectify是 4x4 矩阵。如果测出来的深度整体偏大或偏小先检查squareSize和Q矩阵里的T分量是否一致。常见错误是标定用毫米测距时忘了单位换算结果差 1000 倍。3.3 校正映射initUndistortRectifyMap 和 remapBM 匹配前必须先把左右图校正到极线水平对齐这一步用initUndistortRectifyMap加remap。cv::Mat mapLx, mapLy, mapRx, mapRy; cv::initUndistortRectifyMap(M1, D1, R1, P1, imgL.size(), CV_32FC1, mapLx, mapLy); cv::initUndistortRectifyMap(M2, D2, R2, P2, imgR.size(), CV_32FC1, mapRx, mapRy); cv::Mat imgL_rect, imgR_rect; cv::remap(imgL, imgL_rect, mapLx, mapLy, cv::INTER_LINEAR); cv::remap(imgR, imgR_rect, mapRx, mapRy, cv::INTER_LINEAR);逻辑说明initUndistortRectifyMap把畸变校正和极线校正合并成一张映射表remap按表采样。映射表只需要算一次之后每帧直接remap这是实时测距的标准做法。INTER_LINEAR是双线性插值速度和质量平衡追求质量可以用INTER_CUBIC但会慢不少。参数说明P1、P2是校正后的投影矩阵来自stereoRectify。如果校正后图像边缘出现黑边是正常的因为校正会做旋转和裁剪。黑边区域不要参与匹配可以在remap后加个掩码。4. 避坑与排查标定和匹配里最容易翻车的五件事4.1 角点检测失败或角点顺序错乱现象findChessboardCorners返回 false或者返回 true 但角点顺序跟世界坐标对不上标定误差巨大。原因棋盘格打印不平整、光照不均、棋盘格在图像里太小或太偏都会导致角点检测失败。角点顺序错乱通常是棋盘格被部分遮挡或者图像畸变太严重。解决标定图里棋盘格要占图像面积的三分之一以上尽量放在图像中心区域。打印的棋盘格贴在硬板上别用软纸。如果还是检测不到换cv::CALIB_CB_FAST_CHECK标志先快速筛一遍或者手动点角点。角点顺序问题可以在检测后画出来看一眼drawChessboardCorners能直观看到顺序对不对。4.2 重投影误差大但不知道哪张图有问题现象calibrateCamera返回的总体误差 1.5 像素但不知道是哪几张标定图拖后腿。原因标定图里混了几张角点检测不准的总体误差被拉高。解决calibrateCamera的perViewErrors输出参数会返回每张图的误差逐张看误差明显偏大的那张删掉重新标定。这套源码的标定图有 13 组删掉一两组不影响。MATLAB 里也可以把每张图的重投影角点画出来对比偏得明显的直接剔除。4.3 视差图大面积黑色或全是噪点现象BM 输出的视差图要么大片黑色无视差要么全是雪花噪点。原因大面积黑色通常是numDisparities设小了近处物体视差超出搜索范围或者textureThreshold设太高纹理弱的区域全被跳过。全是噪点一般是blockSize太小加上uniquenessRatio太低误匹配没过滤掉。解决先调numDisparities从 64 开始往上加看黑色区域是否减少。然后降textureThreshold到 5 左右让更多区域参与匹配。噪点多就把blockSize加到 21 以上uniquenessRatio提到 15 到 20speckleWindowSize设 100 以上。这几个参数要联动调单独改一个往往顾此失彼。4.4 测距结果整体偏大或偏小现象视差图看着正常但reprojectImageTo3D出来的深度跟实际距离差一个固定倍数。原因squareSize和Q矩阵里的平移分量单位不一致或者标定时棋盘格实际尺寸填错了。解决确认标定时squareSize用的单位如果是毫米Q矩阵里的T也是毫米深度就是毫米。如果标定时填的是厘米但实际按毫米算结果差 10 倍。重新标定前拿卡尺量一下棋盘格实际边长别信打印时标注的尺寸打印机缩放很常见。4.5 VS2019 编译报 OpenCV 链接错误现象VS2019 里编译报LNK2019未解析的外部符号全是 OpenCV 函数。原因OpenCV 库路径没配全或者 Debug/Release 配置混了。VS2019 的 OpenCV 配置要分 Debug 和 Release 两套opencv_world4xxd.lib是 Debug 版opencv_world4xx.lib是 Release 版混用就报链接错误。解决在项目属性里检查VC 目录的包含目录和库目录Debug 配置链接带d后缀的 libRelease 配置链接不带d的。环境变量Path里也要加上 OpenCV 的bin目录否则运行时报找不到 dll。这套源码的 sln 里已经配好了但换机器后 OpenCV 安装路径变了要重新改属性表。5. 进阶技巧用 MATLAB 反推标定质量与视差调参的联动方法标定和匹配调参最头疼的是「改了参数不知道哪步出了问题」。我一般会先用 MATLAB 把标定质量量化再回到 C 调匹配参数这样每一步都有依据。具体做法是在 MATLAB 里读calibrationresults_L.txt和calibrationresults_R.txt用cameraParameters对象重建相机模型然后把标定图重新投影一遍计算每张图的重投影误差。MATLAB 的estimateCameraParameters能直接输出ReprojectionErrors比 C 里手动算方便。误差超过 0.5 像素的图标记出来回 C 里剔除后重新标定。标定质量确认后视差调参也有个联动方法在 MATLAB 里对同一组校正后图像跑disparitySGM或者disparityBM快速试不同DisparityRange和BlockSize找到合适的参数区间再把这些参数映射到 C 的 BM 设置。MATLAB 调参不用编译改个数字就能看结果比在 VS2019 里反复编译快得多。参数区间确定后C 里按这个区间微调基本一两轮就能出可用的视差图。这里有个参数对照表是我自己调参时整理的MATLAB 参数OpenCV BM 对应参数调整方向DisparityRangenumDisparities近处丢视差就加大16 的倍数BlockSizeblockSize噪点多加大边缘糊减小奇数UniquenessThresholduniquenessRatio误匹配多加大有效视差少减小TextureThresholdtextureThreshold弱纹理区域误匹配多就加大ContrastThresholdpreFilterCap光照不均时调整预处理截断还有一点MATLAB 的rectifyStereoImages和 OpenCV 的initUndistortRectifyMap结果应该一致如果两边校正后图像对不齐说明R1、R2、P1、P2的读取或转置出了问题。常见错误是 MATLAB 读文本时行列读反了R1是 3x3转置后校正结果完全不对。我一般会在 MATLAB 里把R1打印出来跟文本文件逐元素对一遍确认没读错再往下走。从那以后我每次拿到新的双目标定数据都强制先用 MATLAB 把重投影误差和校正对齐过一遍确认标定没问题再进 C 调匹配。这个习惯帮我省了大量在 VS2019 里反复编译排查的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑