相机标定过程(opencv) + matlab参数导入opencv + matlab标定和矫正
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
辛苦原创所得,转载请注明出处
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start -- 摄像机标定 ---------------------------------------------->
摄像机标定的数学过程如下
http://blog.csdn.net/ssw_1990/article/details/53216767
标定事先选用棋盘格要注意一些问题,张正友论文中建议棋盘格数大于7*7。opencv标定时候对正方形的棋盘格标定板是不能识别的,需要长方形的标定板。张正友论文中建议每次拍摄标定板占50%以上,但这是对畸变并不是很大的普通相机而言的,对于球面相机是不适用的,相反球面相机标定使用的标定板占比应该较小比较好(对于格子并不是非常密的棋盘格而言),原因是因为棋盘格每个角点之间的距离越大,这段距离之间的可能发生畸变的点越多,如果占比过大就无法将形变体现在棋盘格中。棋盘格的选用应该根据实际需要选用,对于要求精密识别的情况,则需要高精度的棋盘格,相应的价格也会较高;对于精度要求并不是很高的(如抓取)情况并不需要精度很高的标定板,也能够节省开支。
这里程序的实现是在opencv中,所以就用opencv的程序来说明具体的过程.注意各个版本的opencv之间的程序移植性并不好,以下程序是在opencv2.4.3下编制运行的,每一步的要用到的输入输出都做了红色标记.
立体相机标定分为两个步骤,一个是单目标定(本文档第2步),另一个是双目标定
单目标定获得相机的x,y轴的焦距;x,y轴的坐标原点位置;世界坐标系和平面坐标之间的旋转和平移矩阵,5个畸变系数
双目标定获得两个相机成像平面之间的旋转和平移矩阵
注意
1.程序运行前需要插上摄像头,否则程序有可能不能正常运行
2.单目标定
(1).获取棋盘格图像
for (int i=1; i<=19; i++)//输入左标定板图像
{
std::stringstream str;//声明输入输出流
str << "./left" << i << ".jpg";//以名字方式把图像输入到流
std::cout << str.str() << std::endl;//.str("")清除内容 .clear()清空标记位
leftFileList.push_back(str.str());//.push_back从容器后向前插入数据
leftBoardImage = cv::imread(str.str(),0);//用来显示即时输入的图像
cv::namedWindow("left chessboard image");
cv::imshow("left chessboard image",leftBoardImage);
cv::waitKey(10);
}
(2).定义棋盘格的角点数目
cv::Size boardSize(14,10)
(3).程序定位提取角点
这里建立的是理想成像平面(三维,第三维为0,单位为格子数)和图像坐标系(二维,单位是像素)之间的关系
(a)首先声明两个坐标容器
std::vector<cv::Point2f> imageCorners;//二位坐标点
std::vector<cv::Point3f> objectCorners;//三维坐标点
(b)初始化棋盘角点,令其位置位于(x,y,z)=(i,j,0),一个棋盘格为一个坐标值
for (int i=0; i<boardSize.height; i++)
{
for (int j=0; j<boardSize.width; j++)
{
objectCorners.push_back(cv::Point3f(i, j, 0.0f));
}
}
(c)直接使用opencv内函数找到二维角点坐标,并建立标定标定格子和实际坐标间的关系(像素级别)
这个函数使用时,当标定板是长方形时可以找到角点,但是标定板是正方形时,就找不到,原因还未知.
cv::findChessboardCorners(image, boardSize, imageCorners);
(d)获得像素精度往往是不够的,还需要获得亚像素的精度
cv::cornerSubPix(image,
imageCorners, //输入/输出
cv::Size(5,5),//搜索框的一半,表示在多大窗口定位角点
cv::Size(-1,-1), //死区
cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS,
30, // max number of iterations//迭代最大次数
0.01)); // min accuracy//最小精度
注:
TermCriteria模板类,取代了之前的CvTermCriteria,这个类是作为迭代算法的终止条件的,这个类在参考手册里介绍的很简单,我查了些资料,这里介绍一下。该类变量需要3个参数,一个是类型,第二个参数为迭代的最大次数,最后一个是特定的阈值。类型有CV_TERMCRIT_ITER、CV_TERMCRIT_EPS、CV_TERMCRIT_ITER+CV_TERMCRIT_EPS,分别代表着迭代终止条件为达到最大迭代次数终止,迭代到阈值终止,或者两者都作为迭代终止条件。以上的宏对应的c++的版本分别为TermCriteria::COUNT、TermCriteria::EPS,这里的COUNT也可以写成MAX_ITER。
(4)将角点图像显示出来
cv::drawChessboardCorners(image, boardSize, imageCorners, found);
cv::imshow("Corners on Chessboard", image);
cv::waitKey(10);
(5).相机标定
calibrateCamera(leftObjectPoints, // the 3D points,3d点,整数的物理坐标(角点数目为单位)
leftImagePoints, // the image points,图像点
imageSize,
//cv::Size(310,240), // image size, 以像素衡量的图像尺寸
leftCameraMatrix, // output camera matrix,输出的3*3相机矩阵
leftDistCoeffs, // output distortion matrix.输出的5*1畸变系数
leftRvecs, leftTvecs, // Rs, Ts.旋转和平移(每一张图都有各自的旋转平移向量)
leftFlag); // set options.额外选项
// ,CV_CALIB_USE_INTRINSIC_GUESS);
其中:
leftObjectPoints为世界坐标系中的点。在使用时,应该输入一个三维点的vector的vector类型
imagePoints为其对应的二维图像点。和objectPoints一样,应该输入vector类型。
imageSize为图像的大小
leftCameraMatrix是通过这个标定函数我们可以获得的内参矩阵,内参矩阵总是3*3的
leftDistCoeffs可以获得的畸变矩阵,畸变矩阵是一个5*1的矩阵(k1,k2,p1,p2,k3)
leftRvecs, leftTvecs,可以获得的图像平面相对于世界坐标的旋转和平移矩阵
flags为标定是所采用的算法。程序中直接使用0即可,还可以使用如下某个或者某几个参数:
CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,cx,cy的估
计值。否则的话,将初始化(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参数,
CV_CALIB_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个
畸变参数。
3.双目标定
(1)右相机的标定,过程如上.其实是个重复的过程
(2).双目标定.立体标定需要有一个相机作为基准,常用的是用左相机作为基准
在这里需要提前明确什么是本征矩阵,基础矩阵
本征矩阵E:它包含了物理空间中两个摄像机相关的旋转(R)和平移信息(T)。T和R描述了一台摄像机相
对于另外一台摄像机在全局坐标系中的相对位置,与基础矩阵相对应,这建立的相机主轴之间的关
系,联系的是物理坐标,不是像素坐标。
基础矩阵F:除了包含本征矩阵E的信息外,还包含了两个摄像机的内参数。由于F包含了这些内参数,
因此它可以在像素坐标系将两个摄像机关联起来。我们观察摄像机内参数矩阵M,如果图像没有
畸变,即cx,cy为0,并且焦距进行了归一化处理,那么M就成了单位阵,此时本征矩阵F就等于
基础矩阵E,即F=E。
更加详细的关于本征矩阵和基础矩阵的数学知识可以参考如下网址
http://blog.csdn.net/xiaoyinload/article/details/49000855
https://www.zhihu.com/question/27581884
(3)使用函数进行双目标定
stereoCalibrate(stereoObjectPoints,
leftImagePoints,//输入左相机图像点
rightImagePoints,//输入右相机图像点
leftCameraMatrix,//输入/输出左相机内参矩阵(是输入还是输出由最后一项flag决定)
leftDistCoeffs,//输入/输出左相机的畸变矩阵(是输入还是输出由最后一项flag决定)(k1,k2,p1,p2,k3)
rightCameraMatrix,//输入/输出右相机的内参矩阵(是输入还是输出由最后一项flag决定)
rightDistCoeffs,//输入/输出右相机的畸变矩阵(是输入还是输出由最后一项flag决定)
cv::Size(310,240),//图像的大小
stereoR,//获得两相机旋转矩阵
stereoT,//获得两相机平移矩阵
stereoE,//获得两相机本征矩阵
stereoF,//获得两相机基础矩阵
cvTermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 100, 1e-5),
CV_CALIB_FIX_INTRINSIC
);
CV_CALIB_FIX_INTRINSIC要确认cameraMatrix? and distCoeffs?所以只有R, T, E , 和F矩阵被估计出来
CV_CALIB_USE_INTRINSIC_GUESS根据指定的FLAG优化一些或全部的内在参数。初始值是由用户提供。
CV_CALIB_FIX_PRINCIPAL_POINT在优化过程中确定主点。
CV_CALIB_FIX_FOCAL_LENGTH确定和 .
CV_CALIB_FIX_ASPECT_RATIO优化 . 确定的比值.
CV_CALIB_SAME_FOCAL_LENGTH执行以及 .
CV_CALIB_ZERO_TANGENT_DIST设置每个相机切向畸变系数为零并且设为固定值。
CV_CALIB_FIX_K1,...,CV_CALIB_FIX_K6在优化中不改变相应的径向畸变系数. 如果设置CV_CALIB_USE_INTRINSIC_GUESS , 使用distCoeffs矩阵提供的系数。否则将其置零.
CV_CALIB_RATIONAL_MODEL能够输出系数k4,k5,k6。提供向后兼容性,这额外FLAG应该明确指定校正函数使用理性模型和返回8个系数。如果FLAG没有被设置,该函数计算并只返回5畸变系数。
注:
(k1,k2,p1,p2,k3)中的k3一般都为0,只有对特种相机(如鱼眼相机)才不为0
这里旋转矩阵(M*3)和平移矩阵(M*3)与相机和世界坐标系的旋转矩阵和平移矩阵不同,每个旋转向量可以通过调用cvRodrigues2()来转换为3*3的旋转矩阵.
(4)相机矫正,得到3*3的旋转矩阵,得到3*4的投影矩阵
stereoRectify(
leftCameraMatrix, //输入左相机内参矩阵
leftDistCoeffs, //输入左相机畸变矩阵
rightCameraMatrix, //输入右相机内参矩阵
rightDistCoeffs, //输入右相机畸变矩阵
cv::Size(310,240), //用于校正的图像大小
stereoR, //输入第一和第二相机坐标系之间的旋转矩阵
stereoT, //输入第一和第二相机坐标系之间的平移矩阵
correctLeftR, //输出第一个相机的3x3矫正变换(旋转矩阵)
correctRightR, //输出第二个相机的3x3矫正变换(旋转矩阵)
projectLeft, //在第一台相机的新的坐标系统(矫正过的)输出 3x4 的投影矩阵
projectRight, //在第二台相机的新的坐标系统(矫正过的)输出 3x4 的投影矩阵
Q,
CV_CALIB_ZERO_DISPARITY,
1
//Size newImageSize=Size(),
//Rect* validPixROI1=0,
//Rect* validPixROI2=0
);
Q: 输出深度视差映射矩阵,矩阵Q是一个任意提供的矩阵(比如, stereoRectify()所能得出的矩阵).
flags: 操作的 flag可以是零或者是CV_CALIB_ZERO_DISPARITY . 如果设置了CV_CALIB_ZERO_DISPARITY,函数的作用是使每个相机的主点在校正后的图像上有相同的像素坐标。如果未设置标志,功能还可以改变图像在水平或垂直方向(取决于极线的方向)来最大化有用的图像区域。
alpha: 自由缩放参数。如果是-1或没有,该函数执行默认缩放。否则,该参数应在0和1之间。alpha=0,校正后的图像进行缩放和偏移,只有有效像素是可见的(校正后没有黑色区域)。alpha= 1意味着校正图像的抽取和转移,所有相机原始图像素像保留在校正后的图像(源图像像素没有丢失)。显然,任何中间值产生这两种极端情况之间的中间结果。
o newImageSize– 校正后新的图像分辨率。相同的尺寸应传递给initUndistortRectifyMap()(见OpenCV样品目录stereo_calib.cpp样品)。当(0,0)传递(默认),它设置为原始图像大小。设置为较大的值能帮助你保存原始图像的细节,特别是当有一个大的径向畸变时。
o validPixROI1– 校正后的图像可选的输出矩形,里面所有像素都是有效的。如果alpha= 0,ROIs覆盖整个图像。否则,他们可能会比较小。
o validPixROI2– 校正后的图像可选的输出矩形,里面所有像素都是有效的。如果alpha= 0,ROIs覆盖整个图像。否则,他们可能会比较小。
(5)用于计算最后的映射
cv::initUndistortRectifyMap(//用于计算畸变映射
leftCameraMatrix, //输入3*3相机内参矩阵.computed camera matrix
leftDistCoeffs, //输入5*1相机畸变矩阵.computed distortion matrix
//cv::Mat(),
//cv::Mat(),
stereoR, //输入第一和第二相机坐标系之间的旋转矩阵
projectLeft,//输入的3*4投影矩阵camera matrix to generate undistorted
或者使用 correctLeftR, //输入第一个相机的3x3矫正变换(旋转矩阵)
cv::Size(310,240),
//leftImage.size(), //去畸变图像尺寸.size of undistorted
CV_32FC1, //输出映射图像的类型.type of output map
leftMap1, leftMap2 //输出x坐标和y坐标映射函数.the x and y mapping functions
);
(6)将矫正后的像素重新投影到图片上
cv::remap(leftImage, undistorted, leftMap1, leftMap2,
cv::INTER_LINEAR); //插值类型interpolation type
cv::remap(rightImage, undistorted, rightMap1, rightMap2,
cv::INTER_LINEAR); //插值类型interpolation type
(7)最后重投影后的图片是含有黑边的,要进行裁剪
<-------------------------------------------------------------------- end----- 摄像机标定
start ---- matlab参数(若使用matlab传统标定工具箱是不能在matlab2017以上版本运行的)导入opencv ------------------------------------------------------------->
这里使用的导入方式是通过直接赋值的方式来做的,并没有使用.xml方式导入,.xml方式导入可以参照<学习opencv>书中所述,因为这种导入方式是针对opencv1使用的,不知道是否在opencv2上也适用,懒了下,就使用直接赋值的方式做了,具体如下:
leftCameraMatrix.at<double>(0,0) = 0; leftCameraMatrix.at<double>(0,1) = 991.67704 ; leftCameraMatrix.at<double>(0,2) = 259.63864;
leftCameraMatrix.at<double>(1,0) = 0; leftCameraMatrix.at<double>(1,1) = 991.67704 ; leftCameraMatrix.at<double>(1,2) = 259.63864;
leftCameraMatrix.at<double>(2,0) = 0; leftCameraMatrix.at<double>(2,1) = 0 ; leftCameraMatrix.at<double>(2,2) = 1;
leftDistCoeffs.at<double>(0,0) = -0.38645; leftDistCoeffs.at<double>(0,1) = 0.61461; leftDistCoeffs.at<double>(0,2) = -0.00030;
leftDistCoeffs.at<double>(0,3) = 0.00508;leftDistCoeffs.at<double>(0,4) = 0.00000;
1.先利用Matlab标定得到如下所示的结果:
Intrinsic parameters of left camera:
Focal Length: fc_left = [ 209.35975 209.91146 ] ?[ 1.29389 1.28810 ]
Principal point: cc_left = [ 112.70811 83.34529 ] ?[ 2.94940 2.31088 ]
Skew: alpha_c_left = [ 0.00000 ] ?[ 0.00000 ] => angle of pixel axes = 90.00000 ?0.00000 degrees
Distortion: kc_left = [ 0.08414 -2.06248 -0.00441
0.00295 0.00000 ] ?[ 0.08103 0.71627 0.00294 0.00358 0.00000 ]
Intrinsic parameters of right camera:
Focal Length: fc_right = [ 1208.84080 1208.58242 ] ?[ 3.04212 3.10299 ]
Principal point: cc_right = [ 593.84687 340.22721 ] ?[ 5.56734 4.44145 ]
Skew: alpha_c_right = [ 0.00000 ] ?[ 0.00000 ] => angle of pixel axes = 90.00000 ?0.00000 degrees
Distortion: kc_right = [ -0.17114 0.00931 -0.00018
0.00056 0.00000 ] ?[ 0.01366 0.10070 0.00083 0.00097 0.00000 ]
Extrinsic parameters (position of right camera wrt left camera):
Rotation vector: om = [ -0.02893 -0.02493 0.00643 ] ?[ 0.01112 0.01464 0.00069 ]
Translation vector: T = [ -39.21639 -0.63083 15.74866 ] ?[ 0.47172 0.45645 2.87469 ]
A.内参对应关系
opencv内参矩阵如下
Fx 0 Cx
0 Fy Cy
0 0 1
而Matlab的存储格式为
Focal Length: fc_left = [ 209.35975 209.91146 ] = [fx fy]
Principal point: cc_left = [ 112.70811 83.34529 ] = [cx cy]
B.畸变矩阵对应关系
opencv对应畸变矩阵为 [k1 k2 p1 p2 k3],
而Matlab的存储格式为(一般对于非球面相机,k3为0)
Distortion: kc_left = [ 0.08414 -2.06248 -0.00441 0.00295 0.00000 ] = [k1 k2 p1 p2 k3]
C.两相机旋转矩阵对应关系
matlab标定的结果为
Rotation vector: om = [ -0.02893 -0.02493 0.00643 ] ?[ 0.01112 0.01464 0.00069 ]
处理三维问题是,通常采用的是旋转矩阵的方式来描述.一个向量乘以旋转矩阵等价于向量以某种方式进行旋转.除了采用旋转矩阵来描述外,还可以用旋转向量来描述旋转,旋转向量的长度(模)表示绕轴逆时针旋转的角度(弧度).旋转向量与旋转矩阵可以通过罗德里格斯(Rodrigues)变换进行转换.matlab中使用的是旋转向量,进行移植时要进行转换变为旋转矩阵.变换程序如下
////start ------------使用cvRodrigues2()将 3*1矩阵变为3*3矩阵 --------------------
int i;
double r_vec[3]={-2.100418,-2.167796,0.273330};
double R_matrix[9];
CvMat pr_vec;
CvMat pR_matrix;
cvInitMatHeader(&pr_vec,1,3,CV_64FC1,r_vec,CV_AUTOSTEP);
cvInitMatHeader(&pR_matrix,3,3,CV_64FC1,R_matrix,CV_AUTOSTEP);
cvRodrigues2(&pr_vec, &pR_matrix,0);
for(i=0; i<9; i++)
{
printf("%f\n",R_matrix[i]);
}
////end ------------使用cvRodrigues2()将 3*1矩阵变为3*3矩阵 (matlab使用 rodrigues(),从向量变到矩阵用rotationVectorToMatrix())--------------------
d.两相机平移矩阵对应关系
matlab参数如下,matlab下是以右相机为基准变到左相机的坐标系.直接使用就可以
Translation vector: T = [ -39.21639 -0.63083 15.74866 ] ?[ 0.47172 0.45645 2.87469 ]
<----------------------------------------------------------------------- end --- matlab参数导入opencv
start --- 新版matlab(2014以后)标定方式----------------------------------------->
单目可以直接使用camera calibrator模块进行标定(需要选择app模块),注意无论使用哪种标定,对于export cameraParams参数操作都是导出到matlab运行环境中,如果再想保存在电脑中,需要通过matlab环境右键保存
双目标定的目的是获取相机的内参和外参,外参包括旋转矩阵和平移向量;
在进行双目标定时,有两种常见的数据库:matlab视觉处理库和opencv开源视觉库;
通常matlab中标定的结果比较稳定,可以在matlab中标定之后再使用opencv标定;
在matlab标定中,大家比较熟悉的是使用标定工具箱,一幅幅图像进行手动标定;
这很耗时间;
本经验主要介绍一下,使用Matlab中的函数进行一键自动标定双目相机参数。
-
打开matlab,输入命令:“stereoCameraCalibrator”,
弹出标定窗口,
将窗口上方Skew、Tangential Distortion以及3 Coefficients选项选中,
将2 Coefficients选项去掉,
-
点击“Add Images”按钮,
弹出添加对话框,输入左标定图像所在文件夹和右标定图像所在文件夹;
并且输入棋盘格上每个格子所占的真实尺寸大小;
-
选择好左右相机所在文件之后,
点击确定按钮,系统会自动检测效果好的标定图像,并剔除效果不好的图像;
-
点击“Calibrate”按钮,进行标定;
窗口界面包含:
标定的效果;或双目矫正结果;
重投影误差柱状图:对于重投影误差较大的图像可以手动删除掉,然后重新标定;
相机外参结构图;
-
点击“Exprot Camera Parameters”按钮,
保存标定的参数数据;
-
标定结果分析:
对于双目标定数据,
通常只需要得到:
相机内参:Intrinsic Matrix;
畸变:径向畸变+切向畸变;[k1,k2,k3,p1,p2]
重投影平均误差;
相机外参:旋转矩阵3*3 +平移向量3*1
-
此工具箱优点就是可以自动进行标定;但标定结果对于图像的要求比较高,如果图像效果不好,标定所得到的结果用于立体校正的话会出现很大的偏差。 鲁棒性不如手动标定的好;
<---------------------------------------- end matlab新版双目标定
-- matlab双目矫正-------------------------------------->
~ rectifystereimage()函数使用
令附普通相机和红外相机关系的处理:
http://blog.csdn.net/aichipmunk/article/details/9264703
注:
~ 内参解释
其中fx,fy为焦距,u0和v0为主点坐标,表示图像的中心像素坐标和图像原点像素坐标之间相差的横向和纵向像素数
请发表评论