相关文章推荐
玩手机的饭卡  ·  javascript - ...·  1 年前    · 
失恋的鞭炮  ·  教你如何成为Java OOM ...·  1 年前    · 
博学的圣诞树  ·  c# ...·  1 年前    · 

http://blog.csdn.net/wangchao7281/article/details/52506691?locationNum=12&fps=1

这篇文章对相机标定流程描述的还是很到位的。

本文采用MATLAB标定工具箱和OpenCV3.10来实现双目测距,设备为两个CMOS工业相机和相应的双目云台。
首先感谢CSDN上两位大神前辈邹宇华和scyscyao,虽然是六年前的博客, OpenCV 也从1.0的版本更新到了3.1版本,但博客对机器视觉初学者来说仍然提供了巨大的帮助。本文主要参考了 OpenCV 学习笔记(17)双目测距与三维重建的 opencv 实现问题集锦(二)双目定标与双目校正、OpenCV学习笔记(18)双目测距与三维重建的OpenCV实现问题集锦(三)立体匹配与视差计算、分享一些OpenCV实现立体视觉的经验、双摄像头测距的OpenCV实现、学习笔记:使用opencv做双目测距(相机标定+立体匹配+测距).、OPENCV3.0 双目立体标定等文章,并摘录了部分内容。
由于初学机器视觉,有些问题尚不是很清楚,文中不免存在错误,还请观者指正。
这里写图片描述

一、双目标定
双目摄像头标定不仅要得出每个摄像头的内部参数,还需要通过标定来测量两个摄像头之间的相对位置(即右摄像头相对于左摄像头的平移向量 t和旋转矩阵R)。
由于OpenCV中StereoCalibrate标定的结果极其不稳定,甚至会得到很夸张的结果,所以最后还是决定Matlab标定工具箱立体标定,再将标定的结果读入OpenCV,来进行后续图像校准和匹配。
首先对左右摄像头分别进行标定,得到两个摄像头各自的内参矩阵和畸变参数向量。下图是单目标定的结果示意图和反投影误差分析图。
这里写图片描述
这里写图片描述

左右摄像头都标定完成之后,就可以利用单目标定得到的两个摄像头的参数进行立体标定。下图是双目标定的结果示意图。
这里写图片描述

注意事项:
1. 采集棋盘图的时候要注意,尽量让棋盘占据尽可能多的画面,这样可以得到更多有关摄像头畸变方面的信息。
2. 在标定工具箱中,可以通过reproject on images,得到根据当前标定结果得到的反投影误差,从点云的聚集情况和分散的最大范围可以看出反投影误差的大小。Recomp. corners选项,主要完成根据反向投影得到的角点坐标重作为对角点的估计,重新计算角点的功能。针对第一次标定结果误差太大的情况,可以通过此方法重新计算焦点。计算完成后,点击Calibration根据新的焦点进行标定。此时,得到的标定信息,比第一次得到的反投影误差分布更集中,直径也小。(该步骤在标定过程中需谨慎使用,因为往往首次得到的三维坐标精确度并不高,如果参考误差较大的话,有可能使结果与正解偏差更大。)
3. 两个摄像头的焦距应该保持一致,因为在后续的视差图转换为三维图时的Q矩阵只有一个f值。所以必须要求至少焦距相近。而且立体成像的三角测量(Learning OpenCV书中提到)的前提假设就是fl=fr。(调整两个摄像头的焦距相同的方法:离两个相机相同远处放置标定板,分别调节两个相机的焦距,使得两个画面的清晰度相似。)

二、双目校正
要计算目标点在左右两个视图上形成的视差,首先要把该点在左右视图上两个对应的像点匹配起来。然而,在二维空间上匹配对应点是非常耗时的,为了减少匹配搜索范围,我们可以利用极线约束使得对应点的匹配由二维搜索降为一维搜索。而双目校正的作用就是要把消除畸变后的两幅图像严格地行对应,使得两幅图像的对极线恰好在同一水平线上,这样一幅图像上任意一点与其在另一幅图像上的对应点就必然具有相同的行号,只需在该行进行一维搜索即可匹配到对应点。
这里写图片描述

