【OpenCV】 相机标定 calibrateCamera

简介

本篇文章通过《学习Opencv3》的18.01例子来介绍相机标定流程,本文不涉及OpenCV calibrateCamera()函数的数学原理。如果想了解数学原理请参考《学习Opencv3》p578或者张正友相机标定法原理与实现

本篇文章将会介绍《学习Opencv3》的18.01例子中的几个重点函数,并展示详细注释的代码,以及标定时注意事项

calibrateCamera()的实质

OpenCV求解相机参数分为两步:1、求解焦距和偏移,2、求解畸变参数。

有多种方法来求解相机参数,OpenCV选用求解焦距和偏移的算法是张正友相机标定法。因为,张正友相机标定法平面物体上表现良好,但OpenCV求解畸变参数的方法是参考Brown--《Close-range camera calibration》。

Code

简要分析

1.首先,Code会读取棋盘上的角点并调用calibrateCamera自行校准

读取棋盘上角点利用findChessboardCorners()函数,并通过drawChessboardCorners()画在输入图像上类似于图1。

图1

2.然后保存校准矩阵,之后重新加载校准矩阵,进行图像去畸变展示。

3.然后将图像去畸变实时显示。

Code展示

// Example 18-1. Reading a chessboard’s width and height, reading and collecting
// the requested number of views, and calibrating the camera
#include <iostream>
#include <opencv2/opencv.hpp>

using std::vector;
using std::cout;
using std::cerr;
using std::endl;

void help(char **argv) {  // todo rewrite this
    cout << "\n\n"
         << "Example 18-1:\nReading a chessboard’s width and height,\n"
         << "              reading and collecting the requested number of views,\n" 
         << "              and calibrating the camera\n\n" 
         << "Call:\n" << argv[0] << " <board_width> <board_height> <number_of_boards> <if_video,_delay_between_framee_capture> <image_scaling_factor>\n\n"
         << "Example:\n" << argv[0] << " 9 6 15 500 0.5\n"
         << "-- to use the checkerboard9x6.png provided\n\n"
         << " * First it reads in checker boards and calibrates itself\n" 
         << " * Then it saves and reloads the calibration matricies\n"
         << " * Then it creates an undistortion map and finally\n"
         << " * It displays an undistorted image\n"
         << endl;
}

