OpenCV开发笔记(八十一):通过棋盘格使用鱼眼方式标定相机内参矩阵矫正摄像头图像

前言

对于广角摄像头通过相机图片可以识别出棋盘角点计算相机内参矩阵,通过畸变校准可以得到较好的效果,但是鱼眼摄像头通过这种方式获得周围四周的图像效果并不是很好。所以,鱼眼摄像头在校准上与普通摄像头有一些区别。

本篇通过一张图片来识别计算得到相机内参矩阵,并鱼眼矫正的方式矫正图像畸形。

Demo

鱼眼方式畸变校准效果

普通畸变校准效果

校准实例

注意:这里demo只使用了可识别的两张基本相似的棋盘图作为计算,鱼眼畸变矫正,若是区别大,那么校准的时候会因为计算误差超限而奔溃。

步骤一:初始化图片列表

cpp 复制代码
QStringList list;
list.append("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/34.png"); list.append("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/35.png"); int chessboardColCornerCount = 8; int chessboardRowCornerCount = 11; 

步骤二:循环识别图片棋盘格并且将其世界坐标和识别的角点放入列表

cpp 复制代码
std::vector<std::vector<cv::Point3f>> vectorObjectPoint; std::vector<std::vector<cv::Point2f>> vectorImagePoint; cv::Mat grayMat; cv::Mat srcMat; for(int n = 0; n < list.size(); n++) { QString str = list.at(n); std::string srcFilePath = str.toStdString(); // 步骤一:读取文件 cv::Mat mat = cv::imread(srcFilePath); LOG << mat.cols << mat.rows; #if 1 srcMat = cv::Mat(mat.rows * 2, mat.cols * 2, CV_8UC3); cv::Mat matRoi = srcMat(cv::Rect(mat.cols / 2, mat.rows / 2, mat.cols, mat.rows)); cv::addWeighted(mat, 1.0f, matRoi, 0, 0, matRoi); #else srcMat = mat.clone(); #endif // 步骤二:缩放,太大了缩放下(可省略) cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2)); cv::Mat srcMat2 = srcMat.clone(); cv::Mat srcMat3 = srcMat.clone(); // 步骤三:灰度化 cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY); cv::imshow("grayMat", grayMat); // 步骤四:检测角点 std::vector<cv::Point2f> vectorPoint2fCorners; bool patternWasFound = false; patternWasFound = cv::findChessboardCorners(grayMat, cv::Size(chessboardColCornerCount, chessboardRowCornerCount), vectorPoint2fCorners, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE); if(!patternWasFound) { LOG << "not find ChessboardCorners:" << chessboardColCornerCount << chessboardRowCornerCount; continue; } /* enum { CALIB_CB_ADAPTIVE_THRESH = 1, // 使用自适应阈值将图像转化成二值图像 CALIB_CB_NORMALIZE_IMAGE = 2, // 归一化图像灰度系数(用直方图均衡化或者自适应阈值) CALIB_CB_FILTER_QUADS = 4, // 在轮廓提取阶段,使用附加条件排除错误的假设 CALIB_CB_FAST_CHECK = 8 // 快速检测 }; */ cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false"); cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size()); // 步骤五:绘制棋盘点 cv::drawChessboardCorners(srcMat2, cv::Size(chessboardColCornerCount, chessboardRowCornerCount), vectorPoint2fCorners, patternWasFound); // 步骤六:进一步提取亚像素角点 cv::TermCriteria criteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, // 类型 30, // 参数二: 最大次数 0.001); // 参数三:迭代终止阈值 /* #define CV_TERMCRIT_ITER 1 // 终止条件为: 达到最大迭代次数终止 #define CV_TERMCRIT_NUMBER CV_TERMCRIT_ITER // #define CV_TERMCRIT_EPS 2 // 终止条件为: 迭代到阈值终止 */ cv::imshow("srcMat2", srcMat2); cv::cornerSubPix(grayMat, vectorPoint2fCorners, cv::Size(5, 5), cv::Size(-1, -1), criteria); // 步骤七:绘制棋盘点 cv::drawChessboardCorners(srcMat3, cv::Size(chessboardColCornerCount, chessboardRowCornerCount), vectorPoint2fCorners, patternWasFound); cv::imshow("5", srcMat3); // 步骤八:角点对应的三维坐标(一张图一组) std::vector<cv::Point3f> objectPoints; // 三维世界坐标系 for(int i = 0; i < chessboardRowCornerCount; i++) { for(int j = 0; j < chessboardColCornerCount; j++) { objectPoints.push_back(cv::Point3f(j, i, 0)); } } vectorObjectPoint.push_back(objectPoints); // 步骤九:图像识别出来的角点(一张图一组) vectorImagePoint.push_back(vectorPoint2fCorners); } 

步骤三:计算内参和畸变系数,然后校准

cpp 复制代码
#if 1 // 步骤十(1):计算内参和畸变系数,未设 flags 和 无 迭代终止条件 图象 >=1张即可,这是常规广角标定矫正方式(非鱼眼) cv::Mat dstMat; { cv::Mat cameraMatrix; // 相机矩阵(接收输出) cv::Mat distCoeffs; // 畸变系数(接收输出) cv::Mat rotate; // 旋转量(接收输出) cv::Mat translate; // 偏移量(接收输出) cv::calibrateCamera(vectorObjectPoint, vectorImagePoint, grayMat.size(), cameraMatrix, distCoeffs, rotate, translate); cv::undistort(srcMat, dstMat, cameraMatrix, distCoeffs); cv::imshow("dstMat", dstMat); } #endif LOG; #if 1 // 步骤十(2):计算内参和畸变系数,设 flags 和 增加迭代终止条件 需要 图象数>=2张,这是鱼眼摄像头常规方式, 但是使用 undistortImage cv::Mat dstMat2; { cv::Mat cameraMatrix2; // 相机矩阵(接收输出) cv::Mat distCoeffs2; // 畸变系数(接收输出) cv::Mat rotate2; // 旋转量(接收输出) cv::Mat translate2; // 偏移量(接收输出) int flags = 0; flags |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; flags |= cv::fisheye::CALIB_CHECK_COND; flags |= cv::fisheye::CALIB_FIX_SKEW; LOG; cv::fisheye::calibrate(vectorObjectPoint, vectorImagePoint, grayMat.size(), cameraMatrix2, distCoeffs2, rotate2, translate2, flags, cv::TermCriteria(3, 20, 1e-6)); LOG; cv::fisheye::undistortImage(srcMat, // 输入的畸变图像。 dstMat2, // 输出的矫正后的图像。 cameraMatrix2, // 相机内参矩阵 distCoeffs2); // 畸变系数 LOG; cv::imshow("dstMat2", dstMat2); } LOG; #endif 

函数原型

calibrateCamera:相机标定求解函数

OpenCV中的一个函数,用于相机标定。相机标定是估计相机内参(如焦距、主点坐标等)和畸变系数的过程,这些参数对于后续的图像处理任务(如三维重建、目标跟踪等)至关重要。

cpp 复制代码
double calibrateCamera(InputArrayOfArrays objectPoints,  
                     InputArrayOfArrays imagePoints, Size imageSize, OutputArray cameraMatrix, OutputArray distCoeffs, OutputArray rvecs, OutputArray tvecs, int flags=0, TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 1e-6)); 