经过双目标定得到摄像头的各项参数后,采用OpenCV中的stereoRectify得到校正旋转矩阵R、投影矩阵P、重投影矩阵Q,再采用initUndistortRectifyMap函数得出校准映射参数,然后用remap来校准输入的左右图像。其中remap的图像剪裁系数alpha,取值范围是-1、0~1。当取值为 0 时,OpenCV会对校正后的图像进行缩放和平移,使得remap图像只显示有效像素(即去除不规则的边角区域),适用于 机器人 避障导航等应用;当alpha取值为1时,remap图像将显示所有原图像中包含的像素,该取值适用于畸变系数极少的高端摄像头;alpha取值在0-1之间时,OpenCV按对应比例保留原图像的边角区域像素。Alpha取值为-1时,OpenCV自动进行缩放和平移。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

二、立体匹配
采用Block Matching 算法 进行立体匹配,Block Matching用的是SAD方法,速度比较快,但效果一般。
参数设置:
MinDisparity设置为0,因为两个摄像头是前向平行放置,相同的物体在左图中一定比在右图中偏右。如果为了追求更大的双目重合区域而将两个摄像头向内偏转的话,这个参数是需要考虑的。
UniquenessRatio主要可以防止误匹配,此参数对于最后的匹配结果是有很大的影响。立体匹配中,宁愿区域无法匹配,也不要误匹配。如果有误匹配的话,碰到障碍检测这种应用,就会很麻烦。该参数不能为负值,一般5-15左右的值比较合适,int型。
BlockSize:SAD窗口大小,容许范围是[5,255],一般应该在 5x5..21x21 之间,参数必须为奇数值, int型。
NumDisparities:视差窗口,即最大视差值与最小视差值之差,窗口大小必须是 16的整数倍,int型。
在BM 算法 的参数中,对视差生成效果影响较大的主要参数是BlockSize、NumDisparities和UniquenessRatio三个,一般只需对这三个参数进行调整,其余参数按默认设置即可。
双目测距的原理如下图所示。 这里写图片描述
这里写图片描述
BM算法计算出的视差disp是CV_16S格式,通过disp.convertTo(disp8, CV_8U, 255/(numberOfDisparities*16.))变换才能得到真实的视差值。
然后通过reprojectImageTo3D这个函数将视差矩阵转换成实际的物理坐标矩阵。在实际求距离时,reprojectImageTo3D出来的X / W, Y / W, Z / W都要乘以16(也就是W除以16),才能得到正确的三维坐标信息。
这里写图片描述
这里写图片描述
这里写图片描述
三个物体分别放置在距离摄像头40cm,50cm和70cm处,通过双目测距测出来的距离基本能接受。

鉴于最近咨询的朋友较多,我也是OpenCV新手,很多问题也还没太弄明白,在此将我的代码(不包括相机标定部分,我用MATLAB标定的)粘在这里,希望能对大家有所帮助。我的编程能力较弱,请轻拍。

/******************************/
/*        立体匹配和测距        */
/******************************/
#include <opencv2/opencv.hpp>  
#include <iostream>  
using namespace std;
using namespace cv;
const int imageWidth = 800;                             //摄像头的分辨率  
const int imageHeight = 600;
Size imageSize = Size(imageWidth, imageHeight);
Mat rgbImageL, grayImageL;
Mat rgbImageR, grayImageR;
Mat rectifyImageL, rectifyImageR;
Rect validROIL;//图像校正之后,会对图像进行裁剪,这里的validROI就是指裁剪之后的区域  
Rect validROIR;
Mat mapLx, mapLy, mapRx, mapRy;     //映射表  
Mat Rl, Rr, Pl, Pr, Q;              //校正旋转矩阵R,投影矩阵P 重投影矩阵Q
Mat xyz;              //三维坐标
Point origin;         //鼠标按下的起始点
Rect selection;      //定义矩形选框
bool selectObject = false;    //是否选择对象
int blockSize = 0, uniquenessRatio =0, numDisparities=0;
Ptr<StereoBM> bm = StereoBM::create(16, 9);
事先标定好的相机的参数
fx 0 cx
0 fy cy
0 0  1
Mat cameraMatrixL = (Mat_<double>(3, 3) << 682.55880, 0, 384.13666,
    0, 682.24569, 311.19558,
    0, 0, 1);
Mat distCoeffL = (Mat_<double>(5, 1) << -0.51614, 0.36098, 0.00523, -0.00225, 0.00000);
Mat cameraMatrixR = (Mat_<double>(3, 3) << 685.03817, 0, 397.39092,
    0, 682.54282, 272.04875,
    0, 0, 1);
