<视觉>——单目相机的标定(简单原理+Opencv实现)

单目相机的标定原理大致如下:

  

 

S_{0}m=K[R,T]X 

X=[X,Y,Z,1]^{T}---->m=[u,v,1]^{T}

世界坐标到像素坐标的转换。

期间的参数有S尺度因子,内参矩阵K,旋转矩阵R,平移矩阵T,一共八个未知数。

在Opencv中我们可以方便的根据相机拍摄不同位姿的标定板图片来标定相机,从而求得这些参数,从而可以将相机下的坐标和实际的世界坐标联系起来,求取单应性矩阵H

参考:

单目相机综述:写的特别好

https://www.cnblogs.com/zyly/p/9366080.html     

https://blog.csdn.net/qq_37791134/article/details/80942171  

张正有标定原理:

https://blog.csdn.net/u010128736/article/details/52860364

https://www.jianshu.com/p/9d2fe4c2e3b7

接下来我具体的说明一下Opencv中的单目相机的标定:

  1. 准备20-30张拍摄的标定板图像,以20张图片为例。
  2. 提取20张图片的的角点信息。                                                 
  3. 在上一步的基础上,提取精度更高的角点坐标。 
  4. 把找到的角点在图上画出来,显得更加直观。                      
  5. 相机标定计算,根据上面提取到的角点坐标信息和世界坐标系中的三维点进行计算。    
  6. 对标定结果进行评价
  7. 查看标定效果——利用标定结果对棋盘图进行矫正 (两种方法)

输入:标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标

输出:摄像机的内参、外参系数。

 31张棋盘图下载地址:

https://pan.baidu.com/s/1kqybghdOE6HtdyCAhB5QdA          提取码:lxmt

main.cpp 

#include"11.h"

int main() 
{
	//初始化棋盘数据,载入图片
	calibrator calib(IMAGE_FOLDER, 25.f, 5, 4);   

	//把所有的图片的角点坐标找到     
        calib.calc_image_points(true);              
                                          	
	//回车切换下张棋盘图
	cout << "计算相机参数中......请等待" << endl;

	//相机标定计算
	calib.calibrate();                         

	cout << "--------标定完成--------" << endl;
	
	//对标定结果进行评价
        //利用相机标定计算得到的每张图片的转换关系,将其三维点转换成二维点
        //和之前得到的角点二维坐标进行对比    三维--》二维
	calib.get_error();                          

	//查看标定效果——利用标定结果对棋盘图进行矫正
        //利用内参和畸变系数求出   无畸变和修正转换映射
        //去除镜头畸变的图像拉伸
	calib.Correct_image();                     

	string filename = DATA_FOLDER + string("cam_calib.xml");    
	FileStorage fs(filename, FileStorage::WRITE);              
        fs << "cameraMatrix" << calib.get_cameraMatrix(); 
        fs << "distCoeffs" << calib.get_distCoeffs();
        fs.release();
        cout << "保存相机参数到:" << filename << endl;

	system("pause");
        return 0;
}

 

 11.h

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>


#define DATA_FOLDER "./date/"
#define IMAGE_FOLDER "./img/"


using namespace cv;
using namespace std;
//using namespace boost::filesystem;

class calibrator 
{
public:
	calibrator(string, float, int, int);           //构造函数,读取图像的信息
	
	void calc_image_points(bool);                  //找到图像的内角点

	void calibrate();                              //计算相机参数
	
	Mat get_cameraMatrix();                        //得到相机的内参矩阵
	
	Mat get_distCoeffs();                          //得到相机的畸变系数

	void get_error();                              //对标定结果进行评价

	void Correct_image();                          //查看标定效果——利用标定结果对棋盘图进行矫正

private:
	string pattern;                       //30张标定图片的位置
	vector<Mat> images;                //用于保存标定图片
	Mat cameraMatrix, distCoeffs;      //定义的相机内参矩阵、畸变系数


