相机标定的原理、程序实现、畸变矫正(二,程序实现)

6 篇文章 0 订阅
2 篇文章 0 订阅
  1. 原理
  2. 标定
  3. 畸变修复
    (有什么其他要补充的或者要提问,留言就行)
    github工程:

原理

https://blog.csdn.net/shiyuqing457/article/details/106764866

标定程序

opencv版本4.0
程序思路(最下方会有全部代码,复制可直接用)
1、读取图片
2、提取图像的棋盘格格点
3、进行图像标定

对每个函数进行分开说明

读取图片

#include "opencv.hpp"
#include <iostream>
#include <fstream>
#include <iostream>  
#include <io.h>  
#include <string>  
#include<vector>

using namespace cv;
using namespace std;

void get_file(string &src_dir, string &file_type, vector<string> &file_name)
{
	struct _finddata_t FileInfo;
	long handle;
	string src_tmp = src_dir + "\\*" + file_type;
	string src_full_name;
	handle = _findfirst(src_tmp.c_str(), &FileInfo);
	if (!handle)
		cout << "the dir is not exist" << endl;
	src_full_name = src_dir + "\\" + FileInfo.name;   //将路径连接起来,存入容器
	file_name.push_back(src_full_name);
	while (!_findnext(handle, &FileInfo))
	{
		src_full_name = src_dir + "\\"+FileInfo.name;
		file_name.push_back(src_full_name);
	}	
	_findclose(handle);
	/*decltype(file_name.size()) cnt=0;
	for (auto file_num : file_name)
	{
		cout << "the num " << cnt << " file is:" << file_name[cnt] << endl;
		cnt++;
	}*/
}

Struct _finddata_t结构体可以用来处理各种文件的文件信息,使用该结构体需要添加头文件io.h
它的定义如下所示

struct _finddata_t
{
unsigned attrib;
time_t time_create;
time_t time_access;
time_t time_write;
_fsize_t size;
char name[_MAX_FNAME];
};
对于该结构体中的各个变量的定义如下所示:

unsigned attrib:无符号整形、位表示。它的作用是表示文件的属性,一般来说,文件的属性有如下几种:

_A_ARCH(存档)、 _A_HIDDEN(隐藏)、_A_NORMAL(正常)、_A_RDONLY(只读)、_A_SUBDIR(文件夹)、_A_SYSTEM(系统)

time_t time_create:表示从1970年1月1日0时0分0秒到现在时刻的秒数。

time_t time_access:文件最后一次被访问的时间

time_t time_write:文件最后一次被修改的时间

_fsize_t size:文件的大小。

char name[_MAX_FNAME]:文件的文件名,这里的_MAX_FNAME是一个常量宏,它在头文件中被定义,表示的是文件名的最大长度。

为了使用这个结构体将文件的信息存储到该结构体的内存空间,需要搭配使用_findfirst()、_findnext()、_findclose()三个函数使用。首先用_findfirst函数查找第一个文件,若成功则用返回的句柄,调用_findnext函数查找其他的文件,当查找文件完成之后,就会用_findclose函数结束查找。

long _findfirst( char *filespec, struct _finddata_t *fileinfo );

返回值:如果查找成功,那么就返回一个long型的唯一查找用的句柄。这个句柄会在_findnext函数中被使用。失败的话就会返回0.

参数:filespec:标明文件的字符串。例如:*.c

fileinfo:这里就是用来存放文件信息的结构体的指针。函数成功后,函数会把找到的文件的信息放入这个结构体所分配的内存空间中。

int _findnext( long handle, struct _finddata_t *fileinfo );

返回值:若成功返回0,否则返回-1。

参数:handle:即由_findfirst函数返回回来的句柄。

int _findclose( long handle );

返回值:成功返回0,失败返回-1。

指针与句柄的区别

指针:指针通俗来着就是地址,他是内存的编号,通过指针我们可以直接对内存进行操作,只要地址不变,我们每次操作的物理位置是绝对不变的。

句柄:一般是指向系统的资源的位置,可以说也是地址。但是这些资源的位置真的不变,我们都知道window支持虚拟内存的技术,同一时间内可能有些资源被换出内存,一些被换回来,这就是说同一资源在系统的不同时刻,他在内存的物理位置是不确定的,那么window是如何解决这个问题呢,就是通过句柄来处理资源的物理位置不断变化的这个问题的。window会在物理位置固定的区域存储一张对应表,表中记录了所有的资源实时地址,句柄其实没有直接指向资源的物理地址,而是指向了这个对应表中的一项,这样无论资源怎样的换进换出,通过句柄都可以找到他的实时位置。

提取角点与标定

1、**cv::findChessboardCorners()**棋盘格角点检测