Mat distCoeffR = (Mat_<double>(5, 1) << -0.46640, 0.22148, 0.00947, -0.00242, 0.00000);
Mat T = (Mat_<double>(3, 1) << -61.34485, 2.89570, -4.76870);//T平移向量
Mat rec = (Mat_<double>(3, 1) << -0.00306, -0.03207, 0.00206);//rec旋转向量
Mat R;//R 旋转矩阵
/*****立体匹配*****/
void stereo_match(int,void*)
    bm->setBlockSize(2*blockSize+5);     //SAD窗口大小,5~21之间为宜
    bm->setROI1(validROIL);
    bm->setROI2(validROIR);
    bm->setPreFilterCap(31);
    bm->setMinDisparity(0);  //最小视差,默认值为0, 可以是负值,int型
    bm->setNumDisparities(numDisparities*16+16);//视差窗口,即最大视差值与最小视差值之差,窗口大小必须是16的整数倍,int型
    bm->setTextureThreshold(10); 
    bm->setUniquenessRatio(uniquenessRatio);//uniquenessRatio主要可以防止误匹配
    bm->setSpeckleWindowSize(100);
    bm->setSpeckleRange(32);
    bm->setDisp12MaxDiff(-1);
    Mat disp, disp8;
    bm->compute(rectifyImageL, rectifyImageR, disp);//输入图像必须为灰度图
    disp.convertTo(disp8, CV_8U, 255 / ((numDisparities * 16 + 16)*16.));//计算出的视差是CV_16S格式
    reprojectImageTo3D(disp, xyz, Q, true); //在实际求距离时,ReprojectTo3D出来的X / W, Y / W, Z / W都要乘以16(也就是W除以16),才能得到正确的三维坐标信息。
    xyz = xyz * 16;
    imshow("disparity", disp8);
/*****描述:鼠标操作回调*****/
static void onMouse(int event, int x, int y, int, void*)
    if (selectObject)
        selection.x = MIN(x, origin.x);
        selection.y = MIN(y, origin.y);
        selection.width = std::abs(x - origin.x);
        selection.height = std::abs(y - origin.y);
    switch (event)
    case EVENT_LBUTTONDOWN:   //鼠标左按钮按下的事件
        origin = Point(x, y);
        selection = Rect(x, y, 0, 0);
        selectObject = true;
        cout << origin <<"in world coordinate is: " << xyz.at<Vec3f>(origin) << endl;
        break;
    case EVENT_LBUTTONUP:    //鼠标左按钮释放的事件
        selectObject = false;
        if (selection.width > 0 && selection.height > 0)
        break;
