在标定之前我要想一想为什么要标定,什么相机需要标定,标定的输入是啥,标定的输出是啥
标定的目的:为了求出相机的内参和外参,内参和外参就可以对之后相机拍出来的照片进行矫正,得到畸变很小的图片。
标定的输入:用相机拍出来一系列的棋盘格图片。
标定的输出:相机的内参和外参。
1,采集一系列棋盘格图;
2,对每一张图,提取其角点信息;
3,对每一张图,提取其亚像素角点信息;
4,对相机进行标定
5,查看标定结果,并对结果进行评价
6,利用标定结果对棋盘图进行矫正。
标定图片需要标定板在不同位置、不同角度、不同姿态下拍摄,最少需要三张,以10-20张为宜。
CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners,
int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );
image:传进来的棋盘格图
patternSize:一张图中角点的行列数,比方说棋盘格子大小是12*9,那么角点就有11*9个。vector
为了进一步的提高精度,需要在初步提取的角点位置上进一步提取亚像素信息,降低标定的误差
CV_EXPORTS_W void cornerSubPix( InputArray image, InputOutputArray corners,
Size winSize, Size zeroZone,
TermCriteria criteria );
image:标定的棋盘图。
corners:一系列的初始的角点坐标,同时也作为亚像素坐标的输出,一般需要float型,或者double型。
winSize:搜索的半径。
zeroZone:死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为(-1,-1)是表示没有死区。
criteria:定义求角点的终止条件,可以是迭代次数和角点精度的组合。
CV_EXPORTS bool find4QuadCornerSubpix( InputArray img, InputOutputArray corners, Size region_size );
image:标定的棋盘图。
corners:一系列的初始的角点坐标,同时也作为亚像素坐标的输出,一般需要float型,或者double型。
region_size:搜索窗口的半径。
画图展示一下效果:
CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,
InputArray corners, bool patternWasFound );
获得一系列的图的角点信息之后就可以对相机进行标定了,然后计算相机的内参和外参。
CV_EXPORTS_W double calibrateCamera( InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints, Size imageSize,
InputOutputArray cameraMatrix, InputOutputArray distCoeffs,
OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,
int flags = 0, TermCriteria criteria = TermCriteria(
TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON) );
objectPoints:世界坐标系中的三维点,棋盘格之间的真实坐标是知道的,我们认为他是在同一平面上的点 vector
imagePoints:每一张图角点对用的坐标点 ;vector
imageSize:图片的像素尺寸大小
cameraMatrix:相机的内参矩阵;Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));
distCoeffs:畸变矩阵(外参);Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))
rvecs:旋转向量;vector
tvecs:位移向量;vector
flag:标定所采取的方法;
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:迭代的终止条件;
CV_EXPORTS_W void projectPoints( InputArray objectPoints,
InputArray rvec, InputArray tvec,
InputArray cameraMatrix, InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian = noArray(),
double aspectRatio = 0 );
利用相机的内外参,对一幅图中的三维点重新投影计算,得到新的投影点
objectPoints:相机坐标系中的三维坐标
rvec, tvec:旋转矩阵和位移矩阵
cameraMatrix, distCoeffs:相机的内参矩阵和畸变矩阵
imagePoints:每个点对应图像中的坐标点
jacobian是雅可比行列式;
aspectRatio是跟相机传感器的感光单元有关的可选参数,如果设置为非0,则函数默认感光单元的dx/dy是固定的,会依此对雅可比矩阵进行调整;
方法一:使用initUndistortRectifyMap和remap两个函数配合实现。
initUndistortRectifyMap用来计算畸变映射,remap把求得的映射应用到图像上。
initUndistortRectifyMap的函数原型:
方法二:使用undistort函数实现
CV_EXPORTS_W void undistort( InputArray src, OutputArray dst,
InputArray cameraMatrix,
InputArray distCoeffs,
InputArray newCameraMatrix = noArray() );
src:棋盘格图
dst:矫正好的棋盘格图
cameraMatrix:相机内参
distCoeffs:相机外参
srcMat和dstMat:
void CameraCalibrationZhang()
{
int imageHeight, imageWidth;
vector leftStrings, rightStrings;
vector leftMats, rightMats;
glob("C:\\Users\\Ring\\Desktop\\0420\\left\\", leftStrings);
glob("C:\\Users\\Ring\\Desktop\\0420\\right\\", rightStrings);
for (int i = 0; i < leftStrings.size(); i++)
{
leftMats.push_back(imread(leftStrings[i], 0));
rightMats.push_back(imread(rightStrings[i], 0));
}
vector> allCorners; allCorners.clear();
int boardWidth = 11, boardHeight = 8;
float squaresize = 25; //棋盘格一格的实际大小是25mm
for (int i = 0; i < leftMats.size(); i++)
{
Mat src = leftMats[i];
//1,设置一些参数,比方说:棋盘格总共右多少个角点,棋盘格的真实大小啊,等等
//2,寻找角点
vector corners;
bool isFind = findChessboardCorners(src, Size(boardWidth, boardHeight), corners);
//3,寻找亚像素角点信息
cornerSubPix(src, corners, Size(5, 5), Size(-1, -1), TermCriteria(1 + 2, 20, 0.1));
//find4QuadCornerSubpix(src, corners, Size(5, 5)); //第二种方法
allCorners.push_back(corners);
/*Mat rgbMat;
cvtColor(src, rgbMat, CV_GRAY2RGB);
Mat showMat = rgbMat.clone();
drawChessboardCorners(showMat, Size(boardWidth, boardHeight), corners, isFind);*/
cout << "";
}
//4,相机标定
vector> obj;
vector imgpoint;
for (int rowIndex = 0; rowIndex < boardHeight; rowIndex++)
{
for (int colIndex = 0; colIndex < boardWidth; colIndex++)
{
imgpoint.push_back(Point3f(rowIndex * squaresize, colIndex * squaresize, 0));
}
}
for (int imgIndex = 0; imgIndex < leftMats.size(); imgIndex++)
{
obj.push_back(imgpoint);
}
Mat cameraMatrix = Mat(3, 3, CV_32FC1, Scalar::all(0));
Mat distCoeffs = Mat(1, 5, CV_32FC1, Scalar::all(0));
vector rvecs, tvecs;
calibrateCamera(obj, allCorners, leftMats[0].size(), cameraMatrix, distCoeffs, rvecs, tvecs);
//5,对标定结果进行评价
vector image_points2; /* 保存重新计算得到的投影点 */
double total_err = 0.0; /* 所有图像的平均误差的总和 */
double err = 0.0; /* 每幅图像的平均误差 */
for (int i = 0; i tempPointSet = obj[i];
/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
projectPoints(tempPointSet, rvecs[i], tvecs[i], cameraMatrix, distCoeffs, image_points2);
/* 计算新的投影点和旧的投影点之间的误差*/
vector tempImagePoint = allCorners[i];
Mat tempImagePointMat = Mat(1, tempImagePoint.size(), CV_32FC2);
Mat image_points2Mat = Mat(1, image_points2.size(), CV_32FC2);
for (int j = 0; j < tempImagePoint.size(); j++)
{
image_points2Mat.at(0, j) = Vec2f(image_points2[j].x, image_points2[j].y);
tempImagePointMat.at(0, j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
}
double err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
total_err += err /= 88;
std::cout << "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
cout<< "第" << i + 1 << "幅图像的平均误差:" << err << "像素" << endl;
}
//6,查看标定的结果,并对棋盘图进行矫正
Mat srcMat = leftMats[0], dstMat;
undistort(srcMat, dstMat, cameraMatrix, distCoeffs);
cout << "";
}