参数说明:

  • objectPoints:世界坐标系中的三维点。通常,这些点是通过在标定板上定义的一系列点来获取的,这些点的坐标是已知的。对于每个图像,它应该是一个 Nx3 的数组(或数组列表),其中 N 是点的数量,而 3 表示每个点的 (X, Y, Z) 坐标。
  • imagePoints:图像坐标系中的二维点,即对应于 objectPoints 中的三维点在图像中的投影。对于每个图像,它应该是一个 Nx2 的数组(或数组列表),其中 N 是点的数量,而 2 表示每个点的 (x, y) 坐标。
  • imageSize:图像的大小,表示为 Size 类型的对象,包含图像的宽度和高度。
  • cameraMatrix:输出参数,存储 3x3 的相机内参矩阵。
  • distCoeffs:输出参数,存储畸变系数。通常有 5 个系数(k1, k2, p1, p2, k3)对于径向和切向畸变,或 8 个系数(k1, k2, k3, k4, k5, k6, p1, p2)对于鱼眼相机模型。
  • rvecs:输出参数,对于每个图像,存储旋转向量的数组。
  • tvecs:输出参数,对于每个图像,存储平移向量的数组。
  • flags:不同标志的组合,用于指定标定过程中使用的算法。
      CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,将包含有效的fx,fy,cx,cy的估计值的内参矩阵cameraMatrix,作为初始值输入,然后函数对其做进一步优化。如果不使用这个参数,用图像的中心点初始化光轴点坐标(cx, cy),使用最小二乘估算出fx,fy(这种求法好像和张正友的论文不一样,不知道为何要这样处理)。注意,如果已知内部参数(内参矩阵和畸变系数),就不需要使用这个函数来估计外参,可以使用solvepnp()函数计算外参数矩阵。
      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_USE_INTRINSIC_GUESS参数,就从提供的畸变系数矩阵中得到。否则,设置为0。
      CV_CALIB_RATIONAL_MODEL(理想模型):启用畸变k4,k5,k6三个畸变参数。使标定函数使用有理模型,返回8个系数。如果没有设置,则只计算其它5个畸变参数。
      CALIB_THIN_PRISM_MODEL (薄棱镜畸变模型):启用畸变系数S1、S2、S3和S4。使标定函数使用薄棱柱模型并返回12个系数。如果不设置标志,则函数计算并返回只有5个失真系数。
      CALIB_FIX_S1_S2_S3_S4 :优化过程中不改变薄棱镜畸变系数S1、S2、S3、S4。如果cv_calib_use_intrinsic_guess设置,使用提供的畸变系数矩阵中的值。否则,设置为0。
      CALIB_TILTED_MODEL (倾斜模型):启用畸变系数tauX and tauY。标定函数使用倾斜传感器模型并返回14个系数。如果不设置标志,则函数计算并返回只有5个失真系数。
      CALIB_FIX_TAUX_TAUY :在优化过程中,倾斜传感器模型的系数不被改变。如果cv_calib_use_intrinsic_guess设置,从提供的畸变系数矩阵中得到。否则,设置为0。
  • criteria:迭代优化的终止条件。通常包含最大迭代次数和收敛的精度。
      这个函数返回一个双精度浮点数,表示重投影误差的估计值,即实际图像点与通过相机参数和畸变系数计算出的图像点之间的平均误差。
      为了获得准确的相机标定结果,通常需要多个视图(即多张不同角度和姿态拍摄的标定板图像),并确保标定板在不同图像中占据足够的视场。此外,图像应该清晰,且标定板上的特征点(如棋盘格的角点)应准确检测。

