简单来说,相机标定的目的主要就是通过构建方程来求解相机内参数,相机的内参数包括Fx,Fy,Cx,Cy以及畸变参数。通过求解得到相机的内参数可以实现2D到3D转换的(当然,这只是一个例子)。具体的一些原理或者名词这里就不做太多描述,大家可以自己看。接下来主要是如何利用C++和Opencv实现。
代码如下(示例):
#include
#include
#include
#include
#include
#include
#include
int main()
{
// Input the calibration picture text
std::ifstream fin ("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/img.txt");
// Calibration result out file
std::ofstream fout("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/out.txt");
if(!fin)
{
std::cerr << "Related file not found !" << std::endl;
return EOF;
}
// Init the number of calibration images
int ImgNums = 0;
// Input file path
std::string FileName;
// Image size
cv::Size ImgSize;
// Calibration board size
cv::Size BoardSize = cv::Size(11,8);
// Storage the corner of board
std::vector<cv::Point2f> img_points_buf;
// Storage all corner of board
std::vector<std::vector<cv::Point2f>> img_points_seq;
while (getline(fin,FileName))
{
ImgNums++;
std::cout << "processing: " << FileName << std::endl;
// The currently processed image path
FileName = "/home/chen//QtProject-II/Camera-Calibrations/Checkerboard-Calibration/SrcImage/"+FileName;
std::cout<< " img_nums = "<< ImgNums << std::endl;
cv::Mat img = cv::imread(FileName);
cv::Mat ColorImg = img.clone();
const int kenerl=3;
medianBlur(img,img,kenerl);
// Output image size through the first image
if(ImgNums == 1)
{
ImgSize.height = img.rows;
ImgSize.width = img.cols;
std::cout << "img_size.height = " << ImgSize.height << std::endl;
std::cout << "img_size.width = " << ImgSize.width << std::endl;
}
// Determine whether a corner is detected
if(cv::findChessboardCorners(img,BoardSize,img_points_buf) == 0)
{
std::cout << "No." << ImgNums << "Could not detect Corner !" << " "<< std::endl;
cv::imshow("detect fail picture", img);
cv::waitKey(0);
}
else
{
cv::Mat ImgGray; // 创建一个存储灰度图像的矩阵
cv::cvtColor(img,ImgGray,CV_RGB2GRAY);
cv::cornerSubPix(ImgGray,img_points_buf,cv::Size(5,5),cv::Size(-1,-1),cv::TermCriteria(CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,30,0.1));
img_points_seq.push_back(img_points_buf);
drawChessboardCorners(ColorImg,BoardSize,img_points_buf,true);
namedWindow("F",cv::WINDOW_NORMAL);
imshow("F",ColorImg);
cv::waitKey(2000);
}
}
// Corner detection and extraction completed
std::cout << "Corner detection and extraction completed !" << std::endl;
// Start Camera Calibration
std::cout << " Start calibrate..."<< std::endl;
// Lattice size
cv::Size squre_size = cv::Size(15,15);
std::vector<std::vector<cv::Point3f>> object_points;
// Internal parameter matrix 3*3
cv::Mat cameraMatrix = cv::Mat(3,3,CV_32FC1,cv::Scalar::all(0));
// Distortion matrix 1*5
cv::Mat distCoeffs = cv::Mat(1,5,CV_32FC1,cv::Scalar::all(0));
// rotation matrix
std::vector<cv::Mat> rotationMat;
// translation matrix
std::vector<cv::Mat> translationMat;
int i,j;
for (int t=0;t < ImgNums;++t)
{
std::vector<cv::Point3f> tempPointSet;
for (i=0;i < BoardSize.height;++i)
{
for (j=0;j < BoardSize.width;j++)
{
cv::Point3f realPoint;
realPoint.x = i*squre_size.width;
realPoint.y = j*squre_size.height;
realPoint.z = 0;
tempPointSet.push_back(realPoint);
}
}
object_points.push_back(tempPointSet);
}
std::vector<int> point_counts;
for (i=0; i < ImgNums; ++i)
{
point_counts.push_back(BoardSize.width * BoardSize.height);
}
// Calibrate
calibrateCamera(object_points, img_points_seq, ImgSize, cameraMatrix, distCoeffs,rotationMat, translationMat, 0); // 拥有八个参数的标定函数
std::cout << " Calibrate done!" << std::endl << std::endl;
// Evaluate the calibration results
double total_err = 0.0;
double err = 0.0;
std::vector<cv::Point2f> img_pointsre;
std::cout<< "\t每幅图像的标定误差: " << std::endl;
fout << "每幅图像的标定误差: " << std::endl;
for (int i = 0;i < ImgNums; ++i)
{
std::vector<cv::Point3f> tempPointSet = object_points[i]; // 将角点的三维坐标存放到一个新的容器中
cv::projectPoints(tempPointSet,rotationMat[i],translationMat[i],cameraMatrix,distCoeffs,img_pointsre); // 通过之前标定得到的相机的内参和外参,对三维点进行重投影
std::vector<cv::Point2f> tempImagePoint = img_points_seq[i]; // 二维图像下亚像素的角点
cv::Mat tempImagePointMat = cv::Mat(1,tempImagePoint.size(),CV_32FC2); // 创建亚像素角点矩阵
cv::Mat img_pointsreMat = cv::Mat(1,img_pointsre.size(),CV_32FC2); // 创建重投影后的角点矩阵
for (int j = 0;j < tempImagePoint.size();j++)
{
tempImagePointMat.at<cv::Vec2f>(0,j) = cv::Vec2f(tempImagePoint[j].x,tempImagePoint[j].y);
img_pointsreMat.at<cv::Vec2f>(0,j) = cv::Vec2f(img_pointsre[j].x,tempImagePoint[j].y);
}
err = norm(img_pointsreMat,tempImagePointMat,cv::NORM_L2);
total_err += err/= point_counts[i];
std::cout << "No." << i+1 << " picture's Error: " << err << " pixel " << std::endl;
fout << "No." << i+1 << " picture's Error: " << err << " pixel " << std::endl;
}
std::cout<<"Overall mean error: "<< total_err/ImgNums << std::endl;
fout << "Overall mean error: "<< total_err/ImgNums << std::endl;
std::cout << "Assessment completed !" << std::endl;
// Write the calibration results to a .txt file
std::cout << "Start save the result....." << std::endl;
cv::Mat rotate_Mat = cv::Mat(3,3,CV_32FC1,cv::Scalar::all(0)); // 保存旋转矩阵
std::cout << "Camera matrix: " << std::endl << cameraMatrix << std::endl << std::endl;
fout << " Camera matrix: " << std::endl;
fout << cameraMatrix << std::endl << std::endl;
fout << "Distortion parameter: " << std::endl;
fout << distCoeffs << std::endl << std::endl << std::endl;
std::cout << "Distortion parameter: " << std::endl << distCoeffs << std::endl << std::endl << std::endl;
for (int i = 0;i<ImgNums;i++)
{
Rodrigues(rotationMat[i],rotate_Mat); // 将旋转向量通过罗德里格斯公式转换为旋转矩阵
fout << "No." << i+1<< "picture's rotation matrix is: " << std::endl;
fout << rotate_Mat << std::endl;
fout << "No."<<i+1<<"picture's translation vector " << std::endl;
fout << translationMat[i] << std::endl;
}
std::cout << "Save done!" << std::endl;
fout << std::endl;
return 0;
}
如需要直接使用代码,则文中几处需要进行更改:
1、std::ifstream fin ("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/img.txt"); std::ofstream fout("/home/chen/QtProject-II/Camera-Calibrations/Checkerboard-Calibration/out.txt");
这里需要注意修改为自己的路径,这里采用的是绝对路径
2、cv::Size BoardSize = cv::Size(11,8);
这里是需要根据自己实际使用的棋盘格来修改,注意自己棋盘格格子的实际大小也就是cv::Size squre_size = cv::Size(15,15);
要注意是否修改,我的棋盘格格子大小是15mm*15mm。
3、FileName = "/home/chen//QtProject-II/Camera-Calibrations/Checkerboard-Calibration/SrcImage/"+FileName;
此处也需要根据自己图片的实际位置来进行修改。
我的实际项目工程组成大概是这样子的:
SrcImage文件夹中是标定板图像,img.txt是图片名称,是需要自己创建的
最终的标定输出结果可通过终端或者输出out.txt中查看。
没啥大问题的话相机标定也就结束了,通过标定结果可以得到相机的内参数、畸变参数、以及标定的误差等。如果,文中有些地方表达不准确或者存在错误,欢迎指正。