首页 > 代码库 > OpenCV Tutorials —— Camera calibration With OpenCV

OpenCV Tutorials —— Camera calibration With OpenCV

获取摄像机参数是为了来处理图像失真或者实现图像度量 ~~

Unfortunately, this cheapness comes with its price: significant distortion. Luckily, these are constants and with a calibration and some remapping we can correct this.

Furthermore, with calibration you may also determine the relation between the camera’s natural units (pixels) and the real world units (for example millimeters).

 

失真 —— 径向畸变和切向畸变

For the radial factor one uses the following formula:

x_{corrected} = x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \y_{corrected} = y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6)

So for an old pixel point at (x,y) coordinates in the input image, its position on the corrected output image will be (x_{corrected} y_{corrected}). The presence of the radial distortion manifests in form of the “barrel” or “fish-eye” effect.

 

Tangential distortion occurs because the image taking lenses are not perfectly parallel to the imaging plane. It can be corrected via the formulas: 切向畸变是由于镜片不完全平行于图像面

x_{corrected} = x + [ 2p_1xy + p_2(r^2+2x^2)] \y_{corrected} = y + [ p_1(r^2+ 2y^2)+ 2p_2xy]

 

So we have five distortion parameters which in OpenCV are presented as one row matrix with 5 columns:

Distortion_{coefficients}=(k_1 \hspace{10pt} k_2 \hspace{10pt} p_1 \hspace{10pt} p_2 \hspace{10pt} k_3)

 

 

Now for the unit conversion we use the following formula:

\left [  \begin{matrix}   x \\   y \\  w \end{matrix} \right ] = \left [ \begin{matrix}   f_x & 0 & c_x \\  0 & f_y & c_y \\   0 & 0 & 1 \end{matrix} \right ] \left [ \begin{matrix}  X \\  Y \\   Z \end{matrix} \right ]

Here the presence of w is explained by the use of homography coordinate system (and w=Z) 单应性系统.

The unknown parameters are f_x and f_y (camera focal lengths 焦距 ) and (c_x, c_y) which are the optical centers expressed in pixels coordinates 像素坐标系统中的光心.

 

If for both axes a common focal length is used with a given a aspect ratio (usually 1), then f_y=f_x*a and in the upper formula we will have a single focal length f.

 

The matrix containing these four parameters is referred to as the camera matrix.

 

While the distortion coefficients are the same regardless of the camera resolutions used, these should be scaled along with the current resolution from the calibrated resolution. 与分辨率无关

 

程序支持的三种校准模式

CHESSBOARD
CIRCLES_GRID
ASYMMETRIC_CIRCLES_GRID
 
大致的程序流程:
1,读入设置文件(直接用FileStorage 完成class的读写) —— 完成初始化设置,并验证输入的有效性
2,读入足够多的图像文件,执行 校准方法
3,根据选择的模式,获取当前模式下关键点的坐标位置
4,计算并保存照相机参数
5,对图像进行失真矫正
(以后用到的时候再细看吧 ~~ )
 
