天天看點

OpenCV 相機标定

相機标定

相機标定:簡單的說,就是獲得相機參數的過程。參數如:相機内參數矩陣,投影矩陣,旋轉矩陣和平移矩陣等

什麼叫相機參數?

簡單的說,将現實世界中的人、物,拍成一張圖像(二維)。人或物在世界中的三維坐标,和圖像上對應的二維坐标間的關系。表達兩種不同次元坐标間的關系用啥表示?用相機參數。

相機的成像原理

先來看一下,相機的成像原理:

OpenCV 相機标定

如圖所示,這是一個相機模型。将物體簡化看成一個點。來自物體的光,通過鏡頭,擊中圖像平面(圖像傳感器),以此成像。d0是物體到鏡頭的距離,di是鏡頭到圖像平面的距離,f是鏡頭的焦距。三者滿足以下關系。

OpenCV 相機标定

現在,簡化上面的相機模型。

  将相機孔徑看成無窮小,隻考慮中心位置的射線,這樣就忽視了透鏡的影響。然後由于d0遠遠大于di,将圖像平面放在焦距處,這樣物體在圖像平面上成像為倒立的影像(沒有透鏡的影響,隻考慮從中心的孔徑進入的光線)。這個簡化的模型就是小孔錄影機模型。然後,我們在鏡頭前,将圖像平面放在焦距距離的位置,就可以簡單獲得一個筆直的圖像(不倒立)。當然,這隻是理論上的,你不可能将圖像傳感器從相機裡拿出來,放在鏡頭前面。實際應用中,小孔錄影機應該是将成像後的圖像倒過來,以獲得正立的圖像。

  到此,我們獲得了一個簡化的模型,如下圖:

  

OpenCV 相機标定

h0是物體的高,hi是圖像上物體的高,f是焦距(距離),d0是圖像到鏡頭的距離。四者滿足如下關系:

OpenCV 相機标定

物體在圖像中的高度hi,和d0成反比。也就是說,離鏡頭越遠,物體在圖像中越小,離得越近越大(好吧,這句話是廢話)。

但通過這個式子,我們便能夠預測三維中的物體,在圖像(二維)中的位置。那麼怎麼預測?

相機标定

如下圖所示,根據上面簡化的模型,考慮三維世界中的一個點,和其在圖像(二維)中的坐标關系。

OpenCV 相機标定

(X,Y,Z)為點的三維坐标,(x,y)為其通過相機成像後在圖像(二維)上的坐标。u0和v0是相機的中心點(主點),該點位于圖像平面中心(理論上是這樣。但實際的相機會有幾個像素的偏差)

現在隻考慮y方向上,由于需要将三維世界中的坐标,轉換為圖像上的像素(圖像上的坐标,實際上是像素的位置),需要求y方向上焦距

等于多少個像素(用像素值表示焦距),Py表示像素的高,焦距f(米或毫米)。垂直像素表示的焦距為

OpenCV 相機标定

根據式子(1),隻考慮y方向。我們三維世界中得點,在圖像(二維)中y的坐标。

OpenCV 相機标定

同理,得到x的坐标。

OpenCV 相機标定

現在,将上圖中的坐标系的原點O,移動到圖像的左上角。由于(x,y)是關于(u0,v0)的偏移,上面表示圖像(二維)中點的坐标的式子不變。将式子以矩陣的形式重寫,得。

OpenCV 相機标定

其中,等式左邊的第一個矩陣,叫做“相機内參數矩陣”,第二個矩陣叫(投影矩陣)。

更為一般的情況,開始時的參考坐标系不位于主點(中心點),需要額外兩個參數“旋轉向量”和“平移向量”來表示這個式子,這兩個參數在不同視角中是不一樣的。整合後,上述式子重寫為。

OpenCV 相機标定

校正畸變

通過相機标定,獲得了相機參數後,可以計算兩個映射函數(x坐标和y坐标),它們分别給出了沒有畸變的圖像坐标。将畸變的圖像重新映射成為沒有畸變的圖像。

代碼:

做相機标定時,一般用标定闆(棋盤)拍攝一組圖像,利用這些圖像提取角點,通過角點在圖像中得坐标和三維世界中的坐标(通常自定義3維坐标),計算相機參數。

1 std::vector<cv::Point2f>imageConers;
2 //提取标定圖像角點,儲存角點坐标(二維)
3  cv::findChessboardCorners(image,
4   boardSize, //角點數目如(6,4)六行,四列
5   imageConers);      

函數calibrateCamera完成相機标定工作。

cv::calibrateCamera(objectPoints,//三維坐标
 imagePoints, //二維坐标
 imageSize,//圖像大小
 camerMatirx,//相機内參數矩陣
 disCoeffs,//投影矩陣
  rvecs, //旋轉
  tvecs,//平移
flag  //标記opencv提供幾種參數,可以參看線上的opencv document
);      

計算畸變參數,去畸變

//計算畸變參數
cv::initUndistortRectifyMap(camerMatirx, disCoeffs,
    cv::Mat(), cv::Mat(), image.size(), CV_32FC1,
    map1, //x映射函數
    map2  //y映射函數
    );
//應用映射函數
cv::remap(image, //畸變圖像
undistorted, //去畸變圖像
map1, map2, cv::INTER_LINEAR);      

