opencv双目标定+立体校正+立体匹配(源码&讲解)

//双目标定无非就是重复两次单目标定的流程,单目标定参考我上一篇博客。
//在学习双目视觉之前,建议大家补充下,双目视觉模型,对极几何的知识,今天只讲源码的流程,以后出一篇对极几何的讲解。
opencv双目标定+立体校正+立体匹配(源码&讲解)_第1张图片
//老规矩先来一段源码

#include "opencv2/core.hpp"
#include 
#include 
#include "opencv2/calib3d.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include 
#include 
#include 
#include 
#include 

using namespace std;
using namespace cv;
static double computeReprojectionErrors(
	const vector<vector<Point3f> >& objectPoints,
	const vector<vector<Point2f> >& imagePoints,
	const vector<Mat>& rvecs, const vector<Mat>& tvecs,
	const Mat& cameraMatrix, const Mat& distCoeffs
)
{
	vector<Point2f> imagePoints2;
	int i, totalPoints = 0;
	double totalErr = 0, err;
	for (i = 0; i < (int)objectPoints.size(); i++)
	{
		projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i],
			cameraMatrix, distCoeffs, imagePoints2);
		err = norm(Mat(imagePoints[i]), Mat(imagePoints2), NORM_L2);
		int n = (int)objectPoints[i].size();

		totalErr += err*err;
		totalPoints += n;
	}
	return std::sqrt(totalErr / totalPoints);
}