Code
#include <iostream>#include <sstream>#include <time.h>#include <stdio.h>#include <opencv2/core/core.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/calib3d/calib3d.hpp>#include <opencv2/highgui/highgui.hpp>#ifndef _CRT_SECURE_NO_WARNINGS# define _CRT_SECURE_NO_WARNINGS#endifusing namespace cv;using namespace std;static void help(){    cout <<  "This is a camera calibration sample." << endl         <<  "Usage: calibration configurationFile"  << endl         <<  "Near the sample file you‘ll find the configuration file, which has detailed help of "             "how to edit it.  It may be any OpenCV supported file format XML/YAML." << endl;}class Settings{public:    Settings() : goodInput(false) {}    enum Pattern { NOT_EXISTING, CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };    enum InputType {INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST};    void write(FileStorage& fs) const                        //Write serialization for this class    {        fs << "{" << "BoardSize_Width"  << boardSize.width                  << "BoardSize_Height" << boardSize.height                  << "Square_Size"         << squareSize                  << "Calibrate_Pattern" << patternToUse                  << "Calibrate_NrOfFrameToUse" << nrFrames                  << "Calibrate_FixAspectRatio" << aspectRatio                  << "Calibrate_AssumeZeroTangentialDistortion" << calibZeroTangentDist                  << "Calibrate_FixPrincipalPointAtTheCenter" << calibFixPrincipalPoint                  << "Write_DetectedFeaturePoints" << bwritePoints                  << "Write_extrinsicParameters"   << bwriteExtrinsics                  << "Write_outputFileName"  << outputFileName                  << "Show_UndistortedImage" << showUndistorsed                  << "Input_FlipAroundHorizontalAxis" << flipVertical                  << "Input_Delay" << delay                  << "Input" << input           << "}";    }    void read(const FileNode& node)                          //Read serialization for this class    {        node["BoardSize_Width" ] >> boardSize.width;        node["BoardSize_Height"] >> boardSize.height;        node["Calibrate_Pattern"] >> patternToUse;        node["Square_Size"]  >> squareSize;        node["Calibrate_NrOfFrameToUse"] >> nrFrames;        node["Calibrate_FixAspectRatio"] >> aspectRatio;        node["Write_DetectedFeaturePoints"] >> bwritePoints;        node["Write_extrinsicParameters"] >> bwriteExtrinsics;        node["Write_outputFileName"] >> outputFileName;        node["Calibrate_AssumeZeroTangentialDistortion"] >> calibZeroTangentDist;        node["Calibrate_FixPrincipalPointAtTheCenter"] >> calibFixPrincipalPoint;        node["Input_FlipAroundHorizontalAxis"] >> flipVertical;        node["Show_UndistortedImage"] >> showUndistorsed;        node["Input"] >> input;        node["Input_Delay"] >> delay;        interprate();    }    void interprate()    {        goodInput = true;        if (boardSize.width <= 0 || boardSize.height <= 0)        {            cerr << "Invalid Board size: " << boardSize.width << " " << boardSize.height << endl;            goodInput = false;        }        if (squareSize <= 10e-6)        {            cerr << "Invalid square size " << squareSize << endl;            goodInput = false;        }        if (nrFrames <= 0)        {            cerr << "Invalid number of frames " << nrFrames << endl;            goodInput = false;        }        if (input.empty())      // Check for valid input                inputType = INVALID;        else        {            if (input[0] >= ‘0‘ && input[0] <= ‘9‘)            {                stringstream ss(input);                ss >> cameraID;                inputType = CAMERA;            }            else            {                if (readStringList(input, imageList))                    {                        inputType = IMAGE_LIST;                        nrFrames = (nrFrames < (int)imageList.size()) ? nrFrames : (int)imageList.size();                    }                else                    inputType = VIDEO_FILE;            }            if (inputType == CAMERA)                inputCapture.open(cameraID);            if (inputType == VIDEO_FILE)                inputCapture.open(input);            if (inputType != IMAGE_LIST && !inputCapture.isOpened())                    inputType = INVALID;        }        if (inputType == INVALID)        {            cerr << " Inexistent input: " << input;            goodInput = false;        }        flag = 0;        if(calibFixPrincipalPoint) flag |= CV_CALIB_FIX_PRINCIPAL_POINT;        if(calibZeroTangentDist)   flag |= CV_CALIB_ZERO_TANGENT_DIST;        if(aspectRatio)            flag |= CV_CALIB_FIX_ASPECT_RATIO;        calibrationPattern = NOT_EXISTING;        if (!patternToUse.compare("CHESSBOARD")) calibrationPattern = CHESSBOARD;        if (!patternToUse.compare("CIRCLES_GRID")) calibrationPattern = CIRCLES_GRID;        if (!patternToUse.compare("ASYMMETRIC_CIRCLES_GRID")) calibrationPattern = ASYMMETRIC_CIRCLES_GRID;        if (calibrationPattern == NOT_EXISTING)            {                cerr << " Inexistent camera calibration mode: " << patternToUse << endl;                goodInput = false;            }        atImageList = 0;    }    Mat nextImage()    {        Mat result;        if( inputCapture.isOpened() )        {            Mat view0;            inputCapture >> view0;            view0.copyTo(result);        }        else if( atImageList < (int)imageList.size() )            result = imread(imageList[atImageList++], CV_LOAD_IMAGE_COLOR);        return result;    }    static bool readStringList( const string& filename, vector<string>& l )    {        l.clear();        FileStorage fs(filename, FileStorage::READ);        if( !fs.isOpened() )            return false;        FileNode n = fs.getFirstTopLevelNode();        if( n.type() != FileNode::SEQ )            return false;        FileNodeIterator it = n.begin(), it_end = n.end();        for( ; it != it_end; ++it )            l.push_back((string)*it);        return true;    }public:    Size boardSize;            // The size of the board -> Number of items by width and height    Pattern calibrationPattern;// One of the Chessboard, circles, or asymmetric circle pattern    float squareSize;          // The size of a square in your defined unit (point, millimeter,etc).    int nrFrames;              // The number of frames to use from the input for calibration    float aspectRatio;         // The aspect ratio    int delay;                 // In case of a video input    bool bwritePoints;         //  Write detected feature points    bool bwriteExtrinsics;     // Write extrinsic parameters    bool calibZeroTangentDist; // Assume zero tangential distortion    bool calibFixPrincipalPoint;// Fix the principal point at the center    bool flipVertical;          // Flip the captured images around the horizontal axis    string outputFileName;      // The name of the file where to write    bool showUndistorsed;       // Show undistorted images after calibration    string input;               // The input ->    int cameraID;    vector<string> imageList;    int atImageList;    VideoCapture inputCapture;    InputType inputType;    bool goodInput;    int flag;private:    string patternToUse;};static void read(const FileNode& node, Settings& x, const Settings& default_value = http://www.mamicode.com/Settings())"default.xml";    FileStorage fs(inputSettingsFile, FileStorage::READ); // Read the settings    if (!fs.isOpened())    {        cout << "Could not open the configuration file: \"" << inputSettingsFile << "\"" << endl;        return -1;    }    fs["Settings"] >> s;    fs.release();                                         // close Settings file    if (!s.goodInput)    {        cout << "Invalid input detected. Application stopping. " << endl;        return -1;    }    vector<vector<Point2f> > imagePoints;    Mat cameraMatrix, distCoeffs;    Size imageSize;    int mode = s.inputType == Settings::IMAGE_LIST ? CAPTURING : DETECTION;    clock_t prevTimestamp = 0;    const Scalar RED(0,0,255), GREEN(0,255,0);    const char ESC_KEY = 27;    for(int i = 0;;++i)    {      Mat view;      bool blinkOutput = false;      view = s.nextImage();      //-----  If no more image, or got enough, then stop calibration and show result -------------      if( mode == CAPTURING && imagePoints.size() >= (unsigned)s.nrFrames )      {          if( runCalibrationAndSave(s, imageSize,  cameraMatrix, distCoeffs, imagePoints))              mode = CALIBRATED;          else              mode = DETECTION;      }      if(view.empty())          // If no more images then run calibration, save and stop loop.      {            if( imagePoints.size() > 0 )                runCalibrationAndSave(s, imageSize,  cameraMatrix, distCoeffs, imagePoints);            break;      }        imageSize = view.size();  // Format input image.        if( s.flipVertical )    flip( view, view, 0 );        vector<Point2f> pointBuf;        bool found;        switch( s.calibrationPattern ) // Find feature points on the input format        {        case Settings::CHESSBOARD:            found = findChessboardCorners( view, s.boardSize, pointBuf,                CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);            break;        case Settings::CIRCLES_GRID:            found = findCirclesGrid( view, s.boardSize, pointBuf );            break;        case Settings::ASYMMETRIC_CIRCLES_GRID:            found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID );            break;        default:            found = false;            break;        }        if ( found)                // If done with success,        {              // improve the found corners‘ coordinate accuracy for chessboard                if( s.calibrationPattern == Settings::CHESSBOARD)                {                    Mat viewGray;                    cvtColor(view, viewGray, COLOR_BGR2GRAY);                    cornerSubPix( viewGray, pointBuf, Size(11,11),                        Size(-1,-1), TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));                }                if( mode == CAPTURING &&  // For camera only take new samples after delay time                    (!s.inputCapture.isOpened() || clock() - prevTimestamp > s.delay*1e-3*CLOCKS_PER_SEC) )                {                    imagePoints.push_back(pointBuf);                    prevTimestamp = clock();                    blinkOutput = s.inputCapture.isOpened();                }                // Draw the corners.                drawChessboardCorners( view, s.boardSize, Mat(pointBuf), found );        }        //----------------------------- Output Text ------------------------------------------------        string msg = (mode == CAPTURING) ? "100/100" :                      mode == CALIBRATED ? "Calibrated" : "Press ‘g‘ to start";        int baseLine = 0;        Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);        Point textOrigin(view.cols - 2*textSize.width - 10, view.rows - 2*baseLine - 10);        if( mode == CAPTURING )        {            if(s.showUndistorsed)                msg = format( "%d/%d Undist", (int)imagePoints.size(), s.nrFrames );            else                msg = format( "%d/%d", (int)imagePoints.size(), s.nrFrames );        }        putText( view, msg, textOrigin, 1, 1, mode == CALIBRATED ?  GREEN : RED);        if( blinkOutput )            bitwise_not(view, view);        //------------------------- Video capture  output  undistorted ------------------------------        if( mode == CALIBRATED && s.showUndistorsed )        {            Mat temp = view.clone();            undistort(temp, view, cameraMatrix, distCoeffs);        }        //------------------------------ Show image and check for input commands -------------------        imshow("Image View", view);        char key = (char)waitKey(s.inputCapture.isOpened() ? 50 : s.delay);        if( key  == ESC_KEY )            break;        if( key == ‘u‘ && mode == CALIBRATED )           s.showUndistorsed = !s.showUndistorsed;        if( s.inputCapture.isOpened() && key == ‘g‘ )        {            mode = CAPTURING;            imagePoints.clear();        }    }    // -----------------------Show the undistorted image for the image list ------------------------    if( s.inputType == Settings::IMAGE_LIST && s.showUndistorsed )    {        Mat view, rview, map1, map2;        initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),            getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0),            imageSize, CV_16SC2, map1, map2);        for(int i = 0; i < (int)s.imageList.size(); i++ )        {            view = imread(s.imageList[i], 1);            if(view.empty())                continue;            remap(view, rview, map1, map2, INTER_LINEAR);            imshow("Image View", rview);            char c = (char)waitKey();            if( c  == ESC_KEY || c == ‘q‘ || c == ‘Q‘ )                break;        }    }    return 0;}static double computeReprojectionErrors( const vector<vector<Point3f> >& objectPoints,                                         const vector<vector<Point2f> >& imagePoints,                                         const vector<Mat>& rvecs, const vector<Mat>& tvecs,                                         const Mat& cameraMatrix , const Mat& distCoeffs,                                         vector<float>& perViewErrors){    vector<Point2f> imagePoints2;    int i, totalPoints = 0;    double totalErr = 0, err;    perViewErrors.resize(objectPoints.size());    for( i = 0; i < (int)objectPoints.size(); ++i )    {        projectPoints( Mat(objectPoints[i]), rvecs[i], tvecs[i], cameraMatrix,                       distCoeffs, imagePoints2);        err = norm(Mat(imagePoints[i]), Mat(imagePoints2), CV_L2);        int n = (int)objectPoints[i].size();        perViewErrors[i] = (float) std::sqrt(err*err/n);        totalErr        += err*err;        totalPoints     += n;    }    return std::sqrt(totalErr/totalPoints);}static void calcBoardCornerPositions(Size boardSize, float squareSize, vector<Point3f>& corners,                                     Settings::Pattern patternType /*= Settings::CHESSBOARD*/){    corners.clear();    switch(patternType)    {    case Settings::CHESSBOARD:    case Settings::CIRCLES_GRID:        for( int i = 0; i < boardSize.height; ++i )            for( int j = 0; j < boardSize.width; ++j )                corners.push_back(Point3f(float( j*squareSize ), float( i*squareSize ), 0));        break;    case Settings::ASYMMETRIC_CIRCLES_GRID:        for( int i = 0; i < boardSize.height; i++ )            for( int j = 0; j < boardSize.width; j++ )                corners.push_back(Point3f(float((2*j + i % 2)*squareSize), float(i*squareSize), 0));        break;    default:        break;    }}static bool runCalibration( Settings& s, Size& imageSize, Mat& cameraMatrix, Mat& distCoeffs,                            vector<vector<Point2f> > imagePoints, vector<Mat>& rvecs, vector<Mat>& tvecs,                            vector<float>& reprojErrs,  double& totalAvgErr){    cameraMatrix = Mat::eye(3, 3, CV_64F);    if( s.flag & CV_CALIB_FIX_ASPECT_RATIO )        cameraMatrix.at<double>(0,0) = 1.0;    distCoeffs = Mat::zeros(8, 1, CV_64F);    vector<vector<Point3f> > objectPoints(1);    calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);    objectPoints.resize(imagePoints.size(),objectPoints[0]);    //Find intrinsic and extrinsic camera parameters    double rms = calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix,                                 distCoeffs, rvecs, tvecs, s.flag|CV_CALIB_FIX_K4|CV_CALIB_FIX_K5);    cout << "Re-projection error reported by calibrateCamera: "<< rms << endl;    bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs);    totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints,                                             rvecs, tvecs, cameraMatrix, distCoeffs, reprojErrs);    return ok;}// Print camera parameters to the output filestatic void saveCameraParams( Settings& s, Size& imageSize, Mat& cameraMatrix, Mat& distCoeffs,                              const vector<Mat>& rvecs, const vector<Mat>& tvecs,                              const vector<float>& reprojErrs, const vector<vector<Point2f> >& imagePoints,                              double totalAvgErr ){    FileStorage fs( s.outputFileName, FileStorage::WRITE );    time_t tm;    time( &tm );    struct tm *t2 = localtime( &tm );    char buf[1024];    strftime( buf, sizeof(buf)-1, "%c", t2 );    fs << "calibration_Time" << buf;    if( !rvecs.empty() || !reprojErrs.empty() )        fs << "nrOfFrames" << (int)std::max(rvecs.size(), reprojErrs.size());    fs << "image_Width" << imageSize.width;    fs << "image_Height" << imageSize.height;    fs << "board_Width" << s.boardSize.width;    fs << "board_Height" << s.boardSize.height;    fs << "square_Size" << s.squareSize;    if( s.flag & CV_CALIB_FIX_ASPECT_RATIO )        fs << "FixAspectRatio" << s.aspectRatio;    if( s.flag )    {        sprintf( buf, "flags: %s%s%s%s",            s.flag & CV_CALIB_USE_INTRINSIC_GUESS ? " +use_intrinsic_guess" : "",            s.flag & CV_CALIB_FIX_ASPECT_RATIO ? " +fix_aspectRatio" : "",            s.flag & CV_CALIB_FIX_PRINCIPAL_POINT ? " +fix_principal_point" : "",            s.flag & CV_CALIB_ZERO_TANGENT_DIST ? " +zero_tangent_dist" : "" );        cvWriteComment( *fs, buf, 0 );    }    fs << "flagValue" << s.flag;    fs << "Camera_Matrix" << cameraMatrix;    fs << "Distortion_Coefficients" << distCoeffs;    fs << "Avg_Reprojection_Error" << totalAvgErr;    if( !reprojErrs.empty() )        fs << "Per_View_Reprojection_Errors" << Mat(reprojErrs);    if( !rvecs.empty() && !tvecs.empty() )    {        CV_Assert(rvecs[0].type() == tvecs[0].type());        Mat bigmat((int)rvecs.size(), 6, rvecs[0].type());        for( int i = 0; i < (int)rvecs.size(); i++ )        {            Mat r = bigmat(Range(i, i+1), Range(0,3));            Mat t = bigmat(Range(i, i+1), Range(3,6));            CV_Assert(rvecs[i].rows == 3 && rvecs[i].cols == 1);            CV_Assert(tvecs[i].rows == 3 && tvecs[i].cols == 1);            //*.t() is MatExpr (not Mat) so we can use assignment operator            r = rvecs[i].t();            t = tvecs[i].t();        }        cvWriteComment( *fs, "a set of 6-tuples (rotation vector + translation vector) for each view", 0 );        fs << "Extrinsic_Parameters" << bigmat;    }    if( !imagePoints.empty() )    {        Mat imagePtMat((int)imagePoints.size(), (int)imagePoints[0].size(), CV_32FC2);        for( int i = 0; i < (int)imagePoints.size(); i++ )        {            Mat r = imagePtMat.row(i).reshape(2, imagePtMat.cols);            Mat imgpti(imagePoints[i]);            imgpti.copyTo(r);        }        fs << "Image_points" << imagePtMat;    }}bool runCalibrationAndSave(Settings& s, Size imageSize, Mat&  cameraMatrix, Mat& distCoeffs,vector<vector<Point2f> > imagePoints ){    vector<Mat> rvecs, tvecs;    vector<float> reprojErrs;    double totalAvgErr = 0;    bool ok = runCalibration(s,imageSize, cameraMatrix, distCoeffs, imagePoints, rvecs, tvecs,                             reprojErrs, totalAvgErr);    cout << (ok ? "Calibration succeeded" : "Calibration failed")        << ". avg re projection error = "  << totalAvgErr ;    if( ok )        saveCameraParams( s, imageSize, cameraMatrix, distCoeffs, rvecs ,tvecs, reprojErrs,                            imagePoints, totalAvgErr);    return ok;}

OpenCV Tutorials —— Camera calibration With OpenCV