int main(int argc, char *argv[]) {
    int n_boards = 0;           // will be set by input list
    float image_sf = 0.5f;      // image scaling factor
    float delay = 1.f;
    int board_w = 0;
    int board_h = 0;

    if (argc < 4 || argc > 6) {
        cout << "\nERROR: Wrong number of input parameters\n";
        help(argv);
        return -1;
    }

    board_w = atoi(argv[1]);
    board_h = atoi(argv[2]);
    n_boards = atoi(argv[3]);

    if (argc > 4) {
        delay = atof(argv[4]);
    }
    if (argc > 5) {
        image_sf = atof(argv[5]);
    }

    /*
    image_sf是一个用来缩放图像的尺度因子(scale factor)。            
        角点检测的稳定性: 通过缩放图像,可以在一定程度上增强棋盘格角点检测的稳定性。
            有时候,一些图像处理算法对于图像的尺寸大小比较敏感,通过调整图像的尺寸可以帮助算法更好地检测到棋盘格角点。
        提高匹配精度: 调整图像的尺寸可以使棋盘格角点在缩小或放大后仍然保持一定的像素间距,有助于提高匹配的准确性。这样在后续的相机标定过程中,可以更准确地匹配到对应的图像坐标和三维空间坐标。
        加快算法速度: 在某些情况下,缩小图像的尺寸可以减少计算量,从而加快算法的执行速度。特别是对于较大分辨率的图像,通过缩小尺寸可以降低处理的复杂度,提高算法的效率。
    */

    int board_n = board_w * board_h;
    cv::Size board_sz = cv::Size(board_w, board_h);
    cv::VideoCapture capture(0);
    if (!capture.isOpened()) {
        cout << "\nCouldn't open the camera\n";
        help(argv);
        return -1;
    }

    // ALLOCATE STORAGE
    //
    vector<vector<cv::Point2f> > image_points;
    vector<vector<cv::Point3f> > object_points;

    // Capture corner views: loop until we've got n_boards successful
    // captures (all corners on the board are found).
    //
    double last_captured_timestamp = 0;
    cv::Size image_size;
    while (image_points.size() < (size_t)n_boards) {
    cv::Mat image0, image;
    capture >> image0;
    image_size = image0.size();
    cv::resize(image0, image, cv::Size(), image_sf, image_sf, cv::INTER_LINEAR);

    // Find the board
    //
    vector<cv::Point2f> corners;
    bool found = cv::findChessboardCorners(image, board_sz, corners);

    // Draw it
    //
    drawChessboardCorners(image, board_sz, corners, found);

    // If we got a good board, add it to our data
    //
    double timestamp = static_cast<double>(clock()) / CLOCKS_PER_SEC;
    if (found && timestamp - last_captured_timestamp > 1) {
        last_captured_timestamp = timestamp;
        image ^= cv::Scalar::all(255);
        /*
        代码使用了异或运算符 ^,并且将图像 image 与所有像素值为255的白色 cv::Scalar 进行异或操作。
        换句话说,这行代码将图像中所有像素值与255进行逐位异或操作,实现了反转图像像素的效果。
        具体来说,对于每个像素的每个通道,如果像素值为0,异或255后为255;
        如果像素值为255,异或255后为0。因此,总体上就是将原图像中的亮度值取反,白色变为黑色,黑色变为白色,实现了一种图像反色的效果。
        这种操作常常用于在图像处理中实现一些效果,比如图像的反色展示或者图像的二值化处理。
        */
        cv::Mat mcorners(corners);
        /*
        将存储棋盘格角点的corners向量转换为cv::Mat类型的mcorners矩阵,并对矩阵中的数据进行缩放操作。
        具体解释如下:
        cv::Mat mcorners(corners);: 这一行代码通过将corners向量作为参数,创建了一个新的cv::Mat类型的mcorners矩阵。这将导致mcorners矩阵拥有与corners向量相同的数据内容。
        mcorners *= (1.0 / image_sf);: 这行代码对mcorners矩阵中的所有元素进行缩放操作。具体地,每个元素除以image_sf,这样就实现了对角点坐标的缩放操作。
        这是由于之前将图像缩放了image_sf倍,而角点坐标也需要相应进行缩放处理。
        通过这两行代码,将角点坐标存储在corners向量中的数据转换为了cv::Mat类型的矩阵,
        并对矩阵中的数据进行了缩放操作。这是为了保持角点坐标和图像坐标的一致性。
         */
        // 后续似乎没有用到
        // do not copy the data
        mcorners *= (1.0 / image_sf);

        // scale the corner coordinates
        image_points.push_back(corners);
        object_points.push_back(vector<cv::Point3f>());
        vector<cv::Point3f> &opts = object_points.back();

        opts.resize(board_n);
        for (int j = 0; j < board_n; j++) {
            opts[j] = cv::Point3f(static_cast<float>(j / board_w),
                                  static_cast<float>(j % board_w), 0.0f);
        }
        cout << "Collected our " << static_cast<uint>(image_points.size())
             << " of " << n_boards << " needed chessboard images\n" << endl;
    }
    cv::imshow("Calibration", image);

    // show in color if we did collect the image
    if ((cv::waitKey(30) & 255) == 27)
      return -1;

    }

    // END COLLECTION WHILE LOOP.
    cv::destroyWindow("Calibration");
    cout << "\n\n*** CALIBRATING THE CAMERA...\n" << endl;

    // CALIBRATE THE CAMERA!
    //
    cv::Mat intrinsic_matrix, distortion_coeffs;
    double err = cv::calibrateCamera(
        object_points, image_points, image_size, intrinsic_matrix,
        distortion_coeffs, cv::noArray(), cv::noArray(),
        cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_PRINCIPAL_POINT);

    /*
    err表示相机标定的平均重投影误差。
    在 OpenCV 中,calibrateCamera 函数会返回相机标定的重投影误差,
    即用标定结果对相机拍摄的图像进行重投影并计算误差的平均值。
    */

   /*
   一些常见的重投影误差标准范围:
    一般标准: 对于一般应用,重投影误差在0.1到1像素之间可以被接受。
    精度要求高: 对于需要高精度测量的应用,重投影误差应该控制在0.1像素以下。
    实时应用: 对于实时图像处理或运动跟踪等应用,重投影误差在1到2像素之间可能是可以接受的。
   */

    // SAVE THE INTRINSICS AND DISTORTIONS
    cout << " *** DONE!\n\nReprojection error is " << err
         << "\nStoring Intrinsics.xml and Distortions.xml files\n\n";
    cv::FileStorage fs("intrinsics.xml", cv::FileStorage::WRITE);
    fs << "image_width" << image_size.width << "image_height" << image_size.height
       << "camera_matrix" << intrinsic_matrix << "distortion_coefficients"
       << distortion_coeffs;
    fs.release();

    // EXAMPLE OF LOADING THESE MATRICES BACK IN:
    fs.open("intrinsics.xml", cv::FileStorage::READ);
    cout << "\nimage width: " << static_cast<int>(fs["image_width"]);
    cout << "\nimage height: " << static_cast<int>(fs["image_height"]);
    cv::Mat intrinsic_matrix_loaded, distortion_coeffs_loaded;
    fs["camera_matrix"] >> intrinsic_matrix_loaded;
    fs["distortion_coefficients"] >> distortion_coeffs_loaded;
    cout << "\nintrinsic matrix:" << intrinsic_matrix_loaded;
    cout << "\ndistortion coefficients: " << distortion_coeffs_loaded << endl;

    // Build the undistort map which we will use for all
    // subsequent frames.
    //
    cv::Mat map1, map2;
    cv::initUndistortRectifyMap(intrinsic_matrix_loaded, distortion_coeffs_loaded,
                              cv::Mat(), intrinsic_matrix_loaded, image_size,
                              CV_16SC2, map1, map2);

    // Just run the camera to the screen, now showing the raw and
    // the undistorted image.
    //
    for (;;) {
        cv::Mat image, image0;
        capture >> image0;

        if (image0.empty()) {
            break;
        }
        cv::remap(image0, image, map1, map2, cv::INTER_LINEAR,
            cv::BORDER_CONSTANT, cv::Scalar());
        cv::imshow("Undistorted", image);
        if ((cv::waitKey(30) & 255) == 27) {
            break;
        }
    }

    return 0;
}

