Opencv——相機標定


相機標定的目的:獲取攝像機的內參和外參矩陣(同時也會得到每一幅標定圖像的選擇和平移矩陣),內參和外參系數可以對之后相機拍攝的圖像就進行矯正,得到畸變相對很小的圖像。

相機標定的輸入:標定圖像上所有內角點的圖像坐標,標定板圖像上所有內角點的空間三維坐標(一般情況下假定圖像位於Z=0平面上)。

相機標定的輸出:攝像機的內參、外參系數。

標定流程

1. 准備標定圖片

2. 對每一張標定圖片,提取角點信息

3. 對每一張標定圖片,進一步提取亞像素角點信息

4. 在棋盤標定圖上繪制找到的內角點(非必須,僅為了顯示)

5. 相機標定

6. 對標定結果進行評價

7. 查看標定效果——利用標定結果對棋盤圖進行矯正

 

1. 准備標定圖片


標定圖片需要使用標定板在不同位置、不同角度、不同姿態下拍攝,最少需要3張,以10~20張為宜。標定板需要是黑白相間的矩形構成的棋盤圖,制作精度要求較高,如下圖所示:

2.對每一張標定圖片,提取角點信息

需要使用findChessboardCorners函數提取角點,這里的角點專指的是標定板上的內角點,這些角點與標定板的邊緣不接觸。

 findChessboardCorners函數原型:

//! finds checkerboard pattern of the specified size in the image
CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize,
                                         OutputArray corners,
                                         int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE );

第一個參數Image,傳入拍攝的棋盤圖Mat圖像,必須是8位的灰度或者彩色圖像;

第二個參數patternSize,每個棋盤圖上內角點的行列數,一般情況下,行列數不要相同,便於后續標定程序識別標定板的方向

第三個參數corners,用於存儲檢測到的內角點圖像坐標位置,一般用元素是Point2f的向量來表示:vector<Point2f> image_points_buf;

第四個參數flage:用於定義棋盤圖上內角點查找的不同處理方式,有默認值。

 

 

3. 對每一張標定圖片,進一步提取亞像素角點信息

為了提高標定精度,需要在初步提取的角點信息上進一步提取亞像素信息,降低相機標定偏差,常用的方法是cornerSubPix,另一個方法是使用find4QuadCornerSubpix函數,這個方法是專門用來獲取棋盤圖上內角點的精確位置的,或許在相機標定的這個特殊場合下它的檢測精度會比cornerSubPix更高?

cornerSubPix函數原型:

//! adjusts the corner locations with sub-pixel accuracy to maximize the certain cornerness criteria
CV_EXPORTS_W void cornerSubPix( InputArray image, InputOutputArray corners,
                                Size winSize, Size zeroZone,
                                TermCriteria criteria );

第一個參數image,輸入的Mat矩陣,最好是8位灰度圖像,檢測效率更高;

第二個參數corners,初始的角點坐標向量,同時作為亞像素坐標位置的輸出,所以需要是浮點型數據,一般用元素是Pointf2f/Point2d的向量來表示:vector<Point2f/Point2d> iamgePointsBuf;

第三個參數winSize,大小為搜索窗口的一半;

第四個參數zeroZone,死區的一半尺寸,死區為不對搜索區的中央位置做求和運算的區域。它是用來避免自相關矩陣出現某些可能的奇異性。當值為(-1,-1)時表示沒有死區;

第五個參數criteria,定義求角點的迭代過程的終止條件,可以為迭代次數和角點精度兩者的組合;

find4QuadCornerSubpix函數原型:

//! finds subpixel-accurate positions of the chessboard corners
CV_EXPORTS bool find4QuadCornerSubpix(InputArray img, InputOutputArray corners, Size region_size);

第一個參數img,輸入的Mat矩陣,最好是8位灰度圖像,檢測效率更高;

第二個參數corners,初始的角點坐標向量,同時作為亞像素坐標位置的輸出,所以需要是浮點型數據,一般用元素是Pointf2f/Point2d的向量來表示:vector<Point2f/Point2d> iamgePointsBuf;

