【筆記】C++版Andrea Fusiello立體視覺極線矯正算法

之前在做立體匹配的時候,一直使用opencv3的stereoRectify函數,不過後來出了點問題,也一直沒法找到原因,最後決定閱讀經典論文Fusiello A, Trucco E, Verri A. A compact algorithm for rectification of stereo pairs[J]. Machine Vision & Applications, 2000, 12(1):16-22.
使用該論文裏所介紹的極線矯正算法,該論文還提供了相應的Matlab代碼,由於編程需要使用C++,所以在此記錄下其C++代碼

變量說明

根據相機針孔投影模型
這裏寫圖片描述
如圖所示,C 爲相機中心 , W 是世界座標系下的空間點,MW 在圖像平面上經過針孔投影后所得到的點(像點)
現有W 的齊次座標爲w=[xyz1]T 像點M 的齊次座標爲m=[uv1]T , 則可以定義P 爲投影矩陣P=A[R|t] , A 是相機內參數矩陣,[R|t] 是相機外參數矩陣,則投影變換關係如下:
λm=Pw
其中A 是相機內參數矩陣

A=[fxγu00fyv0001]

矩陣P=A[R|t]=[AR|At]=[Q|q]
若定義相機中心爲CC 的齊次座標爲c ,則有c=Q1q=R1t
若定義λ 爲深度值,則有世界座標與像點座標的關係爲w=c+λQ1m ,該式子可以理解爲從相機中心出發,沿着相機中心至m 像點的射線上距離λ 處的某一點即爲物方空間點W

極線矯正

首先,在進行極限矯正之前,先確定我們的已知條件:①相機已經過標定,相機內參數矩陣A1A2 是已知的。②原相機的外方位元素是已知的,即我們已知相機的投影矩陣Po1Po2 。已知以上 條件後,我們即將下圖進行極線矯正
圖1圖2

如上圖可以知道,經過極線矯正後,相機中心座標並未發生改變,即①矯正後的新的相機中心座標c1,c2 未發生改變。②矯正後新的左右相機內參數矩陣A 是相同的(此時的內參數矩陣可任意選取,只要相同就行)。 ③矯正後的左右相機的旋轉方向都是相同的。

因此可以將新的左右相機看作只沿着相機基線X軸平移了某個長度而已,他們的旋轉朝向都是相同的,都假設爲R

因此可以根據以上矯正條件,寫出新的矯正後的新的左右投影矩陣Pn1Pn2 ,有如下:
Pn1=A[R|Rc1]
Pn2=A[R|Rc2]
其中,矯正後的相機內參數矩陣A 可任意選取,c1,c2 與矯正前的c1,c2 相同,因此,需要求解的僅僅只有一個參數R 而已, 那麼根據以上推論, 可作以下假設:
首先將新投影矩陣中的外方位元素的旋轉矩陣R 寫爲列向量形式:

R=[r1Tr2Tr3T]

三個分量r1Tr2Tr3T 分別對應相機參考座標系的X , Y , Z
根據以上推論得到以下三個條件:
①新的X 軸應該與基線向量baseline平行:即r1=c1c2||c1c2||
②新的Y 軸應該與X 軸垂直,且同時垂直於k 軸,該k 軸論文原文裏選擇了矯正前座標系的Z 軸的單位向量,因此有:r2=k×r1
③ 新的Z 軸 應該同時垂直於新的X 軸與 Y 軸, 因此有r3=r1×r2

完成,由此便可得到新的投影矩陣Pn1Pn2 了。

下一步,就該計算由矯正前至矯正後的映射矩陣了

由前文知w=c+λQ1m ,則有:
w=c1+λoQo11mo1
w=c1+λnQn11mn1
因此得到矯正後的像點座標與矯正前的像點座標之間的映射關係爲
mn1=λoλnQn1Qo11mo1
因此有映射矩陣T1=Qn1Qo11
對於右相機同理:
mn2=λoλnQn2Qo21mo2
右相機映射矩陣T2=Qn2Qo21

C++代碼

原文給出了Matlab代碼,若需要查看原文代碼的可以直接下載原文查看,這裏就是直接將MATLAB的代碼轉化爲了C++的而已,矩陣等運算都是基於OpenCV3的

#include <iostream>
#include <vector>
#include <string>
#include <opencv2/opencv.hpp>

using namespace std;