输入分析

    int n_boards = 0;           // will be set by input list
    float image_sf = 0.5f;      // image scaling factor
    float delay = 1.f;
    int board_w = 0;
    int board_h = 0;

    if (argc < 4 || argc > 6) {
        cout << "\nERROR: Wrong number of input parameters\n";
        help(argv);
        return -1;
    }

    board_w = atoi(argv[1]);
    board_h = atoi(argv[2]);
    n_boards = atoi(argv[3]);

    if (argc > 4) {
        delay = atof(argv[4]);
    }
    if (argc > 5) {
        image_sf = atof(argv[5]);
    }

Help()函数主要是根据具体使用的棋盘来传入参数。

void help(char **argv) {  // todo rewrite this
    cout << "\n\n"
         << "Example 18-1:\nReading a chessboard’s width and height,\n"
         << "              reading and collecting the requested number of views,\n" 
         << "              and calibrating the camera\n\n" 
         << "Call:\n" << argv[0] << " <board_width> <board_height> <number_of_boards> <if_video,_delay_between_framee_capture> <image_scaling_factor>\n\n"
         << "Example:\n" << argv[0] << " 9 6 15 500 0.5\n"
         << "-- to use the checkerboard9x6.png provided\n\n"
         << " * First it reads in checker boards and calibrates itself\n" 
         << " * Then it saves and reloads the calibration matricies\n"
         << " * Then it creates an undistortion map and finally\n"
         << " * It displays an undistorted image\n"
         << endl;
}

<board_width>:棋盘格横向(左右)内角点数。

<board_height>:棋盘格纵向(上下)内角点数。

如图2所示:

图2

<number_of_boards>:采集的图片数量(25张以上为宜)

<if_video,_delay_between_framee_capture>:采集图像是视频流的延时时间(应该是ms)

<image_scaling_factor>:图像的缩放因子,详细作用见下文。

打开视频流

  cv::VideoCapture capture(0);
    if (!capture.isOpened()) {
        cout << "\nCouldn't open the camera\n";
        help(argv);
        return -1;
    }