第三個參數region_size,角點搜索窗口的尺寸;

在其中一個標定的棋盤圖上分別運行cornerSubPix和find4QuadCornerSubpix尋找亞像素角點,兩者定位到的亞像素角點坐標分別為:

 

4. 在棋盤標定圖上繪制找到的內角點(非必須,僅為了顯示)

drawChessboardCorners函數用於繪制被成功標定的角點,函數原型: 

//! draws the checkerboard pattern (found or partly found) in the image
CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,
                                         InputArray corners, bool patternWasFound );

第一個參數image,8位灰度或者彩色圖像;

第二個參數patternSize,每張標定棋盤上內角點的行列數;

第三個參數corners,初始的角點坐標向量,同時作為亞像素坐標位置的輸出,所以需要是浮點型數據,一般用元素是Pointf2f/Point2d的向量來表示:vector<Point2f/Point2d> iamgePointsBuf;

第四個參數patternWasFound,標志位,用來指示定義的棋盤內角點是否被完整的探測到,true表示別完整的探測到,函數會用直線依次連接所有的內角點,作為一個整體,false表示有未被探測到的內角點,這時候函數會以(紅色)圓圈標記處檢測到的內角點;

5. 相機標定

獲取到棋盤標定圖的內角點圖像坐標之后,就可以使用calibrateCamera函數進行標定,計算相機內參和外參系數,

calibrateCamera函數原型:

//! finds intrinsic and extrinsic camera parameters from several fews of a known calibration pattern.
CV_EXPORTS_W double calibrateCamera( InputArrayOfArrays objectPoints,
                                     InputArrayOfArrays imagePoints,
                                     Size imageSize,
                                     CV_OUT InputOutputArray cameraMatrix,
                                     CV_OUT InputOutputArray distCoeffs,
                                     OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,
                                     int flags=0, TermCriteria criteria = TermCriteria(
                                         TermCriteria::COUNT+TermCriteria::EPS, 30, DBL_EPSILON) );

第一個參數objectPoints,為世界坐標系中的三維點。在使用時,應該輸入一個三維坐標點的向量的向量,即vector<vector<Point3f>> object_points。需要依據棋盤上單個黑白矩陣的大小,計算出(初始化)每一個內角點的世界坐標。

第二個參數imagePoints,為每一個內角點對應的圖像坐標點。和objectPoints一樣,應該輸入vector<vector<Point2f>> image_points_seq形式的變量;

第三個參數imageSize,為圖像的像素尺寸大小,在計算相機的內參和畸變矩陣時需要使用到該參數;

第四個參數cameraMatrix為相機的內參矩陣。輸入一個Mat cameraMatrix即可,如Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0));

第五個參數distCoeffs為畸變矩陣。輸入一個Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0))即可;

第六個參數rvecs為旋轉向量;應該輸入一個Mat類型的vector,即vector<Mat>rvecs;

第七個參數tvecs為位移向量,和rvecs一樣,應該為vector<Mat> tvecs;

第八個參數flags為標定時所采用的算法。有如下幾個參數:

CV_CALIB_USE_INTRINSIC_GUESS:使用該參數時,在cameraMatrix矩陣中應該有fx,fy,u0,v0的估計值。否則的話,將初始化(u0,v0)圖像的中心點,使用最小二乘估算出fx,fy。 
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_RATIONAL_MODEL:計算k4,k5,k6三個畸變參數。如果沒有設置,則只計算其它5個畸變參數。

第九個參數criteria是最優迭代終止條件設定。

在使用該函數進行標定運算之前,需要對棋盤上每一個內角點的空間坐標系的位置坐標進行初始化,標定的結果是生成相機的內參矩陣cameraMatrix、相機的5個畸變系數distCoeffs,另外每張圖像都會生成屬於自己的平移向量和旋轉向量。

6. 對標定結果進行評價

對標定結果進行評價的方法是通過得到的攝像機內外參數,對空間的三維點進行重新投影計算,得到空間三維點在圖像上新的投影點的坐標,計算投影坐標和亞像素角點坐標之間的偏差,偏差越小,標定結果越好。

