Opencv实现张正友法相机标定
相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵),内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。
相机标定的输入:标定图像上所有内角点的图像坐标,标定板图像上所有内角点的空间三维坐标(一般情况下假定图像位于Z=0平面上)。
相机标定的输出:摄像机的内参、外参系数。
矫正原始图像的完整流程:
1. 准备标定图片
2. 对每一张标定图片,提取角点信息
3. 对每一张标定图片,进一步提取亚像素角点信息
4. 在棋盘标定图上绘制找到的内角点(非必须,仅为了显示)
5. 相机标定
6. 对标定结果进行评价
7. 查看标定效果——利用标定结果对棋盘图进行矫正
1. 准备标定图片
标定图片需要使用标定板在不同位置、不同角度、不同姿态下拍摄,最少需要3张,以10~20张为宜。标定板需要是黑白相间的矩形构成的棋盘图,制作精度要求较高
2.对每一张标定图片,提取角点信息
需要使用findChessboardCorners函数提取角点,这里的角点专指的是标定板上的内角点,这些角点与标定板的边缘不接触。
findChessboardCorners
CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize,
OutputArray corners,
int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE );
第一个参数Image,传入拍摄的棋盘图Mat图像,必须是8位的灰度或者彩色图像;
第二个参数patternSize,每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向;
第三个参数corners,用于存储检测到的内角点图像坐标位置,一般用元素是Point2f的向量来表示:vector<Point2f> image_points_buf;
第四个参数flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。
通过调用cvFindChessboardCorners实现
CvCBQuad用来存储棋盘上的方格信息,CvCBCorner用来存储棋盘方格的四个角点
标识符:CV_CALIB_CB_NORMALIZE_IMAGE 是否进行灰度直方图均衡化
CV_CALIB_CB_FAST_CHECK 检查是否有要找的棋盘格
CV_CALIB_CB_ADAPTIVE_THRESH 是否进行自适应阈值
CV_CALIB_CB_FILTER_QUADS 是否使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的错误方块。
1. 将图片从RGB转为GRAY,并进行灰度直方图均衡化cvEqualizeHist,并检查是否有要找的棋盘格cvCheckChessboard 要求:图像为8bit,3通道或单通道
对于下列的操作,如未找到角点,会重复进行6次,在每一次中,图像膨胀的核的大小会不断增大。
2. 根据标识符进行图像二值化,并进行图像膨胀,使用cvStartFindContours寻找轮廓,排除面积小于25的轮廓,并对每个轮廓多次进行多边形逼近cvApproxPoly,获取四边形。之后判断轮廓是否为凸边形cvCheckContourConvexity,最后通过面积、边长进一步筛选。
3. 寻找每个方格的相邻方格,并记相邻方格的个数,连同相邻方格的信息存在相应CvCBQuad结构体中。二值图像在膨胀后原本相邻的方格,分开了,原来相连部分有一个公共点,现在分开变成了两个点。找到相邻的方格之后,计算出原来的公共点,用公共点替代膨胀后分开的点。这主要由icvFindQuadNeighborhors函数完成。
4. 对所有“方格”(包括被误判的)分类,分类的原则是类内所有方格是相邻的。由icvFindConnectedQuads函数完成。
5. 根据已知所求的角点个数,判别每个类中方格是否为所求的棋盘方格,并对棋盘方格排序,即该方格位于哪行那列。在这个过程中,可以添加每类方格总缺少的方格,也可以删除每类方格中多余的方格。icvOrderFoundConnetedQuads函数完成该过程。
6. icvCleanFoundConnectedQuads函数、icvCheckQuadGroup函数根据已知棋盘的方格个数(由棋盘的角点数计算出来)确认方格位置及个数是否正确,并确定粗略强角点的位置(两个方格的相连位置)。icvCheckBoardMonotony再次检验棋盘方格是否提取正确。icvCheckBoardMonotony确定每一行中的点在以该行为直线的圆内,同理对每一列进行判断。
7. 以上如果有一步所有方格都不符合要求,则进入一个新的循环。若循环结束,还尚未找到符合要求的方格,则棋盘定位失败,退出函数。最后,调用cvFindCornerSubpix()根据上步的强角点位置,确定强角点的精确位置。
3. 对每一张标定图片,进一步提取亚像素角点信息
为了提高标定精度,需要在初步提取的角点信息上进一步提取亚像素信息,降低相机标定偏差,常用的方法是cornerSubPix,另一个方法是使用find4QuadCornerSubpix函数,这个方法是专门用来获取棋盘图上内角点的精确位置的。
find4QuadCornerSubpix
CV_EXPORTS bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);
第一个参数img,输入的Mat矩阵,最好是8位灰度图像,检测效率更高;
第二个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示:vector<Point2f/Point2d> iamgePointsBuf;
第三个参数region_size,角点搜索窗口的尺寸;
对每个角点进行如下操作
1. 根据输入的搜索窗口的尺寸,确定以该角点为中心的ROI区域,并计算该区域的直方图
2. 找出直方图中最大的一段segment_hist_max,据此进行图像二值化,之后进行腐蚀操作,共进行两次
3. 对两幅图使用findContours进行轮廓提取,对于轮廓根据到该角点的最小距离排序
4. 以每幅图最小的两个轮廓为研究对象,先进行多边形逼近approxPolyDP,并使用findCorner找到轮廓中到该角点最近的点
5. 对找到的四个点使用findLinesCrossPoint,获取精确的角点位置
4. 在棋盘标定图上绘制找到的内角点(非必须,仅为了显示)
drawChessboardCorners
CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,
InputArray corners, bool patternWasFound );
第一个参数image,8位灰度或者彩色图像;
第二个参数patternSize,每张标定棋盘上内角点的行列数;
第三个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示:vector<Point2f/Point2d> iamgePointsBuf;
第四个参数patternWasFound,标志位,用来指示定义的棋盘内角点是否被完整的探测到,true表示别完整的探测到,函数会用直线依次连接所有的内角点,作为一个整体,false表示有未被探测到的内角点,这时候函数会以(红色)圆圈标记处检测到的内角点;
patternWasFound=ture时,依次连接各个内角点
patternWasFound=false时,以(红色)圆圈标记处角点位置
通过调用drawChessboardCorners实现
5. 相机标定
获取到棋盘标定图的内角点图像坐标之后,就可以使用calibrateCamera函数进行标定,计算相机内参和外参系数
calibrateCamera
CV_EXPORTS_W 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,为世界坐标系中的三维点。在使用时,应该输入一个三维坐标点的向量的向量,即vector<vector<Point3f>> object_points。需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标。
第二个参数imagePoints,为每一个内角点对应的图像坐标点。和objectPoints一样,应该输入vector<vector<Point2f>> image_points_seq形式的变量;
第三个参数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,另外每张图像都会生成属于自己的平移向量和旋转向量。
通过调用cvCalibrateCamera2实现
1. 使用cvConvertPointsHomogeneous将图像坐标点与世界坐标点转换为齐次坐标
2. 对于每一张图片,使用cvFindHomography计算单应矩阵
3. 根据约束条件,计算出相机内参
4. 使用cvFindExtrinsicCameraParams2计算相机外参
5. 通过最小二乘法估算,实际存在径向畸变下的畸变系数
6. 极大似然法,优化估计,提升估计精度
6. 对标定结果进行评价
对标定结果进行评价的方法是通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到空间三维点在图像上新的投影点的坐标,计算投影坐标和亚像素角点坐标之间的偏差,偏差越小,标定结果越好。
对空间三维坐标点进行反向投影的函数是projectPoints
projectPoints
void cv::projectPoints( InputArray _opoints,
InputArray _rvec,
InputArray _tvec,
InputArray _cameraMatrix,
InputArray _distCoeffs,
OutputArray _ipoints,
OutputArray _jacobian,
double aspectRatio )
_points
物体点的坐标,为3xN或者Nx3的矩阵,这儿N是视图中的所有所有点的数目。
_rvec
旋转向量,1x3或者3x1。
_tvec
平移向量,1x3或者3x1。
_cameraMatrix
摄像机内参数矩阵A:
_distCoeffs
形变参数向量,4x1或者1x4,为[k1,k2,p1,p2]。如果是NULL,所有形变系数都设为0。
_ipoints
输出数组,存储图像点坐标。大小为2xN或者Nx2,这儿N是视图中的所有点的数目。
_jacobian
可选参数,Nx10矩阵,从上到下分为4个部分。
dpdrot
关于旋转向量部分的图像上点的导数,Nx3矩阵。
dpdt
关于平移向量部分的图像上点的导数,Nx3矩阵。
dpdf
关于fx和fy的图像上点的导数,Nx2矩阵。
dpdc
关于cx和cy的图像上点的导数,Nx2矩阵。
aspectRatio
可选参数,关于形变系数的图像上点的导数,Nx4矩阵。
通过调用cvProjectPoints2实现
Norm范数
定义1. 设 ,满足
1. 正定性:║x║≥0,║x║=0 iff x=0
2. 齐次性:║cx║=│c│║x║,
3. 三角不等式:║x+y║≤║x║+║y║
则称Cn中定义了向量范数,║x║为向量x的范数.
可见向量范数是向量的一种具有特殊性质的实值函数.
常用向量范数有,令x=( x1,x2,…,xn)T
1-范数:║x║1=│x1│+│x2│+…+│xn│ //向量中的绝对值求和,
2-范数:║x║2=(│x1│2+│x2│2+…+│xn│2)^1/2 //就是欧几里得距离,其跟勾股定理不太一样
∞-范数:║x║∞=max(│x1│,│x2│,…,│xn│) //向量中的maxm范数求解函数
1. #define CV_C 1 //[1]先对数组中的所有元素取绝对值,然后返回这些绝对值之间最大的那个数,作为cvNorm()函数的返回值
2. #define CV_L1 2 //[2]先对数组中的所有元素取绝对值,然后对这些数进行绝对值累加----绝对值的累加和
3. #define CV_L2 4 //[3]数组中所有元素平方的累加和
计算新的投影点和旧的投影点之间的误差
7. 查看标定效果——利用标定结果对棋盘图进行矫正
利用求得的相机的内参和外参数据,可以对图像进行畸变的矫正,这里有两种方法可以达到矫正的目的,分别说明一下。
方法一:使用initUndistortRectifyMap和remap两个函数配合实现。
initUndistortRectifyMap用来计算畸变映射,remap把求得的映射应用到图像上。
方法二:使用undistort函数实现
正向矫正的流程为:畸变像素坐标→畸变物理坐标→标准物理坐标→标准像素坐标
逆向矫正的流程为:标准像素坐标→标准物理坐标→畸变物理坐标→畸变像素坐标
UndistortPoints就是执行的正向矫正过程,而initUndistortRectifyMap执行的是逆向矫正过程。
initUndistortRectifyMap
cv::initUndistortRectifyMap( InputArray _cameraMatrix, InputArray _distCoeffs,
InputArray _matR, InputArray _newCameraMatrix,
Size size, int m1type, OutputArray _map1, OutputArray _map2 )