调用findChessboardCorners()提取角点

vector<cv::Point2f> corners;
bool found = cv::findChessboardCorners(image, board_sz, corners);

bool cv::findChessboardCorners(                //如果寻找到角点则返回true

       cv::InputArray    image,                        //输入的棋盘格图像,格式8UC1 or8UC3

       cv::Size               patternSize,                //行列对应的角点数

       cv::OutputArray corners,                      //检测到的角点矩阵,作为函数输出

       int                        fags                            //参数项,下面详细介绍

);

findChessboardCorners()函数

参数image,:将包含棋盘的单个图像作为参数,该图像必须是8比特图像。

参数patternsize:表示棋盘的每行和每列有多少角点(例如cv::Size(cols,rows))。这个值是内角点数;因此,对于一个9×6棋盘,正确的值是cv::Size(8, 5)。

Note:在实际中,使用不对称的偶-奇维数的棋盘格通常更为方便,例如(5,6)。这种偶-奇不对称模式使棋盘只有一个对称轴,所以棋盘的方向可以唯一确定。

参数corners:记录角点位置的输出矩阵。用像素坐标来表示角点位置的每个值。

参数flags:用于实现一个或多个附加滤波步骤,以帮助找到棋盘上的角点。可以使用布尔OR来组合以下任意或所有的参数:

CV::CALIB_CBADAPTIVE_THRESH

cv::findchessboardCorners()的默认方式是根据平均亮度对图像进行阈值化,如果设置此标志,则会使用自适应阈值法。

CV::CALIB_CB_NORMALIZE_IMAGE

如果设置此标志,则在应用阈值化操作之前使用cv::equalizeHist()来归一化图像。

CV::CALIB_CB_FILTER_QUADS

一旦图像阈值化,算法就会尝试从棋盘上的黑色方块的透视图中找出四边形。这是一个近似,因为假设四边形的每个边缘的线是直的,当图像中存在径向畸变时,就会不正确。如果设置了这个标志,就会对这些四边形应用各种附加约束,以防出现错误的四边形。

CV::CALIB_CV_FASTCHECK

当出现此标志时,则对图像进行快速扫描,以确保图像中存在角点。如果不存在角点,则直接跳过此图像。如果确定输入数据是“干净的”并且每幅图像中都有棋盘,则此标志就不需要存在。否则,如果在输入中存在没有棋盘的图像,那么使用该标志将节省大量的时间。

如果可以找到并排序图案中的所有角点,cv::findChessboardcorners()的返回值将被设置为true否则为false。

Note:如果参数patternsize设置不正确,比如cv::Size(8, 5),设置成cv::Size(6, 4)将会引起排序图案中角点困难,从而不容易提取图像帧。

在此处,排序意味着可以构建找到的点的模型,这与它们在平面上实际上是共线点的集合的命题是一致的。显然,并不是所有的图像都包含49个点,这是由平面上常规的7x7网格生成的。

绘制棋盘格角点

drawChessboardCorners(image, board_sz, corners, found);

特别是在调试时,经常需要把找到的棋盘角点绘制到图像上(通常是第一次计算角点的图像);

这样,我们可以看到投影的角点是否与观察到的角点相匹配。

为此,函数cv::drawChessboardCorners()将cv::findchessboardcorners()找到的角点绘制到提供的图像上。

如果没有找到所有的角点,则可用的角点将被表示为小的红色圆圈

如果整个图案上的角点都找到,那么角点将被绘制成不同的颜色(每一行都将有自己的颜色),并将角点以一定顺序用线连接起来。

void cv::drawChessboardCorners(

       cv::InputOutputArray      image,

       cv::Size                             patternSize,

       cv::InputArray                  corners,

       bool                                   patternWasFound

);

参数image:需要绘制的图像。由于角点是用有颜色的圆圈表示的,因此必须为8位的彩色图像(8UC3)。如果不是三通道图像,则需要转换为三通道图像。

参数patternSize、corners与cV::findChessboardCorners()中对应的参数一样。

参数patternWasFound表示是否整个棋盘图案上的角点都被成功找到,这可以设置为cv::findChessboardCcorners()的返回值。