该函数的具体调用形式如下:

bool cv::findChessboardCorners( // 如果找到角点则返回true
	cv::InputArray image, // 输入的棋盘格图像(8UC1或8UC3)
	cv::Size patternSize, // 棋盘格内部角点的行、列数
	cv::OutputArray corners, // 输出的棋盘格角点
	int flags = cv::CALIB_CB_ADAPTIVE_THRESH 
	| cv::CALIB_CB_NORMALIZE_IMAGE
);

第一个参数是输入的棋盘格图像(可以是8位单通道或三通道图像)。
第二个参数是棋盘格内部的角点的行列数(注意:不是棋盘格的行列数,如下图棋盘格的行列数分别为4、8,而内部角点的行列数分别是3、7,因此这里应该指定为cv::Size(3, 7))。

第三个参数是检测到的棋盘格角点,类型为std::vectorcv::Point2f。

第四个参数flag,用于指定在检测棋盘格角点的过程中所应用的一种或多种过滤方法,可以使用下面的一种或多种,如果都是用则使用OR:

cv::CALIB_CB_ADAPTIVE_THRESH:cv::findChessboardCorners()默认的阈值化处理基于平均亮度,如果该标志指定,则使用自适应滤波(自适应滤波见

OpenCV3中的阈值化操作——cv::threshold()与cv::adaptiveThreshold()详解 )。
cv::CALIB_CB_NORMALIZE_IMAGE:阈值化前使用cv::equalizeHist()进行直方图均衡化处理。

cv::CALIB_CB_FILTER_QUADS:

cv::CALIB_CV_FAST_CHECK:

2,**cv::cornerSubPix()**对角点进行亚像素精度优化。

使用亚像素级别角点检测,返回角点的浮点数值,它的精度比整数像素更准确。可以用cornerSubPix()函数将角点定位到子像素,从而取得亚像素级别的角点检测效果。
使用函数:

void cv::cornerSubPix ( InputArray image,//输入图像
InputOutputArray corners,//检测到的角点的位置
Size winSize,//搜索窗口
Size zeroZone,
TermCriteria criteria//终止条件
)

参考:https://blog.csdn.net/holybin/article/details/41122493

函数参数说明如下:

image:输入图像

corners:输入角点的初始坐标以及精准化后的坐标用于输出。

winSize:搜索窗口边长的一半,例如如果winSize=Size(5,5),则一个大小为(5x2+1)(5x2+1)=1111的搜索窗口将被使用。
zeroZone:搜索区域中间的dead region边长的一半,有时用于避免自相关矩阵的奇异性。如果值设为(-1,-1)则表示没有这个区域。
criteria:角点精准化迭代过程的终止条件。也就是当迭代次数超过criteria.maxCount,或者角点位置变化小于criteria.epsilon时,停止迭代过程。

3、**cv::drawChessboardCorners()**棋盘格角点的绘制

cv::drawChessboardCorners()的具体调用形式如下:

void cv::drawChessboardCorners(
		cv::InputOutputArray image, // 棋盘格图像(8UC3)即是输入也是输出
		cv::Size patternSize, // 棋盘格内部角点的行、列数
		cv::InputArray corners, // findChessboardCorners()输出的角点
		bool patternWasFound // findChessboardCorners()的返回值
	);

第一个参数是棋盘格图像(8UC3)。

第二个参数是棋盘格内部角点的行、列,和cv::findChessboardCorners()指定的相同。

第三个参数是检测到的棋盘格角点。

第四个参数是cv::findChessboardCorners()的返回值。

4, **cv::calibrateCamera()**相机标定程序

double cv::calibrateCamera  (   
InputArrayOfArrays  objectPoints,		//世界坐标下角点的坐标vector< vector< Point3f > >
InputArrayOfArrays  imagePoints,		//提取出的角点的坐标vector< vector< Point2f > >
Size    imageSize,						//图像的大小Size(图像高rows,图像宽cols)
InputOutputArray    cameraMatrix,		//内参矩阵Mat
InputOutputArray    distCoeffs,			//畸变系数,顺序为k1,k2,p1,p2,k3
OutputArrayOfArrays     rvecs,			//旋转矩阵	
OutputArrayOfArrays     tvecs,			//平移向量
OutputArray     stdDeviationsIntrinsics,//可以不用(感兴趣自己看下面)
OutputArray     stdDeviationsExtrinsics,//可以不用(感兴趣自己看下面)
OutputArray     perViewErrors,			//可以不用(感兴趣自己看下面)
int             flags = 0,				//标定方式
TermCriteria    criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) 							//可以不写
)      

参数

