精华内容
下载资源
问答
  • 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、什么是拍照测距...目前的进度是能在PC上利用双目进行测距,近距离双目测距精度还行,后续需要将代码移植至树莓派B4板子上,由树莓
    
    
    


    前言

    最近有项目需要用到摄像测距,于是开始接触opencv机器视觉。学了好几天的摄像机测距相关的知识后就开始动手验证,刚开始是单目测距,搞了个树莓派的开发板,然后下载网上的一些代码验证,发现单目需要预先知道被测物,因为要实现避障功能,所以后面选了双目测距。目前的进度是能在PC上利用双目进行测距,近距离双目测距精度还行,后续需要将代码移植至树莓派B4板子上,由树莓派来进一步论证项目可行性


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、什么是拍照测距?

    个人了解到拍照测距有两种方式,单目测距和双目测距,单目是只用一个摄像头,测距前提是需要知道被测物体是什么。双目测距需要一个双目的摄像头。

    二、双目测距步骤

    1.双目标定

    原来的双目标定是直接使用的opencv自带的标定功能,但是发现对图片的要求比较高,而且标定的结果在后面的测距上并不准(可能是我标定时候不准确导致的),后面就使用的MATLAB进行标定。

    我是在淘宝上买了个双目摄像头,自己A4纸打印的棋盘格。用厂家提供的图像采集方式进行了拍照。取了15张照片(左目和右目各15张),后用matlab标定时候有几张图片的误差有点大就给删了,最后取了12张照片进行标定。标定过程就不说了,可以通过以下链接进行了解
    https://blog.csdn.net/u013451663/article/details/89954646

    重点说一下用matlab得出来的参数的对应关系:
    在这里插入图片描述

    在这里插入图片描述
    先看一下畸变参数,得到的值就是K1,K2,K3,和P1,P2,代码赋值的时候应该是K1,K2,P1,P2,K3

    Mat distCoeffL = (Mat_<double>(5, 1) << 0.310458259610102, -2.26297309194147, -0.00764246381079933, 0.00634878271949189, 6.11556840747635);
    

    具体说一下内参,内参是一个3x3的矩阵,双击matlab里面的值,得到的表格是这样的
    在这里插入图片描述
    这个表格里面和代码需要的数据要置换一下

    fx 0 cx
    0 fy cy
    0  0  1
    

    在这里插入图片描述
    代码赋值如下:

    Mat cameraMatrixL = (Mat_<double>(3, 3) << 337.709436280709, 0.741962718557881, 150.799143051710,
    	0, 337.863855604370, 123.314225146904,
    	0, 0, 1);
    

    旋转矩阵也是3x3,所以也和内参一样,置换一下。
    平移矩阵是可以直接使用的
    在这里插入图片描述
    附上赋值代码如下

    Mat rec = (Mat_<double>(3, 3) << 0.999989526750612, 0.00232022184998904, 0.00394499171785794,
    	-0.00228582974058393, 0.999959540028791, -0.00870018895234802,
    	-0.00396501847211287, 0.00869108025370437, 0.999954370835259); 
    	  
    Mat T = (Mat_<double>(3, 1) << -41.5775139873461, 0.241065317743041, 0.408815384344912);
    

    至此,标定就结束了。

    2.测距

    测距是使用的该大神的代码,我把它拷贝过来了。
    https://guo-pu.blog.csdn.net/article/details/86744936?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai

    #include <opencv2/opencv.hpp>  
    #include <iostream>  
    #include <math.h> 
    
    using namespace std;
    using namespace cv;
    
    const int imageWidth = 320;                             //图片大小  
    const int imageHeight = 240;
    Vec3f  point3;
    float d;
    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);
    
    Mat cameraMatrixL = (Mat_<double>(3, 3) << 337.709436280709, 0.741962718557881, 150.799143051710,
    	0, 337.863855604370, 123.314225146904,
    	0, 0, 1);
    
    Mat distCoeffL = (Mat_<double>(5, 1) << 0.310458259610102, -2.26297309194147, -0.00764246381079933, 0.00634878271949189, 6.11556840747635);
    
    Mat cameraMatrixR = (Mat_<double>(3, 3) << 337.093687218177, 0.587984682766586, 152.848985873868,
    	0, 336.971855005274, 117.527331021388,
    	0, 0, 1);
    
    Mat distCoeffR = (Mat_<double>(5, 1) << 0.439757633387106, -5.17644381384173, -0.0103643563681475, 0.00184932125612765, 23.4806041578594);
    
    Mat T = (Mat_<double>(3, 1) << -41.5775139873461, 0.241065317743041, 0.408815384344912);//T平移向量
    
    Mat rec = (Mat_<double>(3, 3) << 0.999989526750612, 0.00232022184998904, 0.00394499171785794,
    	-0.00228582974058393, 0.999959540028791, -0.00870018895234802,
    	-0.00396501847211287, 0.00869108025370437, 0.999954370835259);                //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;
    		point3 = xyz.at<Vec3f>(origin);
    		point3[0];
    		cout << "世界坐标:" << endl;
    		cout << "x: " << point3[0] << "  y: " << point3[1] << "  z: " << point3[2] << endl;
    		d = point3[0] * point3[0] + point3[1] * point3[1] + point3[2] * point3[2];
    		d = sqrt(d);   //mm
    		d = d / 10.0;   //cm
    		cout << "距离是:" << d << "cm" << 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("C:\\Users\\DELL\\picture\\left_33.png", CV_LOAD_IMAGE_COLOR);
    	cvtColor(rgbImageL, grayImageL, CV_BGR2GRAY);
    	rgbImageR = imread("C:\\Users\\DELL\\picture\\right_33.png", 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);
    
    	//单独显示
    	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));
    	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));
    	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;![在这里插入图片描述](https://img-blog.csdnimg.cn/20201203180831933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zMDA3MjEwMw==,size_16,color_FFFFFF,t_70#pic_center)
    
    }
    

    测试结果

    测出来的结果还可以,精度挺高,最后附上测试的结果图。先记录这么多,后续还要移植到树莓派。

    在这里插入图片描述

    展开全文
  • 双目测距

    千次阅读 2016-12-11 20:27:21
    双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,...

    双目测距的基本原理

    clip_image002[10]

    如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差clip_image004 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,f的量纲是像素点,T的量纲由定标板棋盘格的实际尺寸和用户输入值确定,一般是以毫米为单位(当然为了精度提高也可以设置为0.1毫米量级),d=xl-xr的量纲也是像素点。因此分子分母约去,Z的量纲与T相同。 

    clip_image006

    假设目标点在左视图中的坐标为(x,y),在左右视图上形成的视差为d,目标点在以左摄像头光心为原点的世界坐标系中的坐标为(X,Y,Z),则存在上图所示的变换矩阵Q,使得 Q*[x y d 1]’ = [X Y Z W]’。

    @scyscyao :为了精确地求得某个点在三维空间里的距离Z,我们需要获得的参数有焦距f、视差d、摄像头中心距Tx。如果还需要获得X坐标和Y坐标的话,那么还需要额外知道左右像平面的坐标系与立体坐标系中原点的偏移cx和cy。其中f, Tx, cx和cy可以通过立体标定获得初始值,并通过立体校准优化,使得两个摄像头在数学上完全平行放置,并且左右摄像头的cx, cy和f相同(也就是实现图2中左右视图完全平行对准的理想形式)。而立体匹配所做的工作,就是在之前的基础上,求取最后一个变量:视差d(这个d一般需要达到亚像素精度)。从而最终完成求一个点三维坐标所需要的准备工作。在清楚了上述原理之后,我们也就知道了,所有的这几步:标定、校准和匹配,都是围绕着如何更精确地获得 f, d, Tx, cx 和cy 而设计的 。 ”

     

    一、图像的获取

    1. 如何打开两个或多个摄像头?

    可以通过OpenCV的capture类函数或者结合DirectShow来实现双摄像头的捕获,具体可见我的读书笔记《OpenCV学习笔记(6)基于 VC+OpenCV+DirectShow 的多个摄像头同步工作 》。文中曾提及不能用cvCreateCameraCapture 同时读取两个摄像头,不过后来一位研友来信讨论说只要把摄像头指针的创建代码按照摄像头序号降序执行,就可以顺利打开多个摄像头 ,例如:

     

    [c-sharp] view plaincopy
    1. CvCapture* capture2 = cvCreateCameraCapture( 1 );  
    2. CvCapture* capture1 = cvCreateCameraCapture( 0 );  

    采用DirectShow的方式读入时:

     

    [c-sharp] view plaincopy
    1. camera2.OpenCamera(1, false, 640,480);  
    2. camera1.OpenCamera(0, false, 640,480);  

     

    这样就可以同时采集两个摄像头。我也验证过这种方法确实有效,而且还解决了我遇到的cvSetCaptureProperty调整帧画面大小速度过慢的问题。当摄像头的打开或创建代码按照摄像头序号从0开始以升序编写执行时,使用cvSetCaptureProperty就会出现第一个摄像头(序号为0)的显示窗口为灰色(即无图像)、且程序运行速度缓慢的现象。而改为降序编写执行后,则能正常、实时地显示各摄像头的画面。具体原因有待分析讨论。

     

    2. 如何实现多个摄像头帧画面的同步抓取?

    在单摄像头情况下用 cvQueryFrame 即可抓取一帧画面,实际上这个函数是由两个routine组成的:cvGrabFrame和cvRetrieveFrame(详见Learning OpenCV第103页)。cvGrabFrame将摄像头帧画面即时复制到内部缓存中,然后通过cvRetrieveFrame把我们预定义的一个IplImage型空指针指向缓存内的帧数据。注意这时我们并没有真正把帧数据取出来,它还保存在OpenCV的内部缓存中,下一次读取操作就会被覆盖掉。所以一般我们要另外定义一个IplImage来复制所抓取的帧数据,然后对这个新IplImage进行操作。

    由上面的解释也可以看出,cvGrabFrame的作用就是尽可能快的将摄像头画面数据复制到计算机缓存,这个功能就方便我们实现对多个摄像头的同步抓取,即首先用cvGrabFrame依次抓取各个CvCapture*,然后再用cvRetrieveFrame把帧数据取出来。例如:

     

    [c-sharp] view plaincopy
    1. cvGrabFrame( lfCam );  
    2. cvGrabFrame( riCam );  
    3. frame1 = cvRetrieveFrame( lfCam );  
    4. frame2 = cvRetrieveFrame( riCam );  
    5. if( !frame1|| !frame2) break;  
    6. cvCopyImage(frame1, image1);   
    7. cvCopyImage(frame2, image2);  

    二、摄像头定标

    摄像头定标一般都需要一个放在摄像头前的特制的标定参照物(棋盘纸),摄像头获取该物体的图像,并由此计算摄像头的内外参数。标定参照物上的每一个特征点相对于世界坐标系的位置在制作时应精确测定,世界坐标系可选为参照物的物体坐标系。在得到这些已知点在图像上的投影位置后,可计算出摄像头的内外参数。

    clip_image008

    如上图所示,摄像头由于光学透镜的特性使得成像存在着径向畸变,可由三个参数k1,k2,k3确定;由于装配方面的误差,传感器与光学镜头之间并非完全平行,因此成像存在切向畸变,可由两个参数p1,p2确定。单个摄像头的定标主要是计算出摄像头的内参(焦距f和成像原点cx,cy、五个畸变参数(一般只需要计算出k1,k2,p1,p2,对于鱼眼镜头等径向畸变特别大的才需要计算k3))以及外参(标定物的世界坐标)。 OpenCV 中使用的求解焦距和成像原点的算法是基于张正友的方法( pdf ),而求解畸变参数是基于 Brown 的方法( pdf )。


    1. 图像坐标系、摄像头坐标系和世界坐标系的关系

    clip_image010 clip_image012

    摄像头成像几何关系,其中Oc 点称为摄像头(透镜)的光心,Xc 轴和Yc 轴与图像的x轴和Y轴平行,Zc 轴为摄像头的光轴,它与图像平面垂直。光轴与图像平面的交点O1 ,即为图像坐标系的原点。由点Oc 与Xc 、Yc 、Zc 轴组成的坐标系称为摄像头坐标系,Oc O1 的距离为摄像头焦距,用f表示。

    图像坐标系是一个二维平面,又称为像平面,“@scyscyao :实际上就是摄像头的CCD传感器的表面。每个CCD传感器都有一定的尺寸,也有一定的分辨率,这个就确定了毫米与像素点之间的转换关系。举个例子,CCD的尺寸是8mm X 6mm,帧画面的分辨率设置为640X480,那么毫米与像素点之间的转换关系就是80pixel/mm。”设CCD传感器每个像素点的物理大小为dx*dy,相应地,就有 dx=dy=1/80。


    2. 进行摄像头定标时,棋盘方格的实际大小 square_size (默认为 1.0f )的设置对定标参数是否有影响?

    @scyscyao :当然有。在标定时,需要指定一个棋盘方格的长度,这个长度(一般以毫米为单位,如果需要更精确可以设为0.1毫米量级)与实际长度相同,标定得出的结果才能用于实际距离测量。一般如果尺寸设定准确的话,通过立体标定得出的Translation向量的第一个分量Tx的绝对值就是左右摄像头的中心距。一般可以用这个来验证立体标定的准确度。比如我设定的棋盘格大小为270 (27mm),最终得出的Tx大小就是602.8 (60.28mm),相当精确。”

     

    3. 定标所得的摄像头内参数,即焦距和原点坐标,其数值单位都是一致的吗?怎么把焦距数值换算为实际的物理量?

    @wobject :是的,都是以像素为单位。假设像素点的大小为k x l,单位为mm,则fx = f / k, fy = f / (l * sinA), A一般假设为 90°,是指摄像头坐标系的偏斜度(就是镜头坐标和CCD是否垂直)。摄像头矩阵(内参)的目的是把图像的点从图像坐标转换成实际物理的三维坐标。因此其中的fx, fy, cx, cy 都是使用类似上面的纲量。同样,Q 中的变量 f,cx, cy 也应该是一样的。”

     

    4. 棋盘图像数目应该取多少对摄像头定标比较适宜?

    OpenCV中文论坛上piao的帖子《在OpenCV中用cvCalibrateCamera2进行相机标定(附程序) 》中指出影响摄像头定标结果的准确性和稳定性的因素主要有三个:

    (1) 标定板所在平面与成像平面(image plane)之间的夹角;

    (2) 标定时拍摄的图片数目(棋盘图像数目);

    (3) 图像上角点提取的不准确。

    感觉OpenCV1.2以后对图像角点的提取准确度是比较高的,cvFindChessboardCorners 和 cvFindCornerSubPix结合可以获得很好的角点检测效果(hqhuang1在《[HQ]角点检测(Corner Detection) cvFindCornerSubPix 使用范例 》中给出了相关的应用范例)。因此,影响定标结果较大的就是标定板与镜头的夹角和棋盘图像数目,在实际定标过程中,我感觉棋盘图像数目应该大于20张,每成功检测一次完整的棋盘角点就要变换一下标定板的姿态(包括角度、距离) 

     

    5. 单目定标函数cvCalibrateCamera2采用怎样的 flags 比较合适?

    由于一般镜头只需要计算k1,k2,p1,p2四个参数,所以我们首先要设置 CV_CALIB_FIX_K3;其次,如果所用的摄像头不是高端的、切向畸变系数非常少的,则不要设置 CV_CALIB_ZERO_TANGENT_DIST,否则单目校正误差会很大;如果事先知道摄像头内参的大概数值,并且cvCalibrateCamera2函数的第五个参数intrinsic_matrix非空,则也可设置 CV_CALIB_USE_INTRINSIC_GUESS ,以输入的intrinsic_matrix为初始估计值来加快内参的计算;其它的 flag 一般都不需要设置,对单目定标的影响不大。

     

    P.S. 使用OpenCV进行摄像机定标虽然方便,但是定标结果往往不够准确和稳定,最好是使用 Matlab标定工具箱 来进行定标,再将定标结果取回来用于立体匹配和视差计算。工具箱的使用官方主页 有图文并茂的详细说明,此外,有两篇博文也进行了不错的总结,推荐阅读:

    (1)分享一些OpenCV实现立体视觉的经验

    (2)Matlab标定工具箱使用的一些注意事项


    三、双目定标和双目校正

    双目摄像头定标不仅要得出每个摄像头的内部参数,还需要通过标定来测量两个摄像头之间的相对位置(即右摄像头相对于左摄像头的三维平移 t 和旋转 R 参数)。

    clip_image002

    图6

    要计算目标点在左右两个视图上形成的视差,首先要把该点在左右视图上两个对应的像点匹配起来。然而,在二维空间上匹配对应点是非常耗时的,为了减少匹配搜索范围,我们可以利用极线约束使得对应点的匹配由二维搜索降为一维搜索。

          clip_image004  clip_image006

    图7

    而双目校正的作用就是要把消除畸变后的两幅图像严格地行对应,使得两幅图像的对极线恰好在同一水平线上,这样一幅图像上任意一点与其在另一幅图像上的对应点就必然具有相同的行号,只需在该行进行一维搜索即可匹配到对应点。

    clip_image008

    图8

     

    1. 关于cvStereoCalibrate的使用

    如果按照 Learning OpenCV 的例程,直接通过cvStereoCalibrate来实现双目定标,很容易产生比较大的图像畸变,边角处的变形较厉害。最好先通过cvCalibrateCamera2() 对每个摄像头单独进行定标,再利用cvStereoCalibrate进行双目定标。这样定标所得参数才比较准确,随后的校正也不会有明显的畸变。我使用的程序主要基于Learning OpenCV 的例程ch12_ex12_3.cpp,其中主要部分如下:

        //
        // 是否首先进行单目定标?
        cvCalibrateCamera2(&_objectPoints, &_imagePoints1, &_npoints, imageSize,    
            &t_M1, &t_D1, NULL, NULL, CV_CALIB_FIX_K3);
        cvCalibrateCamera2(&_objectPoints, &_imagePoints2, &_npoints, imageSize,    
            &t_M2, &t_D2, NULL, NULL, CV_CALIB_FIX_K3);
        //
        // 进行双目定标
        cvStereoCalibrate( &_objectPoints, &_imagePoints1,
            &_imagePoints2, &_npoints,
            &t_M1, &t_D1, &t_M2, &t_D2,
            imageSize, &t_R, &t_T, &t_E, &t_F,
            cvTermCriteria(CV_TERMCRIT_ITER+
            CV_TERMCRIT_EPS, 100, 1e-5));  // flags为默认的CV_CALIB_FIX_INTRINSIC

    上面的t_M1(2), t_D1(2) 分别是单目定标后获得的左(右)摄像头的内参矩阵3*3)和畸变参数向量(1*5);t_R, t_T 分别是右摄像头相对于左摄像头的旋转矩阵(3*3)和平移向量(3*1), t_E是包含了两个摄像头相对位置关系的Essential Matrix(3*3),t_F 则是既包含两个摄像头相对位置关系、也包含摄像头各自内参信息的Fundamental Matrix(3*3)。

    clip_image010

    图9

     

    2. cvStereoCalibrate 是怎样计算 Essential Matrix 和 Fundamental Matrix 的?

    首先我们以Learning OpenCV第422页为基础,讨论下 Essential Matrix 和 Fundamental Matrix 的构造过程,再看看 OpenCV 是如何进行计算的。

    clip_image012

    图10

     注:原文中对pl、p和ql、q物理意义和计算公式的表述有误,已修正。(2011-04-12)

    (1)Essential Matrix

    如上图所示,给定一个目标点P,以左摄像头光心Ol为原点。点P相对于光心Ol的观察位置为Pl,相对于光心Or的观察位置为Pr。点P在左摄像头成像平面上的位置为pl,在右摄像头成像平面上的位置为pr注意Pl、Pr、pl、p都处于摄像机坐标系,其量纲是与平移向量T相同的(pl、p在图像坐标系中对应的像素坐标为 ql、q)。

    假设右摄像头相对于左摄像头的相对位置关系由旋转矩阵R和平移向量T表示,则可得:Pr = R(Pl-T)。

    现在我们要寻找由点P、Ol和Or确定的对极平面的表达式。注意到平面上任意一点x与点a的连线垂直于平面法向量n,即向量 (x-a) 与向量 n 的点积为0:(x-a)·n = 0。在Ol坐标系中,光心Or的位置为T,则P、Ol和Or确定的对极平面可由下式表示:(Pl-T)T·(Pl×T) = 0。

    Pr = R(Pl-T) 和 RT=R-1 可得:(RTPr)T·(Pl×T) = 0。

    另一方面,向量的叉积又可表示为矩阵与向量的乘积,记向量T的矩阵表示为S,得:Pl×T = SPl

    clip_image014

    图11

    那么就得到:(Pr)TRSPl = 0。这样,我们就得到Essential Matrix:E = RS。

    通过矩阵E我们知道Pl和Pr的关系满足:(Pr)TEPl = 0。进一步地,由 pl = fl*Pl/Zl 和 pr = fr*Pr/Zr 我们可以得到点P在左右两个摄像机坐标系中的观察点 p和 pr应满足的极线约束关系为:(pr)TEpl = 0。

    注意到 E 是不满秩的,它的秩为2,那么 (pr)TEpl = 0 表示的实际上是一条直线,也就是对极线。

    (2)Fundamental Matrix

    由于矩阵E并不包含摄像头内参信息,且E是面向摄像头坐标系的。实际上我们更感兴趣的是在图像像素坐标系上去研究一个像素点在另一视图上的对极线,这就需要用到摄像机的内参信息将摄像头坐标系和图像像素坐标系联系起来。在(1)中,pl和pr是物理坐标值,对应的像素坐标值为ql和qr,摄像头内参矩阵为M,则有:p=M-1q。从而:(pr)TEpl = 0 à qrT(Mr-1)TE Ml-1ql = 0。这里,我们就得到Fundamental Matrix:F = (Mr-1)TE Ml-1。并有 qrTFql = 0。

    (3)OpenCV的相关计算

    由上面的分析可见,求取矩阵E和F关键在于旋转矩阵R和平移向量T的计算,而cvStereoCalibrate的代码中大部分(cvcalibration.cpp的第1886-2180行)也是计算和优化R和T的。在cvcalibration.cpp的第1913-1925行给出了计算R和T初始估计值的基本方法:

        /*
           Compute initial estimate of pose
    
           For each image, compute:
              R(om) is the rotation matrix of om
              om(R) is the rotation vector of R
              R_ref = R(om_right) * R(om_left)'
              T_ref_list = [T_ref_list; T_right - R_ref * T_left]
              om_ref_list = {om_ref_list; om(R_ref)]
    
           om = median(om_ref_list)
           T = median(T_ref_list)
        */

    具体的计算过程比较繁杂,不好理解,这里就不讨论了,下面是计算矩阵E和F的代码:

     

        if( matE || matF )
        {
            double* t = T_LR.data.db;
            double tx[] =
            {
                0, -t[2], t[1],
                t[2], 0, -t[0],
                -t[1], t[0], 0
            };
            CvMat Tx = cvMat(3, 3, CV_64F, tx);
            double e[9], f[9];
            CvMat E = cvMat(3, 3, CV_64F, e);
            CvMat F = cvMat(3, 3, CV_64F, f);
            cvMatMul( &Tx, &R_LR, &E );
            if( matE )
                cvConvert( &E, matE );
            if( matF )
            {
                double ik[9];
                CvMat iK = cvMat(3, 3, CV_64F, ik);
                cvInvert(&K[1], &iK);
                cvGEMM( &iK, &E, 1, 0, 0, &E, CV_GEMM_A_T );
                cvInvert(&K[0], &iK);
                cvMatMul(&E, &iK, &F);
                cvConvertScale( &F, matF, fabs(f[8]) > 0 ? 1./f[8] : 1 );
            }
        }

     

    3. 通过双目定标得出的向量T中,Tx符号为什么是负的?

    @scyscyao:这个其实我也不是很清楚。个人的解释是,双目定标得出的T向量指向是从右摄像头指向左摄像头(也就是Tx为负),而在OpenCV坐标系中,坐标的原点是在左摄像头的。因此,用作校准的时候,要把这个向量的三个分量符号都要换一下,最后求出的距离才会是正的。

    clip_image016

    图12

    但是这里还有一个问题,就是Learning OpenCV中Q的表达式,第四行第三列元素是-1/Tx,而在具体实践中,求出来的实际值是1/Tx。这里我和maxwellsdemon讨论下来的结果是,估计书上Q表达式里的这个负号就是为了抵消T向量的反方向所设的,但在实际写OpenCV代码的过程中,那位朋友却没有把这个负号加进去。”

    clip_image018

    图13

    scyscyao 的分析有一定道理,不过我觉得还有另外一种解释:如上图所示,摄像机C1(C2)与世界坐标系相对位置的外部参数为旋转矩阵R1(R2)和平移向量 t1(t2),如果下标1代表左摄像机,2代表右摄像机,显然在平移向量的水平分量上有 t1x > t2x;若以左摄像机C1为坐标原点,则可得到如上图所示的旋转矩阵R和平移向量t,由于t1x > t2x,则有 tx < 0。

    为了抵消Tx为负,在矩阵Q中元素(4,3)应该加上负号,但在cvStereoRectify代码中并没有添加上,这就使得我们通过 cvReprojectImageTo3D 计算得到的三维数据都与实际值反号了。

     

        if( matQ )
        {
            double q[] =
            {
                1, 0, 0, -cc_new[0].x,
                0, 1, 0, -cc_new[0].y,
                0, 0, 0, fc_new,
                0, 0, 1./_t[idx],
                (idx == 0 ? cc_new[0].x - cc_new[1].x : cc_new[0].y - cc_new[1].y)/_t[idx]
            };
            CvMat Q = cvMat(4, 4, CV_64F, q);
            cvConvert( &Q, matQ );
        }

     

    为了避免上述反号的情况,可以在计算得到Q矩阵后,添加以下代码更改 Q[3][2] 的值。

     

    // Q 是 Mat 类型矩阵,OpenCV2.1 C++ 模式
        Q.at<double>(3, 2) = -Q.at<double>(3, 2);    
    // Q 是 double 数组定义时
        double Q[4][4];
        CvMat t_Q = cvMat(4, 4, CV_64F, Q );
        cvStereoRectify(…);
        Q[3][2]=-Q[3][2];

     

    4. 双目校正原理及cvStereoRectify 的应用。

    clip_image020

    图14

    如图14所示,双目校正是根据摄像头定标后获得的单目内参数据(焦距、成像原点、畸变系数)和双目相对位置关系(旋转矩阵和平移向量),分别对左右视图进行消除畸变和行对准,使得左右视图的成像原点坐标一致(CV_CALIB_ZERO_DISPARITY 标志位设置时发生作用)、两摄像头光轴平行、左右成像平面共面、对极线行对齐。在OpenCV2.1版之前,cvStereoRectify 的主要工作就是完成上述操作,校正后的显示效果如图14(c) 所示。可以看到校正后左右视图的边角区域是不规则的,而且对后续的双目匹配求取视差会产生影响,因为这些边角区域也参与到匹配操作中,其对应的视差值是无用的、而且一般数值比较大,在三维重建和机器人避障导航等应用中会产生不利影响。

    因此,OpenCV2.1 版中cvStereoRectify新增了4个参数用于调整双目校正后图像的显示效果,分别是 double alpha, CvSize newImgSize, CvRect* roi1, CvRect* roi2。下面结合图15-17简要介绍这4个参数的作用:

    (1)newImgSize:校正后remap图像的分辨率。如果输入为(0,0),则是与原图像大小一致。对于图像畸变系数比较大的,可以把newImgSize 设得大一些,以保留图像细节。

    (2)alpha:图像剪裁系数,取值范围是-1、0~1。当取值为 0 时,OpenCV会对校正后的图像进行缩放和平移,使得remap图像只显示有效像素(即去除不规则的边角区域),如图17所示,适用于机器人避障导航等应用;当alpha取值为1时,remap图像将显示所有原图像中包含的像素,该取值适用于畸变系数极少的高端摄像头;alpha取值在0-1之间时,OpenCV按对应比例保留原图像的边角区域像素。Alpha取值为-1时,OpenCV自动进行缩放和平移,其显示效果如图16所示。

    (3)roi1, roi2:用于标记remap图像中包含有效像素的矩形区域。对应代码如下:

     

    02433     if(roi1)
    02434     {
    02435         *roi1 = cv::Rect(cvCeil((inner1.x - cx1_0)*s + cx1),
    02436                      cvCeil((inner1.y - cy1_0)*s + cy1),
    02437                      cvFloor(inner1.width*s), cvFloor(inner1.height*s))
    02438             & cv::Rect(0, 0, newImgSize.width, newImgSize.height);
    02439     }
    02440     
    02441     if(roi2)
    02442     {
    02443         *roi2 = cv::Rect(cvCeil((inner2.x - cx2_0)*s + cx2),
    02444                      cvCeil((inner2.y - cy2_0)*s + cy2),
    02445                      cvFloor(inner2.width*s), cvFloor(inner2.height*s))
    02446             & cv::Rect(0, 0, newImgSize.width, newImgSize.height);
    02447     }
    

     

    clip_image022

    图15

    clip_image024

    图16

    clip_image026

    图17

    在cvStereoRectify 之后,一般紧接着使用 cvInitUndistortRectifyMap 来产生校正图像所需的变换参数(mapx, mapy)。

     

    //
    // 执行双目校正
    // 利用BOUGUET方法或HARTLEY方法来校正图像
        mx1 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
        my1 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
        mx2 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
        my2 = cvCreateMat( imageSize.height, imageSize.width, CV_32F );
        double R1[3][3], R2[3][3], P1[3][4], P2[3][4], Q[4][4];
        CvMat t_R1 = cvMat(3, 3, CV_64F, R1);
        CvMat t_R2 = cvMat(3, 3, CV_64F, R2);
        CvMat t_Q = cvMat(4, 4, CV_64F, Q );
        CvRect roi1, roi2;
    
    // IF BY CALIBRATED (BOUGUET'S METHOD)    
        CvMat t_P1 = cvMat(3, 4, CV_64F, P1);
        CvMat t_P2 = cvMat(3, 4, CV_64F, P2);
        cvStereoRectify( &t_M1, &t_M2, &t_D1, &t_D2, imageSize,
            &t_R, &t_T, &t_R1, &t_R2, &t_P1, &t_P2, &t_Q,
            CV_CALIB_ZERO_DISPARITY, 
            0, imageSize, &roi1, &roi2); 
    // Precompute maps for cvRemap()
        cvInitUndistortRectifyMap(&t_M1,&t_D1,&t_R1,&t_P1, mx1, my1);
        cvInitUndistortRectifyMap(&t_M2,&t_D2,&t_R2,&t_P2, mx2, my2);
    

     

    5. 为什么cvStereoRectify求出的Q矩阵cx, cy, f都与原来的不同?

    @scyscyao:在实际测量中,由于摄像头摆放的关系,左右摄像头的f, cx, cy都是不相同的。而为了使左右视图达到完全平行对准的理想形式从而达到数学上运算的方便,立体校准所做的工作事实上就是在左右像重合区域最大的情况下,让两个摄像头光轴的前向平行,并且让左右摄像头的f, cx, cy相同。因此,Q矩阵中的值与两个instrinsic矩阵的值不一样就可以理解了。”

    注:校正后得到的变换矩阵Q,Q[0][3]、Q[1][3]存储的是校正后左摄像头的原点坐标(principal point)cx和cy,Q[2][3]是焦距f。


    展开全文
  • 提出了一种局部稠密匹配与人工干预相结合的测距方案,利用非特征点与特征点的位置关系,构建“最小矩形”以缩小匹配范围,再应用NCC(归一化互相关)算法对非特征点进行稠密立体匹配,最后根据双目测距原理直接获取...
  • 双目测距实现

    千次阅读 2019-06-07 12:27:31
    不过效果很一般,也就几米内的精度还不错,根本无法达到商用价值,玩一下也就行了。 原理介绍https://blog.csdn.net/xiaohaijiejie/article/details/49721415 实现教程https://blog.csdn.net/hejingkui/artic...

    前言:这个方法是网上通用的,也是目前唯一可以使用的,网上教程有都是,我此文主要是把疑难点做一下补充。不过效果很一般,也就几米内的精度还不错,根本无法达到商用价值,玩一下也就行了。

    原理介绍 https://blog.csdn.net/xiaohaijiejie/article/details/49721415

    实现教程 https://blog.csdn.net/hejingkui/article/details/80488763

    一. 左右摄像头同时拍照并保存于本地

    先固定好左右相机,拿棋盘标定图摆拍并保存,我左右相机各15张,网上看的说是总共30~40张为宜,这个大家随意。

    拍照的程序随意,自己写个就行了,c++或者python等都行,可以参考网上的这个

    // 同时调用两个摄像头,暂停并保存左右相机的标定棋盘图
    
    #include <opencv2/opencv.hpp>
    #include <iostream>
    #include <math.h>
    
    using namespace cv;
    using namespace std;                             //开头我是从教程学的,一般不变,直接用
    
    int main(int argc, char* argv[])
    {
    	VideoCapture cap(0);
    	VideoCapture cap1(1);                   //打开两个摄像头
    
    	if(!cap.isOpened())
    	{
    		printf("Could not open camera0...\n");
    		return -1;
    	}
    	if (!cap1.isOpened())
    	{
    		printf("Could not open camera1...\n");
    		return -2;
    
    	}                                                        //判断还是加上为好,便于调程序
    
    
    
    	Mat frame, frame1;
    	bool stop = false;
    	while (!stop)
    	{
    		cap.read(frame);
    		cap1.read(frame1);
    		imshow("camera0", frame);
    		imshow("camera1", frame1);
    
    		int delay = 30;
    		if (delay >= 0 && waitKey(delay) >= 0)
    		{
    			waitKey(0);                                           //实时拍摄暂停的程序
    		}
    		imwrite("C:/Users/Administrator/Desktop/11/left1.jpg", frame1);
    		imwrite("C:/Users/Administrator/Desktop/11/right1.jpg", frame);  //给个位置保存图片,注意图片到底是                                                                                                                 左相机还是右相机的(用手在摄像头前晃晃),我用                                                                                                        的笨方法,保存一下,再把(left1.jpg/right1.jpg)+1,接着保存
    	}
    
    	cap.release();
    	cap1.release();                             //两个摄像头数据释放  
    	return 0;
    }
    

    注:

    ①这个棋盘标定图网上是分opencv版(如26mm)与matlab版(如15mm)的,而且每张摆拍的姿态建议参照opencv自带的28张标定图姿态(各种),具体可参考https://blog.csdn.net/wwp2016/article/details/84676142 。我是之前一直只用一个姿势摆拍,结果效果会有翻转、畸变等问题。

    ②拍出的照片应该是有要求的:拍出的棋盘图尽量占满整张照片。所以两个镜头建议还是越靠近平行着摆放越好。因为两镜头相距越远,那么为了保证左右的成像里都能拍到完整的棋盘图,棋盘图也要相对远一些摆放,导致拍出的棋盘图很难大部分占到整张照片。

    二. 单目标定

    以下代码就是只用opencv实现的。网上说要更精确还是建议先用matlab做单目标定,再把得到的左右各自的单目数据放入双目标定代码里(由opencv实现)。我是两个方法都试了,结果差不多。matlab教程网上找一下就有,只需几步,很简单。

    //左右单目相机分别标定  
      
    #include "opencv2/core/core.hpp"  
    #include "opencv2/imgproc/imgproc.hpp"  
    #include "opencv2/calib3d/calib3d.hpp"  
    #include "opencv2/highgui/highgui.hpp"  
      
    #include <opencv2/opencv.hpp>  
    #include "cv.h"  
    #include <cv.hpp>  
    #include <iostream>  
    
    using namespace std;
    using namespace cv;                                      //人家这开头都长,遇到有红线标记的就删了,把你知道的开头加上,没问题
    
    const int boardWidth = 9;                               //横向的角点数目  
    const int boardHeight = 6;                              //纵向的角点数据  
    const int boardCorner = boardWidth * boardHeight;       //总的角点数据  
    const int frameNumber = 15;                             //相机标定时需要采用的图像帧数(取左右拍下的具体张数,如因为左右各拍了15张,故取15)  
    const int squareSize = 25;                              //标定板黑白格子的大小 单位mm  
    const Size boardSize = Size(boardWidth, boardHeight);   //总的内角点
    
    Mat intrinsic;                                                //相机内参数  
    Mat distortion_coeff;                                   //相机畸变参数  
    vector<Mat> rvecs;                                        //旋转向量  
    vector<Mat> tvecs;                                        //平移向量  
    vector<vector<Point2f>> corners;                        //各个图像找到的角点的集合 和objRealPoint 一一对应  
    vector<vector<Point3f>> objRealPoint;                   //各副图像的角点的实际物理坐标集合  
    
    vector<Point2f> corner;                                   //某一副图像找到的角点  
    
    /*计算标定板上模块的实际物理坐标*/
    void calRealPoint(vector<vector<Point3f>>& obj, int boardwidth, int boardheight, int imgNumber, int squaresize)
    {
    	vector<Point3f> imgpoint;
    	for (int rowIndex = 0; rowIndex < boardheight; rowIndex++)
    	{
    		for (int colIndex = 0; colIndex < boardwidth; colIndex++)
    		{
    			imgpoint.push_back(Point3f(rowIndex * squaresize, colIndex * squaresize, 0));
    		}
    	}
    	for (int imgIndex = 0; imgIndex < imgNumber; imgIndex++)
    	{
    		obj.push_back(imgpoint);
    	}
    }
    
    
    /*设置相机的初始参数 也可以不估计*/
    void guessCameraParam(void)
    {
    	/*分配内存*/
    	intrinsic.create(3, 3, CV_64FC1);    //相机内参数
    	distortion_coeff.create(5, 1, CV_64FC1);  //畸变参数
    
    	/*
    	fx 0 cx
    	0 fy cy
    	0 0  1     内参数
    	*/
    	intrinsic.at<double>(0, 0) = 256.8093262;   //fx         
    	intrinsic.at<double>(0, 2) = 160.2826538;   //cx  
    	intrinsic.at<double>(1, 1) = 254.7511139;   //fy  
    	intrinsic.at<double>(1, 2) = 127.6264572;   //cy  
    
    	intrinsic.at<double>(0, 1) = 0;
    	intrinsic.at<double>(1, 0) = 0;
    	intrinsic.at<double>(2, 0) = 0;
    	intrinsic.at<double>(2, 1) = 0;
    	intrinsic.at<double>(2, 2) = 1;
    
    	/*
    	k1 k2 p1 p2 k3    畸变参数
    	*/
    	distortion_coeff.at<double>(0, 0) = -0.193740;  //k1  
    	distortion_coeff.at<double>(1, 0) = -0.378588;  //k2  
    	distortion_coeff.at<double>(2, 0) = 0.028980;   //p1  
    	distortion_coeff.at<double>(3, 0) = 0.008136;   //p2  
    	distortion_coeff.at<double>(4, 0) = 0;          //k3(默认取0)  
    }
    
    
    void outputCameraParam(void)
    {
    	/*保存数据*/
    	//cvSave("cameraMatrix.xml", &intrinsic);  
    	//cvSave("cameraDistoration.xml", &distortion_coeff);  
    	//cvSave("rotatoVector.xml", &rvecs);  
    	//cvSave("translationVector.xml", &tvecs);  
    	/*输出数据*/
    	//cout << "fx :" << intrinsic.at<double>(0, 0) << endl << "fy :" << intrinsic.at<double>(1, 1) << endl;
    	//cout << "cx :" << intrinsic.at<double>(0, 2) << endl << "cy :" << intrinsic.at<double>(1, 2) << endl;//内参数
    	printf("fx:%lf...\n", intrinsic.at<double>(0, 0));
    	printf("fy:%lf...\n", intrinsic.at<double>(1, 1));
    	printf("cx:%lf...\n", intrinsic.at<double>(0, 2));
    	printf("cy:%lf...\n", intrinsic.at<double>(1, 2));                  //我学的是printf,就试着改了一下,都能用
    
    
    	//cout << "k1 :" << distortion_coeff.at<double>(0, 0) << endl;
    	//cout << "k2 :" << distortion_coeff.at<double>(1, 0) << endl;
    	//cout << "p1 :" << distortion_coeff.at<double>(2, 0) << endl;
    	//cout << "p2 :" << distortion_coeff.at<double>(3, 0) << endl;
    	//cout << "p3 :" << distortion_coeff.at<double>(4, 0) << endl;   //畸变参数
    	printf("k1:%lf...\n", distortion_coeff.at<double>(0, 0));
    	printf("k2:%lf...\n", distortion_coeff.at<double>(1, 0));
    	printf("p1:%lf...\n", distortion_coeff.at<double>(2, 0));
    	printf("p2:%lf...\n", distortion_coeff.at<double>(3, 0));
    	printf("p3:%lf...\n", distortion_coeff.at<double>(4, 0));
    }
    
    int main()
    {
    	int imageHeight;     //图像高度
    	int imageWidth;      //图像宽度
    	int goodFrameCount = 0;    //有效图像的数目
    
    	Mat rgbImage,grayImage;
    	Mat tImage = imread("C:/Users/Administrator/Desktop/11/right1.jpg");
    	if (tImage.empty())
    	{
    		printf("Could not load tImage...\n");
    		return -1;
    	}
    	imageHeight = tImage.rows;
    	imageWidth = tImage.cols;
    	
    	grayImage = Mat::ones(tImage.size(), CV_8UC1);
    	while (goodFrameCount < frameNumber)
    	{
    		char filename[100];
    		sprintf_s(filename, "C:/Users/Administrator/Desktop/11/right%d.jpg", goodFrameCount + 1);
    
    		rgbImage = imread(filename);
    		if (rgbImage.empty())
    		{
    			printf("Could not load grayImage...\n");
    			return -2;
    		}
    		
    		cvtColor(rgbImage, grayImage, CV_BGR2GRAY);
    	
    		imshow("Camera", grayImage);
    
    		bool isFind = findChessboardCorners(rgbImage, boardSize, corner, CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE);
    		//bool isFind = findChessboardCorners( rgbImage, boardSize, corner,  
    		//CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);  
    
    		if (isFind == true) //所有角点都被找到 说明这幅图像是可行的  
    		{
    			//精确角点位置,亚像素角点检测
    			cornerSubPix(grayImage, corner, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1));
    			//绘制角点
    			drawChessboardCorners(rgbImage, boardSize, corner, isFind);
    			imshow("chessboard", rgbImage);
    			corners.push_back(corner);
    			goodFrameCount++;
    			/*cout << "The image" << goodFrameCount << " is good" << endl;*/
    			printf("The image %d is good...\n", goodFrameCount);
    		}
    		else
    		{
    			printf("The image is bad please try again...\n");
    		}
    
    
    		if (waitKey(10) == 'q')
    		{
    			break;
    		}
    	}
    
    	/*
    	图像采集完毕 接下来开始摄像头的校正
    	calibrateCamera()
    	输入参数 objectPoints  角点的实际物理坐标
    	imagePoints   角点的图像坐标
    	imageSize     图像的大小
    	输出参数
    	cameraMatrix  相机的内参矩阵
    	distCoeffs    相机的畸变参数
    	rvecs         旋转矢量(外参数)
    	tvecs         平移矢量(外参数)
    	*/
    
    
    	/*设置实际初始参数 根据calibrateCamera来 如果flag = 0 也可以不进行设置*/
    	guessCameraParam();
    	printf("guess successful...\n");
    	/*计算实际的校正点的三维坐标*/
    	calRealPoint(objRealPoint, boardWidth, boardHeight, frameNumber, squareSize);
    	printf("calculate real successful...\n");
    	/*标定摄像头*/
    	calibrateCamera(objRealPoint, corners, Size(imageWidth, imageHeight), intrinsic, distortion_coeff, rvecs, tvecs, 0);
    	printf("calibration successful...\n");
    	/*保存并输出参数*/
    	outputCameraParam();
    	printf("output successful...\n");
    
    	/*显示畸变校正效果*/
    	Mat cImage;
    	undistort(rgbImage, cImage, intrinsic, distortion_coeff);  //矫正相机镜头变形
    	imshow("Corret Image", cImage);
    	printf("Corret Image....\n");
    	printf("Wait for Key....\n");
    	
    	waitKey(0);
    	return 0;
    
    }

    三. 双目标定

    把上步得到的左右各自的内参矩阵fx、cx、fy、cy等,以及畸变参数k1、k2、p1、p2、k3和R、T矢量值填入如下代码

    // 双目相机标定  
    
    #include "opencv2/core/core.hpp"  
    #include "opencv2/imgproc/imgproc.hpp"  
    #include "opencv2/calib3d/calib3d.hpp"  
    #include "opencv2/highgui/highgui.hpp"  
    
    #include <vector>  
    #include <string>  
    #include <algorithm>  
    #include <iostream>  
    #include <iterator>  
    #include <stdio.h>  
    #include <stdlib.h>  
    #include <ctype.h>  
     
    #include <opencv2/opencv.hpp>  
    #include "cv.h"  
    #include <cv.hpp>  
    
    using namespace std;
    using namespace cv;                                         //依旧很长的开头
    
    
    const int imageWidth = 640;                             //摄像头的分辨率  
    const int imageHeight = 480;
    const int boardWidth = 9;                               //横向的角点数目  
    const int boardHeight = 6;                              //纵向的角点数据  
    const int boardCorner = boardWidth * boardHeight;       //总的角点数据  
    const int frameNumber = 15;                             //相机标定时需要采用的图像帧数  
    const int squareSize = 25;                              //标定板黑白格子的大小 单位mm  
    const Size boardSize = Size(boardWidth, boardHeight);   //标定板的总内角点  
    Size imageSize = Size(imageWidth, imageHeight);
    
    Mat R, T, E, F;                                                  //R 旋转矢量 T平移矢量 E本征矩阵 F基础矩阵  
    vector<Mat> rvecs;                                        //旋转向量  
    vector<Mat> tvecs;                                        //平移向量  
    vector<vector<Point2f>> imagePointL;                    //左边摄像机所有照片角点的坐标集合  
    vector<vector<Point2f>> imagePointR;                    //右边摄像机所有照片角点的坐标集合  
    vector<vector<Point3f>> objRealPoint;                   //各副图像的角点的实际物理坐标集合  
    
    vector<Point2f> cornerL;                              //左边摄像机某一照片角点坐标集合  
    
    vector<Point2f> cornerR;                              //右边摄像机某一照片角点坐标集合  
    
    Mat rgbImageL, grayImageL;
    Mat rgbImageR, grayImageR;
    
    Mat Rl, Rr, Pl, Pr, Q;                                  //校正旋转矩阵R,投影矩阵P 重投影矩阵Q (下面有具体的含义解释)   
    Mat mapLx, mapLy, mapRx, mapRy;                         //映射表  
    Rect validROIL, validROIR;                              //图像校正之后,会对图像进行裁剪,这里的validROI就是指裁剪之后的区域  
    
    /*
    事先标定好的左相机的内参矩阵
    fx 0 cx
    0 fy cy
    0 0  1
    */
    Mat cameraMatrixL = (Mat_<double>(3, 3) << 462.279595, 0, 312.781587,
    	0, 460.220741, 208.225803,
    	0, 0, 1);                                                                           //这时候就需要你把左右相机单目标定的参数给写上
    //获得的畸变参数
    Mat distCoeffL = (Mat_<double>(5, 1) << -0.054929, 0.224509, 0.000386, 0.001799, -0.302288);
    /*
    事先标定好的右相机的内参矩阵
    fx 0 cx
    0 fy cy
    0 0  1
    */
    Mat cameraMatrixR = (Mat_<double>(3, 3) << 463.923124, 0, 322.783959,
    	0, 462.203276, 256.100655,
    	0, 0, 1);
    Mat distCoeffR = (Mat_<double>(5, 1) << -0.049056, 0.229945, 0.001745, -0.001862, -0.321533);
    
    /*计算标定板上模块的实际物理坐标*/
    void calRealPoint(vector<vector<Point3f>>& obj, int boardwidth, int boardheight, int imgNumber, int squaresize)
    { 
    	vector<Point3f> imgpoint;
    	for (int rowIndex = 0; rowIndex < boardheight; rowIndex++)
    	{
    		for (int colIndex = 0; colIndex < boardwidth; colIndex++)
    		{
    			imgpoint.push_back(Point3f(rowIndex * squaresize, colIndex * squaresize, 0));
    		}
    	}
    	for (int imgIndex = 0; imgIndex < imgNumber; imgIndex++)
    	{
    		obj.push_back(imgpoint);
    	}
    }
    
    
    void outputCameraParam(void)                   
    {
    	/*保存数据*/
    	/*输出数据*/
    	FileStorage fs("intrinsics.yml", FileStorage::WRITE);  //文件存储器的初始化
    	if (fs.isOpened())
    	{
    		fs << "cameraMatrixL" << cameraMatrixL << "cameraDistcoeffL" << distCoeffL << "cameraMatrixR" << cameraMatrixR << "cameraDistcoeffR" << distCoeffR;
    		fs.release();
    		cout << "cameraMatrixL=:" << cameraMatrixL << endl << "cameraDistcoeffL=:" << distCoeffL << endl << "cameraMatrixR=:" << cameraMatrixR << endl << "cameraDistcoeffR=:" << distCoeffR << endl;
    	}
    	else
    	{
    		cout << "Error: can not save the intrinsics!!!!!" << endl;
    	}
    
    
    	fs.open("extrinsics.yml", FileStorage::WRITE);
    	if (fs.isOpened())
    	{
    		fs << "R" << R << "T" << T << "Rl" << Rl << "Rr" << Rr << "Pl" << Pl << "Pr" << Pr << "Q" << Q;
    		cout << "R=" << R << endl << "T=" << T << endl << "Rl=" << Rl << endl << "Rr=" << Rr << endl << "Pl=" << Pl << endl << "Pr=" << Pr << endl << "Q=" << Q << endl;
    		fs.release();
    	}
    	else
    		cout << "Error: can not save the extrinsic parameters\n";
    }
    
    int main(int argc, char* argv[])
    {
    	Mat img;
    	int goodFrameCount = 0;
    	while (goodFrameCount < frameNumber)
    	{
    		char filename[100];
    		/*读取左边的图像*/
    		sprintf_s(filename, "C:/Users/Administrator/Desktop/11/left%d.jpg", goodFrameCount + 1);
    		rgbImageL = imread(filename, CV_LOAD_IMAGE_COLOR);
    		cvtColor(rgbImageL, grayImageL, CV_BGR2GRAY);
    
    		/*读取右边的图像*/
    		sprintf_s(filename, "C:/Users/Administrator/Desktop/11/right%d.jpg", goodFrameCount + 1);
    		rgbImageR = imread(filename, CV_LOAD_IMAGE_COLOR);
    		cvtColor(rgbImageR, grayImageR, CV_BGR2GRAY);
    
    		bool isFindL, isFindR;
    
    		isFindL = findChessboardCorners(rgbImageL, boardSize, cornerL);
    		isFindR = findChessboardCorners(rgbImageR, boardSize, cornerR);
    		if (isFindL == true && isFindR == true)  //如果两幅图像都找到了所有的角点 则说明这两幅图像是可行的  
    		{
    			/*
    			Size(5,5) 搜索窗口的一半大小
    			Size(-1,-1) 死区的一半尺寸
    			TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1)迭代终止条件
    			*/
    			cornerSubPix(grayImageL, cornerL, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1));
    			drawChessboardCorners(rgbImageL, boardSize, cornerL, isFindL);
    			imshow("chessboardL", rgbImageL);
    			imagePointL.push_back(cornerL);
    
    			cornerSubPix(grayImageR, cornerR, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 20, 0.1));
    			drawChessboardCorners(rgbImageR, boardSize, cornerR, isFindR);
    			imshow("chessboardR", rgbImageR);
    			imagePointR.push_back(cornerR);
    
    
    			//string filename = "res\\image\\calibration";  
    			//filename += goodFrameCount + ".jpg";  
    			//cvSaveImage(filename.c_str(), &IplImage(rgbImage));       //把合格的图片保存起来  
    			goodFrameCount++;
    			cout << "The image" << goodFrameCount << " is good" << endl;
    		}
    		else
    		{
    			cout << "The image is bad please try again" << endl;
    		}
    
    		if (waitKey(10) == 'q')
    		{
    			break;
    		}
    	}
    
    	/*
    	计算实际的校正点的三维坐标
    	根据实际标定格子的大小来设置
    	*/
    	calRealPoint(objRealPoint, boardWidth, boardHeight, frameNumber, squareSize);
    	cout << "cal real successful" << endl;
    
    	/*
    	标定摄像头
    	由于左右摄像机分别都经过了单目标定
    	所以在此处选择flag = CALIB_USE_INTRINSIC_GUESS
    	*/
    	double rms = stereoCalibrate(objRealPoint, imagePointL, imagePointR,
    		cameraMatrixL, distCoeffL,
    		cameraMatrixR, distCoeffR,
    		Size(imageWidth, imageHeight),R, T,E, F, CALIB_USE_INTRINSIC_GUESS,
    		TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, 1e-5)); //需要注意,应该是版本的原因,该函数最                                                                                                                            后两个参数,我是调换过来后才显示不出错的
    
    	cout << "Stereo Calibration done with RMS error = " << rms << endl;
    
    	/*
    	立体校正的时候需要两幅图像共面并且行对准 以使得立体匹配更加的可靠
    	使得两幅图像共面的方法就是把两个摄像头的图像投影到一个公共成像面上,这样每幅图像从本图像平面投影到公共图像平面都需要一个旋转矩阵R
    	stereoRectify 这个函数计算的就是从图像平面投影到公共成像平面的旋转矩阵Rl,Rr。 Rl,Rr即为左右相机平面行对准的校正旋转矩阵。
    	左相机经过Rl旋转,右相机经过Rr旋转之后,两幅图像就已经共面并且行对准了。
    	其中Pl,Pr为两个相机的投影矩阵,其作用是将3D点的坐标转换到图像的2D点的坐标:P*[X Y Z 1]' =[x y w]
    	Q矩阵为重投影矩阵,即矩阵Q可以把2维平面(图像平面)上的点投影到3维空间的点:Q*[x y d 1] = [X Y Z W]。其中d为左右两幅图像的时差
    	*/
    	//对标定过的图像进行校正
    	stereoRectify(cameraMatrixL, distCoeffL, cameraMatrixR, distCoeffR, imageSize, R, T, Rl, Rr, Pl, Pr, Q,
    		CALIB_ZERO_DISPARITY, -1, imageSize, &validROIL, &validROIR);   
    	/*
    	根据stereoRectify 计算出来的R 和 P 来计算图像的映射表 mapx,mapy
    	mapx,mapy这两个映射表接下来可以给remap()函数调用,来校正图像,使得两幅图像共面并且行对准
    	ininUndistortRectifyMap()的参数newCameraMatrix就是校正后的摄像机矩阵。在openCV里面,校正后的计算机矩阵Mrect是跟投影矩阵P一起返回的。
    	所以我们在这里传入投影矩阵P,此函数可以从投影矩阵P中读出校正后的摄像机矩阵
    	*/
    	//摄像机校正映射
    	initUndistortRectifyMap(cameraMatrixL, distCoeffL, Rl, Pr, imageSize, CV_32FC1, mapLx, mapLy); 
    	initUndistortRectifyMap(cameraMatrixR, distCoeffR, Rr, Pr, imageSize, CV_32FC1, mapRx, mapRy);
    
    	Mat rectifyImageL, rectifyImageR;
    	cvtColor(grayImageL, rectifyImageL, CV_GRAY2BGR);
    	cvtColor(grayImageR, rectifyImageR, CV_GRAY2BGR);
    
    	imshow("Rectify Before", rectifyImageL);
    	cout << "按Q1退出 ..." << endl;
    
    	/*
    	经过remap之后,左右相机的图像已经共面并且行对准了
    	*/
    	Mat rectifyImageL2, rectifyImageR2;
    	remap(rectifyImageL, rectifyImageL2, mapLx, mapLy, INTER_LINEAR);
    	remap(rectifyImageR, rectifyImageR2, mapRx, mapRy, INTER_LINEAR);
    	cout << "按Q2退出 ..." << endl;
    
    	imshow("rectifyImageL", rectifyImageL2);
    	imshow("rectifyImageR", rectifyImageR2);
    
    	/*保存并输出数据*/
    	outputCameraParam();
    
    	/*
    	把校正结果显示出来
    	把左右两幅图像显示到同一个画面上
    	这里只显示了最后一副图像的校正结果。并没有把所有的图像都显示出来
    	*/
    	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(rectifyImageL2, 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(rectifyImageR2, 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, 255, 0), 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);
    
    	cout << "wait key" << endl;
    	waitKey(0);
    	//system("pause");  
    	return 0;
    }

    四.  最后,转为深度图

    import numpy as np
    import cv2
    import camera_configs
     
    cv2.namedWindow("left")
    cv2.namedWindow("right")
    cv2.namedWindow("depth")
    cv2.moveWindow("left", 0, 0)
    cv2.moveWindow("right", 600, 0)
    cv2.createTrackbar("num", "depth", 0, 10, lambda x: None)
    cv2.createTrackbar("blockSize", "depth", 5, 255, lambda x: None)
    camera1 = cv2.VideoCapture(0)
    camera2 = cv2.VideoCapture(1)
     
    # 添加点击事件,打印当前点的距离
    def callbackFunc(e, x, y, f, p):
        if e == cv2.EVENT_LBUTTONDOWN:        
            print threeD[y][x]
     
    cv2.setMouseCallback("depth", callbackFunc, None)
     
    while True:
        ret1, frame1 = camera1.read()
        ret2, frame2 = camera2.read()
     
        if not ret1 or not ret2:
            break
     
        # 根据更正map对图片进行重构
        img1_rectified = cv2.remap(frame1, camera_configs.left_map1, camera_configs.left_map2, cv2.INTER_LINEAR)
        img2_rectified = cv2.remap(frame2, camera_configs.right_map1, camera_configs.right_map2, cv2.INTER_LINEAR)
     
        # 将图片置为灰度图,为StereoBM作准备
        imgL = cv2.cvtColor(img1_rectified, cv2.COLOR_BGR2GRAY)
        imgR = cv2.cvtColor(img2_rectified, cv2.COLOR_BGR2GRAY)
     
        # 两个trackbar用来调节不同的参数查看效果
        num = cv2.getTrackbarPos("num", "depth")
        blockSize = cv2.getTrackbarPos("blockSize", "depth")
        if blockSize % 2 == 0:
            blockSize += 1
        if blockSize < 5:
            blockSize = 5
     
        # 根据Block Maching方法生成差异图(opencv里也提供了SGBM/Semi-Global Block Matching算法,有兴趣可以试试)
        stereo = cv2.StereoBM_create(numDisparities=16*num, blockSize=blockSize)
        disparity = stereo.compute(imgL, imgR)
     
        disp = cv2.normalize(disparity, disparity, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
        # 将图片扩展至3d空间中,其z方向的值则为当前的距离
        threeD = cv2.reprojectImageTo3D(disparity.astype(np.float32)/16., camera_configs.Q)
     
        cv2.imshow("left", img1_rectified)
        cv2.imshow("right", img2_rectified)
        cv2.imshow("depth", disp)
     
        key = cv2.waitKey(1)
        if key == ord("q"):
            break
        elif key == ord("s"):
            cv2.imwrite("./snapshot/BM_left.jpg", imgL)
            cv2.imwrite("./snapshot/BM_right.jpg", imgR)
            cv2.imwrite("./snapshot/BM_depth.jpg", disp)
     
    camera1.release()
    camera2.release()
    cv2.destroyAllWindows()
    

     

     

    展开全文
  • 双目测距的基本原理

    千次阅读 2014-05-06 23:02:14
    双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,...

    双目测距的基本原理

    clip_image002[10]

    如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差clip_image004 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,f的量纲是像素点,T的量纲由定标板棋盘格的实际尺寸和用户输入值确定,一般是以毫米为单位(当然为了精度提高也可以设置为0.1毫米量级),d=xl-xr的量纲也是像素点。因此分子分母约去,Z的量纲与T相同。 

    clip_image006

    假设目标点在左视图中的坐标为(x,y),在左右视图上形成的视差为d,目标点在以左摄像头光心为原点的世界坐标系中的坐标为(X,Y,Z),则存在上图所示的变换矩阵Q,使得 Q*[x y d 1]’ = [X Y Z W]’。

    @scyscyao :为了精确地求得某个点在三维空间里的距离Z,我们需要获得的参数有焦距f、视差d、摄像头中心距Tx。如果还需要获得X坐标和Y坐标的话,那么还需要额外知道左右像平面的坐标系与立体坐标系中原点的偏移cx和cy。其中f, Tx, cx和cy可以通过立体标定获得初始值,并通过立体校准优化,使得两个摄像头在数学上完全平行放置,并且左右摄像头的cx, cy和f相同(也就是实现图2中左右视图完全平行对准的理想形式)。而立体匹配所做的工作,就是在之前的基础上,求取最后一个变量:视差d(这个d一般需要达到亚像素精度)。从而最终完成求一个点三维坐标所需要的准备工作。在清楚了上述原理之后,我们也就知道了,所有的这几步:标定、校准和匹配,都是围绕着如何更精确地获得 f, d, Tx, cx 和cy 而设计的 。 ”


    二、摄像头定标

    摄像头定标一般都需要一个放在摄像头前的特制的标定参照物(棋盘纸),摄像头获取该物体的图像,并由此计算摄像头的内外参数。标定参照物上的每一个特征点相对于世界坐标系的位置在制作时应精确测定,世界坐标系可选为参照物的物体坐标系。在得到这些已知点在图像上的投影位置后,可计算出摄像头的内外参数。

    clip_image008

    如上图所示,摄像头由于光学透镜的特性使得成像存在着径向畸变,可由三个参数k1,k2,k3确定;由于装配方面的误差,传感器与光学镜头之间并非完全平行,因此成像存在切向畸变,可由两个参数p1,p2确定。单个摄像头的定标主要是计算出摄像头的内参(焦距f和成像原点cx,cy、五个畸变参数(一般只需要计算出k1,k2,p1,p2,对于鱼眼镜头等径向畸变特别大的才需要计算k3))以及外参(标定物的世界坐标)。 OpenCV 中使用的求解焦距和成像原点的算法是基于张正友的方法( pdf ),而求解畸变参数是基于 Brown 的方法( pdf )。


    1. 图像坐标系、摄像头坐标系和世界坐标系的关系

    clip_image010 clip_image012

    摄像头成像几何关系,其中Oc 点称为摄像头(透镜)的光心,Xc 轴和Yc 轴与图像的x轴和Y轴平行,Zc 轴为摄像头的光轴,它与图像平面垂直。光轴与图像平面的交点O1 ,即为图像坐标系的原点。由点Oc 与Xc 、Yc 、Zc 轴组成的坐标系称为摄像头坐标系,Oc O1 的距离为摄像头焦距,用f表示。

    图像坐标系是一个二维平面,又称为像平面,“@scyscyao :实际上就是摄像头的CCD传感器的表面。每个CCD传感器都有一定的尺寸,也有一定的分辨率,这个就确定了毫米与像素点之间的转换关系。举个例子,CCD的尺寸是8mm X 6mm,帧画面的分辨率设置为640X480,那么毫米与像素点之间的转换关系就是80pixel/mm。”设CCD传感器每个像素点的物理大小为dx*dy,相应地,就有 dx=dy=1/80。(他咋算的???)


    2. 进行摄像头定标时,棋盘方格的实际大小 square_size (默认为 1.0f )的设置对定标参数是否有影响?

    @scyscyao :当然有。在标定时,需要指定一个棋盘方格的长度,这个长度(一般以毫米为单位,如果需要更精确可以设为0.1毫米量级)与实际长度相同,标 定得出的结果才能用于实际距离测量。一般如果尺寸设定准确的话,通过立体标定得出的Translation向量的第一个分量Tx的绝对值就是左右摄像头的中心距。一般可以用这个来验证立体标定的准确度。比如我设定的棋盘格大小为270 (27mm),最终得出的Tx大小就是602.8 (60.28mm),相当精确。”

     

    3. 定标所得的摄像头内参数,即焦距和原点坐标,其数值单位都是一致的吗?怎么把焦距数值换算为实际的物理量?

    @wobject :是的,都是以像素为单位。假设像素点的大小为k x l,单位为mm,则fx = f / k, fy = f / (l * sinA), A一般假设为 90°,是指摄像头坐标系的偏斜度(就是镜头坐标和CCD是否垂直)。摄像头矩阵(内参)的目的是把图像的点从图像坐标转换成实际物理的三维坐标。因此其中的fx, fy, cx, cy 都是使用类似上面的纲量。同样,Q 中的变量 f,cx, cy 也应该是一样的。”

     

    4. 棋盘图像数目应该取多少对摄像头定标比较适宜?

    OpenCV中文论坛上piao的帖子《在OpenCV中用cvCalibrateCamera2进行相机标定(附程序) 》中指出影响摄像头定标结果的准确性和稳定性的因素主要有三个:

    (1) 标定板所在平面与成像平面(image plane)之间的夹角;

    (2) 标定时拍摄的图片数目(棋盘图像数目);

    (3) 图像上角点提取的不准确。

    感觉OpenCV1.2以后对图像角点的提取准确度是比较高的,cvFindChessboardCorners 和 cvFindCornerSubPix结合可以获得很好的角点检测效果(hqhuang1在《[HQ]角点检测(Corner Detection) cvFindCornerSubPix 使用范例 》中给出了相关的应用范例)。因此,影响定标结果较大的就是标定板与镜头的夹角和棋盘图像数目,在实际定标过程中,我感觉棋盘图像数目应该大于20张,每成功检测一次完整的棋盘角点就要变换一下标定板的姿态(包括角度、距离) 

     

    5. 单目定标函数cvCalibrateCamera2采用怎样的 flags 比较合适?

    由于一般镜头只需要计算k1,k2,p1,p2四个参数,所以我们首先要设置 CV_CALIB_FIX_K3;其次,如果所用的摄像头不是高端的、切向畸变系数非常少的,则不要设置 CV_CALIB_ZERO_TANGENT_DIST,否则单目校正误差会很大;如果事先知道摄像头内参的大概数值,并且cvCalibrateCamera2函数的第五个参数intrinsic_matrix非空,则也可设置 CV_CALIB_USE_INTRINSIC_GUESS ,以输入的intrinsic_matrix为初始估计值来加快内参的计算;其它的 flag 一般都不需要设置,对单目定标的影响不大。

     

    P.S. 使用OpenCV进行摄像机定标虽然方便,但是定标结果往往不够准确和稳定,最好是使用 Matlab标定工具箱 来进行定标,再将定标结果取回来用于立体匹配和视差计算。工具箱的使用官方主页 有图文并茂的详细说明,此外,有两篇博文也进行了不错的总结,推荐阅读:

    (1)分享一些OpenCV实现立体视觉的经验

    (2)Matlab标定工具箱使用的一些注意事项


    展开全文
  • 双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,f的...
  • 双目摄像头标定是测距及其三维建模非常重要的步骤,因为标定出来的内外参数将会用来对原始图像进行反畸变校正,而反畸变的效果对立体匹配精度有着非常大的影响。此外,最终在求距离时(其公式为l=B*f/d),这里面...
  • 在OpenCV中,f的量纲是像素点,T的量纲由定标棋盘格的实际尺寸和用户输入值确定,一般总是设成毫米,当然为了精度提高也可以设置为0.1毫米量级,d=xl-xr的量纲也是像素点。因此分子分母约去,z的量纲与T相同 图2, 双...
  • 学习OpenCV双目测距原理及常见问题解答一. 整体思路和问题转化.图1. 双摄像头模型俯视图图1解释了双摄像头测距的原理,书中Z的公式如下:在OpenCV中,f的量纲是像素点,T的量纲由定标棋盘格的实际尺寸和用户输入值...
  • 学习OpenCV(2)双目测距原理

    千次阅读 2016-03-04 10:39:13
    在OpenCV中,f的量纲是像素点,T的量纲由定标棋盘格的实际尺寸和用户输入值确定,一般总是设成毫米,当然为了精度提高也可以设置为0.1毫米量级,d=xl-xr的量纲也是像素点。因此分子分母约去,z的量纲与T相同 图2,...
  • 为了实现高精度的远距离双目CCD被动测距,提出改进的互相关匹配算法与三次曲面拟合算法,对双目CCD拍摄的两幅图像进行亚像素级匹配。...实验表明,3km 以内的目标实际测量精度优于0.5%,满足高精度双目测距精度要求。
  • 虽然单双目标定的流程其实已经固定化了,但是操作的方法会影响标定的最终精度 标定是否正确可以通过TxTx T_xTx​(左右相机光轴的距离)直接看出来 以下措施都可以提高精度: 使用高分辨率的工业相机 先单目标定,...
  • 为了得到一种易实现且精度较高的双目测距方法,立体校正左右相机的非前向平行结构,先将改进的Census变换算法应用于立体匹配,得到准确的视差值,再根据双目视觉特殊的外极线几何结构计算出实际的距离信息。将原始Census...
  • 双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,...
  • 双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差)与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao:在OpenCV中,f的量纲...
  • 双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差)与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao:在OpenCV中,f的量纲是...
  • 针对目前双目成像测距系统由于基线距离、成像焦距等硬件限制,难以进一步提高测距精度的问题,提出一种基于双目变焦的超分辨成像测距方法。通过变焦系统进行变焦超分辨率重建,在系统其余硬件条件不变的情况下提高系统...
  • OpenCV学习笔记(18)双目测距与三维重建的OpenCV实现问题集锦(三)立体匹配与视差计算4. cvFindStereoCorrespondenceBM的输出结果好像不是以像素点为单位的视差? “@scyscyao:在OpenCV2.0中,BM函数得出的结果是以...
  • 双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差)与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,f的...
  • 其中立体匹配是双目视觉中最重要和最困难的环节,不同的立体匹配算法有着不同的匹配策略,其匹配的精度和速度也会有很大差异。 立体标定和立体校正 在利用双目图像进行计算视差图和距离的时候,首先要做的步骤...
  • 同时,为了充分发挥双目视觉传感器和激光测距传感器在目标跟踪方面的优势,利用信息融合技术来提高信息的利用率和测量的精度。在系统量测过程中,由于激光测距传感器受到二维转台机械性能影响,导致测距值成为时滞...
  • 谈到双目相机测距,我们首先要先了解测距的原理:如下图所示,这是双目摄像头的俯视图。 上图解释了双摄像头测距的原理,书中Z的公式如下:b代表基线,根据相似三角形关系, 这里d表示为左右图横坐标之差,称为...
  • 《Adaptive Binocular Fringe Dvnamic ProiectionMethod for ...然而,大多数边缘投影传感器的测量精度和效率仍然受到图像饱和和投影仪非线性效应的严重影响。为了解决这一难题,结合立体视觉技术和边缘投影技术的优...
  • 双目相机标定以及立体测距原理及OpenCV实现

    万次阅读 多人点赞 2016-10-31 22:59:24
    单目相机标定的目标是获取相机的内参和外参,内参(1/dx,1/dy,Cx,Cy,f)表征...Cx和Cy分别代表相机感光芯片的中心点在x和y方向上可能存在的偏移,因为芯片在安装到相机模组上的时候,由于制造精度和组装工艺的影响...
  • 单目相机标定的目标是获取相机的内参和外参,内参(1/dx,1/dy,Cx,Cy,f)...Cx和Cy分别代表相机感光芯片的中心点在x和y方向上可能存在的偏移,因为芯片在安装到相机模组上的时候,由于制造精度和组装工艺的影响,很难...
  • 双目三维重建和误差估计

    千次阅读 2018-08-18 10:09:34
    双目测距的精度和基线长度(两台相机之间的距离)有关,两台相机布放的距离越远,测距精度越高。 但问题是:往往在实际应用中,相机的布放空间是有限的,最多也只有几米或几十米的基线长度,这就导致双目测距在远...
  • OpenCV 单目测距实现

    万次阅读 多人点赞 2018-12-23 00:57:29
    然后是双目测距,然而一个便宜的双目工业相机也要四五百,而且以前也没接触过双目测距...  于是就想试试单目测距的效果怎么样,通过参考网络上的各种资料,加上以前玩过三角激光测距,所以也算比较顺利的写出来个...
  • 然后是双目测距,然而一个便宜的双目工业相机也要四五百,而且以前也没接触过双目测距... 于是就想试试单目测距的效果怎么样,通过参考网络上的各种资料,加上以前玩过三角激光测距,所以也算比较顺利的写出来个简易...

空空如也

空空如也

1 2 3
收藏数 53
精华内容 21
关键字:

双目测距精度