代码见 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。 源码大致思路如下:
- 首先定义2个二维数组
objpoints 和imgpoints ,他们的每个元素分别存放棋盘格的三维世界坐标和他们对应的二维像素坐标,比如有12张图片,角点以6,8为例,那尺寸就是12x48。 - 循环读取一张图片并尝试定位图片中棋盘格的角点;
- 用所有图片计算出的角点坐标计算内外参矩阵和畸变参数;
- 我在后面加上根据内参矩阵对原图进行畸变校正。
用到的几个函数分析
代码中用到的函数主要在calib3d.hpp 文件夹中,代码中也都有详细的注释,我再简要翻译一下。
findChessboardCorners
找到棋盘格中的角点。 参数: image 输入 带棋盘格的图像,应为灰度图 patternSize 输入 棋盘格的角点的数量 corners 输出 每个角点的像素坐标 flags 输入 一些设置
cornerSubPix
亚像素级上精确定位坐标。没看懂,原文和部分翻译如下: The function iterates to find the sub-pixel accurate location of corners or radial saddle points as described in @cite forstner1987fast, and as shown on the figure below. (该函数迭代以查找角点或径向鞍点的亚像素精确位置。) Sub-pixel accurate corner locator is based on the observation that every vector from the center
q
q
q to a point
p
p
p located within a neighborhood of
q
q
q is orthogonal to the image gradient at
p
p
p subject to image and measurement noise. Consider the expression: (亚像素精确的角点定位器基于以下观测,从中心q到位于q邻域内的点p的每个向量与p处受图像和测量噪声影响的图像梯度正交。考虑表达式:)
?
i
=
D
I
p
i
T
?
(
q
?
p
i
)
\epsilon _i = {DI_{p_i}}^T \cdot (q - p_i)
?i?=DIpi??T?(q?pi?) where
D
I
p
i
{DI_{p_i}}
DIpi?? is an image gradient at one of the points
p
i
p_i
pi? in a neighborhood of
q
q
q . The value of
q
q
q is to be found so that
?
i
\epsilon_i
?i? is minimized. A system of equations may be set up with
?
i
\epsilon_i
?i? set to zero:
∑
i
(
D
I
p
i
?
D
I
p
i
T
)
?
q
?
∑
i
(
D
I
p
i
?
D
I
p
i
T
?
p
i
)
\sum _i(DI_{p_i} \cdot {DI_{p_i}}^T) \cdot q - \sum _i(DI_{p_i} \cdot {DI_{p_i}}^T \cdot p_i)
i∑?(DIpi???DIpi??T)?q?i∑?(DIpi???DIpi??T?pi?) where the gradients are summed within a neighborhood (“search window”) of
q
q
q . Calling the first gradient term
G
G
G and the second gradient term
b
b
b gives:
q
=
G
?
1
?
b
q = G^{-1} \cdot b
q=G?1?b The algorithm sets the center of the neighborhood window at this new center
q
q
q and then iterates until the center stays within a set threshold. 参数: 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
drawChessboardCorners
将标出角点的数据写到一个Mat里,后面可以用imshow显示。
calibrateCamera
根据输入的图片和角点坐标计算内外参矩阵。原文中的详细说明: The function estimates the intrinsic camera parameters and extrinsic parameters for each of the views. The algorithm is based on @cite Zhang2000 and @cite BouguetMCT . The coordinates of 3D object points and their corresponding 2D projections in each view must be specified. That may be achieved by using an object with known geometry and easily detectable feature points. Such an object is called a calibration rig or calibration pattern, and OpenCV has built-in support for a chessboard as a calibration rig (see @ref findChessboardCorners). Currently, initialization of intrinsic parameters (when @ref CALIB_USE_INTRINSIC_GUESS is not set) is only implemented for planar calibration patterns (where Z-coordinates of the object points must be all zeros). 3D calibration rigs can also be used as long as initial cameraMatrix is provided. 参数: objectPoints 输入 所有角点的三维世界坐标 imagePoints 输入 所有角点的二维像素坐标 imageSize 输入 图像尺寸 cameraMatrix 输出 3x3的相机内参矩阵 distCoeffs 输出 相机畸变参数,5个参数 rvecs 输出 包含3x3的旋转矩阵的向量,向量长度等于棋盘格图片数量(假设所有准备好的图片中均有棋盘格) tvecs 输出 包含3x1的平移矩阵的向量,向量长度等于棋盘格图片数量 return 返回重映射的均方根误差。 算法步骤如下: (1) 计算初始内参; (2) 计算初始相机姿态; (3) 使用Levenberg-Marquardt全局优化算法最小化重映射误差。
undistort
畸变校正。 参数: src 输入 畸变图像 dst 输出 校正后图像 cameraMatrix 输入 相机内参矩阵 distCoeffs 输入 相机畸变参数
源码
#include <opencv2/opencv.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <iostream>
int CHECKERBOARD[2]{6,8};
int main()
{
std::vector<std::vector<cv::Point3f>> objpoints;
std::vector<std::vector<cv::Point2f>> imgpoints;
std::vector<cv::Point3f> objp;
for(int i{0}; i<CHECKERBOARD[1]; i++)
for(int j{0}; j<CHECKERBOARD[0]; j++)
objp.push_back(cv::Point3f(j,i,0));
std::vector<cv::String> images;
std::string path = "/home/pi/Desktop/myfile/cpp/camera/build/*.png";
cv::glob(path, images);
cv::Mat origin, frame, gray;
std::vector<cv::Point2f> corner_pts;
bool success;
for(int i{0}; i<images.size(); i++) {
origin = cv::imread(images[i]);
frame = origin(cv::Rect(640,0,640,480));
cv::cvtColor(frame,gray,cv::COLOR_BGR2GRAY);
success = cv::findChessboardCorners(gray, cv::Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE);
if(success) {
cv::TermCriteria criteria(cv::TermCriteria::EPS | cv::TermCriteria::MAX_ITER, 30, 0.001);
cv::cornerSubPix(gray,corner_pts,cv::Size(11,11), cv::Size(-1,-1),criteria);
cv::drawChessboardCorners(frame, cv::Size(CHECKERBOARD[0], CHECKERBOARD[1]), corner_pts, success);
objpoints.push_back(objp);
imgpoints.push_back(corner_pts);
}
cv::imshow("Image",frame);
cv::waitKey(0);
}
cv::destroyAllWindows();
cv::Mat cameraMatrix,distCoeffs,R,T;
cv::calibrateCamera(objpoints, imgpoints, cv::Size(gray.rows,gray.cols), cameraMatrix, distCoeffs, R, T);
std::cout << "cameraMatrix : " << cameraMatrix << std::endl;
std::cout << "distCoeffs : " << distCoeffs << std::endl;
std::cout << "Rotation vector : " << R << std::endl;
std::cout << "Translation vector : " << T << std::endl;
std::cout << "finished." << std::endl;
for (int i=images.size()-1; i>=0; --i){
origin = cv::imread(images[i]);
frame = origin(cv::Rect(0,0,640,480));
cv::undistort(frame, origin, cameraMatrix, distCoeffs);
cv::imshow("Image", origin);
cv::waitKey(0);
}
return 0;
}
calib.hpp中常用函数目录
opencv4.5.2版本,部分功能类似的函数被忽略。 findHomography 728 RQDecomp3x3 760 decomposeProjectionMatrix 787 matMulDeriv 807 composeRT 838 projectPoints 879 solvePnP 1078 initCameraMatrix2D 1440 findChessboardCorners 1493 drawFrameAxes 1627 findCirclesGrid 1694 calibrateCamera 1821 stereoCalibrate 2065 stereoRectify 2187 getOptimalNewCameraMatrix 2268 calibrateHandEye 2419 calibrateRobotWorldHandEye 2562 convertPointsToHomogeneous 2576 findFundamentalMat 2652 findEssentialMat 2703 decomposeEssentialMat 2817 recoverPose 2869 computeCorrespondEpilines 2959 triangulatePoints 2986 correctMatches 3005 filterSpeckles 3020 getValidDisparityROI 3025 validateDisparity 3030 reprojectImageTo3D 3073 sampsonDistance 3095 estimateAffine3D 3145 estimateTranslation3D 3192 estimateAffine2D 3258 estimateAffinePartial2D 3310 decomposeHomographyMat 3343 filterHomographyDecompByVisibleRefpoints 3367 StereoMatcher 3376 undistort 3589 initUndistortRectifyMap 3657 getDefaultNewCameraMatrix 3700 undistortPoints 3744 fisheye 3760
|