objectPoints :世界坐标系中的点。在使用时,应该输入vector< vector< Point3f > >。

imagePoints :其对应的图像点。和objectPoints一样,应该输入vector< vector< Point2f > >型的变量。

imageSize :图像的大小,在计算相机的内参数和畸变矩阵需要用到;

cameraMatrix :内参数矩阵。输入一个Mat cameraMatrix即可。

distCoeffs :畸变矩阵。输入一个Mat distCoeffs即可。

rvecs :旋转向量;应该输入一个Mat的vector,即vector< Mat > rvecs因为每个vector< Point3f >会得到一个rvecs。

tvecs :位移向量;和rvecs一样,也应该为vector tvecs。

stdDeviationsIntrinsics :内参数的输出向量。输出顺序为: (fx,fy,cx,cy,k1,k2,p1,p2,k3,k4,k5,k6,s1,s2,s3,s4,τx,τy) ,如果不估计其中某一个参数,值等于0

stdDeviationsExtrinsics :外参数的输出向量。输出顺序: (R1,T1,…,RM,TM) ,M是标定图片的个数, Ri,Ti 是1x3的向量 。

perViewErrors 每个标定图片的重投影均方根误差的输出向量。

criteria: 迭代优化算法的终止准则

flags :标定函数是所采用的模型(重点)”。
可输入如下某个或者某几个参数:

CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,将包含有效的fx,fy,cx,cy的估计值的内参矩阵cameraMatrix,作为初始值输入,然后函数对其做进一步优化。如果不使用这个参数,用图像的中心点初始化光轴点坐标(cx, cy),使用最小二乘估算出fx,fy(这种求法好像和张正友的论文不一样,不知道为何要这样处理)。注意,如果已知内部参数(内参矩阵和畸变系数),就不需要使用这个函数来估计外参,可以使用solvepnp()函数计算外参数矩阵。

CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点,光轴点将保持为图像的中心点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,保持为输入的值。

CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy的实际输入值将会被忽略,只有fx/fy的比值被计算和使用。

CV_CALIB_ZERO_TANGENT_DIST:切向畸变系数(P1,P2)被设置为零并保持为零。

CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变系数在优化中保持不变。如果设置了CV_CALIB_USE_INTRINSIC_GUESS参数,就从提供的畸变系数矩阵中得到。否则,设置为0。

CV_CALIB_RATIONAL_MODEL(理想模型):启用畸变k4,k5,k6三个畸变参数。使标定函数使用有理模型,返回8个系数。如果没有设置,则只计算其它5个畸变参数。

CALIB_THIN_PRISM_MODEL (薄棱镜畸变模型):启用畸变系数S1、S2、S3和S4。使标定函数使用薄棱柱模型并返回12个系数。如果不设置标志,则函数计算并返回只有5个失真系数。

CALIB_FIX_S1_S2_S3_S4 :优化过程中不改变薄棱镜畸变系数S1、S2、S3、S4。如果cv_calib_use_intrinsic_guess设置,使用提供的畸变系数矩阵中的值。否则,设置为0。

CALIB_TILTED_MODEL (倾斜模型):启用畸变系数tauX and tauY。标定函数使用倾斜传感器模型并返回14个系数。如果不设置标志,则函数计算并返回只有5个失真系数。

CALIB_FIX_TAUX_TAUY :在优化过程中,倾斜传感器模型的系数不被改变。如果cv_calib_use_intrinsic_guess设置,从提供的畸变系数矩阵中得到。否则,设置为0。

函数返回

重投影的总的均方根误差。

完整标定程序

#include "opencv.hpp"
#include <iostream>
#include <fstream>
#include <iostream>  
#include <io.h>  
#include <string>  
#include<vector>

using namespace std;
using namespace cv;
void get_file(string &src_dir, string &file_type, vector<string> &file_src)
{
	struct _finddata_t FileInfo;
	long handle;
	string src_tmp = src_dir + "\\*" + file_type;
	string src_full_name;
	handle = _findfirst(src_tmp.c_str(), &FileInfo);
	if (!handle)
		cout << "the dir is not exist" << endl;
	src_full_name = src_dir + "\\" + FileInfo.name;   //将路径连接起来,存入容器vector
	file_src.push_back(src_full_name);
	while (!_findnext(handle, &FileInfo))
	{
		src_full_name = src_dir + "\\"+FileInfo.name;
		file_src.push_back(src_full_name);
	}	
	_findclose(handle);
	/*decltype(file_name.size()) cnt=0;
	for (auto file_num : file_name)
	{
		cout << "the num " << cnt << " file is:" << file_name[cnt] << endl;
		cnt++;
	}*/
}

