1. 程式人生 > >OpenCV計算變換與重投影的矩陣說明

OpenCV計算變換與重投影的矩陣說明

本篇部落格主要討論opencv中兩個函式中幾何變換(矩陣)的對應關係,以下函式介面摘自opencv-2.4.8官方文件

1.Finds an object pose from 3D-2D point correspondences.

bool solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int
flags=ITERATIVE )

2.Projects 3D points to an image plane.

void projectPoints(InputArray objectPoints, InputArray rvec, InputArray tvec, InputArray cameraMatrix, InputArray distCoeffs, OutputArray imagePoints, OutputArray jacobian=noArray(), double aspectRatio=0 )

根據說明,可以看出,函式solvepnp接收一組對應的3D座標和2D座標,計算得到兩組座標對應的幾何變換(旋轉矩陣rvec

,平移矩陣tvec);
函式projectPoints與之相反,根據所給的3D座標和已知的幾何變換來求解投影后的2D座標。

在空間幾何變換中,很多時候會涉及到由座標A變換到B或B變換到A類似容易弄混的情況(即一些矩陣和其逆矩陣具有不同應用場景的特性),一旦把變換矩陣的物件弄反,結果將南轅北轍。本文將通過程式碼實驗的方式來驗證,上述兩個函式在所給變換一致(即tvecrvec相同)的情況下,所關聯的空間點2D、3D座標是否一致。

首先,給出小孔相機模型和一組3D點

camara=fx000fy0cxcy1(1)
這裡為了直觀說明座標值,我們取fxfycxcy均為100
cv::Mat camera = ( cv::Mat_<double
>(3,3) << 100 , 0 , 100 , 0 , 100 , 100 , 0 , 0 , 1 ); std::vector<cv::Point3f> totalPre; totalPre.push_back(cv::Point3f(-1,-1,1)); totalPre.push_back(cv::Point3f(-1,1,1)); totalPre.push_back(cv::Point3f(1,-1,1)); totalPre.push_back(cv::Point3f(1,1,1));

畫素座標系Op和相機座標系Oc的轉換公式如下:

x=(ucx)fx/z,y=(vcy)fy/z(2)
totalPre中的四個點可以看作是相機光心正前方距離為1處(此處不考慮單位,需要單位時,保證各處尺度一致即可)一個2*2的正方形的四個角點,其在成像平面上的畫素座標為(0,0)(0,200)(200,0)(200,200)
假設這四個3D點在空間中進行了統一的運動M(rvec,tvec),導致其在成像平面上的投影畫素座標發生了變化,變成了如下四個點:
std::vector<cv::Point2f> totalPost;
totalPost.push_back(cv::Point2f(50,50));
totalPost.push_back(cv::Point2f(50,150));
totalPost.push_back(cv::Point2f(150,50));
totalPost.push_back(cv::Point2f(150,150));

對比可以看出,正方形面積縮小到了原來的1/4,長寬變為了原來的一半,可以直觀地想象出來,這四個點所進行的運動即垂直遠離成像平面,移動到了原來距離二倍的位置。使用本文引用的第一個函式,求出這個過程中的幾何變換

cv::Mat rvec,tvec;
cv::solvePnP(totalPre,totalPost,camera,cv::Mat(),rvec,tvec);
//由於這個過程中實際上沒有發生旋轉,因此輸出平移資訊觀察結果
cout << "total PnP result : " << tvec << endl;

輸出結果:

total PnP result : [-7.035697327650722e-17; -7.035697327650722e-17; 1]

結果裡tvec的前兩個維度近似為0,第三個維度為1,表示在z方向(成像平面法向)位移為1,也就是說傳給函式的這幾個3D座標向前運動位移為1,和我們的直觀認識一致。
然後來看看我們引用的第二個函式,第二個函式涉及到重投影,就是說,假如沒有這個大小為1的位移,我們通過公式(2)即可由3D座標得到2D座標,但正是由於這個位移,因此需要引入幾何變換(‘重’投影),我們要驗證的是第一個函式計算得到的運動引數tvecrvec直接代入第二個函式,是否就能得到運動後的2D座標,程式碼如下:

vector<cv::Point2f> reProjection;
cv::projectPoints(totalPre,rvec,tvec,camera,cv::Mat(),reProjection);
for (auto i:reProjection)
    cout << i.x << " " << i.y << endl;

結果如下:

50 50
50 150
150 50
150 150

和前面所給2D座標一致,證明這兩個函式的運動引數對應即可得到一致的座標關係。

機器視覺裡面,需要計算變換時,常常是相機在運動,而並非觀測點在運動,而在文中所討論的兩個函式裡,3D空間座標系實際上是依照相機座標系建立的,因此將相機視作靜止,認為觀測點在運動,因而這裡得到的幾何變換未必能直接拿來使用,相機運動的情況請參考另一篇博文 討論opencv位姿估計結果與實際運動軌跡的關係(涉及變換矩陣與其逆矩陣)

完整程式碼如下:

cv::Mat camera = ( cv::Mat_<double>(3,3) << 100 , 0 , 100 , 0 , 100 , 100 , 0 , 0 , 1 );
std::vector<cv::Point2f> totalPost;
std::vector<cv::Point3f> totalPre;
totalPre.push_back(cv::Point3f(-1,-1,1));
totalPre.push_back(cv::Point3f(-1,1,1));
totalPre.push_back(cv::Point3f(1,-1,1));
totalPre.push_back(cv::Point3f(1,1,1));

totalPost.push_back(cv::Point2f(50,50));
totalPost.push_back(cv::Point2f(50,150));
totalPost.push_back(cv::Point2f(150,50));
totalPost.push_back(cv::Point2f(150,150));

cv::Mat rvec,tvec;
cv::solvePnP(totalPre,totalPost,camera,cv::Mat(),rvec,tvec);
//由於這個過程中實際上沒有發生旋轉,因此輸出平移資訊觀察結果
cout << "total PnP result : " << tvec << endl;

vector<cv::Point2f> reProjection;
cv::projectPoints(totalPre,rvec,tvec,camera,cv::Mat(),reProjection);
for (auto i:reProjection)
    cout << i.x << " " << i.y << endl;

若有內容需要討論,歡迎發郵件至[email protected]