initUndistortRectifyMap:计算畸变参数

OpenCV中用于初始化用于图像去畸变和校正的映射表的函数。这个函数的目的是生成两个映射,一个用于x坐标,另一个用于y坐标,它们可以被用于 remap函数来校正图像的畸变。

cpp 复制代码
void initUndistortRectifyMap(InputArray cameraMatrix, 
                         InputArray distCoeffs, InputArray R, InputArray newCameraMatrix, Size size, int m1type, OutputArray map1, OutputArray map2) 

参数说明

  • cameraMatrix:相机的内参矩阵,一个3x3的浮点数矩阵。
  • distCoeffs:畸变系数,一个1x5或1x8的向量,包含径向和切向畸变系数。
  • R:可选的旋转矩阵,一个3x3的浮点数矩阵,表示从原相机坐标系到新的相机坐标系的旋转。如果这个参数是空的,那么newCameraMatrix必须是cameraMatrix。
  • newCameraMatrix:新的相机内参矩阵,一个3x3的浮点数矩阵。这个矩阵可以是原始相机矩阵,或者经过getOptimalNewCameraMatrix调整后的矩阵,以考虑图像的有效视场。
  • size:输出映射的尺寸,表示为Size类型的对象,包含图像的宽度和高度。
  • m1type:输出映射的类型,可以是CV_32FC1或CV_16SC2。
  • map1:输出的第一个映射,用于x坐标,可以被传递给remap函数。
  • map2:输出的第二个映射,用于y坐标,可以被传递给remap函数。
      这两个映射map1和map2可以被传递给remap函数,以对图像进行去畸变和校正。
      如果有一个畸变的图像distortedImage和想要得到校正后的图像undistortedImage,可以这样使用这两个函数:
cpp 复制代码
Mat map1,map2;
initUndistortRectifyMap(cameraMatrix, distCoeffs, R, newCameraMatrix, size, CV_32FC1, map1, map2); remap(distortedImage, undistortedImage, map1, map2, INTER_LINEAR); 