bool calibration(vector<string> &file_src, Size &board_num,Size &square_size, Mat & cameraMatrix, Mat & distCoeffs, vector<Mat> & rvecs, vector<Mat> & tvecs)
{
	Mat file_data;
	vector<Mat> all_pic_data;
	vector<vector<Point2f>> all_pattern_size;
	Size img_size;
	ofstream fout("calibration.txt");//用于保存标定结果
	int img_num = 0;
	cout << "开始读取图像..." << endl;
	//将所有图像存入all_pic_data中
	for (decltype(file_src.size()) i = 0; i >= 0 && i != file_src.size(); i++)
	{
		file_data = imread(file_src[i]);
		all_pic_data.push_back(file_data);
	}
	//提取棋盘格中的角点
	cout << "开始提取角点..." << endl;
	for (decltype(all_pic_data.size()) cnt = 0; cnt >= 0 && cnt != all_pic_data.size(); cnt++)
	{
		
		img_size.height = all_pic_data[cnt].rows;
		img_size.width = all_pic_data[cnt].cols;		//提取图像尺寸并保存,之后标定函数需要
		vector<Point2f> pattern_size;//输出检测到的角点坐标
		
		Mat imgInput;
		int corner_cnt=0;
		imgInput = all_pic_data[cnt];
		if (!findChessboardCorners(imgInput, board_num, pattern_size))
		{
			cout << "第 " << cnt << "幅图的角点检测失败" << endl;
			cout << "跳过第" << cnt << "幅图" << endl;
			continue;
		}
		img_num++;

		//亚像素化精确
		TermCriteria tc = TermCriteria(TermCriteria::EPS + TermCriteria::MAX_ITER, 40, 0.01);
		cvtColor(imgInput, imgInput, COLOR_RGB2GRAY);
		cornerSubPix(imgInput, pattern_size, Size(5, 5), Size(-1, -1), tc);
		all_pattern_size.push_back(pattern_size);
		bool patternWasFound = true;
		drawChessboardCorners(imgInput, board_num, pattern_size, patternWasFound);//将角点绘制到图上
		imshow("Camera Calibration", imgInput);//显示图片
		waitKey(100);
	}
	cout << "角点提取完成..." << endl;
	
	//取z=0为世界坐标系原点位置
	/*注意定义各point的位置,一定不能改,
	不然会导致存世界坐标的容器无法及时清零*/
	vector<vector<Point3f>> all_real_point;			//标定板放在世界坐标系下角点的位置
	for (int num = 0; num < img_num; num++)
	{
		vector<Point3f> tmp_point;
		for(int i=0;i<board_num.height;i++)
			for (int j = 0; j < board_num.width; j++)
			{
				Point3f real_point;
				real_point.x = i * square_size.width;
				real_point.y = j * square_size.height;
				real_point.z = 0;
				tmp_point.push_back(real_point);
			}
		
		all_real_point.push_back(tmp_point);
	}

	
	cout << "开始标定..." << endl;
	double err_first = calibrateCamera(all_real_point, all_pattern_size, img_size, cameraMatrix, distCoeffs, rvecs, tvecs, 0);
	cout << "标定完成..." << endl;
	cout << "....开始保存数据...." << endl;
	fout << "内参矩阵" << endl;
	fout << cameraMatrix << endl<<endl;
	fout << "相机畸变量" << endl;
	fout << distCoeffs << endl<<endl;
	for (int i = 0; i < img_num; i++)
	{
		Mat rotationMatrix;
		Rodrigues(rvecs[i], rotationMatrix);		//标定结果得到的是旋转向量,将旋转向量转为旋转矩阵
		fout << "第" << i + 1 << "副图的旋转矩阵为" << endl;
		fout << rotationMatrix << endl;
		fout << "第" << i + 1 << "副图的平移向量为" << endl;
		fout << tvecs[i] << endl<<endl;
	}
	cout << "结果保存完成" << endl;
	fout.close();
	return 1;
}

int main()
{
	string src_dir = "D:\\calibra\\ZZY\\photo";
	string file_type = ".jpg";
	vector<string> file_src;
	Size board_num(7, 4);
	Size square_size(50, 50);
	
	Mat cameraMatrix(3, 3, CV_32FC1,Scalar::all(0));//内参矩阵
	Mat distCoeffs(1, 5, CV_32FC1, Scalar::all(0));//畸变系数[k1,k2,p1,p1,k3]
	vector<Mat> rvecs;
	vector<Mat> tvecs;
	get_file(src_dir, file_type, file_src);
	calibration(file_src, board_num, square_size, cameraMatrix, distCoeffs, rvecs, tvecs);
	return 0;
}

畸变修复

https://blog.csdn.net/shiyuqing457/article/details/106790847

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值