多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OpenCV+VS2019+MATLAB双目测距实战:从标定到视差计算

OpenCV+VS2019+MATLAB双目测距实战:从标定到视差计算 简介这份资源面向计算机视觉初学者与需要完成课程设计、毕业设计的开发者提供一套基于OpenCV、VS2019与MATLAB的双目视觉测距完整实现。代码覆盖从相机标定、立体校正到利用BM局部匹配算法输出视差图的全流程可帮助读者理解双目测距的标定参数求解与视差计算原理并直接运行验证。压缩包共42个文件约1.16MB包含30张左右相机标定与校正前后的jpg图像、6个txt标定结果与参数文件、CameraCalibration.sln解决方案、cpp源码、vcxproj工程文件及项目说明md目录结构清晰便于按标定、校正、匹配三个阶段分步学习。目前已有661人学习下载适合作为视觉测距入门与工程实践的参考案例读者可据此复现标定流程、查看视差图效果并在此基础上替换自己的双目图像进行测距实验。1. 双目测距这套组合拳OpenCV 算视差VS2019 管工程MATLAB 做标定很多人第一次接触双目视觉测距脑子里想的是「两个摄像头一拍距离就出来了」真上手才发现卡住你的往往不是算法而是环境OpenCV 的 C 接口在 VS2019 里怎么配、相机标定参数从哪来、MATLAB 算出来的矩阵怎么喂进 C 工程。这套「OpenCV VS2019 MATLAB」的组合本质是把三件事拆开各干各的——MATLAB 负责离线标定和参数验证OpenCV 负责在线双目匹配与深度计算VS2019 负责把 C 工程和.sln解决方案组织起来让整个测距流程能编译、能调试、能复现。它解决的是「从两张图到一个真实距离」的完整链路问题适合做机器人避障、AGV 测距、工业料框定位、教学演示这类需要自己掌控每一行代码的从业者。如果你只想调个库拿结果那用现成的深度相机更省事但如果你要理解视差、基线、焦距三者怎么咬合或者要把测距嵌进自己的 C 系统里这套方案值得从头走一遍。下面按「先立住原理、再动手复现、最后讲坑」的顺序拆开讲。2. 双目测距的几何原理与三件套分工为什么不是随便两个相机就行2.1 视差、基线与焦距一个公式决定精度上限双目测距的核心公式只有一行Z (f * B) / d其中Z是目标到相机的深度距离f是相机焦距像素单位B是两个相机光心之间的基线距离毫米或米d是同一个物点在左右两幅图中的横坐标差也就是视差像素。这个公式看着简单但它决定了整套系统的精度天花板d越小Z越大而d的测量误差会被放大到Z上。举个具体数f 800像素B 60毫米当d 60像素时Z 800毫米如果视差测偏了 1 个像素Z就变成约 787 毫米误差 13 毫米。当目标远到d 6像素时同样 1 像素误差会让Z从 8000 毫米跳到约 6857 毫米误差超过 1 米。这就是为什么远距离测距必须靠增大基线或提高分辨率而不是靠调算法参数硬凑。理解这一点你就明白为什么标定必须做准f和B的误差是系统性偏差会整体拉偏所有距离而视差d的误差是随机性的靠匹配算法和滤波去压。MATLAB 的 Camera Calibrator 和 Stereo Camera Calibrator 就是用来把f、B以及畸变系数一次性标出来的工具标定板拍得够多、角度够散参数才稳。2.2 三件套各管一段MATLAB 标定、OpenCV 匹配、VS2019 组织把工具分工讲清楚后面配置才不会乱。MATLAB 这边做的是离线标定用棋盘格拍十几到几十组左右图跑stereoCameraCalibrator导出stereoParams里面包含左右内参矩阵、畸变系数、旋转矩阵R和平移向量T。T的第一个分量就是基线B单位跟标定板方格尺寸一致。标定完可以把参数存成.mat或直接打印成 C 能读的数组。OpenCV 这边做的是在线处理读左右图用initUndistortRectifyMap和remap做去畸变与极线校正让左右图的对应点在同一水平线上然后用StereoSGBM或StereoBM算视差图最后用reprojectImageTo3D把视差转成三维点云或者直接按公式取目标区域的平均视差算Z。VS2019 这边做的是工程组织新建 C 空项目或直接用.sln解决方案把 OpenCV 的include、lib、dll配好把 MATLAB 导出的参数写成头文件或配置文件让整个流程能一键编译运行。提示MATLAB 标定导出的T向量单位取决于你输入的方格尺寸。如果方格是 25 毫米T就是毫米如果写成 0.025 米T就是米。C 里算出来的Z单位跟着T走别混。2.3 从标定到测距的最小可复现路径先给一条能跑通的最小路径后面章节再展开细节。第一步在 MATLAB 里用stereoCameraCalibrator标定导出参数第二步在 VS2019 里建 C 工程配好 OpenCV第三步把参数写进代码读一对左右图做校正、算视差、取目标区域视差中值代入公式得距离。这条路径不依赖任何特定数据集你自己拍一对图就能验证。关键是把每一步的输出都打印出来看校正后的图有没有对齐、视差图有没有大面积空洞、目标区域的视差值是否稳定。只要这三步的输出肉眼可查后面调参就有方向。3. 在 VS2019 里把 OpenCV 工程配起来.sln解决方案的目录结构与属性表3.1 用属性表管理 OpenCV 路径别在每个项目里重复配VS2019 配 OpenCV 最常见的翻车方式是每新建一个项目就手动加一遍包含目录和库目录配到第三个项目自己都记不清哪个路径是对的。正确做法是用属性表.props在「属性管理器」里右键项目 → 添加新项目属性表命名比如opencv_release.props然后在里面统一配include、lib、附加依赖项。以后新建项目只要添加这个属性表路径一次配好处处生效。具体配置项如下表以 OpenCV 4.x 的build目录为例实际路径按你解压位置改配置项值示例说明包含目录D:\opencv\build\include头文件根目录包含目录D:\opencv\build\include\opencv2二级头文件目录库目录D:\opencv\build\x64\vc16\libVS2019 对应 vc16附加依赖项Debugopencv_world4xxd.lib带 d 是 Debug 版附加依赖项Releaseopencv_world4xx.lib不带 d 是 Release 版配完还要把D:\opencv\build\x64\vc16\bin加到系统PATH或者把对应的opencv_world4xx.dll拷到.exe同目录否则运行时会报「找不到 xxx.dll」。这一步是血泪经验编译通过不代表能跑dll不在路径里程序启动就崩。3.2.sln解决方案里放什么一个主工程加一个参数头文件一个干净的.sln解决方案不需要很多工程。常见做法是一个 C 控制台主工程负责读图、校正、匹配、算距离一个params.h头文件放 MATLAB 导出的标定参数如果要做可视化再加一个简单的显示模块。目录结构建议这样StereoRange.sln ├── StereoRange/ │ ├── main.cpp │ ├── stereo_params.h │ └── StereoRange.vcxproj └── opencv_release.propsstereo_params.h里放左右内参矩阵、畸变系数、R、T写成cv::Mat或double数组。这样换一组相机只要改这个头文件主逻辑不动。下面是一个参数头文件的写法示例// stereo_params.h #pragma once #include opencv2/opencv.hpp // 左相机内参 3x3 static double K_left_data[9] { 800.5, 0.0, 640.2, 0.0, 800.9, 360.4, 0.0, 0.0, 1.0 }; // 右相机内参 3x3 static double K_right_data[9] { 801.1, 0.0, 638.7, 0.0, 801.3, 359.8, 0.0, 0.0, 1.0 }; // 畸变系数 1x5 static double D_left_data[5] { -0.12, 0.03, 0.0005, -0.0002, 0.0 }; static double D_right_data[5] { -0.11, 0.02, 0.0004, -0.0001, 0.0 }; // 旋转矩阵 3x3 static double R_data[9] { 0.9999, 0.0012, -0.0031, -0.0011, 0.9999, 0.0022, 0.0030, -0.0021, 0.9999 }; // 平移向量 3x1单位与标定板方格一致 static double T_data[3] { -60.12, 0.35, 1.20 }; inline cv::Mat getKLeft() { return cv::Mat(3, 3, CV_64F, K_left_data).clone(); } inline cv::Mat getKRight() { return cv::Mat(3, 3, CV_64F, K_right_data).clone(); } inline cv::Mat getDLeft() { return cv::Mat(1, 5, CV_64F, D_left_data).clone(); } inline cv::Mat getDRight() { return cv::Mat(1, 5, CV_64F, D_right_data).clone(); } inline cv::Mat getR() { return cv::Mat(3, 3, CV_64F, R_data).clone(); } inline cv::Mat getT() { return cv::Mat(3, 1, CV_64F, T_data).clone(); }这段代码的逻辑是把标定结果硬编码成静态数组再用cv::Mat包装成 OpenCV 能用的矩阵。参数说明K_left、K_right是内参D_left、D_right是畸变R、T是右相机相对左相机的位姿。T_data[0]就是基线B取绝对值参与测距。用clone()是为了避免多个Mat共享同一块内存导致意外修改。换相机时只改这些数组主程序不用动。3.3 编译与运行Debug 和 Release 别混用VS2019 里最容易忽略的是 Debug 和 Release 的库不能混。Debug 配opencv_world4xxd.libRelease 配opencv_world4xx.lib属性表里最好建两份或者用条件宏区分。混用的典型现象是编译能过运行时报「模块计算机类型 x64 与目标计算机类型 x86 冲突」或者直接崩溃。另外OpenCV 的vc16目录对应 VS2019如果你用的是 VS2022 要换vc17这个对应关系别搞错。编译前确认解决方案平台是x64因为现在 OpenCV 预编译包基本都是 64 位选Win32会链接失败。4. MATLAB 标定与参数导出从棋盘格到 C 能读的矩阵4.1 拍标定图的三个硬要求MATLAB 的stereoCameraCalibrator对标定图有要求拍不好后面全白搭。第一棋盘格要平整打印后贴在硬板上别拿在手里弯着拍第二左右图必须同步如果两个相机是分开触发的运动中的标定板会导致左右图不对应标定直接废掉第三标定板要覆盖画面不同区域和不同角度至少 15 到 20 组有效图倾斜、远近、边角都要有。常见做法是固定双目相机移动标定板这样比移动相机更容易保证左右同步。拍完把左右图分别放到两个文件夹命名对应比如left_01.png和right_01.png。MATLAB 里读入后标定器会自动检测角点检测失败的图要剔除别硬留着否则参数会被带偏。4.2 导出参数并转成 C 数组标定完成后在 MATLAB 命令窗口导出stereoParams然后打印关键矩阵% 假设 stereoParams 已由 stereoCameraCalibrator 导出 K_left stereoParams.CameraParameters1.IntrinsicMatrix; K_right stereoParams.CameraParameters2.IntrinsicMatrix; D_left stereoParams.CameraParameters1.RadialDistortion; D_right stereoParams.CameraParameters2.RadialDistortion; R stereoParams.RotationOfCamera2; T stereoParams.TranslationOfCamera2; disp(K_left ); disp(K_left); disp(K_right ); disp(K_right); disp(D_left ); disp(D_left); disp(D_right ); disp(D_right); disp(R ); disp(R); disp(T ); disp(T);这里有个关键点MATLAB 的内参矩阵默认是转置形式IntrinsicMatrix转置后才和 OpenCV 的K一致RotationOfCamera2和TranslationOfCamera2也要转置或调整方向确保R、T表示的是「右相机相对左相机」的变换。T的第一个分量就是基线单位跟你输入的方格尺寸一致。把打印出来的数按第 3 章的stereo_params.h格式填进去注意 MATLAB 默认列优先填 C 数组时按行填别填反。注意MATLAB 的畸变系数顺序和 OpenCV 不完全一样。MATLAB 的RadialDistortion通常只有径向项OpenCV 的D是[k1, k2, p1, p2, k3]。如果 MATLAB 没给切向畸变p1、p2填 0k3也填 0别硬凑。4.3 用 MATLAB 先验证参数再进 C别急着写 C先在 MATLAB 里用标定参数做一次校正和测距确认参数没问题。MATLAB 有rectifyStereoImages和disparitySGM可以快速看校正效果和视差图。如果 MATLAB 里校正后左右图水平对齐、视差图连续那参数就是对的如果 MATLAB 里都不对进 C 只会更难查。这一步是后悔药花十分钟在 MATLAB 验证能省掉在 VS2019 里调半天的痛苦。5. OpenCV 算视差与测距SGBM 参数怎么调、目标距离怎么取5.1 校正与 SGBM 的完整代码骨架下面是一段能跑通的主流程代码读左右图、校正、算视差、取目标区域距离#include opencv2/opencv.hpp #include iostream #include stereo_params.h int main() { cv::Mat K_left getKLeft(), D_left getDLeft(); cv::Mat K_right getKRight(), D_right getDRight(); cv::Mat R getR(), T getT(); cv::Size img_size(1280, 720); // 与标定图分辨率一致 // 1. 计算校正映射 cv::Mat R1, R2, P1, P2, Q; cv::stereoRectify(K_left, D_left, K_right, D_right, img_size, R, T, R1, R2, P1, P2, Q, cv::CALIB_ZERO_DISPARITY, 0, img_size); cv::Mat map1x, map1y, map2x, map2y; cv::initUndistortRectifyMap(K_left, D_left, R1, P1, img_size, CV_32FC1, map1x, map1y); cv::initUndistortRectifyMap(K_right, D_right, R2, P2, img_size, CV_32FC1, map2x, map2y); // 2. 读图并校正 cv::Mat left cv::imread(left.png); cv::Mat right cv::imread(right.png); cv::Mat left_rect, right_rect; cv::remap(left, left_rect, map1x, map1y, cv::INTER_LINEAR); cv::remap(right, right_rect, map2x, map2y, cv::INTER_LINEAR); // 3. SGBM 算视差 int num_disp 16 * 8; // 必须是 16 的倍数 int block_size 5; // 奇数3~11 之间 cv::Ptrcv::StereoSGBM sgbm cv::StereoSGBM::create( 0, num_disp, block_size, 8 * block_size * block_size, // P1 32 * block_size * block_size, // P2 1, 0, 10, 100, 32, cv::StereoSGBM::MODE_SGBM_3WAY); cv::Mat disp; sgbm-compute(left_rect, right_rect, disp); // 4. 视差转真实值并取目标区域 cv::Mat disp_f; disp.convertTo(disp_f, CV_32F, 1.0 / 16.0); // SGBM 输出是 16 倍 double f P1.atdouble(0, 0); // 校正后焦距 double B std::abs(T.atdouble(0, 0)); // 基线 // 取图像中心区域视差中值作为目标距离 cv::Rect roi(560, 320, 160, 80); cv::Mat roi_disp disp_f(roi); std::vectorfloat vals; for (int y 0; y roi_disp.rows; y) for (int x 0; x roi_disp.cols; x) { float d roi_disp.atfloat(y, x); if (d 0) vals.push_back(d); } if (vals.empty()) { std::cout 无有效视差 std::endl; return -1; } std::sort(vals.begin(), vals.end()); float d_med vals[vals.size() / 2]; double Z (f * B) / d_med; std::cout 视差中值: d_med 距离: Z std::endl; return 0; }逻辑说明先用stereoRectify算出校正映射让左右图极线水平remap做实际校正StereoSGBM算视差输出是 16 倍定点数要除以 16最后取目标区域视差中值代入公式。参数说明num_disp是视差搜索范围必须是 16 的倍数太小会截断近处视差太大会增加计算量和误匹配block_size是匹配块大小奇数越大越平滑但边缘越糊P1、P2是平滑惩罚常用8*block_size^2和32*block_size^2。f取校正后P1的(0,0)不是原始内参这点容易搞错。5.2 SGBM 三个必调参数num_disp、block_size、P2num_disp决定能测多近。如果目标很近视差可能超过num_disp视差图对应区域会变成空洞或错误值。判断方法看视差图里目标区域是不是一片黑或一片噪点如果是先把num_disp加倍试。block_size决定匹配的鲁棒性纹理弱的表面白墙、金属需要大块但大块会让深度边缘变钝。P2控制平滑强度P2越大视差图越平滑但过大会把真实的前后景边界抹掉。常见调法是先固定block_size5调num_disp到目标区域有稳定视差再微调P2看边缘是否可接受。5.3 目标距离怎么取才稳区域中值加有效性过滤直接取单个像素的视差算距离结果会跳得厉害。稳的做法是框一个目标区域收集有效视差大于 0排序取中值。中值比均值抗噪因为误匹配的极端值不会把结果拉偏。如果目标区域里有效视差太少比如少于 30%说明匹配失败这时候要么调 SGBM 参数要么检查校正是否对齐。还可以加一步左右一致性检查把左右视差差异大的点剔除进一步压误匹配。取距离时注意Z的单位跟B一致如果B是毫米Z就是毫米。6. 避坑与排查双目测距工程里最容易翻车的五件事6.1 校正后左右图没对齐视差图全是噪点现象remap之后左右图同一物体不在同一水平线上SGBM 输出的视差图几乎全是随机噪点。原因通常是R、T方向搞反或者stereoRectify的输入内参没转置。MATLAB 导出的R、T表示右相对左如果代码里当成左相对右校正就会错。解决在 MATLAB 里用rectifyStereoImages验证确认参数方向C 里打印R1、R2、P1、P2和 MATLAB 对应输出比对。另外确认img_size和标定图分辨率一致不一致会导致映射错位。6.2 编译通过但运行报找不到 dll现象VS2019 里编译成功一运行就弹窗「找不到 opencv_world4xx.dll」。原因是dll不在可执行文件搜索路径里。解决把opencv\build\x64\vc16\bin加到系统PATH或者把对应dll拷到.exe同目录。Debug 用带d的dllRelease 用不带d的别拷错。改完PATH要重启 VS2019 才生效这个细节很多人漏掉。6.3 视差图近处大片空洞现象目标离相机很近时视差图对应区域是黑的算不出距离。原因是num_disp太小近处视差超出了搜索范围。解决把num_disp调大比如从 64 调到 128 或 256代价是计算量增加。也可以先估算最近工作距离对应的视差d f*B/Z_min确保num_disp大于这个值。如果调大后还是空洞检查目标表面是不是反光或纯白这类表面本身难匹配需要补纹理或换算法。6.4 MATLAB 参数填进 C 后距离整体偏大或偏小现象测出来的距离和卷尺量的差一个固定比例。原因通常是T的单位和预期不一致或者f用了原始内参而不是校正后的P1(0,0)。MATLAB 标定时方格尺寸输入 25 毫米T就是毫米如果代码里按米理解距离就差 1000 倍。解决确认标定输入的方格尺寸单位确认f取自P1而不是K_left。校正后焦距可能和原始内参略有不同用错会带来比例误差。6.5 Debug 和 Release 结果不一致现象同一对图Debug 下距离正常Release 下距离跳变或崩溃。原因可能是 Debug 和 Release 链接了不同版本的 OpenCV 库或者代码里有未初始化变量在 Release 下被优化出不同行为。解决检查属性表里 Debug 和 Release 的附加依赖项是否分别对应带d和不带d的lib用/W4打开警告把未初始化变量清掉。SGBM 本身在两种模式下结果应该一致如果不一致先查库版本再查内存越界。7. 把测距精度再压一压亚像素视差与多帧中值滤波前面跑通的是像素级视差精度受限于 1 个像素。想把距离误差再压一半有两个具体技巧可以叠加用。第一个是亚像素视差SGBM 输出的是整数视差除以 16 后是 0.0625 的步进但实际有效精度还是受匹配块影响可以在目标区域对视差值做抛物线拟合用相邻三个视差值插值出亚像素峰值。做法是取目标区域视差中值附近的点按d_sub d (I(d-1) - I(d1)) / (2*(I(d-1) - 2*I(d) I(d1)))插值其中I是匹配代价或视差置信度。这一步能把视差精度提到 0.1 到 0.3 像素对应距离误差按比例缩小。第二个是多帧中值滤波连续采 5 到 10 帧每帧算一个目标区域视差中值再对这些中值取中值。这样能压掉单帧的随机误匹配。实测在静态场景下单帧距离波动可能有几毫米到十几毫米多帧中值后能稳定到 1 到 2 毫米近距离。注意多帧之间目标不能动动了就要重新框区域。技巧作用代价适用场景亚像素插值视差精度提到 0.1~0.3 像素增加少量计算静态或慢速目标多帧中值压随机误匹配需要多帧缓存静态场景左右一致性检查剔除误匹配点计算量翻倍纹理复杂场景增大基线整体提升远距离精度硬件改动远距离测距我自己的习惯是先保证校正和标定没错再调 SGBM 到目标区域视差稳定最后才上亚像素和多帧。顺序反了会浪费时间因为标定不准的时候亚像素插值只会把错误值插得更精细。这套组合拳里MATLAB 标定是地基VS2019 工程是骨架OpenCV 匹配是血肉哪一层虚了测出来的距离都会飘。希望帮到你。本文还有配套的精品资源点击获取
返回列表