對空間三維坐標點進行反向投影的函數是projectPoints,函數原型是:

//! projects points from the model coordinate space to the image coordinates. Also computes derivatives of the image coordinates w.r.t the intrinsic and extrinsic camera parameters
CV_EXPORTS_W void projectPoints( InputArray objectPoints,
                                 InputArray rvec, InputArray tvec,
                                 InputArray cameraMatrix, InputArray distCoeffs,
                                 OutputArray imagePoints,
                                 OutputArray jacobian=noArray(),
                                 double aspectRatio=0 );

第一個參數objectPoints,為相機坐標系中的三維點坐標;

第二個參數rvec為旋轉向量,每一張圖像都有自己的選擇向量;

第三個參數tvec為位移向量,每一張圖像都有自己的平移向量;

第四個參數cameraMatrix為求得的相機的內參數矩陣;

第五個參數distCoeffs為相機的畸變矩陣;

第六個參數iamgePoints為每一個內角點對應的圖像上的坐標點;

第七個參數jacobian是雅可比行列式;

第八個參數aspectRatio是跟相機傳感器的感光單元有關的可選參數,如果設置為非0,則函數默認感光單元的dx/dy是固定的,會依此對雅可比矩陣進行調整;

7. 查看標定效果——利用標定結果對棋盤圖進行矯正

利用求得的相機的內參和外參數據,可以對圖像進行畸變的矯正,這里有兩種方法可以達到矯正的目的,分別說明一下。

方法一:使用initUndistortRectifyMap和remap兩個函數配合實現。

initUndistortRectifyMap用來計算畸變映射,remap把求得的映射應用到圖像上。

initUndistortRectifyMap的函數原型:

//! initializes maps for cv::remap() to correct lens distortion and optionally rectify the image
CV_EXPORTS_W void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs,
                           InputArray R, InputArray newCameraMatrix,
                           Size size, int m1type, OutputArray map1, OutputArray map2 );

第一個參數cameraMatrix為之前求得的相機的內參矩陣;

第二個參數distCoeffs為之前求得的相機畸變矩陣;

第三個參數R,可選的輸入,是第一和第二相機坐標之間的旋轉矩陣;

第四個參數newCameraMatrix,輸入的校正后的3X3攝像機矩陣;

第五個參數size,攝像機采集的無失真的圖像尺寸;

第六個參數m1type,定義map1的數據類型,可以是CV_32FC1或者CV_16SC2;

第七個參數map1和第八個參數map2,輸出的X/Y坐標重映射參數;

 

remap函數原型:

//! warps the image using the precomputed maps. The maps are stored in either floating-point or integer fixed-point format
CV_EXPORTS_W void remap( InputArray src, OutputArray dst,
                         InputArray map1, InputArray map2,
                         int interpolation, int borderMode=BORDER_CONSTANT,
                         const Scalar& borderValue=Scalar());

第一個參數src,輸入參數,代表畸變的原始圖像;

第二個參數dst,矯正后的輸出圖像,跟輸入圖像具有相同的類型和大小;

第三個參數map1和第四個參數map2,X坐標和Y坐標的映射;

第五個參數interpolation,定義圖像的插值方式;

第六個參數borderMode,定義邊界填充方式;

 

方法二:使用undistort函數實現

undistort函數原型:

//! corrects lens distortion for the given camera matrix and distortion coefficients
CV_EXPORTS_W void undistort( InputArray src, OutputArray dst,
                             InputArray cameraMatrix,
                             InputArray distCoeffs,
                             InputArray newCameraMatrix=noArray() );

第一個參數src,輸入參數,代表畸變的原始圖像;

第二個參數dst,矯正后的輸出圖像,跟輸入圖像具有相同的類型和大小;

第三個參數cameraMatrix為之前求得的相機的內參矩陣;

第四個參數distCoeffs為之前求得的相機畸變矩陣;

第五個參數newCameraMatrix,默認跟cameraMatrix保持一致;

方法一相比方法二執行效率更高一些,推薦使用。

 