	bool show_chess_corners;         
	float side_length;                 //棋盘单个正方形方块的长度
	int width, height;                 //内角点在长和宽方向上的数量
	vector<vector<Point2f> > image_points;  //保存2D点
	vector<vector<Point3f> > object_points; //保存3D点
	vector<Mat> rvecs, tvecs;               //保存每张图片R,T的转换矩阵

};

 11.cpp

#include"11.h"

calibrator::calibrator(string _path, float _side_length, int _width, int _height) 
{
	side_length = _side_length;
	width = _width;
	height = _height;

	pattern = _path;
	cout <<"图片的位置:" <<pattern << endl;
	
	//std::string pattern = "./img/*.jpg";
	std::vector<cv::String>image_file;

	glob(pattern, image_file);
	for(int i=0;i<image_file.size();i++)
	{
		images.push_back(imread(image_file[i]));
	}
	
}

void calibrator::calc_image_points(bool show)  //计算内角点
{
	                             //计算物体坐标系中的物体点(左上角原点)
	vector<Point3f> ob_p;        //定义内角点的容器,20个点
	for (int i = 0; i < height; i++) //4
	{
		for (int j = 0; j < width; j++) //5
		{
			ob_p.push_back(Point3f(j * side_length, i * side_length, 0.f));
		}
	}

	if (show) namedWindow("Chessboard corners");

	for (int i = 0; i < images.size(); i++) 
	{
		Mat im = images[i];
		vector<Point2f> im_p;           //在棋盘图像中找到内角点坐标,初始的二维点im_p
		bool pattern_found = findChessboardCorners(im, Size(width, height), im_p, CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE + CALIB_CB_FAST_CHECK);
		if (pattern_found) 
		{
			object_points.push_back(ob_p);//存放20个vector<Point3f>,每张图的20个点现在用不到
			                              //现在用不到,是物体的坐标,不是图像
			Mat gray;
			cvtColor(im, gray, CV_BGR2GRAY);//亚像素级角点检测,提高精度im_p,输出im_p		
			cornerSubPix(gray, im_p, Size(5, 5), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
			
			image_points.push_back(im_p);

			if (show) 
			{
				Mat im_show = im.clone();      //第二个参数patternSize,每张标定棋盘上内角点的行列数
				drawChessboardCorners(im_show, Size(width, height), im_p, true);
				imshow("Chessboard corners", im_show);
				while (char(waitKey(1)) != '\r') //按回车跳出,进行下一张图片
				{

				}
			}
		}
		else images.erase(images.begin() + i);//如果图片无效找不到角点,删除vector中这个图片的接口
	}
}

void calibrator::calibrate() 
{
	//vector<Mat> rvecs, tvecs;
	float rms_error = calibrateCamera(object_points, image_points, images[0].size(), 
		cameraMatrix, distCoeffs, rvecs, tvecs);
	//第一个参数:objectPoints,为世界坐标系中的三维点:vector<vector<Point3f>> object_points,
	//需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标。
	//长100*宽75
	//第二个参数:imagePoints,为每一个内角点对应的图像坐标点:vector<vector<Point2f>> image_points
	//第三个参数:imageSize,为图像的像素尺寸大小
	//第四个参数:cameraMatrix为相机的内参矩阵:Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
	//第五个参数:distCoeffs为畸变矩阵Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0));
   
    //内参数矩阵 M=[fx γ u0,0 fy v0,0 0 1]
    //外参矩阵  5个畸变系数k1,k2,p1,p2,k3



	//第六个参数:rvecs旋转向量R,vector<Mat> tvecs;
	//第七个参数:tvecs位移向量T,和rvecs一样,应该为vector<Mat> tvecs;
	//第八个参数:flags为标定时所采用的算法  第九个参数:criteria是最优迭代终止条件设定。
	//return:重投影的总的均方根误差。

	//总结:得到相机内参矩阵K、相机的5个畸变系数、每张图片属于自己的平移向量T、旋转向量R
	cout << "重投影的总的均方根误差:" << rms_error << endl;
}

Mat calibrator::get_cameraMatrix() 
{
	return cameraMatrix;
}

Mat calibrator::get_distCoeffs() 
{
	return distCoeffs;
}

void calibrator::get_error() 
{
	double total_err = 0.0;   //所有图像的平均误差的总和 
	double err = 0.0;    //每幅图像的平均误差 

	vector<Point2f> image_points2; // 保存重新计算得到的投影点
	cout << "\t每幅图像的标定误差:\n" << endl;
	for(int i=0;i< images.size(); i++)
	{
		vector<Point3f> tempPointSet = object_points[i];
		
		//通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点
		projectPoints(tempPointSet, rvecs[i], tvecs[i], cameraMatrix, distCoeffs, image_points2);

		// 计算新的投影点和旧的投影点之间的误差
		vector<Point2f> tempImagePoint = image_points[i];

		Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2); //cornerSubPix
		Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);   //projectPoints

		//对标定结果进行评价
		for (int j = 0; j < tempImagePoint.size(); j++)
		{                                                               // //分别给两个角点坐标赋值他x,y坐标
			image_points2Mat.at<Vec2f>(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);   
			tempImagePointMat.at<Vec2f>(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
		}
		//norm计算数组src1和src2的相对差范数
		err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
		total_err += err ;
		std::cout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
	}
	
	std::cout << "总体的平均误差:" << total_err/ images.size() << "像素" << endl;	
}