int main(int argc, char** argv)
{
	vector<string> files_left;
	vector<string> files_right;
	glob("E:\\mul_cam_images\\left1", files_left);
	glob("E:\\mul_cam_images\\right1", files_right);
	// 定义变量
	vector<vector<Point2f>> image_leftPoints,image_rightPoints;//像点
	vector<vector<Point3f>> objectPoints;//物点
	TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 30, 0.001);//进行亚像素精度的调整,获得亚像素级别的角点坐标
	int numCornersHor = 8;//heigh
	int numCornersVer = 11;//width
	int numSquares = 15;//单位mm
	Mat gray_l, gray_r;
	Mat image_l, image_r;
	vector<Point3f> obj;
	for (int i = 0; i < numCornersHor; i++)
		for (int j = 0; j < numCornersVer; j++)
			obj.push_back(Point3f((float)j * numSquares, (float)i * numSquares, 0));
	//存放每张图的角点坐标,并存入obj中(物点)	
	Size s1,s2;
	//像点
	for (int i = 0; i < 16; i++) {
		printf("image file : %s \n", files_left[i].c_str());
		 image_l = imread(files_left[i]);
		printf("image file : %s \n", files_right[i].c_str());
		 image_r = imread(files_right[i]);
		s1 = image_l.size();
		s2 = image_r.size();
		
		cvtColor(image_l, gray_l, COLOR_BGR2GRAY);//转灰度
		cvtColor(image_r, gray_r, COLOR_BGR2GRAY);//转灰度
		vector<Point2f> corners_r;
		vector<Point2f> corners_l;
		bool ret1 = findChessboardCorners(gray_r, Size(11, 8), corners_r, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);//该函数的功能就是判断图像内是否包含完整的棋盘图,若能检测完全,就把他们的角点坐标(从上到下,从左到右)记录,并返回true,否则为false,CALIB_CB_FILTER_QUADS用于去除检测到的错误方块。
		bool ret2 = findChessboardCorners(gray_l, Size(11, 8), corners_l, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);
		if (ret1)
		{
			cornerSubPix(gray_l, corners_l, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
			drawChessboardCorners(image_l, Size(11, 8), corners_l, ret1);//将每一个角点出做标记,此为物点对应的像点坐标	
			//imshow("calibration-demo1", image_l);
			waitKey(500);
		}
		if (ret2)
		{
			cornerSubPix(gray_r, corners_r, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
			drawChessboardCorners(image_r, Size(11, 8), corners_r, ret2);//将每一个角点出做标记,此为物点对应的像点坐标
		//将角点坐标存入imagePoints中,此为像点坐标	
			 //imshow("calibration-demo", image_r);
			waitKey(500);
		}
		if (ret1&&ret2)
		{
			image_rightPoints.push_back(corners_r);
			image_leftPoints.push_back(corners_l);//将角点坐标存入imagePoints中,此为像点坐标
			objectPoints.push_back(obj);
		}

	}

		//计算内参与畸变系数
		Mat intrinsic_left = Mat(3, 3, CV_32FC1);
		
		Mat distCoeffs_left;//畸变矩阵
		vector<Mat> rvecs_l;//旋转向量R
		vector<Mat> tvecs_l;//平移向量T
	
						  //内参矩阵
		intrinsic_left.ptr<float>(0)[0] = 1;
		intrinsic_left.ptr<float>(1)[1] = 1;
		calibrateCamera(objectPoints, image_leftPoints, s1, intrinsic_left, distCoeffs_left, rvecs_l, tvecs_l);
		//计算内参与畸变系数
		Mat intrinsic_right = Mat(3, 3, CV_32FC1);
		Mat distCoeffs_right;//畸变矩阵
		vector<Mat> rvecs_r;//旋转向量R
		vector<Mat> tvecs_r;//平移向量T
		Mat R_total;//旋转向量R
		Vec3d T_total;//平移向量T
		Mat E ;//本质矩阵
		Mat F ;//基本矩阵
		intrinsic_right.ptr<float>(0)[0] = 1;
		intrinsic_right.ptr<float>(1)[1] = 1;
		calibrateCamera(objectPoints, image_rightPoints, s2, intrinsic_right, distCoeffs_right, rvecs_r, tvecs_r);
		FileStorage fs("C:\\Users\\Administrator\\Desktop\\claibration——list\\双目参数\\out_calibration.yml", FileStorage::WRITE);//存储标定结果,这里可以选择自己的存放路径
		fs << "intrinsic_left" << intrinsic_left;//存放内参矩阵
		fs << "distCoeffs_left" << distCoeffs_left;//存放畸变矩阵
		fs << "board_width" << 11;//存放标定板长度信息
		fs << "board_height" << 8;//存放标定板宽度信息
		fs << "square_size" << 0.015;//存放标定板格子尺寸信息
		fs << "R_left" << rvecs_l;//R
		fs << "T_left" << tvecs_l;//T
		fs << "intrinsic_right" << intrinsic_right;//存放内参矩阵
		fs << "distCoeffs_right" << distCoeffs_right;//存放畸变矩阵
		fs << "R_right" << rvecs_r;//R
		fs << "T_right" << tvecs_r;//T
		printf("Done Calibration\n");
		Mat R_L;//由R_total拆分的左相机旋转量
		Mat R_R;//由R_total拆分的右相机旋转量
		Mat P1;
		Mat P2;
		Mat Q;
		Rect validROIL, validROIR;
	
		printf("Starting Rectification\n");
		stereoRectify(intrinsic_left, distCoeffs_left, intrinsic_right, distCoeffs_right, s1, R_total, T_total, R_L, R_R, P1, P2, Q, CALIB_ZERO_DISPARITY, -1, s1, &validROIL, &validROIR);

		fs << "R_left" << R_L;//存放内参矩阵
		fs << "R_right" << R_R;//存放畸变矩阵
		fs << "P1" << P1;//存放标定板长度信息
		fs << "P2" << P2;//存放标定板宽度信息
		fs << "Q" << Q;//存放标定板格子尺寸信息
		cout << "left_Calibration error: " << computeReprojectionErrors(objectPoints, image_leftPoints, rvecs_l, tvecs_l, intrinsic_left, distCoeffs_left) << endl;//输出投影的误差
		cout << "right_Calibration error: " << computeReprojectionErrors(objectPoints, image_rightPoints, rvecs_r, tvecs_r, intrinsic_right, distCoeffs_right) << endl;//输出投影的误差
		printf("Done Rectification\n");

		//根据stereoRectify计算出来的R和P来计算图像的映射表 mapx,mapy
		//mapx,mapy这两个映射表接下来可以给remap()函数调用,校正图像(使图像共勉并且行对准)
		//ininUndistortRectifyMap()的参数newCameraMatrix就是校正后的相机矩阵,
		Mat mapLx, mapLy, mapRx, mapRy;
		initUndistortRectifyMap(intrinsic_left, distCoeffs_left, R_L, P1, s1, CV_16SC2, mapLx, mapLy);
		initUndistortRectifyMap(intrinsic_right, distCoeffs_right, R_R, P2, s1, CV_16SC2, mapRx, mapRy);
	     ////https://docs.opencv.org/4.2.0/d9/d0c/group__calib3d.html#ga7dfb72c9cf9780a347fbe3d1c47e5d5a

		printf("get map\n");
		Mat rectifyImageL2, rectifyImageR2;
		remap(image_l, rectifyImageL2, mapLx, mapLy, INTER_LINEAR);
		remap(image_r, rectifyImageR2, mapRx, mapRy, INTER_LINEAR);
		printf("rectify done\n");
		//cout << "按Q2退出 ..." << endl;
		imshow("rectifyImageL", rectifyImageL2);
		imshow("rectifyImageR", rectifyImageR2);
		waitKey(10000);
		Mat canvas;
		double sf;
		int w, h;
		sf = 600./MAX(s1.width, s1.height);
		w = cvRound(s1.width * sf);
		h = cvRound(s1.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); //画上一个矩形  

		/*右图像画到画布上*/
		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);
		
		return 0;
	}

//下面对源码实现流程分段讲解
//先定义一些变量,物点像点,图像路径,图像信息…

//将物点保存

for (int i = 0; i < numCornersHor; i++)
		for (int j = 0; j < numCornersVer; j++)
			obj.push_back(Point3f((float)j * numSquares, (float)i * numSquares, 0));