現在整合代碼。

示例:

标頭.h

#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<opencv2\calib3d\calib3d.hpp>
#include <opencv2/features2d/features2d.hpp>
#include<string>
#include<vector>
class CameraCalibrator
{
private:
    //世界坐标
    std::vector < std::vector<cv::Point3f >> objectPoints;
    //圖像坐标
    std::vector <std::vector<cv::Point2f>> imagePoints;
    //輸出矩陣
    cv::Mat camerMatirx;
    cv::Mat disCoeffs;
    //标記
    int flag;
    //去畸變參數
    cv::Mat map1, map2;
    //是否去畸變
    bool mustInitUndistort;

    ///儲存點資料
    void addPoints(const std::vector<cv::Point2f>&imageConers, const std::vector<cv::Point3f>&objectConers)
    {
        imagePoints.push_back(imageConers);
        objectPoints.push_back(objectConers);
    }
public:
    CameraCalibrator() :flag(0), mustInitUndistort(true){}
    //打開棋盤圖檔,提取角點
    int addChessboardPoints(const std::vector<std::string>&filelist,cv::Size &boardSize)
    {
        std::vector<cv::Point2f>imageConers;
        std::vector<cv::Point3f>objectConers;
        //輸入角點的世界坐标
        for (int i = 0; i < boardSize.height; i++)
        {
            for (int j = 0; j < boardSize.width; j++)
            {
                objectConers.push_back(cv::Point3f(i, j, 0.0f));
            }
        }
        //計算角點在圖像中的坐标
        cv::Mat image;
        int success = 0;
        for (int i = 0; i < filelist.size(); i++)
        {
            image = cv::imread(filelist[i],0);
            //找到角點坐标
            bool found = cv::findChessboardCorners(image, boardSize, imageConers);
            cv::cornerSubPix(image,
                imageConers,
                cv::Size(5, 5),
                cv::Size(-1, -1),
                cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS,
                30, 0.1));
            if (imageConers.size() == boardSize.area())
            {
                addPoints(imageConers, objectConers);
                success++;
            }
            //畫出角點
            cv::drawChessboardCorners(image, boardSize, imageConers, found);
            cv::imshow("Corners on Chessboard", image);
            cv::waitKey(100);
        }
        return success;
    }

    //相機标定
    double calibrate(cv::Size&imageSize)
    {
        mustInitUndistort = true;
        std::vector<cv::Mat>rvecs, tvecs;
        //相機标定
        return cv::calibrateCamera(objectPoints, imagePoints, imageSize,
            camerMatirx, disCoeffs, rvecs, tvecs, flag);
    }
    ///去畸變
    cv::Mat remap(const cv::Mat &image)
    {
        cv::Mat undistorted;
        if (mustInitUndistort)
        {
            //計算畸變參數
            cv::initUndistortRectifyMap(camerMatirx, disCoeffs,
                cv::Mat(), cv::Mat(), image.size(), CV_32FC1, map1, map2);
            mustInitUndistort = false;
        }
        //應用映射函數
        cv::remap(image, undistorted, map1, map2, cv::INTER_LINEAR);
        return undistorted;
    }
    //常成員函數,獲得相機内參數矩陣、投影矩陣資料
    cv::Mat getCameraMatrix() const { return camerMatirx; }
    cv::Mat getDistCoeffs()   const { return disCoeffs; }
};      

源.cpp

#include"标頭.h"
#include<iomanip>
#include<iostream>
int main()
{
    CameraCalibrator Cc;
    cv::Mat image;
    std::vector<std::string> filelist;
    cv::namedWindow("Image");
    for (int i = 1; i <= 22; i++)
    {
        ///讀取圖檔
        std::stringstream s;
        s << "D:/images/chessboards/chessboard" << std::setw(2) << std::setfill('0') << i << ".jpg";
        std::cout << s.str() << std::endl;

        filelist.push_back(s.str());
        image = cv::imread(s.str(),0);
        cv::imshow("Image", image);
        cv::waitKey(100);
    }
    //相機标定
    cv::Size boardSize(6, 4);
    Cc.addChessboardPoints(filelist, boardSize);
    Cc.calibrate(image.size());

    //去畸變
    image = cv::imread(filelist[1]);
    cv::Mat uImage=Cc.remap(image);
    cv::imshow("原圖像", image);
    cv::imshow("去畸變", uImage);
    //顯示相機内參數矩陣
    cv::Mat cameraMatrix = Cc.getCameraMatrix();
    std::cout << " Camera intrinsic: " << cameraMatrix.rows << "x" << cameraMatrix.cols << std::endl;
    std::cout << cameraMatrix.at<double>(0, 0) << " " << cameraMatrix.at<double>(0, 1) << " " << cameraMatrix.at<double>(0, 2) << std::endl;
    std::cout << cameraMatrix.at<double>(1, 0) << " " << cameraMatrix.at<double>(1, 1) << " " << cameraMatrix.at<double>(1, 2) << std::endl;
    std::cout << cameraMatrix.at<double>(2, 0) << " " << cameraMatrix.at<double>(2, 1) << " " << cameraMatrix.at<double>(2, 2) << std::endl;

    cv::waitKey(0);
}