代码见
https://learnopencv.com/camera-calibration-using-opencv
相关知识点请参考《视觉SLAM十四讲从理论到实践》,附链接如下
https://pan.baidu.com/s/154vSodYtNG_mGOxvFJDiQQ
提取码: qkw8
文末的代码在上面源码的基础上进行了部分修改,改正几个错误并添加了使用相机内参进行畸变校正的代码。我用的是双目相机所以代码中有裁剪图像的部分,此处自行修改。遇到什么问题可评论或私信我。
首先在一个文件夹中保存若干张带有棋盘格的图片,越多越好,然后将std::string path
设置为图片保存的路径。
将int CHECKERBOARD
修改为你的棋盘格的角点的数量,比如我的棋盘格有7行9列,角点就应该是6,8。
源码大致流程如下:
objpoints
和imgpoints
,他们的每个元素分别存放棋盘格的三维世界坐标和他们对应的二维像素坐标,比如有12张图片,角点以6,8为例,那尺寸就是12x48。findChessboardCorners
函数尝试定位图片中棋盘格的角点;calibrateCamera
函数,用所有图片计算出的角点坐标计算内外参矩阵和畸变参数;undistort
函数对原图进行畸变校正。代码中用到的函数主要在calib3d.hpp
文件夹中,opencv文档中对应Main modules->calib3d。
找到棋盘格中的角点。
参数:
image
输入 带棋盘格的图像,应为灰度图
patternSize
输入 棋盘格的角点的数量
corners
输出 每个角点的像素坐标
flags
输入 一些设置
亚像素级上精确定位坐标。没看懂,以后再理解。文档中该函数位置:Main modules->imgproc->Feature Detection
参数:
image
输入 图片
corners
输入 角点坐标
winSize
输入 搜索窗口边长的一半。比如如果设置窗口为(5,5),那么将使用 ( 5 ∗ 2 + 1 ) × ( 5 ∗ 2 + 1 ) = 11 × 11 (5*2+1)\times(5*2+1)=11\times 11 (5∗2+1)×(5∗2+1)=11×11的搜索窗口
zeroZone
设置死区范围以避免极点。一般设为(-1,-1)即可
criteria
将标出角点的数据写到一个Mat里,后面可以用imshow显示。
根据输入的图片和角点坐标计算内外参矩阵。
参数:
objectPoints
输入 所有角点的三维世界坐标
imagePoints
输入 所有角点的二维像素坐标
imageSize
输入 图像尺寸
cameraMatrix
输出 3x3的相机内参矩阵
distCoeffs
输出 相机畸变参数,5个参数
rvecs
输出 包含3x3的旋转矩阵的向量,向量长度等于棋盘格图片数量(假设所有准备好的图片中均有棋盘格)
tvecs
输出 包含3x1的平移矩阵的向量,向量长度等于棋盘格图片数量
return
返回重映射的均方根误差。
算法步骤如下:
(1) 计算初始内参;
(2) 计算初始相机姿态;
(3) 使用Levenberg-Marquardt全局优化算法最小化重映射误差。
畸变校正。该函数是initUndistortRectifyMap
和remap
两个函数的组合。推荐分别使用两个函数,只需要预先使用initUndistortRectifyMap
函数计算好map1
和map2
然后对每张图片使用remap
函数校正畸变。
参数:
src
输入 畸变图像
dst
输出 校正后图像
cameraMatrix
输入 相机内参矩阵
distCoeffs
输入 相机畸变参数
#include
int CHECKERBOARD[2]{6,8};
int main()
{
using namespace std;
using namespace cv;
vector<vector<Point3f>> objpoints;
vector<vector<Point2f>> imgpoints;
vector<Point3f> objp;
for(int i{0}; i<CHECKERBOARD[1]; i++)
for(int j{0}; j<CHECKERBOARD[0]; j++)
objp.push_back(Point3f(j,i,0));
vector<String> images;
string path = "/home/pi/Desktop/myfile/cpp/camera/build/*.png";
glob(path, images);
Mat origin, frame, gray;
vector<Point2f> corner_pts;
bool success;
for(int i{0}; i<images.size(); i++) {
origin = imread(images[i]);
frame = origin(Rect(0,0,640,480));
cvtColor(frame,gray,COLOR_BGR2GRAY);
success = findChessboardCorners(gray, Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FAST_CHECK | CALIB_CB_NORMALIZE_IMAGE);
if(success) {
TermCriteria criteria(TermCriteria::EPS | TermCriteria::MAX_ITER, 30, 0.001);
cornerSubPix(gray,corner_pts,Size(11,11), Size(-1,-1),criteria);
drawChessboardCorners(frame, Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, success);
objpoints.push_back(objp);
imgpoints.push_back(corner_pts);
}
imshow("Image",frame);
waitKey(0);
}
destroyAllWindows();
Mat cameraMatrix,distCoeffs,R,T;
Size imageSize = Size(gray.cols, gray.rows);
calibrateCamera(objpoints, imgpoints, imageSize, cameraMatrix, distCoeffs, R, T);
Mat newCameraMatrix = getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1);
cout << "NewCameraMatrix : " << newCameraMatrix << endl;
cout << "cameraMatrix : " << cameraMatrix << endl;
cout << "distCoeffs : " << distCoeffs << endl;
cout << "Rotation vector : " << R << endl;
cout << "Translation vector : " << T << endl;
cout << "finished." << endl;
Mat map1, map2;
initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0),
imageSize, CV_16SC2, map1, map2);
for (int i=images.size()-1; i>=0; --i){
origin = imread(images[i]);
frame = origin(Rect(0,0,640,480));
// undistort(frame, origin, cameraMatrix, distCoeffs);
remap(frame, origin, map1, map2, INTER_LINEAR);
imshow("Image", origin);
waitKey(0);
}
return 0;
}