图3显示了在棋盘图像上应用cv::drawChessboardcorners()的结果。

图3

image ^= cv::Scalar::all(255);

代码使用了异或运算符 ^,并且将图像 image 与所有像素值为255的白色 cv::Scalar 进行异或操作。

换句话说,这行代码将图像中所有像素值与255进行逐位异或操作,实现了反转图像像素的效果。

具体来说,对于每个像素的每个通道,如果像素值为0,异或255后为255;

 如果像素值为255,异或255后为0。因此,总体上就是将原图像中的亮度值取反,白色变为黑色,黑色变为白色,实现了一种图像反色的效果。

这种操作常常用于在图像处理中实现一些效果,比如图像的反色展示或者图像的二值化处理。效果如图4所示,这个操作是为了提醒图像已采集。

图4

添加image_points、object_points

image_points是图像的角点、object_points是对应的世界三维点。

        scale the corner coordinates
        image_points.push_back(corners);
        object_points.push_back(vector<cv::Point3f>());
        vector<cv::Point3f> &opts = object_points.back();

        opts.resize(board_n);
        for (int j = 0; j < board_n; j++) {
            opts[j] = cv::Point3f(static_cast<float>(j / board_w),
                                  static_cast<float>(j % board_w), 0.0f);
        }
        cout << "Collected our " << static_cast<uint>(image_points.size())
             << " of " << n_boards << " needed chessboard images\n" << endl;

object_points的设定格式将在介绍calibrateCamera()时说明。

调用calibrateCamera()标定相机

    cv::Mat intrinsic_matrix, distortion_coeffs;
    double err = cv::calibrateCamera(
        object_points, image_points, image_size, intrinsic_matrix,
        distortion_coeffs, cv::noArray(), cv::noArray(),
        cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_PRINCIPAL_POINT);

double cv::calibrateCamera(

       cv::InputArrayOfArrays          objectPoints

       cv::InputArrayOfArrays          imagePoints,

       cv::Size                                    imageSize,

       cv::InputOutputArray              cameraMatrix,

       cv::InputOutputArray              distCoeffs,

       cv::0utputArrayOfArrays       rvecs,

       cv::0utputArrayOfArrays       tvecs,

       int                                             fags       =     0

       cv::TermCriteria                      criteria  = cv::TermCriteria(…)

);

参数obiectPoints:它是向量的向量,每个向量包含特定图像的标定图案上的点的坐标。那些坐标在物体坐标系中,所以可以简单使它们在x维和y维中是整数,而在z维中为零。

Note:定义objectPoints输入时,将隐式更改cv::calibrateCamera的某些输出的比例。具体来说,会影响tvecs输出。如果棋盘上的一个角点是(0,0,0),下一个是在(0,1,0),再下一个是在(0,2,0)等等,那么隐含地说明,想要在“棋盘方格”中测量距离。如果想要输出的物理单位,必须以该物理单位测量棋盘。例如,如果想要以米为单位定义距离,那么需要测量棋盘,并用米来得到正确的方格尺寸。如果方格为25mm,则应将相同的角点设置为(0,0,0),(0,0.025,0),(0,0.050,0)等等。相反,相机内参矩阵总是以像素为单位表示的。

但是为什么设定objectPoints为对应比例,即可得到内参?

       可能与成像是一个射影变换有关。后续在研究。

参数imagePoints:它也是向量的向量,并且包含每个图像中找到的每个点的位置。如果使用的是棋盘,则每个向量将是相应图像中的角点输出矩阵。

imagesize参数只是告诉cv::calibrateCamera()提取imagePoints中的点的图像有多大(以像素为单位)。

相机的内在参数返回到cameraMatrix和distCoeffs矩阵中。前者将包含线性内在参数,应为3x3矩阵。后者可以是4,5或8个元素。如果distCoeffs的长度为4,则返回的矩阵将包含系数(k1,k2,p1,和p2)。如果长度为5或8,则元素分别为(k1,k2,p1,p2和k3)或(k1,k2,p1,p2,k3,k4,k5,和k6)。五元素形式通常只适用于鱼眼透镜。只有当设置CV::CALIB_RATIONAL_MODEL,并且对非常高精度的特殊透镜进行校准时,才能使用8元素形式。请着重记住,需要的图像数量将随着求解的参数数量而急剧增加。