//查看标定效果——利用标定结果对棋盘图进行矫正
void calibrator::Correct_image()
{
	
	Mat mapx = Mat(images[0].size(), CV_32FC1);
	Mat mapy = Mat(images[0].size(), CV_32FC1);
	Mat R = Mat::eye(3, 3, CV_32F);      //单位矩阵
        //计算出对应的映射表
	initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, images[0].size(), CV_32FC1, mapx, mapy);
	//第三个参数R,可选的输入,是第一和第二相机坐标之间的旋转矩阵;
	//第四个参数newCameraMatrix,输入的校正后的3X3摄像机矩阵;
	//第五个参数size,摄像机采集的无失真的图像尺寸;
	//第六个参数m1type,定义map1的数据类型,可以是CV_32FC1或者CV_16SC2;
	//第七个参数map1和第八个参数map2,输出的X / Y坐标重映射参数;

	for (int i = 0; i!= images.size(); i++)
	{
		Mat imageSource = images[i];
		Mat newimage = images[i].clone();
		Mat newimage1 = images[i].clone();

		//方法一:使用initUndistortRectifyMap和remap两个函数配合实现。
		//initUndistortRectifyMap用来计算畸变映射,remap把求得的映射应用到图像上。
	

		
		remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);
		//第五个参数interpolation,定义图像的插值方式;
		//第六个参数borderMode,定义边界填充方式;

		//方法二:使用undistort函数实现
		undistort(imageSource, newimage1, cameraMatrix, distCoeffs);
		//第五个参数newCameraMatrix,默认跟cameraMatrix保持一致;		
		
		//输出图像
		string str = "./img_remap/" + to_string(i + 1) + ".jpg";
		string str1 = "./img_remap_undistort/" + to_string(i + 1) + ".jpg";
		//string str = "d:\\" + to_string(i + 1) + ".jpg";
		imwrite(str, newimage);
		imwrite(str1, newimage1);
	}



}

最后效果:相机内外参数,矫正前后图片

 

单目标定我们得到:

  1.  cameraMatrix-----相机的内参矩阵
  2. distCoeffs-----相机镜头的畸变矩阵
  3. rvecs-----旋转向量R-----用于三维初始点转换为二维投影点
  4. tvecs-----位移向量T-----用于三维初始点转换为二维投影点

图像去畸变:

第一种:
Mat mapx = Mat(images[0].size(), CV_32FC1);
Mat mapy = Mat(images[0].size(), CV_32FC1);
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cameraMatrix, 
           images[0].size(), CV_32FC1, mapx, mapy);
remap(imageSource, newimage, mapx, mapy, INTER_LINEAR);

第二种:
undistort(imageSource, newimage1, cameraMatrix, distCoeffs);

mapx、mapy用于二维图像的畸变矫正,也可以直接使用undistort()矫正图像。

 

  • 14
    点赞
  • 179
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值