void art(const cv::Mat &P , cv::Mat &A , cv::Mat &R , cv::Mat &t){

    cv::Mat t_temp;
    cv::decomposeProjectionMatrix(P  ,A , R , t_temp); // 得到的t 是一個四維向量 , 是指相機所在的位置的3D齊次座標, 需要將其化爲非齊次座標並乘以一個-R轉化爲平移向量
    t = cv::Mat::zeros(3 , 1 , CV_64F);

    t.row(0) = t_temp.row(0) / t_temp.row(3);
    t.row(1) = t_temp.row(1) / t_temp.row(3);
    t.row(2) = t_temp.row(2) / t_temp.row(3);
    t = -R * t ;

}

void my_stereoRectify(const cv::Mat &P_o1,
                      const cv::Mat &P_o2,
                      cv::Mat &T1,
                      cv::Mat &T2,
                      cv::Mat &P_n1,
                      cv::Mat &P_n2)
{
    cv::Mat A1 , R1 , t1;
    cv::Mat A2 , R2 , t2;
    art(P_o1 , A1 , R1 , t1);
    art(P_o2 , A2 , R2 , t2);

    // 相機中心c1 , c2
    cv::Mat c1 = cv::Mat::zeros(3 , 1 , CV_64F);
    cv::Mat c2 = cv::Mat::zeros(3 , 1 , CV_64F);
    c1 = - R1.inv() * t1;
    c2 = - R2.inv() * t2;


    cv::Mat v1 = cv::Mat::zeros(3 , 1 , CV_64F);
    cv::Mat v2 = cv::Mat::zeros(3 , 1 , CV_64F);
    cv::Mat v3 = cv::Mat::zeros(3 , 1 , CV_64F);

   // 新的x軸new x axis (baseline , from c1 to c2)
    v1 = c2 - c1 ;
    v2 = R1.row(2).t().cross(v1);
    v3 = v1.cross(v2);

    //新的外方位元素中的R
    cv::Mat R = cv::Mat::eye(3 , 3 , CV_64F);
    R.row(0) = v1.t() / cv::norm(v1);
    R.row(1) = v2.t() / cv::norm(v2);
    R.row(2) = v3.t() / cv::norm(v3);

    // 新的內方位元素
    cv::Mat A_n1 = cv::Mat::eye(3 , 3 , CV_64F);
    cv::Mat A_n2 = cv::Mat::eye(3 , 3 , CV_64F);
    A_n1 = A2;
    A_n1.at<double>(0 , 1) = 0;
    A_n2 = A2;
    A_n2.at<double>(0 , 1) = 0;

    A_n1.at<double>(0 , 2) = A_n1.at<double>(0 , 2);
    A_n1.at<double>(1 , 2) = A_n1.at<double>(1 , 2);
    A_n2.at<double>(0 , 2) = A_n2.at<double>(0 , 2);
    A_n2.at<double>(1 , 2) = A_n2.at<double>(1 , 2);


    // 新的投影矩陣
    cv::Mat t1_new = cv::Mat::zeros(3 , 1 , CV_64F);
    cv::Mat t2_new = cv::Mat::zeros(3 , 1 , CV_64F);
    t1_new = -R * c1;
    t2_new = -R * c2;

    P_n1.create(3 , 4 , CV_64F);
    P_n2.create(3 , 4 , CV_64F);
    R.convertTo(P_n1.colRange(0 , 3) , CV_64F);
    R.convertTo(P_n2.colRange(0 , 3) , CV_64F);


    t1_new.convertTo(P_n1.col(3) , CV_64F);
    t2_new.convertTo(P_n2.col(3) , CV_64F);

    //cout << "A_n1 = " <<  A_n1 << "\nA_n2 = " << A_n2 << endl;

    cout << "P_n1 = " <<  P_n1 << "\nP_n2 = " << P_n2 << endl;
    P_n1 = A_n1 * P_n1;
    P_n2 = A_n2 * P_n2;

    // 立體矯正的變換矩陣
    T1.create(3 , 3 , CV_64F);
    T2.create(3 , 3 , CV_64F);

    T1 = P_n1.colRange(0 , 3) * P_o1.colRange(0 , 3).inv();
    T2 = P_n2.colRange(0 , 3) * P_o2.colRange(0 , 3).inv();
}

測試

本人使用了Middleburry網站的temple數據,temple數據裏明確的給出了每一張相片的內外參數矩陣,可直接將其參數拿來使用
矯正前:
這裏寫圖片描述
矯正後:
這裏寫圖片描述

結束

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章