OpenCV 张正友标定法的实现
来源:互联网 发布:xp系统链接网络打印机 编辑:程序博客网 时间:2024/06/05 17:42
关于张正友标定法的原理,网上的资料很多,本人虽然看了一些,但觉得还没有到能讲的非常清楚的程度,因此不在这里做太多原理描述。有兴趣了解细节的可以看张大神的原文,或者这篇文章。
需要大概知道的是,相机标定中内参、外参和畸变参数的概念。
内参有五个,分别是:
摄像头拍摄到的物体和实际物体在x,y轴上的映射关系(两个参数)。
摄像头中心和图像中心的偏移关系(两个参数)。
摄像头和镜头安装非完全垂直,存在一个角度的偏差。(一个参数)
外参有六个,分别是x,y,z方向上的平移和旋转。
有了上面两种参数,我们基本上知道摄像头拍摄到的图像和现实事物的对应关系了,但“畸变”亦不能忽略。它是由于镜头质量等原因导致的2D点的偏移。举个简单的例子就是用摄像头拍摄一个正方形,图像上会变成一个桶形或者其他的形状。在张氏标定法中张大神用“极大似然法”去计算出畸变的各项参数(如果想加深理解,也可以参考本人之前写过的一篇相关的文章)。
到此,就介绍完了相机标定三个最为重要的概念。一般我们要处理摄像头的畸变,只要求内参和畸变参数就可以了,而要做双目标定则需要把外参数也求出来。
本篇文章主要介绍用OpenCV自带的张正友标定法相关的函数来对摄像头进行标定(求取摄像头内外参数和畸变参数)并对单张图像进行校正的方法。主要参考的是这篇文章(如同雷同,是我抄的)。
需要自行准备标定板,长相如下(因为快递比较慢,自己先做了一个,但因为这个的精度直接影响到后面标定的精确度,建议还是买一块给力一点的):
标定的流程是:角点提取->相机标定
一.角点提取
会用到比较重要的函数是:
//用于提取标定板的内角点,也就是提取上图中每四个黑白格中间的那些角点bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE );
有四个参数:
第一个“Image”,是拍摄到的棋盘图像,也就是上图那样的图像;
第二个“patternSize”,即每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向,像上面那样的板子就是Size(7, 5),也就是每行7个角点,每列5个角点;
第三个“corners”,用于存储检测到的内角点图像坐标位置,一般用元素是Point2f的向量来表示;
第四个“flage”:用于定义棋盘图上内角点查找的不同处理方式,有默认值。
另外返回值很重要,它会告诉你是不是真的从图中找到了角点。如果后面想做成自动标定的程序,这个非常有用;
例如如果输入的图像如上图所示,而我们的第二个参数是Size(10, 5),则会返回错误;
//用于在初步提取的角点信息上进一步提取亚像素信息,降低相机标定偏差,该方法专门用来获取棋盘图上内角点的精确位置。而比较普遍的提取亚像素角点的方法是cornerSubPix,这里不做赘述bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);
有五个参数:
第一个“mage”,输入的Mat矩阵,最好是8位灰度图像,检测效率更高;
第二个“corners”,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示。也即输入上面findChessboardCorners函数的第三个参数。
第三个“winSize”,大小为搜索窗口的一半;
第四个“zeroZone”,死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为(-1,-1)时表示没有死区;
第五个“criteria”,定义求角点的迭代过程的终止条件,可以为迭代次数和角点精度两者的组合;
//用于画出求得的角点,以便查看是否标定正确void drawChessboardCorners( InputOutputArray image, Size patternSize, InputArray corners, bool patternWasFound );
有四个参数:
第一个“image”,8位灰度或者彩色图像;
第二个“patternSize”,每张标定棋盘上内角点的行列数,即findChessboardCorners的第二个参数;
第三个“corners”,角点坐标向量,可用find4QuadCornerSubpix函数的第二个参数输出做输入;
第四个“patternWasFound”,标志位,用来指示定义的棋盘内角点是否被完整的探测到,true表示被完整的探测到,函数会用直线依次连接所有的内角点,作为一个整体,false表示有未被探测到的内角点,这时候函数会以(红色)圆圈标记处检测到的内角点;
总的查找角点的示例代码如下:
Mat imageInput = imread("1.bmp");Size board_size = Size(7, 5);//标定板上每行、列的角点数vector<Point2f> image_points_buf;//缓存每幅图像上检测到的角点/*提取角点*/if (!findChessboardCorners(imageInput, board_size, image_points_buf)){ cout << "can not find chessboard corners!\n"; //找不到角点 return;}else{ Mat view_gray; cvtColor(imageInput, view_gray, CV_RGB2GRAY); /*亚像素精确化*/ find4QuadCornerSubpix(view_gray, image_points_buf, Size(5, 5)); //对粗提取的角点进行精确化 drawChessboardCorners(view_gray, board_size, image_points_buf, true); //用于在图片中标记角点 imshow("Camera Calibration", view_gray);//显示图片 waitKey(0); }
二.相机标定
利用上面获取到的图像角点(理论上需要三张图像,即三组数据,事实上以10~20张为宜,因为这样误差会比较小),便可以用calibrateCamera函数做摄像头标定,计算出摄像头的内参、外参和畸变参数了。当然前面代码在本人只做了一张图像的角点提取,可以改成求多张的,代码如下:
Size board_size = Size(7, 5);//标定板上每行、列的角点数vector<Point2f> image_points_buf;//缓存每幅图像上检测到的角点vector<vector<Point2f>> image_points_seq; //保存检测到的所有角点/* 提取角点 */char filename[10];for (size_t image_num = 1; image_num <= 14; image_num++){ sprintf_s(filename, "%d.bmp", image_num); Mat imageInput = imread(filename); if (!findChessboardCorners(imageInput, board_size, image_points_buf)) { cout << "can not find chessboard corners!\n"; //找不到角点 return; } else { Mat view_gray; cvtColor(imageInput, view_gray, CV_RGB2GRAY); /*亚像素精确化*/ find4QuadCornerSubpix(view_gray, image_points_buf, Size(5, 5)); //对粗提取的角点进行精确化 drawChessboardCorners(view_gray, board_size, image_points_buf, true); //用于在图片中标记角点 image_points_seq.push_back(image_points_buf);//保存亚像素角点 imshow("Camera Calibration", view_gray);//显示图片 waitKey(500);//停半秒 } imageInput.release();}
double calibrateCamera(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints, Size imageSize, CV_OUT InputOutputArray cameraMatrix, CV_OUT InputOutputArray distCoeffs, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags=0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON));
参数好多,有九个之多。。。
第一个“objectPoints”,为世界坐标系中的三维点。在使用时,应该输入一个三维坐标点的向量集合。一般我们假定标定板放在z=0的平面上,然后依据棋盘上单个黑白方块的大小(也可以直接都取10,如果不需要很准确的映射到现实事物的话)可以计算出每个内角点的世界坐标。
第二个“imagePoints”,为每一个内角点对应的图像坐标点。也即是上面求得的各张图像的角点集合;
第三个“imageSize”,为图像的像素尺寸大小,在计算相机的内参和畸变矩阵时需要使用到该参数;
第四个“cameraMatrix”为相机的内参矩阵。输入一个Mat cameraMatrix即可,如Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
第五个“distCoeffs“为畸变矩阵。输入一个Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0));
即可。
第六个“rvecs”为旋转向量;应该输入一个Mat类型的vector,即vector<Mat>rvecs;
第七个“tvecs”为位移向量,和rvecs一样,应该为vector<Mat> tvecs;
第八个“flags”为标定时所采用的算法。有如下几个参数(直接不写则依据下面参数描述中没设参数的情况进行):
CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy。 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_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数。
第九个“criteria“是最优迭代终止条件设定。
在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化,标定的结果是生成相机的内参矩阵cameraMatrix、相机的5个畸变系数distCoeffs,另外每张图像都会生成属于自己的平移向量和旋转向量。
具体的实现代码如下:
Size image_size;//图像的尺寸Size board_size = Size(7, 5); //标定板上每行、列的角点数vector<Point2f> image_points_buf; //缓存每幅图像上检测到的角点vector<vector<Point2f>> image_points_seq; //保存检测到的所有角点/*提取角点*/char filename[10];for (size_t image_num = 1; image_num <= IMGCOUNT; image_num++){ sprintf_s(filename, "%d.bmp", image_num); Mat imageInput = imread(filename); if (!findChessboardCorners(imageInput, board_size, image_points_buf)) { cout << "can not find chessboard corners!\n";//找不到角点 return; } else { Mat view_gray; cvtColor(imageInput, view_gray, CV_RGB2GRAY); /*亚像素精确化*/ find4QuadCornerSubpix(view_gray, image_points_buf, Size(5, 5));//对粗提取的角点进行精确化 drawChessboardCorners(view_gray, board_size, image_points_buf, true);//用于在图片中标记角点 image_points_seq.push_back(image_points_buf);//保存亚像素角点 imshow("Camera Calibration", view_gray);//显示图片 //waitKey(500);//停半秒 } image_size.width = imageInput.cols; image_size.height = imageInput.rows; imageInput.release();}/*相机标定*/vector<vector<Point3f>> object_points; //保存标定板上角点的三维坐标,为标定函数的第一个参数Size square_size = Size(10, 10);//实际测量得到的标定板上每个棋盘格的大小,这里其实没测,就假定了一个值,感觉影响不是太大,后面再研究下for (int t = 0; t<IMGCOUNT; t++){ vector<Point3f> tempPointSet; for (int i = 0; i<board_size.height; i++) { for (int j = 0; j<board_size.width; j++) { Point3f realPoint; //假设标定板放在世界坐标系中z=0的平面上 realPoint.x = i*square_size.width; realPoint.y = j*square_size.height; realPoint.z = 0; tempPointSet.push_back(realPoint); } } object_points.push_back(tempPointSet);}//内外参数对象Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));//摄像机内参数矩阵vector<int> point_counts;// 每幅图像中角点的数量 Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));//摄像机的5个畸变系数:k1,k2,p1,p2,k3vector<Mat> tvecsMat;//每幅图像的旋转向量vector<Mat> rvecsMat;//每幅图像的平移向量calibrateCamera(object_points, image_points_seq, image_size, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, 0);//摄像头标定
到此我们已经完成了标定的过程,得到了摄像头的各个参数,后面就可以用这些得到的参数来做摄像头的矫正了。
矫正可以使用下面的函数:
void undistort( InputArray src, OutputArray dst,InputArray cameraMatrix,InputArray distCoeffs,InputArray newCameraMatrix=noArray() );
有五个参数:
第一个“src”,输入参数,代表畸变的原始图像;
第二个“dst”,矫正后的输出图像,跟输入图像具有相同的类型和大小;
第三个“cameraMatrix”为之前求得的相机的内参矩阵;
第四个“distCoeffs”为之前求得的相机畸变矩阵;
第五个“newCameraMatrix”,默认跟cameraMatrix保持一致;
具体代码如下:
/*用标定的结果矫正图像*/for (size_t image_num = 1; image_num <= IMGCOUNT; image_num++){ sprintf_s(filename, "%d.bmp", image_num); Mat imageSource = imread(filename); Mat newimage = imageSource.clone(); undistort(imageSource, newimage, cameraMatrix, distCoeffs); imshow("source", imageSource);//显示图片 imshow("drc", newimage);//显示图片 waitKey(500);//停半秒 imageSource.release(); newimage.release();}
到此就完成了摄像头的标定和图像的矫正的整个流程,如果想要知道标定的效果如何评定,可以参考上文提到过的参考文章,本文的很多函数说明基本是照搬的,只是做了一些代码上的拆分。
如果想要把标定的结果保存下载后面直接用,则可以如下代码保存:
/*保存内参和畸变系数,以便后面直接矫正*/ofstream fout("caliberation_result.txt");//保存标定结果的文件fout << "相机内参数矩阵:" << endl;fout << cameraMatrix << endl << endl;fout << "畸变系数:\n";fout << distCoeffs << endl << endl << endl;fout.close();
读取标定文件文件代码如下:
char read[100];double getdata;Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));//摄像机内参数矩阵 Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));//摄像机的5个畸变系数:k1,k2,p1,p2,k3ifstream fin("caliberation_result.txt");//读取保存标定结果的文件,以供矫正fin >> read;fin.seekg(3, ios::cur);for (size_t j = 0; j < 3; j++) for (size_t i = 0; i < 3; i++) { fin >> getdata; cameraMatrix.at<float>(j, i) = getdata; fin >> read; }fin >> read;fin.seekg(3, ios::cur);for (size_t i = 0; i < 5; i++){ fin >> getdata; distCoeffs.at<float>(i) = getdata; fin >> read;} fin.close();
本文的全部代码如下:
#include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/calib3d/calib3d.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> #include <fstream> #define IMGCOUNT 20using namespace cv;using namespace std;void main(){ Size image_size;//图像的尺寸 Size board_size = Size(9, 6); //标定板上每行、列的角点数 vector<Point2f> image_points_buf; //缓存每幅图像上检测到的角点 vector<vector<Point2f>> image_points_seq; //保存检测到的所有角点 /*提取角点*/ char filename[10]; for (size_t image_num = 1; image_num <= IMGCOUNT; image_num++) { sprintf_s(filename, "%d.bmp", image_num); Mat imageInput = imread(filename); if (!findChessboardCorners(imageInput, board_size, image_points_buf)) { cout << "can not find chessboard corners!\n";//找不到角点 return; } else { Mat view_gray; cvtColor(imageInput, view_gray, CV_RGB2GRAY); /*亚像素精确化*/ find4QuadCornerSubpix(view_gray, image_points_buf, Size(5, 5));//对粗提取的角点进行精确化 drawChessboardCorners(view_gray, board_size, image_points_buf, true);//用于在图片中标记角点 image_points_seq.push_back(image_points_buf);//保存亚像素角点 imshow("Camera Calibration", view_gray);//显示图片 waitKey(500);//停半秒 } image_size.width = imageInput.cols; image_size.height = imageInput.rows; imageInput.release(); } /*相机标定*/ vector<vector<Point3f>> object_points; //保存标定板上角点的三维坐标,为标定函数的第一个参数 Size square_size = Size(10, 10);//实际测量得到的标定板上每个棋盘格的大小,这里其实没测,就假定了一个值,感觉影响不是太大,后面再研究下 for (int t = 0; t<IMGCOUNT; t++) { vector<Point3f> tempPointSet; for (int i = 0; i<board_size.height; i++) { for (int j = 0; j<board_size.width; j++) { Point3f realPoint; //假设标定板放在世界坐标系中z=0的平面上 realPoint.x = i*square_size.width; realPoint.y = j*square_size.height; realPoint.z = 0; tempPointSet.push_back(realPoint); } } object_points.push_back(tempPointSet); } //内外参数对象 Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));//摄像机内参数矩阵 vector<int> point_counts;// 每幅图像中角点的数量 Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));//摄像机的5个畸变系数:k1,k2,p1,p2,k3 vector<Mat> tvecsMat;//每幅图像的旋转向量 vector<Mat> rvecsMat;//每幅图像的平移向量 calibrateCamera(object_points, image_points_seq, image_size, cameraMatrix, distCoeffs, rvecsMat, tvecsMat, 0);//相机标定 /*用标定的结果矫正图像*/ for (size_t image_num = 1; image_num <= IMGCOUNT; image_num++) { sprintf_s(filename, "%d.bmp", image_num); Mat imageSource = imread(filename); Mat newimage = imageSource.clone(); undistort(imageSource, newimage, cameraMatrix, distCoeffs); imshow("source", imageSource);//显示图片 imshow("drc", newimage);//显示图片 sprintf_s(filename, "%d_d.bmp", image_num); imwrite(filename, newimage);//显示图片 waitKey(500);//停半秒 imageSource.release(); newimage.release(); } /*保存内参和畸变系数,以便后面直接矫正*/ ofstream fout("caliberation_result.txt");//保存标定结果的文件 fout << "相机内参数矩阵:" << endl; fout << cameraMatrix << endl << endl; fout << "畸变系数:\n"; fout << distCoeffs << endl << endl << endl; fout.close(); ///*读取之前标定好的数据直接矫正*/ //char read[100]; //double getdata; //Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));//摄像机内参数矩阵 //Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));//摄像机的5个畸变系数:k1,k2,p1,p2,k3 //ifstream fin("caliberation_result.txt");//读取保存标定结果的文件,以供矫正 //fin >> read; //fin.seekg(3, ios::cur); //for (size_t j = 0; j < 3; j++) // for (size_t i = 0; i < 3; i++) // { // fin >> getdata; // cameraMatrix.at<float>(j, i) = getdata; // fin >> read; // } //fin >> read; //fin.seekg(3, ios::cur); //for (size_t i = 0; i < 5; i++) //{ // fin >> getdata; // distCoeffs.at<float>(i) = getdata; // fin >> read; //} //fin.close(); //char filename[10]; //for (size_t image_num = 1; image_num <= IMGCOUNT; image_num++) //{ // sprintf_s(filename, "%d.bmp", image_num); // Mat imageSource = imread(filename); // Mat newimage = imageSource.clone(); // undistort(imageSource, newimage, cameraMatrix, distCoeffs); // imshow("source", imageSource);//显示图片 // imshow("drc", newimage);//显示图片 // sprintf_s(filename, "%d_d.bmp", image_num); // imwrite(filename, newimage);//显示图片 // waitKey(500);//停半秒 // imageSource.release(); // newimage.release(); //}}
阅读全文
1 0
- OpenCV 张正友标定法的实现
- opencv实现摄像机标定(张正友的标定方法)
- Opencv双目标定的实现
- 张正友相机标定 Opencv实现
- 张正友相机标定Opencv实现以及标定流程&&标定结果评价&&图像矫正流程解析
- OPENCV版本的摄像机标定(张正友)
- OPENCV版本的摄像机标定(张正友)
- OPENCV版本的摄像机标定(张正友)
- 张正友摄像机标定的研究(MATLAB+OpenCV)
- 张正友摄像机标定的研究(MATLAB+OpenCV)
- 张正友摄像机标定的研究(MATLAB+OpenCV)
- Opencv摄像机的标定
- 经典手眼标定算法之Tsai-Lenz的OpenCV实现
- 经典手眼标定算法之Navy的OpenCV实现
- 经典手眼标定算法之Tsai-Lenz的OpenCV实现
- 基于matlab标定数据,使用opencv实现双目立体摄像头的标定(源代码)
- 基于matlab标定数据,使用opencv实现双目立体摄像头的标定(源代码)
- 基于张正友平面标定法的摄像机标定及GUI实现
- 软件工程(C编码实践篇)”实验报告 实验二:命令行菜单小程序V1.0
- java中插入汉字到mysql中变成?的处理办法
- Java之多线程CountDownLatch 用法 -yellowcong
- 练习题(基础练习)
- MyBatis绑定错误:Invalid bound statement (not found)
- OpenCV 张正友标定法的实现
- N皇后问题
- 【数据结构】【OI】二项堆的原理及代码实现
- asp.net创建helloWord
- HQL 多表联合查询
- JavaScript中forEach、for-in、for-of循环的比较
- 使用pyspark进行机器学习(回归问题)
- 1.break 2.continue 3. return
- AndFix实例讲解