/*****主函数*****/
int main()
    Rodrigues(rec, R); //Rodrigues变换
    stereoRectify(cameraMatrixL, distCoeffL, cameraMatrixR, distCoeffR, imageSize, R, T, Rl, Rr, Pl, Pr, Q, CALIB_ZERO_DISPARITY,
        0, imageSize, &validROIL, &validROIR);
    initUndistortRectifyMap(cameraMatrixL, distCoeffL, Rl, Pr, imageSize, CV_32FC1, mapLx, mapLy);
    initUndistortRectifyMap(cameraMatrixR, distCoeffR, Rr, Pr, imageSize, CV_32FC1, mapRx, mapRy);
    rgbImageL = imread("left1.jpg", CV_LOAD_IMAGE_COLOR);
    cvtColor(rgbImageL, grayImageL, CV_BGR2GRAY);
    rgbImageR = imread("right1.jpg", CV_LOAD_IMAGE_COLOR);
    cvtColor(rgbImageR, grayImageR, CV_BGR2GRAY);
    imshow("ImageL Before Rectify", grayImageL);
    imshow("ImageR Before Rectify", grayImageR);
    经过remap之后,左右相机的图像已经共面并且行对准了
    remap(grayImageL, rectifyImageL, mapLx, mapLy, INTER_LINEAR);
    remap(grayImageR, rectifyImageR, mapRx, mapRy, INTER_LINEAR);
    把校正结果显示出来
    Mat rgbRectifyImageL, rgbRectifyImageR;
    cvtColor(rectifyImageL, rgbRectifyImageL, CV_GRAY2BGR);  //伪彩色图
    cvtColor(rectifyImageR, rgbRectifyImageR, CV_GRAY2BGR);
    //单独显示
    //rectangle(rgbRectifyImageL, validROIL, Scalar(0, 0, 255), 3, 8);
    //rectangle(rgbRectifyImageR, validROIR, Scalar(0, 0, 255), 3, 8);
    imshow("ImageL After Rectify", rgbRectifyImageL);
    imshow("ImageR After Rectify", rgbRectifyImageR);
    //显示在同一张图上
    Mat canvas;
    double sf;
    int w, h;
    sf = 600. / MAX(imageSize.width, imageSize.height);
    w = cvRound(imageSize.width * sf);
    h = cvRound(imageSize.height * sf);
    canvas.create(h, w * 2, CV_8UC3);   //注意通道
    //左图像画到画布上
    Mat canvasPart = canvas(Rect(w * 0, 0, w, h));                                //得到画布的一部分  
    resize(rgbRectifyImageL, canvasPart, canvasPart.size(), 0, 0, INTER_AREA);     //把图像缩放到跟canvasPart一样大小  
    Rect vroiL(cvRound(validROIL.x*sf), cvRound(validROIL.y*sf),                //获得被截取的区域    
        cvRound(validROIL.width*sf), cvRound(validROIL.height*sf));
    //rectangle(canvasPart, vroiL, Scalar(0, 0, 255), 3, 8);                      //画上一个矩形  
    cout << "Painted ImageL" << endl;
    //右图像画到画布上
    canvasPart = canvas(Rect(w, 0, w, h));                                      //获得画布的另一部分  
    resize(rgbRectifyImageR, canvasPart, canvasPart.size(), 0, 0, INTER_LINEAR);
    Rect vroiR(cvRound(validROIR.x * sf), cvRound(validROIR.y*sf),
        cvRound(validROIR.width * sf), cvRound(validROIR.height * sf));
    //rectangle(canvasPart, vroiR, Scalar(0, 0, 255), 3, 8);
    cout << "Painted ImageR" << endl;
    //画上对应的线条
    for (int i = 0; i < canvas.rows; i += 16)
        line(canvas, Point(0, i), Point(canvas.cols, i), Scalar(0, 255, 0), 1, 8);
    imshow("rectified", canvas);
    namedWindow("disparity", CV_WINDOW_AUTOSIZE);
    // 创建SAD窗口 Trackbar
    createTrackbar("BlockSize:\n", "disparity",&blockSize, 8, stereo_match);
    // 创建视差唯一性百分比窗口 Trackbar
    createTrackbar("UniquenessRatio:\n", "disparity", &uniquenessRatio, 50, stereo_match);
    // 创建视差窗口 Trackbar
    createTrackbar("NumDisparities:\n", "disparity", &numDisparities, 16, stereo_match);
    //鼠标响应函数setMouseCallback(窗口名称, 鼠标回调函数, 传给回调函数的参数,一般取0)
    setMouseCallback("disparity", onMouse, 0);
    stereo_match(0,0);
    waitKey(0);
    return 0;
                    http://blog.csdn.net/wangchao7281/article/details/52506691?locationNum=12&fps=1这篇文章对相机标定流程描述的还是很到位的。本文采用MATLAB标定工具箱和OpenCV3.10来实现双目测距,设备为两个CMOS工业相机和相应的双目云台。 首先感谢CSDN上两位大神前辈邹宇华和scyscyao,虽然是
3. 支持功能
摄像头1 & 摄像头2:将摄像头的USB口插入电脑后,打开软件,软件会自动扫描设备,自动打开视频;如果长时间没有加载视频,那么请点击刷新设备按钮,如果刷新后也没有画面,那么请检查摄像头与电脑的连接是否有问题。
刷新设备:当测完一个摄像头设备后,无需关闭软件,下
				
