一、相机模型与投影1、单目相机模型1.1.1 单目相机成像原理现实中的相机本质上可以看成一个小孔成像模型Pinhole Camera Model即光线通过一个针孔投影到成像平面上1.1.2 小孔成像模型设空间三维点P(X,Y,Z)成像点p(x,y)焦距f根据相似三角形关系可以发现距离相机越远Z越大成像越小1.1.3 相机坐标系1世界坐标系用于描述真实世界中的物体位置2相机坐标系以相机光心为原点Z轴朝前X轴向右Y轴向下3图像坐标系单位通常为mm或物理长度成像平面中心一般为原点。4像素坐标系OpenCV 最终使用的是像素坐标(u,v)左上角为原点u向右v向下1.1.4 相机内参相机从“物理成像平面”转换到“像素坐标”时需要内参矩阵。相机内参矩阵水平方向焦距单位像素垂直方向焦距单位像素主点坐标光轴中心即相机光轴与图像平面的交点通常接近图像中心内参矩阵作用将“归一化成像平面坐标”转换为“像素坐标”。1.1.5 单目相机完整投影模型世界坐标 → 相机坐标相机坐标 → 成像平面成像平面 → 像素坐标2、相机畸变相机畸变是指真实镜头成像时图像发生几何变形的现象。畸变数学表示3、模型投影函数/* 用途用于将三维空间中的点根据相机参数投影到二维图像平面中。 */ void cv::projectPoints( InputArray objectPoints, InputArray rvec, InputArray tvec, InputArray cameraMatrix, InputArray distCoeffs, OutputArray imagePoints, OutputArray jacobian noArray(), double aspectRatio 0 ); /* objectPoints世界坐标系中3D点的三位坐标可以是Point3f或Point3d类型 rvec旋转向量表示世界坐标系到相机坐标系的旋转关系 tvec平移向量表示世界坐标系到相机坐标系的平移关系 cameraMatrix相机内参矩阵即相机标定得到的K矩阵 distCoeffs相机畸变系数用于描述镜头畸变对应畸变参数[k1, k2, p1, p2, k3] imagePoints输出投影后的二维图像坐标点 jacobian输出雅可比矩阵一般用于优化计算默认情况下不使用 aspectRatio固定纵横比参数默认值为0表示不固定比例 */4、示例代码/*输入计算得到的内参矩阵和畸变矩阵*/ Mat cameraMatrix (Mat_float(3, 3) 532.016297, 0, 332.172519, 0, 531.565159, 233.388075, 0, 0, 1); Mat distCoeffs (Mat_float(1, 5) -0.285188, 0.080097, 0.001274, -0.002415, 0.106579); /*第一张图像相机坐标系与世界坐标系之间的关系*/ Mat rvec (Mat_float(1, 3) -1.977853, -2.002220, 0.130029); Mat tvec (Mat_float(1, 3) -26.88155, -42.79936, 159.19703); /*生成第一张图像中内角点的三维世界坐标*/ Size boardSize Size(9, 6); Size squareSize Size(10, 10);/*棋盘格每个方格的真实尺寸*/ vectorPoint3f PointSets; for (int j 0; j boardSize.height; j) { for (int k 0; k boardSize.width; k) { Point3f realPoint; /*假设标定版为世界坐标系的z平面即z0*/ realPoint.x j * squareSize.width; realPoint.y k * squareSize.height; realPoint.z 0; PointSets.push_back(realPoint); } } /*根据三维坐标和相机与世界坐标系时间的关系估计内角点像素坐标*/ vectorPoint2f imagePoints; projectPoints(PointSets, rvec, tvec, cameraMatrix, distCoeffs, imagePoints); for (int i 0; i imagePoints.size(); i) { cout to_string(i) : imagePoints[i] endl; } waitKey(0);二、单目相机标定1、单目标定原理简介2.1.1 标定的本质问题单目相机标定的核心目标是已知三维空间点与其二维图像点的对应关系求解相机的内参与外参以及畸变参数。本质上是一个参数反求问题。2.1.2 标定输入与输出1输入数据已知量单目标定依赖于多组 3D–2D 对应点三维点世界坐标系标定板上角点的真实物理坐标通常设定在 Z 0 平面棋盘格平面二维点图像坐标系图像中检测到的角点像素坐标2输出参数未知量标定求解以下相机参数内参矩阵 K畸变参数通常包括径向畸变k1,k2,k3切向畸变p1,p2外参每张图像一组旋转向量rvec平移向量tvec2.1.3 标定的数学本质单目标定可以抽象为一个优化问题第 i 张图像第 j 个角点的实际像素坐标通过相机模型投影得到的像素坐标K内参矩阵D畸变参数第 i 张图像外参2、标定版角点提取标定版角点提取/* 返回值bool类型true表示成功检测到完整棋盘角点false表示检测失败 用途用于检测棋盘格标定板中的内角点位置是相机标定流程中的关键步骤 */ bool cv::findChessboardCorners( InputArray image, Size patternSize, OutputArray corners, int flags CALIB_CB_ADAPTIVE_THRESH CALIB_CB_NORMALIZE_IMAGE ); /* image含有棋盘标定板的图像图像必须是CV_8U的灰度图像或者彩色图像 patternSize棋盘格内角点数量Size(cols, rows)指“每行/每列内角点数”不是方格数 corners输出检测到的角点坐标按棋盘扫描顺序排列的2D点集 flags检测算法标志位用于提升检测稳定性 常见选项包括 CALIB_CB_ADAPTIVE_THRESH使用自适应阈值处理图像 CALIB_CB_NORMALIZE_IMAGE归一化图像增强对比度 CALIB_CB_FILTER_QUADS过滤不符合四边形结构的区域 CALIB_CB_FAST_CHECK快速初步检测提高速度可能降低准确性 */圆形标定板中心提取/* 返回值bool类型true表示成功检测到完整圆点阵列false表示检测失败 用途用于检测圆点标定板中的圆心位置是相机标定中的重要函数之一。 */ bool cv::findCirclesGrid( InputArray image, Size patternSize, OutputArray centers, int flags, const PtrFeatureDetector blobDetector, const CirclesGridFinderParameters parameters); /* image输入含有圆形网格的图像图像必须是CV_8U的灰度图像或者彩色图像 patternSize圆点阵列的尺寸Size(cols, rows)表示每行和每列圆形的数目 centers输出检测到的圆心坐标按照圆点阵列顺序排列 flags圆点阵列检测方式标志常见取值 CALIB_CB_SYMMETRIC_GRID对称圆点阵列 CALIB_CB_ASYMMETRIC_GRID非对称圆点阵列 CALIB_CB_CLUSTERING使用聚类提高复杂场景鲁棒性 blobDetectorBlob检测器用于检测图像中的圆形区域一般使用SimpleBlobDetector parameters圆点阵列搜索参数用于控制圆点搜索过程中的细节配置 */角点位置优化/* 返回值bool类型true表示角点优化成功false表示优化失败 用途用于对棋盘格等四边形结构角点进行亚像素级精确优化。 该函数会根据角点附近的灰度变化对原始角点位置进行进一步精细调整 从而获得更高精度的角点坐标 */ bool cv::find4QuadCornerSubpix( InputArray img, InputOutputArray corners, Size region_size ); /* img计算处内角点的图像 corners输入和输出角点坐标输入为初始检测到的角点输出为亚像素级精确角点坐标 region_size角点搜索区域大小表示在角点周围进行亚像素优化的邻域窗口尺寸 */绘制内角点提取结果/* 用途用于在图像中可视化显示棋盘格角点检测结果。 该函数会在检测到的角点位置绘制彩色圆点和连接关系方便观察 1、棋盘格是否检测成功 2、角点顺序是否正确 3、角点定位是否准确 */ void cv::drawChessboardCorners( InputOutputArray image, Size patternSize, InputArray corners, bool patternWasFound ); /* image需要绘制角点的目标图像必须是CV_8U的彩色图像 patternSize棋盘格内角点数量Size(cols, rows)表示每行和每列内角点个数 corners检测得到的棋盘格角点坐标集合通常来自findChessboardCorners() patternWasFound棋盘格是否成功检测到的标志true表示检测成功false表示检测失败 */相机标定函数/* 返回值重投影误差RMS误差数值越小标定结果通常越准确 用途用于根据多张标定板图像计算相机的内参、畸变参数以及外参。 */ double cv::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世界坐标系中的三维点集合通常为棋盘格内角点的三维坐标 imagePoints图像坐标系中的二维点集合通常为棋盘格内角点在图像中的二维坐标 imageSize输入图像尺寸即图像宽度和高度 cameraMatrix相机的内参矩阵即相机标定后的K矩阵 distCoeffs相机的畸变系数矩阵包括径向畸变和切向畸变参数 rvecs每张标定图像相机坐标系与世界坐标系之间对应的旋转向量 tvecs每张标定图像相机坐标系与世界坐标系之间对应的平移向量 flags相机标定控制标志用于指定优化方式、固定参数等设置 criteria迭代终止条件用于控制优化迭代次数和精度 */3、示例代码QString imgPath QApplication::applicationDirPath() /Images; cv::String s_imgPath imgPath.toLocal8Bit().data(); /*读取所有图像*/ vectorMat imgs; string imageName; ifstream fin(s_imgPath /calibdata.txt); while (getline(fin, imageName)) { string fullPath s_imgPath / imageName; cv::Mat img cv::imread(fullPath);/*left01.jpg left02.jpg left03.jpg left04.jpg*/ if (img.empty()) { qDebug() 读取失败: QString::fromStdString(fullPath); continue; } imgs.push_back(img); } Size board_size Size(9, 6);/*方格标定板内角点数目行列*/ vectorvectorPoint2f imgPoints; for (int i 0; i imgs.size(); i) { Mat img1 imgs[i]; Mat gray1; cvtColor(img1, gray1, COLOR_BGR2GRAY); vectorPoint2f img1_points; findChessboardCorners(gray1, board_size, img1_points);/*计算方格标定板角点*/ find4QuadCornerSubpix(gray1, img1_points, Size(5, 5));/*细化方格标定板角点坐标*/ bool pattern true; drawChessboardCorners(img1, board_size, img1_points, pattern); imshow(img1, img1); waitKey(0); imgPoints.push_back(img1_points); } /*生成棋盘格每个内角点的空间三维坐标*/ Size squareSize Size(10, 10);/*棋盘格每个方格的真实尺寸*/ vectorvectorPoint3f objectPoints; for (int i 0; i imgPoints.size(); i) { vectorPoint3f tempPointSet; for (int j 0; j board_size.height; j) { for (int k 0; k board_size.width; k) { Point3f realPoint; /*假设标定板为世界坐标系的z平面即z0*/ realPoint.x j * squareSize.width; realPoint.y k * squareSize.height; realPoint.z 0; tempPointSet.push_back(realPoint); } } objectPoints.push_back(tempPointSet); } /*初始化每幅图像中的角点数量假定每幅图像中都可以看到完整的标定板*/ //vectorint point_number; //for (int i 0; i imgPoints.size(); i) //{ // point_number.push_back(board_size.width * board_size.height); //} /*图像尺寸*/ Size imageSize; imageSize.width imgs[0].cols; imageSize.height imgs[0].rows; Mat cameraMatrix Mat(3, 3, CV_32FC1, Scalar::all(0));/*摄像机内参矩阵*/ Mat distCoeffs Mat(1, 5, CV_32FC1, Scalar::all(0));/*摄像机的5个畸变系数k1,k2,p1,p2,k3*/ vectorMat rvecs;/*每幅图像的旋转向量*/ vectorMat tvecs;/*每幅图像的平移向量*/ calibrateCamera(objectPoints, imgPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, 0); cout cameraMatrix: endl cameraMatrix endl; cout distCoeffs: endl distCoeffs endl; waitKey(0); destroyAllWindows();