在这个例子中,INTER_LINEAR是插值方法的类型,用于remap函数。其他的插值方法,如INTER_NEAREST、INTER_CUBIC等也可以被使用,具体取决于应用需求。

cv::fisheye::calibrate:鱼眼标定函数

cv::fisheye::calibrate函数是OpenCV中用于标定鱼眼相机镜头的函数。

cpp 复制代码
double cv::fisheye::calibrate( InputArrayOfArrays objectPoints, // 存放世界坐标系中的点集,通常为三维点 InputArrayOfArrays imagePoints, // 存放图像坐标系中的对应点集,通常为二维点 const Size& image_size, // 图像的大小 InputOutputArray K, // 输出相机的内参矩阵 InputOutputArray D, // 输出畸变系数 OutputArrayOfArrays rvecs, // 输出每幅图像的旋转向量 OutputArrayOfArrays tvecs, // 输出每幅图像的平移向量 int flags = 0, // 标定方法的标志位 TermCriteria criteria = TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, 100, DBL_EPSILON) // 迭代算法的终止条件 ); 

参数说明:

  • objectPoints:世界坐标系中的点集,通常是一个三维的点数组。在标定过程中,这些点通常是标定板(如棋盘格)的角点在世界坐标系中的位置。
  • imagePoints:图像坐标系中对应的点集,是objectPoints中的点在图像上的投影。这些点通常是通过图像处理算法(如角点检测)在图像中找到的。
  • image_size:图像的大小,包括宽度和高度。
  • K:输出参数,相机的内参矩阵。内参矩阵包含了焦距(fx, fy)和光心(cx, cy)等参数。
  • D:输出参数,畸变系数。对于鱼眼镜头,畸变系数可能包括多个参数,用于描述镜头的非线性畸变。
  • rvecs:输出参数,每幅图像的旋转向量。旋转向量表示了相机坐标系与世界坐标系之间的旋转关系。
  • tvecs:输出参数,每幅图像的平移向量。平移向量表示了相机坐标系与世界坐标系之间的平移关系。
  • flags:标定方法的标志位,可以通过组合不同的标志来指定标定过程的不同选项。例如,cv::fisheye::CALIB_USE_INTRINSIC_GUESS表示使用提供的内参矩阵作为初始估计。
  • criteria:迭代算法的终止条件。通常使用迭代次数和误差阈值的组合来定义。
      返回值,函数返回重投影误差的平均值,该值越小表示标定结果越好。
      在使用cv::fisheye::calibrate函数之前,需要准备多幅包含标定板(如棋盘格)的图像,并提取出标定板角点在图像中的位置。
      标定过程中,需要确保标定板在图像中的位置和方向具有足够的多样性,以便能够准确地估计相机的内参和畸变系数。
      标定结果的质量受到多种因素的影响,包括标定板的精度、图像的质量、标定过程中相机的稳定性等。

cv::fisheye::undistortImage:鱼眼图像去畸变

fisheye::undistortImage函数是OpenCV中用于鱼眼图像去畸变的一个函数。该函数的原型通常如下所示(注意,具体参数类型和名称可能会根据OpenCV的不同版本有所变化,但基本思想是一致的):

cpp 复制代码
void cv::fisheye::undistortImage(InputArray distorted, OutputArray undistorted, InputArray K, InputArray D, InputArray Knew = cv::noArray(), Size new_size = Size(), double balance = 0.0 ); 

参数说明:

  • distorted:输入的畸变图像。
  • undistorted:输出的矫正后的图像。
  • K:相机内参矩阵。通常是一个3x3的浮点数矩阵,包含了焦距fx, fy(在矩阵的对角线上)和光心(cx, cy)(矩阵的前两行的最后一列)。
  • D:畸变系数。对于鱼眼相机,通常包含四个系数:k1, k2, k3, k4,这些系数描述了径向畸变。
  • Knew(可选):新的相机内参矩阵。如果提供,则使用此矩阵进行去畸变,而不是直接使用原始内参矩阵K。这在某些情况下很有用,比如当你想调整去畸变后图像的视野或比例时。如果未提供,则默认为空(cv::noArray()),此时会使用原始内参矩阵K。
  • new_size(可选):输出图像的尺寸。如果提供,则去畸变后的图像将被缩放到这个尺寸。如果未提供,则输出图像的尺寸将与输入图像的尺寸相同。
  • balance(可选):用于在图像去畸变过程中调整畸变减少和视野保留之间的平衡的参数。值越大,畸变减少得越多,但可能会损失更多的图像视野。默认值为0.0,表示不进行特别的平衡调整。
      请注意,虽然上述参数描述是通用的,但具体的实现和参数名称可能会根据你使用的OpenCV版本有所不同。因此,建议查阅你所使用的OpenCV版本的官方文档以获取最准确的信息。
      此外,fisheye::undistortImage函数是专门为鱼眼镜头设计的,它考虑了鱼眼镜头特有的畸变模式。如果你正在处理的是非鱼眼镜头拍摄的图像,可能需要使用cv::undistort函数或其他适用于普通镜头的去畸变方法。
      
      经典的鱼眼单目标定程序,采用:
cpp 复制代码
int flags = 0;
flags |= myfisheye::CALIB_RECOMPUTE_EXTRINSIC; flags |= myfisheye::CALIB_CHECK_COND; flags |= myfisheye::CALIB_FIX_SKEW; 

cv::fisheye::initUndistortRectifyMap:鱼眼图像去畸变

函数是OpenCV中用于鱼眼相机畸变校正的函数,它计算用于畸变校正的映射表。以下是该函数的原型:

cpp 复制代码
void cv::fisheye::initUndistortRectifyMap( InputArray K, // 相机内参矩阵 InputArray D, // 畸变系数矩阵 InputArray R, // 旋转矩阵,通常为单位矩阵 InputArray P, // 校正后的相机内参矩阵 const cv::Size& size, // 矫正后的图像大小 int m1type, // 映射表类型,通常设置为cv::CV_16SC2 OutputArray map1, // x坐标的映射表 OutputArray map2 // y坐标的映射表 ); 

参数说明:

  • K:相机内参矩阵,它是一个3x3的矩阵,包含了焦距(fx, fy)和光心(cx, cy)等参数。这个矩阵可以通过相机标定得到。
  • D:畸变系数矩阵,它包含了描述镜头畸变程度的参数。对于鱼眼相机,畸变系数通常包括k1, k2, k3, k4, k5五个参数。
  • R:旋转矩阵,用于将相机坐标系转换到校正后的相机坐标系。在大多数情况下,当进行单目鱼眼相机的校正时,这个矩阵可以设置为单位矩阵。
  • P:校正后的相机内参矩阵,它是通过相机标定得到的,并且可能已经考虑了畸变校正。如果不需要改变校正后图像的内参,这个矩阵可以与原相机内参相同。
  • size:矫正后的图像大小,即输出图像的尺寸。
  • m1type:映射表类型,它指定了输出映射表的格式。通常,这个参数被设置为cv::CV_16SC2,表示映射表是一个两通道的16位有符号整数矩阵,其中每个元素是一个包含x和y坐标的二维向量。
  • map1:输出的x坐标映射表,它是一个与矫正后图像大小相同的矩阵,用于存储每个像素点在原畸变图像中的x坐标。
  • map2:输出的y坐标映射表,它也是一个与矫正后图像大小相同的矩阵,用于存储每个像素点在原畸变图像中的y坐标。
      这个函数通常与cv::remap函数一起使用,以根据计算得到的映射表对畸变图像进行校正。首先,使用cv::fisheye::initUndistortRectifyMap函数计算映射表,然后使用cv::remap函数根据映射表对图像进行重映射,从而得到校正后的图像。
      注意,在使用这个函数之前,需要确保已经通过相机标定得到了准确的相机内参矩阵和畸变系数矩阵,矫正后的图像大小(size参数)应该根据实际需求进行设置,以确保输出图像的尺寸符合后续处理或显示的要求。映射表类型(m1type参数)的选择对于最终矫正效果有一定影响,通常建议使用cv::CV_16SC2以获得更好的性能和精度。

cv::remap:x和y重映射

cv::remap 函数是 OpenCV 库中用于图像重映射的函数,它可以将源图像中的像素根据提供的映射关系重新定位到目标图像中。

cpp 复制代码
void cv::remap(InputArray src, OutputArray dst, InputArray map1, InputArray map2, int interpolation, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar()) 

参数说明:

  • src:类型InputArray,输入图像,即源图像。
  • dst:类型OutputArray,输出图像,即目标图像。它的大小与 map1 相同,类型与 src 相同。
  • map1:类型InputArray,映射矩阵的第一个组成部分,可以是单独的 x 坐标映射,也可以是与 map2 一起构成 (x, y) 坐标的映射。其类型可以是 CV_16SC2、CV_32FC1 或 CV_32FC2。
  • map2(可选):类型InputArray,当 map1 是单独的 x 坐标映射时,map2 存放 y 坐标的映射。其类型可以是 CV_16UC1、CV_32FC1。如果 map1 是 (x, y) 坐标的映射,则 map2 可以为空或不使用。
  • interpolation:类型int,插值方法。可选的插值方式包括 INTER_NEAREST(最近邻插值)、INTER_LINEAR(双线性插值,默认)、INTER_CUBIC(双三样条插值)和 INTER_LANCZOS4(Lanczos插值)。注意,INTER_AREA 插值方式在这里是不支持的。
  • borderMode(可选):类型int,边界模式,默认为 BORDER_CONSTANT。表示目标图像中"离群点"的像素值不会被此函数修改。
  • borderValue(可选):类型const Scalar&,当有常数边界时使用的值,其有默认值 Scalar(),即默认值为 0。仅在 borderMode = BORDER_CONSTANT 时需要。
      可对图像翻转(如上下翻转、左右翻转),图像缩放(通过修改映射矩阵中的坐标值来实现),图像旋转(需要更复杂的映射矩阵计算)。
      注意,映射矩阵 map1 和 map2(如果使用)必须与源图像 src 有相同的尺寸。该函数目前只支持小于 32767 × 32767 尺寸的输入图和输出图。插值方法的选择会影响重映射后的图像质量。

Demo源码

cpp 复制代码
void OpenCVManager::testFisheyeChessboard() { QStringList list; list.append("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/34.png"); list.append("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/35.png"); int chessboardColCornerCount = 8; int chessboardRowCornerCount = 11; // list.append("D:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/36.png"); // int chessboardColCornerCount = 4; // int chessboardRowCornerCount = 6; std::vector<std::vector<cv::Point3f>> vectorObjectPoint; std::vector<std::vector<cv::Point2f>> vectorImagePoint; cv::Mat grayMat; cv::Mat srcMat; for(int n = 0; n < list.size(); n++) { QString str = list.at(n); std::string srcFilePath = str.toStdString(); // 步骤一:读取文件 cv::Mat mat = cv::imread(srcFilePath); LOG << mat.cols << mat.rows; srcMat = mat.clone(); // 步骤二:缩放,太大了缩放下(可省略) cv::resize(srcMat, srcMat, cv::Size(srcMat.cols / 2, srcMat.rows / 2)); cv::Mat srcMat2 = srcMat.clone(); cv::Mat srcMat3 = srcMat.clone(); // 步骤三:灰度化 cv::cvtColor(srcMat, grayMat, cv::COLOR_BGR2GRAY); cv::imshow("grayMat", grayMat); // 步骤四:检测角点 std::vector<cv::Point2f> vectorPoint2fCorners; bool patternWasFound = false; patternWasFound = cv::findChessboardCorners(grayMat, cv::Size(chessboardColCornerCount, chessboardRowCornerCount), vectorPoint2fCorners, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FAST_CHECK | cv::CALIB_CB_NORMALIZE_IMAGE); if(!patternWasFound) { LOG << "not find ChessboardCorners:" << chessboardColCornerCount << chessboardRowCornerCount; continue; } /* enum { CALIB_CB_ADAPTIVE_THRESH = 1, // 使用自适应阈值将图像转化成二值图像 CALIB_CB_NORMALIZE_IMAGE = 2, // 归一化图像灰度系数(用直方图均衡化或者自适应阈值) CALIB_CB_FILTER_QUADS = 4, // 在轮廓提取阶段,使用附加条件排除错误的假设 CALIB_CB_FAST_CHECK = 8 // 快速检测 }; */ cvui::printf(srcMat, 0, 0, 1.0, 0xFF0000, "found = %s", patternWasFound ? "true" : "false"); cvui::printf(srcMat, 0, 24, 1.0, 0xFF0000, "count = %d", vectorPoint2fCorners.size()); // 步骤五:绘制棋盘点 cv::drawChessboardCorners(srcMat2, cv::Size(chessboardColCornerCount, chessboardRowCornerCount), vectorPoint2fCorners, patternWasFound); // 步骤六:进一步提取亚像素角点 cv::TermCriteria criteria(CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, // 类型 30, // 参数二: 最大次数 0.001); // 参数三:迭代终止阈值 /* #define CV_TERMCRIT_ITER 1 // 终止条件为: 达到最大迭代次数终止 #define CV_TERMCRIT_NUMBER CV_TERMCRIT_ITER // #define CV_TERMCRIT_EPS 2 // 终止条件为: 迭代到阈值终止 */ cv::imshow("srcMat2", srcMat2); cv::cornerSubPix(grayMat, vectorPoint2fCorners, cv::Size(5, 5), cv::Size(-1, -1), criteria); // 步骤七:绘制棋盘点 cv::drawChessboardCorners(srcMat3, cv::Size(chessboardColCornerCount, chessboardRowCornerCount), vectorPoint2fCorners, patternWasFound); cv::imshow("5", srcMat3); // 步骤八:角点对应的三维坐标(一张图一组) std::vector<cv::Point3f> objectPoints; // 三维世界坐标系 for(int i = 0; i < chessboardRowCornerCount; i++) { for(int j = 0; j < chessboardColCornerCount; j++) { objectPoints.push_back(cv::Point3f(j, i, 0)); } } vectorObjectPoint.push_back(objectPoints); // 步骤九:图像识别出来的角点(一张图一组) vectorImagePoint.push_back(vectorPoint2fCorners); } #if 0 // 步骤十(1):计算内参和畸变系数,未设 flags 和 无 迭代终止条件 图象 >=1张即可,这是常规广角标定矫正方式(非鱼眼) cv::Mat dstMat; { cv::Mat cameraMatrix; // 相机矩阵(接收输出) cv::Mat distCoeffs; // 畸变系数(接收输出) cv::Mat rotate; // 旋转量(接收输出) cv::Mat translate; // 偏移量(接收输出) cv::calibrateCamera(vectorObjectPoint, vectorImagePoint, grayMat.size(), cameraMatrix, distCoeffs, rotate, translate); cv::undistort(srcMat, dstMat, cameraMatrix, distCoeffs); cv::imshow("dstMat", dstMat); } #endif LOG; #if 0 // 步骤十(2):计算内参和畸变系数,设 flags 和 增加迭代终止条件 需要 图象数>=2张,这是鱼眼摄像头常规方式, 但是使用 undistortImage cv::Mat dstMat2; { cv::Mat cameraMatrix2; // 相机矩阵(接收输出) cv::Mat distCoeffs2; // 畸变系数(接收输出) cv::Mat rotate2; // 旋转量(接收输出) cv::Mat translate2; // 偏移量(接收输出) int flags = 0; flags |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; flags |= cv::fisheye::CALIB_CHECK_COND; flags |= cv::fisheye::CALIB_FIX_SKEW; LOG; cv::fisheye::calibrate(vectorObjectPoint, vectorImagePoint, grayMat.size(), cameraMatrix2, distCoeffs2, rotate2, translate2, flags, cv::TermCriteria(3, 20, 1e-6)); LOG; cv::fisheye::undistortImage(srcMat, // 输入的畸变图像。 dstMat2, // 输出的矫正后的图像。 cameraMatrix2, // 相机内参矩阵 distCoeffs2); // 畸变系数 LOG; cv::imshow("dstMat2", dstMat2); } LOG; #endif #endif LOG; cv::waitKey(0); } 

工程模板v1.71.0

入坑

入坑一:鱼眼标定校准后输出全黑

问题


  

尝试

应该是参数不对:


  十张同样图


  三十张同样图


  那么不是图像多少的问题,下一步看是不是其他问题:


  
  发现是矩阵错了.

解决

入坑二:运行鱼眼标定崩溃

问题

运行奔溃

原因

校准的函数失败,经过研究是多张图可能误差较大,导致越过上限从而崩溃,

解决

只输入相似的图,稍微移动一下,保持基本大致不变进行采集: