精华内容
下载资源
问答
  • 2022-01-15 13:23:04

    前言

    项目需要,基于C/C++复现了双目立体校正功能,仅支持双线性插值方式。

    立体校正原理简述

    功能简述:
    立体校正即把左右摄像头采集的图像中同一物点变换到同一水平线(使其在图像中的纵坐标相等),其主要目的是加速后续双目匹配速度。如下图中,红点1与红点2在真实三维世界中表示同一物点,但两个摄像机光心并不处于同一水平线,导致成像时该点纵坐标不相等,极大的增加了双目匹配时的搜索范围。
    在这里插入图片描述
    双目校正原理简述:
    通过一个(模型、方程式)获取校正后的图中每个像素点坐标与原图坐标的映射关系,再把原图中的像素值赋值给校正后图中的对应位置即可。如下图中,假设点1的坐标为(10,10),通过模型计算该点对应原图点2坐标为(35,40),那么便把点2(35,40)的像素值赋值给点1(10,10)。至于这个模型的方程表达式为什么是这个样子,都是学术大佬研究的,我选择只用用就好。(其实就是没看懂原理,哈哈!有懂的大佬可以评论区指点指点)
    在这里插入图片描述
    双线性插值:
    用周围的4个点的像素值,通过分配不同的权重表示该点的像素值。例如坐标点(35.2,40.3)的像素值会通过周围4个点:(35,40)、(36,40)、(35,41)、(36,41)来表示。其中小数点0.2,0.3会用于计算权重,具体公式如下:

    i = 35
    j = 40
    u = 0.2
    v = 0.3
    f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1)

    在这里插入图片描述

    具体代码,opencv主要用于读图和存图,验证我的校正是否正确。

    #include<iostream>
    #include<opencv2/opencv.hpp>
    
    using namespace cv;
    using namespace std;
    
    void GetMatrix(float *R, float *P, float *PRI);
    void GetMap(float *K, float *D, float *PRI, float *mapx, float *mapy, Size size);
    void Imgremap(Mat srcImg, Mat &dstImg, float *mapx, float *mapy);
    void Imgremap_new(Mat srcImg, Mat &dstImg, float *mapx, float *mapy);
    
    int main()
    {
    	/* opencv API实现双目校正 */
    	Mat API_img = imread("left01.jpg", 0);
    
    	Size imgsz(640, 480);
    	/* 相机参数通过单目、双目标定即可获得 */
    	Mat cameraMatrixL = (Mat_<double>(3, 3) << 5.3340331777463712e+02, 0., 3.4251343227755160e+02, 0.,
    		5.3343402398745684e+02, 2.3475353096292952e+02, 0., 0., 1.);
    	Mat distCoeffL = (Mat_<double>(5, 1) << -2.8214652038759541e-01, 4.0840748605552028e-02,
    		1.2058218262263004e-03, -1.2307876204068898e-04,
    		1.1409651538056684e-01);
    
    	Mat API_Rl = (Mat_<double>(3, 3) << 9.9995545010198061e-01, -7.5529503409555278e-03,
    		5.6613384011591078e-03, 7.5729426565523507e-03,
    		9.9996513545382970e-01, -3.5182973615490811e-03,
    		-5.6345674959085556e-03, 3.5610136128317303e-03,
    		9.9997778516884228e-01);
    
    	Mat API_Pl = (Mat_<double>(3, 4) << 5.3500952177482827e+02, 0., 3.3665814208984375e+02, 0., 0.,
    		5.3500952177482827e+02, 2.4442177581787109e+02, 0., 0., 0., 1.,
    		0.);
    
    	Mat API_maplx, API_maply;
    	initUndistortRectifyMap(cameraMatrixL, distCoeffL, API_Rl, API_Pl, imgsz, CV_32FC1, API_maplx, API_maply);
    
    	clock_t start_API, end_API;
    	Mat API_unimg;
    
    	start_API = clock();
    	remap(API_img, API_unimg, API_maplx, API_maply, INTER_LINEAR);
    	end_API = clock();
    	cout << "API run time:" << (double)(end_API - start_API) << endl;
    	
    	imwrite("left01_API.jpg", API_unimg);
    
    
    	/* 复现代码实现双目校正 */
    	/* 左相机参数 */
    	float Kl[3][3] = { { 5.3340331777463712e+02, 0., 3.4251343227755160e+02 }, 
    					   { 0.,5.3343402398745684e+02, 2.3475353096292952e+02 },
    					   { 0., 0., 1. } };
    	float Dl[5] = { -2.8214652038759541e-01, 4.0840748605552028e-02, 1.2058218262263004e-03,
    					-1.2307876204068898e-04, 1.1409651538056684e-01 };
    	float Rl[3][3] = { { 9.9995545010198061e-01, -7.5529503409555278e-03,5.6613384011591078e-03 },
    					   { 7.5729426565523507e-03,9.9996513545382970e-01, -3.5182973615490811e-03 },
    					   { -5.6345674959085556e-03, 3.5610136128317303e-03,9.9997778516884228e-01 } };
    	float Pl[3][3] = { { 5.3500952177482827e+02, 0., 3.3665814208984375e+02 },
    					   { 0., 5.3500952177482827e+02, 2.4442177581787109e+02 }, 
    					   { 0., 0., 1. } };
    	/* 右相机参数 */
    	float Kr[3][3] = { { 5.3699964365956180e+02, 0., 3.2744774682047540e+02 },
    					   { 0., 5.3658501956219982e+02, 2.4990007115682096e+02 },
    					   { 0., 0., 1. } };
    	float Dr[5] = { -2.9611763213840986e-01, 1.3891105660442912e-01, -5.0433529470851200e-04, 
    					 9.4658617944131683e-05, -4.9061152399050519e-02 };
    	float Rr[3][3] = { { 9.9993738772164931e-01, -1.1094841523320539e-02, 1.4577818686240377e-03 },
    					   { 1.1089611831016350e-02, 9.9993221439532998e-01, 3.5478336896012114e-03 },
    					   { -1.4970457045358340e-03, -3.5314453165933707e-03, 9.9999264384701070e-01 } };
    	float Pr[3][3] = { { 5.3500952177482827e+02, 0., 3.3665814208984375e+02 },
    					   { 0., 5.3500952177482827e+02, 2.4442177581787109e+02 },
    					   { 0., 0., 1. } };
    
    	/* 求逆矩阵 */
    	float PRIl[3][3];
    	float PRIr[3][3];
    	memset(PRIl, 0, sizeof(PRIl));
    	memset(PRIr, 0, sizeof(PRIr));
    	GetMatrix((float*)Rl, (float*)Pl, (float*)PRIl);
    	GetMatrix((float*)Rr, (float*)Pr, (float*)PRIr);
    
    	/* 获取映射表 */
    	Size ImgSize(640, 480);
    	float *maplx = new float[ImgSize.width * ImgSize.height];
    	float *maply = new float[ImgSize.width * ImgSize.height];
    	float *maprx = new float[ImgSize.width * ImgSize.height];
    	float *mapry = new float[ImgSize.width * ImgSize.height];
    
    	GetMap((float*)Kl, (float*)Dl, (float*)PRIl, maplx, maply, ImgSize);
    	GetMap((float*)Kr, (float*)Dr, (float*)PRIr, maprx, mapry, ImgSize);
    
    	/* 映射校正 */
    	clock_t start_MY, end_MY;
    
    	Mat imgl = imread("left01.jpg", 0);
    	Mat imgr = imread("right01.jpg", 0);
    	Mat unimgl(imgl.rows, imgl.cols, CV_8UC1);
    	Mat unimgr(imgl.rows, imgl.cols, CV_8UC1);
    
    	start_MY = clock();
    	Imgremap_new(imgl, unimgl, maplx, maply);
    	Imgremap_new(imgr, unimgr, maprx, mapry);
    	end_MY = clock();
    	cout << "MY run time:" << (double)(end_MY - start_MY) << endl;
    
    	imwrite("left01_MY.jpg", unimgl);
    	imwrite("right01_MY.jpg", unimgr);
    
    	delete[] maplx;
    	delete[] maply;
    	delete[] maprx;
    	delete[] mapry;
    
    	system("pause");
    	return 0;
    }
    
    /*
    * brief 获取矩阵PR的逆矩阵
    * param R	输入,旋转矩阵指针,双目标定获得
    * param P	输入,映射矩阵指针,双目标定获得
    * param PRI 输出,逆矩阵指针
    
    */
    void GetMatrix(float *R, float *P, float *PRI)
    {
    	float temp;
    	float PR[3][3];
    	memset(PR, 0, sizeof(PR));
    
    	/* 矩阵P*R */
    	for (int i = 0; i < 3; i++)
    	{
    		for (int k = 0; k < 3; k++)
    		{
    			temp = P[i * 3 + k];
    			for (int j = 0; j < 3; j++)
    			{
    				PR[i][j] += temp * R[k * 3 + j];
    			}
    		}
    	}
    
    	/* 3X3矩阵求逆 */
    	temp = PR[0][0] * PR[1][1] * PR[2][2] + PR[0][1] * PR[1][2] * PR[2][0] + PR[0][2] * PR[1][0] * PR[2][1] -
    		PR[0][2] * PR[1][1] * PR[2][0] - PR[0][1] * PR[1][0] * PR[2][2] - PR[0][0] * PR[1][2] * PR[2][1];
    	PRI[0] = (PR[1][1] * PR[2][2] - PR[1][2] * PR[2][1]) / temp;
    	PRI[1] = -(PR[0][1] * PR[2][2] - PR[0][2] * PR[2][1]) / temp;
    	PRI[2] = (PR[0][1] * PR[1][2] - PR[0][2] * PR[1][1]) / temp;
    	PRI[3] = -(PR[1][0] * PR[2][2] - PR[1][2] * PR[2][0]) / temp;
    	PRI[4] = (PR[0][0] * PR[2][2] - PR[0][2] * PR[2][0]) / temp;
    	PRI[5] = -(PR[0][0] * PR[1][2] - PR[0][2] * PR[1][0]) / temp;
    	PRI[6] = (PR[1][0] * PR[2][1] - PR[1][1] * PR[2][0]) / temp;
    	PRI[7] = -(PR[0][0] * PR[2][1] - PR[0][1] * PR[2][0]) / temp;
    	PRI[8] = (PR[0][0] * PR[1][1] - PR[0][1] * PR[1][0]) / temp;
    
    	return;
    }
    
    /*
    * brief 获取映射表
    * param K		输入,相机内参矩阵指针,单目标定获得
    * param D		输入,相机畸变矩阵指针,单目标定获得
    * param PRI		输入,P*R的逆矩阵
    * param mapx	输出,x坐标映射表
    * param mapy	输出,y坐标映射表
    * param size	输入,图像分辨率
    
    */
    void GetMap(float *K, float *D, float *PRI, float *mapx, float *mapy, Size size)
    {
    	float fx = K[0];
    	float fy = K[4];
    	float u0 = K[2];
    	float v0 = K[5];
    	float k1 = D[0];
    	float k2 = D[1];
    	float p1 = D[2];
    	float p2 = D[3];
    	float k3 = D[4];
    
    	/* 学术大佬研究的模型:去除畸变+双目校正 */
    	for (int i = 0; i < size.height; i++)
    	{
    		float _x = i*PRI[1] + PRI[2];
    		float _y = i*PRI[4] + PRI[5];
    		float _w = i*PRI[7] + PRI[8];
    
    		for (int j = 0; j < size.width; j++, _x += PRI[0] , _y += PRI[3], _w += PRI[6])
    		{
    			float w = 1. / _w;
    			float x = _x * w;
    			float y = _y * w;
    			float x2 = x * x;
    			float y2 = y * y;
    			float r2 = x2 + y2;
    			float _2xy = 2 * x * y;
    			float kr = 1 + ((k3*r2 + k2)*r2 + k1)*r2;
    			float u = fx*(x*kr + p1*_2xy + p2*(r2 + 2 * x2)) + u0;
    			float v = fy*(y*kr + p1*(r2 + 2 * y2) + p2*_2xy) + v0;
    
    			mapx[i*size.width + j] = u;
    			mapy[i*size.width + j] = v;
    		}
    	}
    }
    
    /*
    * brief 执行校正过程
    * param srcImg	输入,原图数据
    * param dstImg	输入,校正后图像数据
    * param mapx	输入,x坐标映射表
    * param mapy	输入,y坐标映射表
    
    */
    void Imgremap(Mat srcImg, Mat &dstImg, float *mapx, float *mapy)
    {
    	int x, y;
    	float u, v;
    
    	/* 纯浮点运算,执行映射+插值过程 */
    	for (int i = 0; i < srcImg.rows; i++)
    	{
    		for (int j = 0; j < srcImg.cols; j++)
    		{
    			x = (int)mapx[i*srcImg.cols + j];
    			y = (int)mapy[i*srcImg.cols + j];
    
    			if (x > 1 && x < (srcImg.cols-1) && y > 1 && y < (srcImg.rows-1))
    			{
    				u = mapx[i*srcImg.cols + j] - x;
    				v = mapy[i*srcImg.cols + j] - y;
    				dstImg.ptr<uchar>(i)[j] = (uchar)((1 - u)*(1 - v)*srcImg.ptr<uchar>(int(y))[int(x)] + (1 - u)*v*srcImg.ptr<uchar>(int(y + 1))[int(x)]
    					+ u*(1 - v)*srcImg.ptr<uchar>(int(y))[int(x + 1)] + u*v*srcImg.ptr<uchar>(int(y + 1))[int(x + 1)]);
    				//cout << (int)(dstImg.ptr<uchar>(i)[j]) << endl;
    			}
    			else
    			{
    				dstImg.ptr<uchar>(i)[j] = 0;
    			}
    
    		}
    	}
    }
    
    /*
    * brief 执行校正过程
    * param srcImg	输入,原图数据
    * param dstImg	输入,校正后图像数据
    * param mapx	输入,x坐标映射表
    * param mapy	输入,y坐标映射表
    
    */
    void Imgremap_new(Mat srcImg, Mat &dstImg, float *mapx, float *mapy)
    {
    	/* 浮点转定点运算,执行映射+插值过程 */
    	for (int i = 0; i < srcImg.rows; ++i)
    	{
    		for (int j = 0; j < srcImg.cols; ++j)
    		{
    			int pdata = i*srcImg.cols + j;
    			int x = (int)mapx[pdata];
    			int y = (int)mapy[pdata];
    			short PartX = (mapx[pdata] - x) * 2048;
    			short PartY = (mapy[pdata] - y) * 2048;
    			short InvX = 2048 - PartX;
    			short InvY = 2048 - PartY;
    
    			if (x > 1 && x < (srcImg.cols - 1) && y > 1 && y < (srcImg.rows - 1))
    			{
    				dstImg.ptr<uchar>(i)[j] = (((InvX*srcImg.ptr<uchar>(y)[x] + PartX*srcImg.ptr<uchar>(y)[x+1])*InvY +
    					(InvX*srcImg.ptr<uchar>(y+1)[x] + PartX*srcImg.ptr<uchar>(y + 1)[x + 1])*PartY) >> 22);
    				//cout << (int)(dstImg.ptr<uchar>(i)[j]) << endl;
    			}
    			else
    			{
    				dstImg.ptr<uchar>(i)[j] = 0;
    			}
    
    		}
    	}
    }
    

    实际效果

    可以看出,校正后的实际效果是一致的!
    在这里插入图片描述

    问题求助

    1.按理说,定点运算应该快于浮点运算,但实际使用时我发现两者的执行效率几乎相等(PC端、ARM开发板端都验证过),有懂的大佬,可以指教下吗?
    2.自己写的代码,debug模式下,执行速度是opencvAPI的一倍,有大佬能指点下加速吗?

    更多相关内容
  • 通过matlab标定和立体校正,加载左右图片,保存标定参数,然后对左右图片进行立体校正,具体效果我博客有介绍。
  • 点击上方“计算机视觉工坊”,选择“星标”干货第一时间送达来源:公众号@计算摄影学一. 直观认识立体校正在文章68. 三维重建3-两视图几何中,我们看到通过三角测量,可以确定一个像点在三维空...

    点击上方“计算机视觉工坊”,选择“星标”

    干货第一时间送达

    f921d79c19042cf0ed59348413bd7bbf.png来源:公众号@计算摄影学

    一. 直观认识立体校正

    在文章68. 三维重建3-两视图几何中,我们看到通过三角测量,可以确定一个像点在三维空间中的位置,其前提是我们提前获取了这个像点在另外一个图像中的对应点,并且知道了两个相机的相机矩阵。

    fc6b0db59b4ab3ce00e3252922397b1b.png

    获取在两个图像中的成对匹配点,也就是同一个3D点在两个图像上的投影点非常重要。我在上述文章中为你展示了两视角几何模型中的对角几何约束关系,这样当需要搜索像点x的对应匹配点x'时,我们可以将搜索范围控制到第二幅图像的极线l'上。

    0d288aebe07444fd8b2ca443a70a9554.png

    然而,l'通常不是水平的,因此我们需要在第二幅图像的一条倾斜线段上进行搜索。这样的算法是很低效的,尤其是当我们需要用到图像块的特征去做点匹配的判断时。在之后的文章中,我会介绍”立体匹配“,这个过程需要对图像中的所有点寻找匹配点,如果每个点都还按照这种倾斜线段搜索的方法去操作,就会特别特别慢。因此,我们必须引入立体校正(Rectification)操作,它对两幅图像做变换,最终使得两幅图像的极线平行,且在水平方向对齐。这样,当我们需要搜索对应的匹配点时,就只需要在水平方向上进行一维搜索,大大加快了速度。

    这个过程,就好像是我们创造了两个内参相同的虚拟相机,它们指向同一个方向进行拍摄原来的场景,得到两幅新的图像,如下图所示:

    ac57c75bc76b02322965071031506f9e.png

    下面展示了在同一个场景用双摄手机的两个摄像头拍摄的一对图像,两个摄像头具有不同的内参数,因此拍摄出的图像视角间有明显的空间变换和尺度差异。

    a689468ddcb8fd454703930895f87c30.png

    这个手机的摄像头是纵向排列的,拍出的图像如下图所示

    214692511c95a2b04e50151dda01470e.png

    所以当我们画出部分极线时,会发现这些极线是倾斜的,没有对齐。

    fc96375dffe07dbdd20158113e6933d7.png

    即便我们根据内参数对图像进行一定比例的缩放,使得两幅图像尺寸一致,我们发现极线依然不能对齐,而且是倾斜的:

    149688e92cd2fdc6c4bbb5c08d22d0b2.png

    当执行我上面提到的立体校正步骤后,两个图像的所有极线将变为水平,且在纵向对齐,此时两个图像的尺度也校正到一致状态。注意这两个摄像头在手机上是纵向排布的,为了让极线在水平方向,我把图像进行了90度旋转。如果不做这样的旋转,极线就是在纵向对齐的,这不利用后续立体匹配这类算法的处理。

    42591d7d08efb25ee38f6858721a2295.png仅为了查看方便,我们可以将图像极线重新调整为纵向,观察和比较一下:

    d36b22cb96bb20ee297ba95928f9e0d5.png

    二. 立体校正算法原理

    2.1 理论推导

    正式来说,图像立体校正是指对两幅图像分别进行一次平面投影变换,使两幅图像的对应极线在同一条水平向上,而对极点被映射到无穷远处, 这样可以使两幅图像只存在水平方向上的视差,立体匹配问题从二维 降到一维,从而提高了匹配的速度。

    虽然有各种各样的立体校正算法,今天我只会介绍一种非常容易理解的方法,而且也是广泛应用的一种方法:

    41048559bda4527afe68f9c62ba84d92.png

    作者给出了一种简单的图像校正方法,只需要知道两个相机的相机矩阵,且要求两个相机视角相差不大——这种假设在我面临的项目里面都是成立的,所以我比较喜欢这种算法。

    现在来进行一点点简单的推导,请抓稳扶好

    由文章66. 三维重建1——相机几何模型和投影矩阵我们知道

    a2494916bc3cad15a0aaba4f5c4ba9d0.png

    又由于

    23398496e11c9c4315d68cad99bedf19.png

    且从Xw到投影成像点的直线一定会过光心,如下图所示

    fc05167b840009c5815e12d9f3db0024.png

    因此,我们可以把三维空间点坐标表示为下面的式子(上横线表示齐次坐标),c是指光心的坐标,\lambda是比例因子,用来调节Xw的位置在这条线上的位置。

    099552a689854b9a4bb079cc1639608c.png

    接下来,我们看看两个相机在校正前和校正后的几何模型:

    f610218b920a3f3ca59636816a52563c.png

    可见,在校正前和校正后,相机的投影矩阵发生了变化,所以同一个3D点的投影点也发生了变化。校正后,我们看到两个光心所在的主平面是平行的,它们都看向同一个方向。我们用下标o代表origin,用下标n代表new,那么,上面的公式就变成了:

    a4a2244ef0725203fc115d4f3e87c29e.png

    在进行立体校正时,我们是已知两个相机的相机矩阵的,因而也就知道了两个相机的光心坐标。同时,在立体校正前后,现在讲的Fusiello等的算法假设光心是不变的,因此我们可以联立上面两个式子,得到投影点的关系:

    18b22ad70eec734a0c37365874fc197d.png

    其中:

    7e8601496fe72d44964fd0593ba84b02.png

    因此,为了做两个相机的校正,我们只需要确定好新的虚拟相机的内参数及旋转矩阵即可。

    先说内参数:在校正之后,我们需要两个内参数完全一致的虚拟相机,因此在Fusiello等的算法中,直接用下面的式子得到新相机的内参数:

    dd199ad7227cf899a45c32f54e6ef986.png

    再看看旋转矩阵:校正之后,两个相机的方向是一致的,它们的旋转矩阵可以展开如下:

    77b9c1e52e76f6d9df7fde413fc72189.png

    其中,旋转矩阵的三行代表着新相机矩阵的X/Y/Z轴,它们分别具有如下的特点:

    • 1. X轴应该平行于两个相机光心的连线,所以有:

    6e69ee2112109f5121b0a422cefafda3.png
    • 2. Y轴应该垂直于X轴和原始相机的视线方向(即原始相机坐标系的Z轴,我们用k来表达)所在的平面:

    0a1601839c7f34a4f6db4e514b940100.png
    • 3. Z轴应该垂直于X轴和Y轴所在的平面:

    1f64cd456191d2d3261951e0b169f42c.png

    到此为止,我们就确定了虚拟相机的方向(旋转矩阵),光心(c1和c2),内参,这样就很容易利用刚提到的公式来对图像进行变换了:

    73df039d9f6fe14f1a2ab79f2d2e6abd.png

    2.2 具体实现

    在具体实施中,我们假设已经通过相机的几何参数标定(见文章67. 三维重建2——相机几何参数标定)获得了两个相机的内参和外参,并且已经对图像进行了畸变校正。那么,整个校正过程可以总结如下:

    f5f5368518a3221237fe556f8a02d977.png

    在作者的论文中,列出了22行Matlab代码完成这个过程,你可以查阅看看。作者还把相关的代码、数据放到了sci.univr.it/∼fusiello/rect.html供我们学习,很赞!

    这个算法的使用要点如下:

    • 算法本身没有考虑图像的镜头畸变,因此使用时需要先对图像点做畸变校正

    • 要注意原始相机之间的排布方式。正如一开始所描述的,如果相机之间是纵向排列的,需要先把图像旋转到水平,才能进行水平的极线校正对齐。比如,常见手机多摄像头的排布方式如下,在应用上述算法时,要提前对图像做一些预先的旋转,才能保证校正后极线位于水平方向

    • 整个算法非常依赖于准确的内参、外参。如果这些信息有误差,最终校正后的极线就不一定能水平对齐了。

    7ef847019013eaed19fe3d0ffd832d1e.png

    2.3 效果展示

    这里我展示用同样一个手机拍出的更多图像,你可以清晰的看到图像在校正前后极线的变化:

    校正前:

    b7b48995b43f2be4035f84a961a52702.png

    校正后:

    581689c512738f18bb4bf2b0a0717119.png

    校正前:

    4854bd93c28caa5f66a552cb27e66afa.png

    校正后:

    dfddd6f623d944d63287ade93ad8d7a8.png

    三. 总结

    今天我给你介绍了一个非常简单,容易复现的立体校正算法。事实上,我这边的实际项目中也是在这个算法的基础上进行扩展,进而进行实际应用的。所以我觉得这个算法非常实用,好用,推荐给你学习,希望能给你带来启发。

    如果你在实际使用中发现了对极线无法水平对齐,就要考虑到标定的精度了。比如标定时的状态和你实际使用相机时的状态发生了轻微的改变——这种情况很常见——你就需要考虑如何对相关参数进行进一步的调优,才能更好的使用这个算法。

    四. 参考文献

    • CMU 2021 Fall Computational Photography Course 15-463, Lecture 17

    • Andrea Fusiello, et al, A compact algorithm for rectification of stereo pairs

    • Richard Szeliski, Computer Vision: Algorithms and Application.

    • Stefano Mattoccia, Stereo vision: algorithms and applications, Università di Firenze, May 2012

    • 68. 三维重建3——两视图几何

    • 67. 三维重建2——相机几何参数标定

    本文仅做学术分享,如有侵权,请联系删除。

    重磅!计算机视觉工坊-学习交流群已成立

    扫码添加小助手微信,可申请加入3D视觉工坊-学术论文写作与投稿 微信交流群,旨在交流顶会、顶刊、SCI、EI等写作与投稿事宜。

    同时也可申请加入我们的细分方向交流群,目前主要有ORB-SLAM系列源码学习、3D视觉CV&深度学习SLAM三维重建点云后处理自动驾驶、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、深度估计、学术交流、求职交流等微信群,请扫描下面微信号加群,备注:”研究方向+学校/公司+昵称“,例如:”3D视觉 + 上海交大 + 静静“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进去相关微信群。原创投稿也请联系。

    d417d35af10b7e5b5e8fdea7e73fa0c7.png

    ▲长按加微信群或投稿

    1a265386f45f73766c2bdd97e86c64dd.png

    ▲长按关注公众号

    3D视觉从入门到精通知识星球:针对3D视觉领域的视频课程(三维重建系列三维点云系列结构光系列手眼标定相机标定、激光/视觉SLAM、自动驾驶等)、知识点汇总、入门进阶学习路线、最新paper分享、疑问解答五个方面进行深耕,更有各类大厂的算法工程人员进行技术指导。与此同时,星球将联合知名企业发布3D视觉相关算法开发岗位以及项目对接信息,打造成集技术与就业为一体的铁杆粉丝聚集区,近4000星球成员为创造更好的AI世界共同进步,知识星球入口:

    学习3D视觉核心技术,扫描查看介绍,3天内无条件退款

    72be15a9c4dd491926fbccc8ec304e95.png

     圈里有高质量教程资料、可答疑解惑、助你高效解决问题

    觉得有用,麻烦给个赞和在看~

    展开全文
  • 立体校正

    千次阅读 2018-10-30 09:50:54
    我们知道cvStereoRectify()函数只是计算了校正需要的参数,并没有完成图像上实际的校正。 下面介绍两个函数: cvInitUndistortRectifyMap()函数来计算左右视图的校正查找映射表mapx,mapy。 cvRemap()函数利用查找...

    我们知道cvStereoRectify()函数只是计算了校正需要的参数,并没有完成图像上实际的校正。

    下面介绍两个函数:

    cvInitUndistortRectifyMap()函数来计算左右视图的校正查找映射表mapx,mapy。

    cvRemap()函数利用查找映射表和插值算法实现将输入图像的重投影,从而得到校正后图像。

    为了理解下面的话,我们将书上图附上:

    clip_image020

    对左右摄像机而言,(a)原始图像(b)非畸变化(c)校正(d)最后裁切成两幅图像间的重叠区域。校正实际上是右(c)到(a)的反过程。

    对任何图像到图像的映射函数,由于目标位置是浮点型的缘故,正向映射(即根据原始图像上的点计算其到目标图像上的点),不会命中目标图像对应的像素位置。

    为了理解上面的话,我们引入OpenCV中cvRemap函数中的解释:

    例如,我们可以说像素的位于(20,17)位置,当这些整数位置映射到新的图像中,可能会有一些差异——源图像中像素中的像素位置是整形的而目标图像是浮点型的,就必须将其四舍五入到最相近的整型,因为可能映射后的位置完全就是没有像素的(联想一下通过拉伸图像扩大两倍,其他的每个目标像素将是空白)。这个问题一般被称为正向投影问题。为了解决这些四舍五入和目标差异的问题,实际上我们可以反过来解决:通过目标图像中的每一个像素去问:“哪个像素需要来填充这个目标像素?“这些源像素的位置几乎都是小数(非整数),所以必须对这些像素进行插值以得到目标位置的正确值。

    因此我们采用逆向映射:对目标图像上的每一个整形的像素位置,首先查找出其对应源图像上的浮点位置,然后利用周围源像素的每个整形值插值出新的值来。


    计算这样映射关系的实现函数cvInitUndistortRectifyMap()被调用两次,即分别求解左右原始图像到校正图像之间的映射查找表,声明如下:

    
      
    1. void cvInitUndistortRectifyMap(
    2. const CvMat* M,
    3. const CvMat* distCoeffs,
    4. const CvMat* Rrect,
    5. const CvMat* Mrect,
    6. CvArr* mapx,
    7. CvArr* mapy
    8. );
    1、M,是3×3的摄像机内参数矩阵,校正前的。

    2、disCoeffs,是5×1的摄像机畸变系数。

    3、Rrect,是cvStereoRecify()函数输出的Rl或者Rr

    4、Mrect是3×3的校正后的摄像机内参数矩阵,该矩阵是cvStereoRectify()计算得到投影矩阵P的前三列。

    5、mapx,mapy,是函数返回的输出查找映射表。

    (个人对查找表的理解:我们知道查找表的大小和目标图像一样,那么我们对于目标图像的每一个位置如(12,8),那么mapx和mapy存储着位于该处(即在mapx和mapy矩阵中也是(12,8)位置处)的源图像对应的x和y坐标位置,此时的在源图像中的坐标为浮点型,我们利用插值算法求解出该位置的像素值,然后将该像素值给目标图像的(12,8)位置的像素,要像正确理解,还需要深入的了解算法的操作流程,这里理解不免有误)。

    利用计算得到查找映射表重投影图像的cvRemap()函数声明如下:

    
      
    1. void cvRemap(
    2. const CvArr* src,
    3. CvArr* dst,
    4. const CvArr* mapx,
    5. const CvArr* mapy,
    6. int flags = CV_INTER_LINEAR | CV_WARP_FILL_OUTLIERS,
    7. CvScalar fillval = cvScalarAll(0)
    8. );

    cvRemap函数将一幅图像中一个位置的像素平滑的重映射到另一个位置。

    1、src源图像

    2、dst目标图像,源图像和目标图像必须大小和通道一致,数据类型可以任意。

    3、mapx,mapy,为单通道,通常为IPL_DEPTH_32F浮点型数据,和源图像、目标图像有同样的大小,指明具体像素重新分别的位置。

    4、flags,插值方法,默认采用双线性插值。

    插值方法:

    CV_INTER_NN 最邻近插值

    CV_INTER_LINEAR 双线性插值(默认)

    CV_INTER_AREA  像素区域重新采样

    CV_INTER_CUBIC 双三次插值


    CV_WARP_FILL_OUTLIERS,其效果是可以用最后一个变量fillval设置的值填充目标图像的像素,而这些像素在原始输入图像中没有任意像素与之对应。



    如果使用cvStereoRectifyUncalibrated()函数,即不使用摄像机内参数来校正视觉时,关于讲解请看Learning OpenCV中文版 475页倒数第二段以及阅读OpenCV自带的stereo_calib.cpp程序来详细了解具体的操作。


    双目定标和双目校正原理如下;

    双目摄像头定标不仅要得出每个摄像头的内部参数,还需要通过标定来测量两个摄像头之间的相对位置(即右摄像头相对于左摄像头的三维平移 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在左右两个摄像机坐标系中的观察点 pl和 p应满足的极线约束关系为:(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)
    </span><span style="color:rgb(0,128,0);">*/</span></pre>
    

    具体的计算过程比较繁杂,不好理解,这里就不讨论了,下面是计算矩阵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。

    转载自:https://blog.csdn.net/yuxiang3986/article/details/51361889

    展开全文
  • 立体视觉–立体校正原理简述 最近在搞双目相机设备,包括相机标定、立体标定和立体校正。对于标定部分,看的资料不多,所以暂时先不提,对于立体校正部分近期看的资料比较多,想简单的叙述一下。 以下内容都是本人...

    立体视觉–立体校正原理简述

    最近在搞双目相机设备,包括相机标定、立体标定和立体校正。对于标定部分,看的资料不多,所以暂时先不提,对于立体校正部分近期看的资料比较多,想简单的叙述一下。
    以下内容都是本人自己的理解,如果有不正确的地方,也欢迎大家指正!!!

    关于立体校正:

    - 1、怎么样算是极线水平,即立体校正后应该达到什么样的效果?
    在这里插入图片描述
    立体校正后的效果如上图所示。即,当满足以下条件时,认为两个相机平面的极线水平
    在这里插入图片描述
    即,认为矫正之后的左右相机之间只存在水平方向的位移(水平放置的双目设备)或垂直方向的位移t=(0,T,0)(垂直放置的双目设备)。
    并通常会为新的相机指定相同的内参(Fusiello方法(论文地址)和Bouguet方法所计算的新内参方法相同,均为k/2)。toolbox标定工具箱对于新的内参计算方法不同。
    关于Fusiello方法,李博士的一篇CSDN讲的很好,在此附上链接地址

    • 2、立体校正的过程

    • 1、第一次的旋转
      如下图所示:
      在这里插入图片描述
      对右图做旋转,使左右图像平面平行
      这个过程一般有两种方法:
      1、直接根据左右图的旋转矩阵R旋转达到平行。(Fusiello方法)
      2、左图正向旋转R/2,右图反向旋转R/2达到平行。(Bouguet方法,toolbox工具箱方法)
      (这里用R/2其实并不准确,应该说是两幅图像之间的夹角的一半)

    • 2、第二次旋转
      设计一个新的相机坐标系,目的通常是使新的像平面共面且平行于相机基线(相机主点之间的连线)
      在这里插入图片描述
      1、Fusiello方法
      在这里插入图片描述
      2、Computer Vision的一种方法
      在这里插入图片描述
      3、Bouguet方法
      在这里插入图片描述
      4、参考文献[3]中的方法
      在这里插入图片描述
      具体的计算过程和方法我就不详细讲了,有感兴趣的可以通过下方的参考文献,获取论文然后仔细观看、琢磨、推敲之后,效果更佳。

    参考文献:
    [1].A compact algorithm for rectification of stereo pairs
    [2].Matlab 相机校准工具箱 toolbox
    [3].Single-Shot Structured Light Sensor for 3D Dense and Dynamic Reconstruction
    [4].Stereo Vision

    展开全文
  • 双目视觉之立体校正

    千次阅读 多人点赞 2020-04-02 09:43:01
    在本篇文章中,将根据上一篇文章得到的两个摄像机的相对位置进行图像的立体校正 引言 当两个像平面式完全行对准的,计算立体视差是最简单的。但是两台摄像机几乎不可能有准确的共面和行对准成像平面,完美的对准...
  • 基于OpenCV立体校正匹配

    热门讨论 2010-08-28 10:26:26
    通过Matlab实现双目视觉的标定,将标定后的数据写入OpenCV中,同过OpenCV实现立体校正、匹配。
  • 本博客将实现Python版本的双目三维重建系统,项目代码实现包含:`双目标定`,`立体校正(含消除畸变)`,`立体匹配`,`视差计算`和`深度距离计算/3D坐标计算` 的知识点。限于篇幅,本博客不会过多赘述算法原理,而是...
  • OpenCV双目视觉:Bouguet立体校正https://jingyan.baidu.com/article/a681b0de74312a3b1843460d.html cvConvert函数用于图像和矩阵之间的相互转换 为什么要用cvConvert 把IplImage转为矩阵? 因为IplImage里的数据,...
  • 双目相机立体校正(Bouguet算法) 在双目立体视觉的三维重建过程中,需要通过立体匹配算法来进行视差图的计算得到左右两幅图像的视差值,进而来计算深度来恢复场景的三维信息。 计算三维场景中目标点在左右两个视图...
  • 从原理到实例,一文详解双目视觉系统和立体校正
  • 双目立体校正

    2018-08-02 15:47:50
    opencv 3.3+vs2017环境。标定,去畸变以及双目立体校正,opencv源码,直接运行,图片等配置文件均打包上传
  • 菜鸟中的菜鸟对C++不是非常熟悉,双目标定原理实在是看不懂。故选择相对简单地采用matlab傻瓜标定+opencv立体校正的方法实现stereo camera calibrator。对于精度什么的还一概不通,后面慢慢学习。。。。。。 ...
  • 双目立体视觉标定以及立体匹配参考图片,可用于立体标定,图像校正立体匹配算法验证
  • opencv双目标定+立体校正+立体匹配(源码&讲解)

    千次阅读 热门讨论 2020-04-05 17:05:51
    //根据stereoRectify计算出来的R和P来计算图像的映射表 mapx,mapy //mapx,mapy这两个映射表接下来可以给remap()函数调用,校正图像(使图像共勉并且行对准) //ininUndistortRectifyMap()的参数...
  • 目的:立体校正就是,把实际中非共面行对准的两幅图像,校正成共面行对准,提高匹配搜索的效率,因为二维搜索变为一维搜索啦!。(共面行对准:两摄像机图像平面在同一平面上,且同一点投影到两个摄像机图像平面时,...
  • 1.利用matlab进行双目标定,得到左右相机内参,畸变参数,以及右相机相对于左相机的旋转和平移 2.注意matlab得到的内参和旋转矩阵都得转置 ...我主要是使用立体校正,即左右两幅图不仅共面而且行对准 ...
  • m~\tilde{m}m~是对应的投影点 可以看到两个图像点变换前后的关系为 变换矩阵为 论文中一些极线校正的结果 matlab提供的极线校正参考代码 官方文档 matlab中的代码是针对没标定好的相机的,只要两张图之间匹配点大于...
  • 【立体视觉】双目立体标定与立体校正

    万次阅读 多人点赞 2017-06-28 09:54:40
    机器视觉学习笔记(8)——基于OpenCV的Bouguet立体校正 双摄像头立体成像(三)-畸变矫正与立体校正人类可以看到3维立体的世界,是因为人的两只眼睛,从不同的方向看世界,两只眼睛中的图像的视差,让我们可以看到3...
  • 一、立体校正的原因 **原因一:**当畸变系数和内外参数矩阵标定完成后,就应该进行畸变矫正,以达到消除畸变的目的。 **原因二:**在立体成像原理中提到,要通过两幅图像估计物点的深度信息,就必须在两幅图像中准确...
  • opencv校正完整源码:/********************************************************************创建 : 2017/1/16文件 : StereoRectify.cpp类名 : StereoCalib功能 : 摄像机立体矫正********************************...
  • 该函数为未校准(几乎对齐)的立体对生成整流单应性。
  • 得到双目相机的参数,标定用的图片来自于OPENCV自带例子的图片,共13组,用Matlab标定共接受里面的7组照片,标定误差为0.18像素,把得到的参数存储到OPENCV里面进行立体校正发现,左右视图上的对应点不在一条直线上...
  • opencv3双目视觉中的立体校正原理

    千次阅读 2018-07-09 14:34:51
    立体校正就是,把实际中非共面行对准的两幅图像,校正成共面行对准。 (1)未校正以前左右眼视图 (2)校正后的左右眼视图 通过图可以很直观的看到效果 我们知道,立体匹配是三维重建、立体导航、非接触测距等...
  • 见博客https://blog.csdn.net/u011574296/article/details/73826420
  • 七、立体标定与立体校正    这篇博文中,让玉米和大家一起了解一下,张氏标定是怎样过渡到立体标定的?在这里主要以双目立体视觉进行分析。对于双目立体视觉,我们有两个摄像头。它们就像人的一双眼睛一样,从...
  • 直接存储校正后的图像老是报错!! from PIL import Image # 使用remap函数完成映射 Left_rectified = cv2.remap(frameL, Left_Stereo_Map[0], Left_Stereo_Map[1], cv2.INTER_LANCZOS4, cv2.BORDER_...
  • 文章目录坑的备忘重新标定及参数畸变校正输出 在对双目相机进行标定之后,将在ubuntu系统中进行开发。首先要做的是编写基础程序从双目相机中实时的获取原始图像并对其进行矫正。 坑的备忘 神坑出现: 在之前的博客...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,264
精华内容 2,105
关键字:

立体校正

友情链接: 例程源码.zip