//我在遍历像点与物点的时候,出现了如下错误:
OpenCV(4.2.0) Error: Unspecified error (> Number of object and image points must be equal (expected: ‘numberOfObjectPoints == numberOfImagePoints’)

原因是:物点,与提取的数量角点不匹配,之前我将提取物点的for循环放到提取像点的for循环中,导致不匹配,
解决方法:物点与像点提取流程分开编写
//保存像点

for (int i = 0; i < 16; i++) {
		printf("image file : %s \n", files_left[i].c_str());
		 image_l = imread(files_left[i]);
		printf("image file : %s \n", files_right[i].c_str());
		 image_r = imread(files_right[i]);
		s1 = image_l.size();
		s2 = image_r.size();
		
		cvtColor(image_l, gray_l, COLOR_BGR2GRAY);//转灰度
		cvtColor(image_r, gray_r, COLOR_BGR2GRAY);//转灰度
		vector<Point2f> corners_r;
		vector<Point2f> corners_l;
		bool ret1 = findChessboardCorners(gray_r, Size(11, 8), corners_r, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);//该函数的功能就是判断图像内是否包含完整的棋盘图,若能检测完全,就把他们的角点坐标(从上到下,从左到右)记录,并返回true,否则为false,CALIB_CB_FILTER_QUADS用于去除检测到的错误方块。
		bool ret2 = findChessboardCorners(gray_l, Size(11, 8), corners_l, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FILTER_QUADS);
		if (ret1)
		{
			cornerSubPix(gray_l, corners_l, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
			drawChessboardCorners(image_l, Size(11, 8), corners_l, ret1);//将每一个角点出做标记,此为物点对应的像点坐标	
			//imshow("calibration-demo1", image_l);
			waitKey(500);
		}
		if (ret2)
		{
			cornerSubPix(gray_r, corners_r, Size(11, 11), Size(-1, -1), criteria);//用于发现亚像素精度的角点位置
			drawChessboardCorners(image_r, Size(11, 8), corners_r, ret2);//将每一个角点出做标记,此为物点对应的像点坐标
		//将角点坐标存入imagePoints中,此为像点坐标	
			 //imshow("calibration-demo", image_r);
			waitKey(500);
		}
		if (ret1&&ret2)
		{
			image_rightPoints.push_back(corners_r);
			image_leftPoints.push_back(corners_l);//将角点坐标存入imagePoints中,此为像点坐标
			objectPoints.push_back(obj);
		}

	}

注意· if (ret1&&ret2)
{
image_rightPoints.push_back(corners_r);
image_leftPoints.push_back(corners_l);//将角点坐标存入imagePoints中,此为像点坐标
objectPoints.push_back(obj);
}
//此处易犯错误,每写入一张图像的像点,要同时写入其物点与其对应。
///////////////////////////////////////////////////////////////////////////////////////////
//标定准备工作完成,现在开始标定

		calibrateCamera(objectPoints, image_rightPoints, s2, intrinsic_right, distCoeffs_right, rvecs_r, tvecs_r);
		calibrateCamera(objectPoints, image_leftPoints, s1, intrinsic_left, distCoeffs_left, rvecs_l, tvecs_l);

//标定过程结束

//立体校正的时候需要两幅图像共面且行对准,
是两幅图像共面,每个相机都需要一个旋转矩阵(Rl,Rr),
Pl,Pr为两个相机的投影矩阵,作用是将3D点的坐标转换为2D点的坐标。
//Q为重投影矩阵,矩阵Q把2D平面的点投影到3D空间
//此函数包含两步,畸变校正和极线校正。
```cpp
		stereoRectify(intrinsic_left, distCoeffs_left, intrinsic_right, distCoeffs_right, s1, R_total, T_total, R_L, R_R, P1, P2, Q, CALIB_ZERO_DISPARITY, -1, s1, &validROIL, &validROIR);

//P1,P2的前三列与内参矩阵类似
opencv双目标定+立体校正+立体匹配(源码&讲解)_第2张图片
//函数输出的R,P来计算图像的映射表 mapx,mapy,给remap()调用,是两幅图像共面且行对准。

//ininUndistorRectifyMap() 的参数newCameraMatrix就是校正后的相机矩阵(内参)。投影矩阵P中包含新的相机矩阵(内参).

	initUndistortRectifyMap(intrinsic_left, distCoeffs_left, R_L, P1, s1, CV_16SC2, mapLx, mapLy);
		initUndistortRectifyMap(intrinsic_right, distCoeffs_right, R_R, P2, s1, CV_16SC2, mapRx, mapRy);

//映射表map输入remap()完成双目校正

printf("get map\n");
		Mat rectifyImageL2, rectifyImageR2;
		remap(image_l, rectifyImageL2, mapLx, mapLy, INTER_LINEAR);
		remap(image_r, rectifyImageR2, mapRx, mapRy, INTER_LINEAR);
		printf("rectify done\n");
		//cout << "按Q2退出 ..." << endl;
		imshow("rectifyImageL", rectifyImageL2);
		imshow("rectifyImageR", rectifyImageR2);
		waitKey(10000);

//校正效果如图
opencv双目标定+立体校正+立体匹配(源码&讲解)_第3张图片

你可能感兴趣的:(opencv双目标定+立体校正+立体匹配(源码&讲解))