关于双目摄像头测距原理,最近在看关于双目摄像机成像的项目,找关于双目摄像头标定的c++代码,但是发现自己对双目摄像机的成像原理不清楚,所以看代码和看天书一样。这篇就写给零基础接触双目摄像机成像原理的小伙伴,等看完这篇之后再去找类似的c++、python标定代码,就简单很多。 当然,之后也会介绍,关于几个坐标系(世界坐标系、像素坐标系、图片坐标系)的转化。 **视差的概念:**是指从两个不同位置观察...
这个是demo中用到的双目摄像头,一个是红外的,一个是正常的rgb摄像头 两个usb接口,在电脑上呈现两路摄像头通道 程序检测RGB输出图像,当检测到有人脸时,用RGB人脸的位置到红外画面的位置去检测人脸 如果没有检测到,说明当前目标为非活体 当在红外画面检测到人脸时,说明当前目标为活体目标 再继续使用RGB图像提取特征值 下面为demo效果图 DEMO源码地址:
目录学习 opencv 自带示例 stereo_calib(立体相机标定)准备调试示例代码目标工程确定输入参数类型调试模式(debug)输入参数F5 调试程序执行完毕(输出项目)内外参数据说明学习(TODO)参考 学习 opencv 自带示例 stereo_calib(立体相机标定) 首先,需要编译 opencv 自带示例,可以参考之前的博客记录: cmake+windows 编译 opencv 自带示例. 调试示例代码 确定输入参数类型 /// 参数: /// @ w:棋盘格(圆形)标定
文章名称:3-D Point Cloud Generation from Rigid and Flexible Stereo Vision Systems 文章(英文)详细讲解了双目视觉的一些基本原理,已经如何使用两个普通的网络摄像头来实现双目视觉,以及如何根据两个摄像头来计算物体深度信息。 代码为文章中附带的代码 仅供参考学习
工作原因,要大规模标定相机,cv::omni::StereoCalibrate出来的结果有明显问题,网上也找不到相关的解析,只有说这个函数怎么使用的,所以只能自己阅读一下StereoCalibrate的源码。 opencv 版本 4.1.1 StereoCalibrate 的步骤不是很难,可以总结为:1、检查输入值 2、确定优化变量,给所有待优化变量求解一个初值 3、利用LMsolver求解器进行求解 首先阅读CvLevMarq求解器文件 其头文件calib3d/calib3d_c.h
semi-global matching(SGM)是一种用于计算双目视觉中视差(disparity)的半全局匹配算法,在OpenCV中的实现为semi-global block matching(SGBM)。 第一部分:SGBM算法原理: 一、预处理 Step1:SGBM采用水平Sobel算子,把图像做处理,公式为: Sobel(x,y)=2[P(x+1,y)-P(x-1,y)]+ P(x...
基于双目图像的视差估计方法研究及实现第一章 绪论1.1 课题的研究背景与意义1.2 双目视差估计的研究现状1.2.1 传统立体匹配方法研究现状1.2.2 统计学习方法研究现状1.2.3 深度学习方法研究现状 第一章 绪论 1.1 课题的研究背景与意义 1、最早出现的非接触测量技术:基于红外光、超声波、雷达等非光学仪器 原理:仪器主动发出信号,利用收到回波的时间来计算距离。 假设光速为V,传播时间为T,距离为S = VT/2 2、光学图像测距:单目测距和双目测距 单目测距:利用翻个摄像头拍摄的图片来还原场景的
OpenCV双目测距是一种通过使用两个相机或摄像头来计算物体距离的技术。根据引用中提到的资料,可以使用MATLAB标定工具箱和OpenCV3.10来实现双目测距。该方法需要使用两个CMOS工业相机和相应的双目云台。邹宇华和scyscyao的博客提供了关于双目测距和三维重建的OpenCV实现的问题解答,对于机器视觉初学者来说非常有帮助。 在OpenCV中,双目测距的实现主要涉及三个主要步骤:相机标定、立体匹配和视差计算。引用中的博客文章提供了关于这些步骤的实现问题集锦。首先,需要对相机进行标定,以确定相机的内部参数和畸变系数,这样可以在后续的计算中进行校正。接下来,需要进行立体匹配,即找到两个图像中对应的像素点,这样可以计算出视差(即像素点之间的水平偏移量)。最后,根据视差计算公式,可以通过将视差值与相机参数进行计算,得到物体的距离。 引用中的资料提供了关于视差滤波的信息,这是双目测距过程中的一个重要步骤。视差滤波可以帮助去除图像中的噪声和无用信息,从而提高双目测距的准确性。 需要注意的是,我提到的这些步骤和方法只是双目测距的一种实现方式,具体的实现细节和算法可能会因实际应用而有所不同。因此,建议参考引用和引用中提供的链接,以了解更详细的信息和具体的实现步骤。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [基于OpenCV双目测距系统实现](https://blog.csdn.net/chenmohousuiyue/article/details/51785016)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【OpenCV双目测距双目标定双目校正立体匹配)](https://blog.csdn.net/wangchao7281/article/details/52506691)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]