相机标定就是设置各种参数(即投影公式中的项目)的过程。
相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵),内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。
相机标定的输入:标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。
相机标定的输出:摄像机的内参、外参系数。
过程:
- 准备标定图片
- 对每一张标定图片,提取角点信息(先说明角点数量,再利用函数findchessboardcorners)
FindChessboardCorners是opencv的一个函数,可以用来寻找棋盘图的内角点位置。
int cvFindChessboardCorners( const void* image, CvSize pattern_size, CvPoint2D32f* corners, int* corner_count=NULL, int flags=CV_CALIB_CB_ADAPTIVE_THRESH );
Image:
输入的棋盘图,必须是8位的灰度或者彩色图像。
pattern_size:
棋盘图中每行和每列角点的个数。
Corners:
检测到的角点
corner_count:
输出,角点的个数。如果不是NULL,函数将检测到的角点的个数存储于此变量。
Flags:
各种操作标志,可以是0或者下面值的组合:
CV_CALIB_CB_ADAPTIVE_THRESH -使用自适应阈值(通过平均图像亮度计算得到)将图像转换为黑白图,而不是一个固定的阈值。
CV_CALIB_CB_NORMALIZE_IMAGE -在利用固定阈值或者自适应的阈值进行二值化之前,先使用cvNormalizeHist来均衡化图像亮度。
CV_CALIB_CB_FILTER_QUADS -使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的错误方块。
注意:
- pattern_size参数传递内点数,8*8的棋盘只有7*7内点。
- 对每一张标定图片,进一步提取亚像素角点信息
cv::goodFeaturesToTrack()提取到的角点只能达到像素级别,在很多情况下并不能满足实际的需求,这时,我们则需要使用cv::cornerSubPix()对检测到的角点作进一步的优化计算,可使角点的精度达到亚像素级别。
具体调用形式如下:
void cv::cornerSubPix( cv::InputArray image, // 输入图像 cv::InputOutputArray corners, // 角点(既作为输入也作为输出) cv::Size winSize, // 区域大小为 NXN; N=(winSize*2+1) cv::Size zeroZone, // 类似于winSize,但是总具有较小的范围,Size(-1,-1)表示忽略 cv::TermCriteria criteria // 停止优化的标准 );
第一个参数是输入图像,和cv::goodFeaturesToTrack()中的输入图像是同一个图像。
第二个参数是检测到的角点,即是输入也是输出。
第三个参数是计算亚像素角点时考虑的区域的大小,大小为NXN; N=(winSize*2+1)。
第四个参数作用类似于winSize,但是总是具有较小的范围,通常忽略(即Size(-1, -1))。
第五个参数用于表示计算亚像素时停止迭代的标准,可选的值有cv::TermCriteria::MAX_ITER 、cv::TermCriteria::EPS(可以是两者其一,或两者均选),前者表示迭代次数达到了最大次数时停止,后者表示角点位置变化的最小值已经达到最小时停止迭代。二者均使用cv::TermCriteria()构造函数进行指定。
- 在棋盘标定图上绘制找到的内角点(非必须,仅为了显示)
cvDrawChessboardCorners( IntPtr image, Size patternSize, IntPtr corners, int count, int patternWasFound )
- 相机标定
double cv::calibrateCamera ( InputArrayOfArrays objectPoints,//世界坐标系中的点。在使用时,应该输入vector< vector< Point3f > >。 InputArrayOfArrays imagePoints,//其对应的图像点。和objectPoints一样,应该输入vector< vector< Point2f > >型的变量。 Size imageSize,//图像的大小,在计算相机的内参数和畸变矩阵需要用到; InputOutputArray cameraMatrix,//内参数矩阵。输入一个Mat cameraMatrix即可。 InputOutputArray distCoeffs,//畸变矩阵。输入一个Mat distCoeffs即可。 OutputArrayOfArrays rvecs,//旋转向量;应该输入一个Mat的vector,即vector< Mat > rvecs因为每个vector< Point3f >会得到一个rvecs。 OutputArrayOfArrays tvecs,//位移向量;和rvecs一样,也应该为vector tvecs。 int flags = 0, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) )
o:
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。
函数返回
重投影的总的均方根误差。
- 对标定结果进行评价
-
查看标定效果——利用标定结果对棋盘图进行矫正
void initUndistortRectifyMap(InputArray cameraMatrix,//输入相机矩阵 InputArray distCoeffs, //畸变矩阵 失真系数为4、5或8个元素的输入矢量。如果矢量为 NULL/空,则假设为零失真系数。 InputArray R,//对象空间(3x3矩阵)中的可选校正转换 InputArray newCameraMatrix,//新相机矩阵
。 Size size, //未畸变的图像大小 int m1type, 第一个输出地图的类型,可以CV_32FC1或CV_16SC2。 OutputArray map1, //对应x方向映射 OutputArray map2)//对应y方向映射
dst(x, y) = src( mapx(x, y), mapy(x, y) )
void remap( InputArray src, OutputArray dst, InputArray map1, InputArray map2, int interpolation, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar() )
InputArray类型的src,输入图像,填Mat类的对象即可,且需要为单通道8位或者浮点型的图像;
2)OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和原图片有一样的尺寸和类型。
3)InputArray类型的map1,它有两种可能的表示对象;
表示点(x, y)的第一个映射;
表示CV_16S, CV_32FC1或CV_32FC2类型的X值;
4)InputArray类型的map2,同样,它有两种可能的表示对象,而且它会根据map1来确定表示那种对象;
若map1表示点(x, y)时,这个参数不代表任何值;
表示CV_16UC1,CV_32FC1类型的Y值(第二个值);
5)int类型的interpolation,插值方式,之前的resize()函数中有讲到,需要注意,resize()函数中提到的INTER_AREA插值方式在这里是不支持的,所以可选的插值方式如下:
INTER_NEAREST:最近邻插值;
INTER_LINEAR:双线性插值(默认值);
INTER_CUBIC:双三次样条插值(逾4x4像素邻域内的双三次插值);
INTER_LANCZOS4:Lanczos插值(逾8x8像素邻域的Lanczos插值)
6)int类型的borderMode,边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点”的像素不会被此函数修改;
7)const Scalar&类型的 borderValue,当有常数边界时使用的值,默认值为0;
后续:相机姿态还原;用标定相机实现三维重建,计算立体图像深度