完整代碼

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <fstream>
 
using namespace cv;
using namespace std;
 
void main() 
{
    ifstream fin("calibdata.txt"); /* 標定所用圖像文件的路徑 */
    ofstream fout("caliberation_result.txt");  /* 保存標定結果的文件 */    
    //讀取每一幅圖像,從中提取出角點,然后對角點進行亞像素精確化    
    cout<<"開始提取角點………………";
    int image_count=0;  /* 圖像數量 */
    Size image_size;  /* 圖像的尺寸 */
    Size board_size = Size(4,6);    /* 標定板上每行、列的角點數 */
    vector<Point2f> image_points_buf;  /* 緩存每幅圖像上檢測到的角點 */
    vector<vector<Point2f>> image_points_seq; /* 保存檢測到的所有角點 */
    string filename;
    int count= -1 ;//用於存儲角點個數。
    while (getline(fin,filename))
    {
        image_count++;        
        // 用於觀察檢驗輸出
        cout<<"image_count = "<<image_count<<endl;        
        /* 輸出檢驗*/
        cout<<"-->count = "<<count;        
        Mat imageInput=imread(filename);
        if (image_count == 1)  //讀入第一張圖片時獲取圖像寬高信息
        {
            image_size.width = imageInput.cols;
            image_size.height =imageInput.rows;            
            cout<<"image_size.width = "<<image_size.width<<endl;
            cout<<"image_size.height = "<<image_size.height<<endl;
        }
 
        /* 提取角點 */
        if (0 == findChessboardCorners(imageInput,board_size,image_points_buf))
        {            
            cout<<"can not find chessboard corners!\n"; //找不到角點
            exit(1);
        } 
        else 
        {
            Mat view_gray;
            cvtColor(imageInput,view_gray,CV_RGB2GRAY);
            /* 亞像素精確化 */
            find4QuadCornerSubpix(view_gray,image_points_buf,Size(11,11)); //對粗提取的角點進行精確化
            image_points_seq.push_back(image_points_buf);  //保存亞像素角點
            /* 在圖像上顯示角點位置 */
            drawChessboardCorners(view_gray,board_size,image_points_buf,true); //用於在圖片中標記角點
            imshow("Camera Calibration",view_gray);//顯示圖片
            waitKey(500);//暫停0.5S        
        }
    }
    int total = image_points_seq.size();
    cout<<"total = "<<total<<endl;
    int CornerNum=board_size.width*board_size.height;  //每張圖片上總的角點數
    for (int ii=0 ; ii<total ;ii++)
    {
        if (0 == ii%CornerNum)// 24 是每幅圖片的角點個數。此判斷語句是為了輸出 圖片號,便於控制台觀看 
        {    
            int i = -1;
            i = ii/CornerNum;
            int j=i+1;
            cout<<"--> 第 "<<j <<"圖片的數據 --> : "<<endl;
        }
        if (0 == ii%3)    // 此判斷語句,格式化輸出,便於控制台查看
        {
            cout<<endl;
        }
        else
        {
            cout.width(10);
        }
        //輸出所有的角點
        cout<<" -->"<<image_points_seq[ii][0].x;
        cout<<" -->"<<image_points_seq[ii][0].y;
    }    
    cout<<"角點提取完成!\n";
 
    //以下是攝像機標定
    cout<<"開始標定………………";
    /*棋盤三維信息*/
    Size square_size = Size(10,10);  /* 實際測量得到的標定板上每個棋盤格的大小 */
    vector<vector<Point3f>> object_points; /* 保存標定板上角點的三維坐標 */
    /*內外參數*/
    Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0)); /* 攝像機內參數矩陣 */
    vector<int> point_counts;  // 每幅圖像中角點的數量
    Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0)); /* 攝像機的5個畸變系數:k1,k2,p1,p2,k3 */
    vector<Mat> tvecsMat;  /* 每幅圖像的旋轉向量 */
    vector<Mat> rvecsMat; /* 每幅圖像的平移向量 */
    /* 初始化標定板上角點的三維坐標 */
    int i,j,t;
    for (t=0;t<image_count;t++) 
    {
        vector<Point3f> tempPointSet;
        for (i=0;i<board_size.height;i++) 
        {
            for (j=0;j<board_size.width;j++) 
            {
                Point3f realPoint;
                /* 假設標定板放在世界坐標系中z=0的平面上 */
                realPoint.x = i*square_size.width;
                realPoint.y = j*square_size.height;
                realPoint.z = 0;
                tempPointSet.push_back(realPoint);
            }
        }
        object_points.push_back(tempPointSet);
    }
    /* 初始化每幅圖像中的角點數量,假定每幅圖像中都可以看到完整的標定板 */
    for (i=0;i<image_count;i++)
    {
        point_counts.push_back(board_size.width*board_size.height);
    }    
    /* 開始標定 */
    calibrateCamera(object_points,image_points_seq,image_size,cameraMatrix,distCoeffs,rvecsMat,tvecsMat,0);
    cout<<"標定完成!\n";
    //對標定結果進行評價
    cout<<"開始評價標定結果………………\n";
    double total_err = 0.0; /* 所有圖像的平均誤差的總和 */
    double err = 0.0; /* 每幅圖像的平均誤差 */
    vector<Point2f> image_points2; /* 保存重新計算得到的投影點 */
    cout<<"\t每幅圖像的標定誤差:\n";
    fout<<"每幅圖像的標定誤差:\n";
    for (i=0;i<image_count;i++)
    {
        vector<Point3f> tempPointSet=object_points[i];
        /* 通過得到的攝像機內外參數,對空間的三維點進行重新投影計算,得到新的投影點 */
        projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,image_points2);
        /* 計算新的投影點和舊的投影點之間的誤差*/
        vector<Point2f> tempImagePoint = image_points_seq[i];
        Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
        Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);
        for (int j = 0 ; j < tempImagePoint.size(); j++)
        {
            image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);
            tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
        }
        err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
        total_err += err/=  point_counts[i];   
        std::cout<<""<<i+1<<"幅圖像的平均誤差:"<<err<<"像素"<<endl;   
        fout<<""<<i+1<<"幅圖像的平均誤差:"<<err<<"像素"<<endl;   
    }   
    std::cout<<"總體平均誤差:"<<total_err/image_count<<"像素"<<endl;   
    fout<<"總體平均誤差:"<<total_err/image_count<<"像素"<<endl<<endl;   
    std::cout<<"評價完成!"<<endl;  
    //保存定標結果      
    std::cout<<"開始保存定標結果………………"<<endl;       
    Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅圖像的旋轉矩陣 */
    fout<<"相機內參數矩陣:"<<endl;   
    fout<<cameraMatrix<<endl<<endl;   
    fout<<"畸變系數:\n";   
    fout<<distCoeffs<<endl<<endl<<endl;   
    for (int i=0; i<image_count; i++) 
    { 
        fout<<""<<i+1<<"幅圖像的旋轉向量:"<<endl;   
        fout<<tvecsMat[i]<<endl;   
        /* 將旋轉向量轉換為相對應的旋轉矩陣 */   
        Rodrigues(tvecsMat[i],rotation_matrix);   
        fout<<""<<i+1<<"幅圖像的旋轉矩陣:"<<endl;   
        fout<<rotation_matrix<<endl;   
        fout<<""<<i+1<<"幅圖像的平移向量:"<<endl;   
        fout<<rvecsMat[i]<<endl<<endl;   
    }   
    std::cout<<"完成保存"<<endl; 
    fout<<endl;
    system("pause");    
    return ;
}

 操作說明:

運行前需要先准備標定圖片和記錄標定圖片列表的文本文件,並放入程序所在目錄下,如下圖所示:

文本文件內容如下

 其他標定工具:

OpenCV: https://docs.opencv.org/master/d4/d94/tutorial_camera_calibration.html 

Matlab:  https://www.mathworks.com/help/vision/ug/single-cameracalibrator-app.html 

ROS:  http://wiki.ros.org/camera_calibration


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM