opencv双目视觉标定代码

///张正友标定法opencv实现///
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace cv;
using namespace std;

static int print_help()
{
	cout <<
		" Given a list of chessboard images, the number of corners (nx, ny)\n"
		" on the chessboards, and a flag: useCalibrated for \n"
		"   calibrated (0) or\n"
		"   uncalibrated \n"
		"     (1: use cvStereoCalibrate(), 2: compute fundamental\n"
		"         matrix separately) stereo. \n"
		" Calibrate the cameras and display the\n"
		" rectified results along with the computed disparity images.   \n" << endl;
	cout << "Usage:\n ./stereo_calib -w board_width -h board_height [-nr /*dot not view results*/] \n" << endl;
	return 0;
}
static bool readStringList(const string& filename, vector& list)
{
	list.resize(0);
	//以FileStorage::READ 方式打开一个已经存在的文件stereo_calib.xml是官方存在的文件,保存图片名称left01.jpg....
	FileStorage fs(filename, FileStorage::READ);
	if (!fs.isOpened())
		return false;
	FileNode n = fs.getFirstTopLevelNode();
	if (n.type() != FileNode::SEQ)
		return false;
	FileNodeIterator it = n.begin(), it_end = n.end();
	for (; it != it_end; ++it)
		list.push_back((string)*it);
	return true;
}

//标定函数
static void  StereoCalib(const vector& imagelist, Size boardSize, 
	bool useCalibrated = true, bool showRectified = true)
{
	if (imagelist.size() % 2 != 0)//图片数要成双
	{
		cout << "Error: the image list contains odd (non-even) number of elements\n";
		return;
	}

	bool displayCorners = true;
	const int maxScale = 2; 
	// 设置真实方格大小,以毫米或者像素为单位的keypoint之间间隔距离, 棋盘间隔为1
	const float squareSize = 20.f;
	
	// 数组储存
	vector > imagePoints[2];
	vector > objectPoints;
	Size imageSize;
	//左右相分割,单个相机图片数,vector.size()是容器的数据个数
	int i, j, k, nimages = (int)imagelist.size() / 2;

	imagePoints[0].resize(nimages);//15
	imagePoints[1].resize(nimages);//15
	vector goodImageList;

	for (i = j = 0; i < nimages; i++)
	{
		for (k = 0; k < 2; k++)
		{
			//奇数读取右图片数组名,偶数左图像名
			const string& filename = imagelist[i * 2 + k]; 
			Mat img = imread(filename, 0);//读取图像
			if (img.empty())
				break;
			//第一次输入时都是空,那么把第一张图的大小赋值
			if (imageSize == Size())
				imageSize = img.size(); 
			else if (img.size() != imageSize)
			{
				cout << "The image " << filename << " has the size different from the first image size. Skipping the pair\n";
				break;
			}
			bool found = false;
			//k=0左图所有角点,k=1右图所有角点,通过findChessboardCorners对向量传参,
			//每一组左右图像用一个向量组(坐标是左右的平均处理)
			//寻找角点,保存到imagePoints
			vector& corners = imagePoints[k][j];
			for (int scale = 1; scale <= maxScale; scale++)
			{
				Mat timg;
				if (scale == 1)
				{
					timg = img;
					//cv::bitwise_not(timg, timg);//反相处理
				}
				else
					resize(img, timg, Size(), scale, scale);
				//scale来防止检测不到,调整图幅的在下面的if( scale > 1 )会用到,如果一次就检测到就不会进入
				/*SimpleBlobDetector::Params parameters;
				parameters.maxArea = 10000000;
				Ptr blobDetector = new SimpleBlobDetector(parameters);*/
				//boardSize棋盘格内部角点的行、列数 ;timg输入的图像;corners输出的棋盘格角点
				found = findCirclesGrid(timg, boardSize, corners,
					CALIB_CB_ASYMMETRIC_GRID);
				/*found = findChessboardCorners(timg, boardSize, corners,
				CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE);*/
				//若找到角点,found为真
				if (found)
				{
					if (scale > 1)
					{
						//)找到的vector角点转化为Mat矩阵,方便计算
						Mat cornersMat(corners);
						cornersMat *= 1. / scale;
					}
					break;
				}
			}
			//displayCorners=false,永远不会进入
			if (displayCorners)  
			{
				cout << filename << endl;
				Mat cimg, cimg1;
				cvtColor(img, cimg, COLOR_GRAY2BGR);
				drawChessboardCorners(cimg, boardSize, corners, found);
				double sf = 640. / MAX(img.rows, img.cols);
				resize(cimg, cimg1, Size(), sf, sf);
				imshow("corners", cimg1);
				char c = (char)waitKey(500);
				if (c == 27 || c == 'q' || c == 'Q') //Allow ESC to quit
					exit(-1);
			}
			else
				putchar('.');
			if (!found)
				break;
			//插值亚像素角点,用来精确得到的corners坐标参数
			/*cornerSubPix(img, corners, Size(11,11), Size(-1,-1),
			TermCriteria(CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,
			30, 0.01));*/
		}
		//上个for( k = 0; k < 2; k++ )循环结束!那么k==2了!每对左右照片都会执行一次这个
		//对图像在做拷贝imagelist时传入的vector的string数组,包含的是图片的路径
		if (k == 2)
		{
			goodImageList.push_back(imagelist[i * 2]);
			goodImageList.push_back(imagelist[i * 2 + 1]);
			j++;
		}
	}
	cout << j << " pairs have been successfully detected.\n";
	nimages = j;
	if (nimages < 2)
	{
		cout << "Error: too little pairs to run the calibration\n";
		return;
	}

	imagePoints[0].resize(nimages);
	imagePoints[1].resize(nimages);
	objectPoints.resize(nimages);//定义Point3f的向量大小是nimages
	//通过角点长宽个数以及squareSize每个角点的步长算出角点位置
	for (i = 0; i < nimages; i++) 
	{
		/* for( j = 0; j < boardSize.height; j++ )
		for( k = 0; k < boardSize.width; k++ )
		objectPoints[i].push_back(Point3f(j*squareSize, k*squareSize, 0));*/
		for (int j = 0; j < boardSize.height; j++)
		for (int k = 0; k< boardSize.width; k++)
		objectPoints[i].push_back(Point3f(float((2 * k + j % 2)*squareSize), float(j*squareSize), 0));
	}

	cout << "Running stereo calibration ...\n";

	Mat cameraMatrix[2], distCoeffs[2];
	cameraMatrix[0] = Mat::eye(3, 3, CV_64F);//定义左相机3*3内参数矩阵
	cameraMatrix[1] = Mat::eye(3, 3, CV_64F);//定义右相机3*3内参数矩阵
	Mat R, T, E, F;
	vector rvecs1, tvecs1, rvecs2, tvecs2;
	calibrateCamera(objectPoints, imagePoints[0], imageSize, cameraMatrix[0],
		distCoeffs[0], rvecs1, tvecs1, CV_CALIB_FIX_K4 | CV_CALIB_FIX_K5);
	cout << "左相机内参" << cameraMatrix[0] << endl;

	calibrateCamera(objectPoints, imagePoints[1], imageSize, cameraMatrix[1],
		distCoeffs[1], rvecs2, tvecs2, CV_CALIB_FIX_K4 | CV_CALIB_FIX_K5);
	cout << "右相机内参" << cameraMatrix[1] << endl;

	//
	//
	double rms = stereoCalibrate(objectPoints,  //校正的图像点向量组
		imagePoints[0], imagePoints[1],         //左右相机观测到的图像上面的向量组
		cameraMatrix[0], distCoeffs[0],         //输入或者输出相机的内参数矩阵
		cameraMatrix[1], distCoeffs[1],         //输入/输出相机的畸变系数向量
		imageSize, R, T, E, F, 256,  //输出俩相机坐标系之间的旋转矩阵、平移向量、本征矩阵、基础矩阵,
		TermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 100, 1e-5)/*,
			  CV_CALIB_FIX_ASPECT_RATIO+
			  CV_CALIB_ZERO_TANGENT_DIST+
			  CV_CALIB_SAME_FOCAL_LENGTH+
			  CV_CALIB_RATIONAL_MODEL+
			  CV_CALIB_FIX_K3+
			  CV_CALIB_FIX_K4+
			  CV_CALIB_FIX_K5*/);
	cout << "done with RMS error=" << rms << endl;
	//cout<<"R"< lines[2];
	for (i = 0; i < nimages; i++)
	{
		int npt = (int)imagePoints[0][i].size();//左某图所有角点数量
		Mat imgpt[2];
		for (k = 0; k < 2; k++)
		{
			imgpt[k] = Mat(imagePoints[k][i]); //某图的角点向量矩阵,校正前坐标
			undistortPoints(imgpt[k], imgpt[k], cameraMatrix[k], distCoeffs[k], Mat(), cameraMatrix[k]);//cameraMatrix 畸变参数四个变形系数组成的向量
			computeCorrespondEpilines(imgpt[k], k + 1, F, lines[k]);  //计算对应点的外极线epilines是一个三元组(a,b,c),表示点在另一视图中对应的外极线ax+by+c=0;  
			                                                          //为一幅图像中的点计算其在另一幅图像中对应的对极线。
		}
		for (j = 0; j < npt; j++)
		{
			//fbs绝对值函数
			double errij = fabs(imagePoints[0][i][j].x*lines[1][j][0] +
				imagePoints[0][i][j].y*lines[1][j][1] + lines[1][j][2]) +
				fabs(imagePoints[1][i][j].x*lines[0][j][0] +
				imagePoints[1][i][j].y*lines[0][j][1] + lines[0][j][2]);
			err += errij;
		}
		npoints += npt;
	}
	//检查图像上点与另一幅图像的极线的距离的远近来评价标定的精度。
	//使用undistortPoints对原始点做去畸变处理。
	//使用computeCorrespondEpilines来计算极线。
	//然后,计算这些点和线的点积(理想情况,这些点积都为0)。
	//累计的绝对距离形成了误差。

	cout << "average reprojection err = " << err / npoints << endl;

	// save intrinsic parameters
	FileStorage fs("intrinsics.yml", CV_STORAGE_WRITE);
	if (fs.isOpened())
	{
		//相机的内参矩阵M1,M2,畸变矩阵D1,D2
		fs << "M1" << cameraMatrix[0] << "D1" << distCoeffs[0] <<
			"M2" << cameraMatrix[1] << "D2" << distCoeffs[1];
		fs.release();
	}
	else
		cout << "Error: can not save the intrinsic parameters\n";
	//计算外参数
	Mat R1, R2, P1, P2, Q;
	Rect validRoi[2];
	//stereoRectify根据内参和畸变系数计算右相机相对左相机的旋转R和平移矩阵T
	stereoRectify(cameraMatrix[0], distCoeffs[0],
		cameraMatrix[1], distCoeffs[1],
		imageSize, R, T, R1, R2, P1, P2, Q,
		CALIB_ZERO_DISPARITY, 1, imageSize, &validRoi[0], &validRoi[1]);
	//Q.at(3, 2) = -Q.at(3, 2);//Z取反
	cout << Q << endl;

	fs.open("Q.yml", CV_STORAGE_WRITE);
	if (fs.isOpened())
	{
		fs << "Q" << Q;
		fs.release();
	}
	else
		cout << "Error: can not save the Q parameters\n";

	fs.open("extrinsics.yml", CV_STORAGE_WRITE);
	if (fs.isOpened())
	{
		//旋转矩阵     平移矩阵   左旋转矫正参数    右旋转矫正参数     左平移矫正参数    右平移矫正参数   深度矫正参数
		fs << "R" << R << "T" << T << "R1" << R1 << "R2" << R2 << "P1" << P1 << "P2" << P2 << "Q" << Q;
		fs.release();
	}
	else
		cout << "Error: can not save the intrinsic parameters\n";

	// OpenCV 能处理左右或者上下相机的布置
	bool isVerticalStereo = fabs(P2.at(1, 3)) > fabs(P2.at(0, 3));

	// 计算和显示校准
	if (!showRectified)
		return;

	Mat rmap[2][2]; //查找映射表
	// 如果校准(用的是BOUGUET极线方法)
	if (useCalibrated)
	{
		// we already computed everything
	}
	// OR ELSE HARTLEY'S METHOD
	else
		// use intrinsic parameters of each camera, but
		// compute the rectification transformation directly
		// from the fundamental matrix
	{
		vector allimgpt[2];
		for (k = 0; k < 2; k++)
		{
			for (i = 0; i < nimages; i++)
				std::copy(imagePoints[k][i].begin(), imagePoints[k][i].end(), back_inserter(allimgpt[k]));
		}
		//计算基础矩阵
		F = findFundamentalMat(Mat(allimgpt[0]), Mat(allimgpt[1]), FM_8POINT, 0, 0);//第一/二个相机所得到的点
		Mat H1, H2;//计算第一/二幅图像矫正后单应性矩阵
		stereoRectifyUncalibrated(Mat(allimgpt[0]), Mat(allimgpt[1]), F, imageSize, H1, H2, 3);//输入基础矩阵

		R1 = cameraMatrix[0].inv()*H1*cameraMatrix[0];
		R2 = cameraMatrix[1].inv()*H2*cameraMatrix[1];
		P1 = cameraMatrix[0];
		P2 = cameraMatrix[1];
	}

	//Precompute maps for cv::remap()
	//八个参数:输入的摄像机内参数矩阵、输入的摄像机畸变系数矩阵、输入的第一和第二相机坐标系之间的旋转矩阵、
	//输入的校正后的3X3摄像机矩阵、摄像机采集的无失真图像尺寸、map1的数据类型,可以是CV_32FC1或CV_16SC2、
	//输出的X坐标重映射参数、输出的Y坐标重映射参数
	initUndistortRectifyMap(cameraMatrix[0], distCoeffs[0], R1, P1, imageSize, CV_16SC2, rmap[0][0], rmap[0][1]);
	initUndistortRectifyMap(cameraMatrix[1], distCoeffs[1], R2, P2, imageSize, CV_16SC2, rmap[1][0], rmap[1][1]);
	fs.open("map.yml", CV_STORAGE_WRITE);
	if (fs.isOpened())
	{
		fs << "leftmapx" << rmap[0][0] << "leftmapy" << rmap[0][1] << "rightmapx" << rmap[1][0] << "rightmapy" << rmap[1][1];
		fs.release();
	}
	else
		cout << "Error: can not save the map parameters\n";
	/*fs.open("leftmapy.yml", CV_STORAGE_WRITE);
	if( fs.isOpened() )
	{
	fs << "leftmapy" <preFilterSize=41;
		BMState->preFilterCap=31;
		BMState->SADWindowSize=41;
		BMState->minDisparity=-64;
		BMState->numberOfDisparities=128;
		BMState->textureThreshold=10;
		BMState->uniquenessRatio=15;
		cvFindStereoCorrespondenceBM(leftimg, rightimg, disp,
		BMState);
		cvNormalize( disp, vdisp, 0, 256, CV_MINMAX );
		cvNamedWindow( "disparity" );
		cvShowImage( "disparity", disp );*/
		char c = (char)waitKey();
		if (c == 27 || c == 'q' || c == 'Q')
			break;
	}

}

int main(int argc, char** argv)
{
	Size boardSize; //标定板尺寸
	string imagelistfn;
	bool showRectified = true; 
	argc = 6; //命令行总的参数个数
    argv[0] = "stereocalib";//项目名称
	argv[1] = "-w";//图片宽度方向上的交点个数  -11
	argv[2] = "11";
	argv[3] = "-h";//图片高度方向上的交点个数  -4
	argv[4] = "4";
	argv[5] = "stereo_calib.xml";

	for (int i = 1; i < argc; i++) //标定板在根目录读取
	{
		if (string(argv[i]) == "-w")
		{
			if (sscanf(argv[++i], "%d", &boardSize.width) != 1 || boardSize.width <= 0)
			{
				cout << "invalid board width" << endl;
				return print_help();
			}
		}
		else if (string(argv[i]) == "-h")
		{
			if (sscanf(argv[++i], "%d", &boardSize.height) != 1 || boardSize.height <= 0)
			{
				cout << "invalid board height" << endl;
				return print_help();
			}
		}
		else if (string(argv[i]) == "-nr")
			showRectified = false;
		else if (string(argv[i]) == "--help")
			return print_help();
		else if (argv[i][0] == '-')
		{
			cout << "invalid option " << argv[i] << endl;
			return 0;
		}
		else
			imagelistfn = argv[i]; //读取stereo_calib.xml
	}

	if (imagelistfn == "")
	{
		imagelistfn = "stereo_calib1.xml";
		//imagelistfn = "stereo_calib_tiff.xml";
		boardSize = Size(4, 11);
	}
	else if (boardSize.width <= 0 || boardSize.height <= 0)
	{
		cout << "if you specified XML file with chessboards, you should also specify the board width and height (-w and -h options)" << endl;
		return 0;
	}

	vector imagelist;
	//此函数在上面定义,第一个参数是"stereo_calib.xml"数组,第二个参数是向量
	bool ok = readStringList(imagelistfn, imagelist);
	if (!ok || imagelist.empty())
	{
		cout << "can not open " << imagelistfn << " or the string list is empty" << endl;
		return print_help();
	}

	StereoCalib(imagelist, boardSize, true, showRectified);
	return 0;
}

 

你可能感兴趣的:(立体视觉)