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類似容易弄混的情況(即一些矩陣和其逆矩陣具有不同應用場景的特性),一旦把變換矩陣的物件弄反,結果將南轅北轍。本文將通過程式碼實驗的方式來驗證,上述兩個函式在所給變換一致(即tvec
和rvec
相同)的情況下,所關聯的空間點2D、3D座標是否一致。
首先,給出小孔相機模型和一組3D點
這裡為了直觀說明座標值,我們取
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));
畫素座標系
則
totalPre
中的四個點可以看作是相機光心正前方距離為1處(此處不考慮單位,需要單位時,保證各處尺度一致即可)一個2*2的正方形的四個角點,其在成像平面上的畫素座標為假設這四個3D點在空間中進行了統一的運動
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的位移,我們通過公式tvec
和rvec
直接代入第二個函式,是否就能得到運動後的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]