目录
  • 一、OpenCV标定的几个常用函数
    • findChessboardCorners()棋盘格角点检测
    • cv::drawChessboardCorners()棋盘格角点的绘制
    • find4QuadCornerSubpix()对粗提取的角点进行精确化
    • cornerSubPix()亚像素检测
    • calibrateCamera()求解摄像机的内在参数和外在参数
    • initUndistortRectifyMap()计算畸变参数
  • 二、绘制棋盘格,拍摄照片
    • 三、相机标定
      • 四、对图片进行校正
        • 总结 

          一、OpenCV标定的几个常用函数

          findChessboardCorners() 棋盘格角点检测

          bool findChessboardCorners( InputArray image, 
                                          Size patternSize, 
                                          OutputArray corners,
                                          int flags = CALIB_CB_ADAPTIVE_THRESH + 
                                          CALIB_CB_NORMALIZE_IMAGE );
          

          第一个参数是输入的棋盘格图像(可以是8位单通道或三通道图像);

          第二个参数是棋盘格内部的角点的行列数(注意:不是棋盘格的行列数,如棋盘格的行列数分别为4、8,而内部角点的行列数分别是3、7,因此这里应该指定为cv::Size(3, 7));

          第三个参数是检测到的棋盘格角点,类型为std::vectorcv::Point2f。

          第四个参数flag,用于指定在检测棋盘格角点的过程中所应用的一种或多种过滤方法,可以使用下面的一种或多种,如果都是用则使用OR:

          • cv::CALIB_CB_ADAPTIVE_THRESH:使用自适应阈值将图像转化成二值图像
          • cv::CALIB_CB_NORMALIZE_IMAGE:归一化图像灰度系数(用直方图均衡化或者自适应阈值)
          • cv::CALIB_CB_FILTER_QUADS:在轮廓提取阶段,使用附加条件排除错误的假设
          • cv::CALIB_CV_FAST_CHECK:快速检测

          cv::drawChessboardCorners() 棋盘格角点的绘制

          drawChessboardCorners( InputOutputArray image, 
                                     Size patternSize,
                                     InputArray corners, 
                                     bool patternWasFound );
          
          • image为8-bit,三通道图像
          • patternSize,每一行每一列的角
          • corners,已经检测到的角
          • patternWasFound,findChessboardCorners的返回值

          find4QuadCornerSubpix() 对粗提取的角点进行精确化

          find4QuadCornerSubpix( InputArray img, 
                                     InputOutputArray corners, 
                                     Size region_size );
          
          • image源图像
          • corners,提供角点的初始坐标
          • region_size: 搜索窗口的一般尺寸

          cornerSubPix() 亚像素检测

          void cornerSubPix( InputArray image, 
                                 InputOutputArray corners,
                                 Size winSize, 
                                 Size zeroZone,
                                 TermCriteria criteria );
          
          • image源图像
          • corners,提供角点的初始坐标,返回更加精确的点
          • winSize,搜索窗口的一般尺寸,如果winSize=Size(5,5),则search windows为11*11
          • winSize,死区的一般尺寸,用来避免自相关矩阵的奇点,(-1,-1)表示没有死区
          • criteria,控制迭代次数和精度

          calibrateCamera() 求解摄像机的内在参数和外在参数

          double 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,世界坐标,用vector<vector>,输入x,y坐标,z坐标为0

          imagePoints,图像坐标,vector<vector>

          imageSize,图像的大小用于初始化标定摄像机的image的size

          cameraMatrix,内参数矩阵

          distCoeffs,畸变矩阵

          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。

          initUndistortRectifyMap() 计算畸变参数

          void initUndistortRectifyMap(InputArray cameraMatrix, 
                                          InputArray distCoeffs, 
                                          InputArray R, 
                                          InputArray newCameraMatrix, 
                                          Size size, 
                                          int m1type, 
                                          OutputArray map1, 
                                          OutputArray map2)
          
          • cameraMatrix,摄像机内参数矩阵
          • distCoeffs, 摄像机的5个畸变系数,(k1,k2,p1,p2[,k3[,k4,k5,k6]])
          • R,在客观空间中的转换对象
          • newCameraMatrix,新的3*3的浮点型矩矩阵
          • size,为失真图像的大小
          • m1type,第一个输出的map,类型为CV_32FC1或CV_16SC2
          • map1,x映射函数
          • map2,y映射函数

          二、绘制棋盘格,拍摄照片

          这里自己画一个棋盘格用作标定,长度为1280像素,宽490像素,横向10方格,纵向7方格

          std_cb = Vision::makeCheckerboard(1280, 490, 10, 7, 0, 
          (char *)"../blizzard/res/calibration/std_cb.png");
          

          效果如图

          OpenCV相机标定的全过程记录

          Vision是我个人创建的视觉类,可以用来绘制标准的棋盘格。

          头文件vision.h

          //
          // Created by czh on 18-10-16.
          //
          #ifndef OPENGL_PRO_VISION_H
          #define OPENGL_PRO_VISION_H
          
          #include "opencv2/opencv.hpp"
          #include <opencv2/core/core.hpp>
          #include <opencv2/highgui/highgui.hpp>
          #include <opencv2/imgcodecs/imgcodecs.hpp>
          
          #include "iostream"
          
          class Vision {
          public:
              static cv::Mat read(std::string file_path, int flags = cv::IMREAD_ANYCOLOR | cv::IMREAD_ANYDEPTH);
          
              static cv::Mat write(std::string file_path, int flags = cv::IMREAD_ANYCOLOR | cv::IMREAD_ANYDEPTH);
          
              static void dispConfig(cv::Mat img);
          
              static cv::Mat makeCheckerboard(int bkgWidth, int bkgHeight, int sqXnum, int sqYnum = 0, int borderThickness = 0, char *savePath = NULL);
          private:
          
          };
          #endif //OPENGL_PRO_VISION_H
          

          源文件vision.cpp

          //
          // Created by czh on 18-10-16.
          //
          
          #include "vision.h"
          #include "string.h"
          
          using namespace std;
          using namespace cv;
          
          const char *findName(const char *ch) {
              const char *name = strrchr(ch, '/');
              return ++name;
          }
          
          cv::Mat Vision::read(std::string file_path, int flags) {
              printf("#Vision read\n");
              cv::Mat img;
              img = cv::imread(file_path, flags);
              if (img.data == NULL) {
                  printf("\tError:vision read\n");
              } else {
                  dispConfig(img);
              }
              return img;
          }
          
          void Vision::dispConfig(cv::Mat img) {
              printf("\tpixel:%d*%d, channels:%d\n", img.size().width, img.size().height, img.channels());
          }
          
          cv::Mat Vision::makeCheckerboard(int bkgWidth, int bkgHeight, int sqXnum, int sqYnum, int thickNum, char *savePath) {
              if(sqYnum == 0){
                  sqYnum = sqXnum;
              }
              if(savePath == NULL){
                  char *defaultPath = (char *)"../res/calibration/maths.png";
                  savePath = defaultPath;
              }
              int checkboardX = 0;//棋盘x坐标
              int checkboardY = 0;//棋盘y坐标
              int xLen = bkgWidth / sqXnum;//x方格长度
              int yLen = bkgHeight / sqYnum;//y方格长度
              cv::Mat img(bkgHeight + thickNum * 2, bkgWidth + thickNum * 2, CV_8UC4, cv::Scalar(0, 255, 255, 255));
              for (int i = 0; i < img.rows; i++) {
          
                  for (int j = 0; j < img.cols; j++) {
          
                      if (i < thickNum || i >= thickNum + bkgHeight || j < thickNum || j >= thickNum + bkgWidth) {
                          img.at<Vec<uchar, 4>>(i, j) = cv::Scalar(0, 0, 0, 255);
                          continue;
                      }
                      checkboardX = j - thickNum;
                      checkboardY = i - thickNum;
                      if (checkboardY / yLen % 2 == 0) {
                          if ((checkboardX) / xLen % 2 == 0) {
                              img.at<Vec<uchar, 4>>(i, j) = cv::Scalar(255, 255, 255, 255);
                          } else {
                              img.at<Vec<uchar, 4>>(i, j) = cv::Scalar(0, 0, 0, 255);
                          }
                      }
                      else{
                          if ((checkboardX) / xLen % 2 != 0) {
                              img.at<Vec<uchar, 4>>(i, j) = cv::Scalar(255, 255, 255, 255);
                          } else {
                              img.at<Vec<uchar, 4>>(i, j) = cv::Scalar(0, 0, 0, 255);
                          }
                      }
                  }
              }
              imwrite(savePath, img);    //保存生成的图片
              printf("#makeCheckerboard %d*%d\n", bkgWidth + thickNum, bkgHeight + thickNum);
              return img;
          }
          

          用A4纸打印棋盘格,相机拍摄照片。

          我偷懒,拿了别人的标定照片

          OpenCV相机标定的全过程记录

          三、相机标定

          下面是相机标定代码

          cv::imwrite("../blizzard/res/calibration/cb_source.png", cb_source);
          
              printf("#Start scan corner\n");
              cv::Mat img;
              std::vector<cv::Point2f> image_points;
              std::vector<std::vector<cv::Point2f>> image_points_seq; /* 保存检测到的所有角点 */
              if (cv::findChessboardCorners(cb_source, cv::Size(aqXnum, aqYnum), image_points, 0) == 0) {
                  printf("#Error: Corners not find ");
                  return 0;
              } else {
                  cvtColor(cb_source, img, CV_RGBA2GRAY);
                  cv::imwrite("../blizzard/res/calibration/cb_gray.png", img);
                  //find4QuadCornerSubpix(img, image_points, cv::Size(5, 5));
          
                  cv::cornerSubPix(img, image_points, cv::Size(11, 11), cv::Size(-1, -1),
                                   cv::TermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 30, 0.01));
          
                  image_points_seq.push_back(image_points);
          
                  cv::Mat cb_corner;
                  cb_corner = cb_source.clone();
                  drawChessboardCorners(cb_corner, cv::Size(aqXnum, aqYnum), image_points, true);
                  cv::imwrite("../blizzard/res/calibration/cb_corner.png", cb_corner);
              }
          
              printf("#Start calibrate\n");
              cv::Size square_size = cv::Size(14.2222, 12);
              std::vector<std::vector<cv::Point3f>> object_points; /* 保存标定板上角点的三维坐标 */
              cv::Mat cameraMatrix = cv::Mat(3, 3, CV_32FC1, cv::Scalar::all(0)); /* 摄像机内参数矩阵 */
              cv::Mat distCoeffs = cv::Mat(1, 5, CV_32FC1, cv::Scalar::all(0)); /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */
              std::vector<cv::Mat> tvecsMat;  /* 每幅图像的旋转向量 */
              std::vector<cv::Mat> rvecsMat;  /* 每幅图像的平移向量 */
          
              std::vector<cv::Point3f> realPoint;
              for (int i = 0; i < aqYnum; i++) {
                  for (int j = 0; j < aqXnum; j++) {
                      cv::Point3f tempPoint;
                      /* 假设标定板放在世界坐标系中z=0的平面上 */
                      tempPoint.x = i * square_size.width;
                      tempPoint.y = j * square_size.height;
                      tempPoint.z = 0;
                      realPoint.push_back(tempPoint);
                  }
              }
              object_points.push_back(realPoint);
          
              printf("#objectPoints: %ld\n", sizeof(object_points[0]));
              std::cout << object_points[0] << std::endl;
          
              printf("#image_points: %ld\n", sizeof(image_points_seq[0]));
              std::cout << image_points << std::endl;
          
              printf("#image size\n");
              std::cout << SCREEN_WIDTH << "*" << SCREEN_HEIGHT << std::endl;
          
              cv::calibrateCamera(object_points, image_points_seq, cb_source.size(), cameraMatrix, distCoeffs, rvecsMat, tvecsMat,
                                  CV_CALIB_FIX_K3);
          
              std::cout << "tvecsMat:\n" << tvecsMat[0] << std::endl;
              std::cout << "rvecsMat:\n" << rvecsMat[0] << std::endl;
          
              std::cout << "#cameraMatrix:\n" << cameraMatrix << std::endl;
              std::cout << "#distCoeffs:\n" << distCoeffs << std::endl;
          

          四、对图片进行校正

          	cv::Mat cb_final;
          
              cv::Mat mapx = cv::Mat(cb_source.size(), CV_32FC1);
              cv::Mat mapy = cv::Mat(cb_source.size(), CV_32FC1);
              cv::Mat R = cv::Mat::eye(3, 3, CV_32F);
              //initUndistortRectifyMap(cameraMatrix, distCoeffs, R, cv::Mat(), cb_source.size(), CV_32FC1,
              //                        mapx, mapy);
              //cv::remap(cb_source, cb_final, mapx, mapy, cv::INTER_LINEAR);
          
              undistort(cb_source, cb_final, cameraMatrix, distCoeffs);
              
              cv::imwrite("../blizzard/res/calibration/cb_final.png", cb_final);
          

          1.校正前的图片

          OpenCV相机标定的全过程记录

          2.校正后的图片

          OpenCV相机标定的全过程记录

          总结 

          声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。