rvecs和tvecs矩阵是向量的向量,类似于输入点的矩阵。它们分别包含每个棋盘的旋转矩阵(以Rodrigues形式表示,即以三分量向量表示)以及平移矩阵。

通过优化找到参数是一个极富艺术性的过程。有时试图一次性求解所有参数可能会产生不准确或不均衡的结果,特别是在参数空间中选择的初始起始位置远离实际结果时。因此,通过分段逼近一个好的参数起始位置,通常可以更好地“解决”此问题。为此,我们经常固定一些参数来求解其他参数,然后把其他参数固定并求解原来的参数等等。最后,当我们认为我们所有的参数都接近于实际的结果时,我们把求得的参数作为起点并求解所有的参数。OpenCV允许你通过设置fags来控制此过程,

flags参数允许对标定过程进行更精确的控制。以下值可以根据需要与布尔OR运算组合在一起。

可以设置的参数如下:

cv::CALIB_USE_INTRINSIC_GUESS

cv::CALIB_FIXPRINCIPAL_POINT

cv::CALIB_FIXASPECT_RATIO

cv::CALIB_FIX FOCAL_LENGTH

cv::CALIB_FIX_K1,cv::CALIB_FIX_K2,...cv::CALIB_FIX_K6

cv::CALIB_RATIONAL_MODEL

cv::calibrateCamera()的最后一个参数是终止标准。通常,终止标准可以是迭代次数,“epsilon”值或两者都有。在使用epsilon值的情况下,计算的是重投影误差。重投影误差是三维点到图像平面上的计算(投影)位置与原始图像中相应点的实际位置之间的距离平方之和。

Note:err表示相机标定的平均重投影误差。

        在 OpenCV 中,calibrateCamera 函数会返回相机标定的重投影误差,

        即用标定结果对相机拍摄的图像进行重投影并计算误差的平均值。

        一些常见的重投影误差标准范围:

        一般标准: 对于一般应用,重投影误差在0.1到1像素之间可以被接受。

        精度要求高: 对于需要高精度测量的应用,重投影误差应该控制在0.1像素以下。

        实时应用: 对于实时图像处理或运动跟踪等应用,重投影误差在1到2像素之间可能是可以接受的。

保存与加载矩阵

    cout << " *** DONE!\n\nReprojection error is " << err
         << "\nStoring Intrinsics.xml and Distortions.xml files\n\n";
    cv::FileStorage fs("intrinsics.xml", cv::FileStorage::WRITE);
    fs << "image_width" << image_size.width << "image_height" << image_size.height
       << "camera_matrix" << intrinsic_matrix << "distortion_coefficients"
       << distortion_coeffs;
    fs.release();

    // EXAMPLE OF LOADING THESE MATRICES BACK IN:
    fs.open("intrinsics.xml", cv::FileStorage::READ);
    cout << "\nimage width: " << static_cast<int>(fs["image_width"]);
    cout << "\nimage height: " << static_cast<int>(fs["image_height"]);
    cv::Mat intrinsic_matrix_loaded, distortion_coeffs_loaded;
    fs["camera_matrix"] >> intrinsic_matrix_loaded;
    fs["distortion_coefficients"] >> distortion_coeffs_loaded;
    cout << "\nintrinsic matrix:" << intrinsic_matrix_loaded;
    cout << "\ndistortion coefficients: " << distortion_coeffs_loaded << endl;

调用initUndistortRectifyMap()、remap()组合去图像畸变

    cv::Mat map1, map2;
    cv::initUndistortRectifyMap(intrinsic_matrix_loaded, distortion_coeffs_loaded,
                              cv::Mat(), intrinsic_matrix_loaded, image_size,
                              CV_16SC2, map1, map2);

    // Just run the camera to the screen, now showing the raw and
    // the undistorted image.
    //
    for (;;) {
        cv::Mat image, image0;
        capture >> image0;

        if (image0.empty()) {
            break;
        }
        cv::remap(image0, image, map1, map2, cv::INTER_LINEAR,
            cv::BORDER_CONSTANT, cv::Scalar());
        cv::imshow("Undistorted", image);
        if ((cv::waitKey(30) & 255) == 27) {
            break;
        }
    }

OpenCV提供了可以直接使用的矫正算法,即通过输入原始图像和由函数cv::calibrateCamera()得到的畸变系数,生成矫正后的图像。既可以只用函数cv::undistort()使用该算法一次性完成所需的任务,也可以用一对函数cv::initUndistortRectifyMap()和cv::remap()来更高效的去除畸变,这通常适用于视频或者同一相机中获取多个图像的应用中。

为什么有了一次性完成所需的任务的cv::undistort(),还需要cv::initUndistortRectifyMap()和cv::remap()?

因为OpenCV在去畸变的过程中分两步:1、计算矫正映射矩阵,2、利用矫正映射矩阵去畸变。

cv::undistort()输入cv::calibrateCamera()得到的畸变系数,每次都需要生成矫正映射矩阵,在进行去畸变。

cv::initUndistortRectifyMap()输入cv::calibrateCamera()得到的畸变系数得到矫正映射矩阵。之后的视频图像就可以直接通过映射矩阵去畸变,从而使去畸变变得高效。

基本过程是首先计算矫正映射,然后将其应用于图像。分开操作的原因是,在大多数实际应用中,只需要计算一次相机的矫正映射,然后随着相机输入新的图像,你将一次又一次地使用这些映射。函数cv::initUndistortRectifyMap()从相机标定信息中计算矫正映射:

void cv::initUndistortRectifyMap(

cv::InputArray           cameraMatrix,

cv::InputArray           distCoeffs,

cv::InputArray           R,

cv::InputArray           newCameraMatrix,

cv::Size                      size,

int                               m1type,

cv::0utputArray        map1,

cv::0utputArray        map2

)

函数cv::initUndistortRectifyMap()计算矫正映射。

前两个参数是相机内参矩阵和畸变系数,都与从cv::calibrateCamera()得到的形式相同。

参数R可以使用或者设置为cv::noArray()。如果使用,它必须是一个3x3的旋转矩阵,这将在矫正前预先使用。该矩阵的功能是补偿相机相对于相机所处的全局坐标系的旋转。

与旋转矩阵类似,newCameraMatrix可用于影响图像的矫正过程。如果使用,它将在矫正之前将图像“更改”为在不同相机的不同内在参数下的图像。实际上,以这种方式改变的是相机中心,而不是焦距。在处理单目成像时通常不会使用它,但在立体图像分析时则会很重要。对于单目图像,通常只需要将此参数设置为cv::noArray()。

参数size是用来进行输出映射的尺寸,对应于用来矫正的图像尺寸。

参数m1type,map1和map2分别指定最终的映射类型(矫正矩阵的格式),提供该映射的存储空间。

m1type的可能值为CV32FC1或CV16SC2,对应于map1的表示类型。当为CV32FC1时,则map2也是CV32FC1类型,当为CV16SC2时,map1将为定点表示(请注意,该单通道矩阵包含插值表系数)。

详细请参考《学习OpenCV3》p587.

cv::remap()矫正图像

一旦计算了矫正映射,就可以使用cv::remap()将它们应用于传入的图像。

cv::remap()函数有两个对应于矫正映射的映射参数,例如由cv::initUndistortRectifyMap()计算得到的映射参数。cv::remap()接受的任何矫正映射格式:双通道浮点型、双矩阵浮点型或定点格式(带或不带插值表索引矩阵)

标定注意事项

在优化25帧的情况下,通过分析重投影误差与主观的去畸变效果:

利用9×6的棋盘格,在三种情况下测试,近距离、远距离、远距离(均匀分布)。

1、近距离(棋盘几乎占满图像)

渐变色代表棋盘格

此时,重投影误差最大

       Err = 0.9076

       Undistortion效果较好,与远距离(均匀分布)展示图片类似。

2、远距离(棋盘占图像1/4,但仅在中央区域)

渐变色代表棋盘格

        Err = 0.31~0.45

        Undistortion效果极差,展示图片如下:

3、远距离(均匀分布,在25帧种棋盘格均匀分布在图像的各个位置)

渐变色代表棋盘格

        Err = 0.50775

        Undistortion效果较好,展示图片如下:

故采集图像时,应该使棋盘格出现在画面的各个位置,特别是边缘位置

参考文献:

《学习OpenCV3》

### 回答1: OpenCV是一个开源的计算机视觉库,提供了很多计算机视觉相关的函数和算法。相机标定是计算机视觉中一个重要的步骤,用于确定相机的内参和外参参数,从而提高计算机视觉的精确度。 opencv相机标定例程是使用OpenCV库函数进行相机标定的示例代码。它包含了一系列对相机进行标定的步骤: 1. 收集标定图像:首先需要收集一组标定图像,这些图像应该包含特定的模式,比如棋盘格。这些图像可以通过相机拍摄或者从其他来源获取。 2. 提取角点:使用OpenCV的函数在标定图像中提取角点。这些角点的提取可以通过函数`findChessboardCorners`来实现,它会返回检测到的角点的坐标。 3. 标定相机:使用提取到的角点坐标,调用OpenCV的函数`calibrateCamera`完成相机的标定。该函数将返回相机的内参矩阵,畸变系数和旋转矩阵。 4. 评估标定结果:标定完成后,可以使用OpenCV的函数`getOptimalNewCameraMatrix`来优化内参矩阵,并使用`initUndistorRectifyMap`函数生成畸变校正的映射。 5. 应用标定结果:使用标定得到的内参矩阵和畸变系数,可以对图像进行畸变校正,使得图像不再有畸变。 OpenCV相机标定例程提供了一个完整的流程,帮助用户准确地对相机进行标定,从而提高计算机视觉算法的准确性和鲁棒性。同时,用户可以根据自己的需求,对例程进行修改和扩展,以适应具体的应用场景。 ### 回答2: OpenCV相机标定例程是一个用于校准相机的程序。相机标定是指确定相机内部和外部参数的过程,以便在三维世界中更准确地测量物体或跟踪物体。 首先,我们需要收集一些被称为棋盘格的二维图像。在例程中,我们使用棋盘格作为标定目标,因为它具有规则的结构和易于检测的特征。然后,我们将这些图像加载到程序中进行处理。 接下来,我们使用OpenCV的标定函数来计算相机的内部参数,例如焦距、主点坐标和径向畸变系数。这些参数将用于校正图像并更准确地测量物体。 在这个例程中,我们使用棋盘格的角点作为特征点来进行标定。我们可以通过使用OpenCV的函数在棋盘格图像中检测角点。然后,我们将这些角点的二维像素坐标与它们在三维空间中的真实坐标进行匹配。 通过对多个图像进行角点的检测和匹配,我们可以获得足够的数据来计算相机的内部参数。一旦内部参数被计算出来,我们就可以将其保存在文件中以备将来使用。 通过相机标定例程,我们可以获得相机的校准参数,从而提高图像的质量和精度。这在计算机视觉应用中特别重要,例如目标跟踪、SLAM(同时定位与地图构建)和立体视觉。 ### 回答3: OpenCV相机标定例程是一种用于标定相机内外参数的方法。相机标定是指确定相机的一些固有参数,如焦距、畸变等,以便于后续图像处理或计算机视觉任务的进行。 OpenCV提供了一个相机标定例程函数`calibrateCamera()`,通过将相机拍摄的多张已知世界坐标和相应图像坐标的图像进行处理,来估计相机的内外参数。 首先,需要收集一组已知世界坐标和相应图像坐标的图像样本。已知世界坐标通常需要以某个坐标系为基准,例如一个棋盘格。然后,通过使用OpenCV提供的函数`findChessboardCorners()`来检测图像中棋盘格角点的位置。 接下来,通过调用`calibrateCamera()`函数,将已知的世界坐标和对应的图像坐标作为输入参数,获得相机的内外参数。该函数将返回相机的一些参数,如相机矩阵、畸变系数、旋转矩阵和平移向量等。 最后,可以使用相机的内外参数进行图像处理或计算机视觉任务。例如,可以通过使用函数`undistort()`对相机采集到的图像进行去畸变操作,从而使图像更符合实际场景。 总之,OpenCV相机标定例程是一种用于确定相机内外参数的方法,通过使用已知的世界坐标和图像坐标的样本,可以得到相机的一些固有参数,以便于后续的图像处理或计算机视觉任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值