本文緊接著前一篇文章《2D-2D相機(jī)位姿估計(jì)》展開杰扫。在得到了兩張圖片對(duì)應(yīng)的相機(jī)位姿之后刀诬,就可以通過三角測(cè)量的方法計(jì)算出配對(duì)特征點(diǎn)的3D位置究抓。
一蜘欲、三角測(cè)量原理
想必三角測(cè)量這個(gè)詞大家并不陌生益眉,因?yàn)榈乩碚n上我們就學(xué)過它的原理。在兩個(gè)不同地方觀測(cè)同一個(gè)點(diǎn)姥份,只要知道兩個(gè)觀測(cè)地點(diǎn)的距離和觀測(cè)角度郭脂,就可以確定觀測(cè)點(diǎn)到兩地的距離。這是因?yàn)橐粭l邊和兩個(gè)內(nèi)角可以完全確定一個(gè)三角形澈歉,僅此而已展鸡。
如下圖所示,已知O1和O2點(diǎn)的坐標(biāo)埃难、O1P的方向和O2P的方向莹弊,就可以唯一確定P點(diǎn)的位置。但是在SLAM中凯砍,問題稍微復(fù)雜一些箱硕。由于噪聲的影響,O1P和O2P有可能根本沒有交點(diǎn)悟衩,這個(gè)時(shí)候就只能用最小二乘法求取使誤差最小的P點(diǎn)的坐標(biāo)了剧罩。
另外,O1和O2之間的距離對(duì)三角測(cè)量的誤差影響很大座泳。距離太短惠昔,也就是說相機(jī)的平移太小時(shí),對(duì)P點(diǎn)觀測(cè)的角度誤差會(huì)導(dǎo)致較大的深度誤差挑势。而距離太遠(yuǎn)镇防,場(chǎng)景的重疊部分會(huì)少很多,使特征匹配變得困難潮饱。因此相機(jī)的平移需要把握一個(gè)度来氧,既不能太大也不能太小,這是三角測(cè)量的一對(duì)矛盾。
無論怎樣啦扬,OpenCV都提供了非常方便的函數(shù)供我們調(diào)用中狂。只需要調(diào)用cv::triangulatePoints
就可以實(shí)現(xiàn)特征點(diǎn)的三角化。
二扑毡、代碼實(shí)現(xiàn)
下面給出關(guān)鍵部分代碼胃榕,包括三角化函數(shù)和主函數(shù)。
void triangulation (
const vector< KeyPoint >& keypoint_1,
const vector< KeyPoint >& keypoint_2,
const std::vector< DMatch >& matches,
const Mat& R, const Mat& t,
vector< Point3d >& points )
{
Mat T1 = (Mat_<float> (3,4) <<
1,0,0,0,
0,1,0,0,
0,0,1,0);
Mat T2 = (Mat_<float> (3,4) <<
R.at<double>(0,0), R.at<double>(0,1), R.at<double>(0,2), t.at<double>(0,0),
R.at<double>(1,0), R.at<double>(1,1), R.at<double>(1,2), t.at<double>(1,0),
R.at<double>(2,0), R.at<double>(2,1), R.at<double>(2,2), t.at<double>(2,0)
);
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
vector<Point2f> pts_1, pts_2;
for ( DMatch m:matches )
{
// 將像素坐標(biāo)轉(zhuǎn)換至相機(jī)坐標(biāo)
pts_1.push_back ( pixel2cam( keypoint_1[m.queryIdx].pt, K) );
pts_2.push_back ( pixel2cam( keypoint_2[m.trainIdx].pt, K) );
}
Mat pts_4d;
cv::triangulatePoints( T1, T2, pts_1, pts_2, pts_4d );
// 轉(zhuǎn)換成非齊次坐標(biāo)
for ( int i=0; i<pts_4d.cols; i++ )
{
Mat x = pts_4d.col(i);
x /= x.at<float>(3,0); // 歸一化 //金戈大王注:此處的歸一化是指從齊次坐標(biāo)變換到非齊次坐標(biāo)瞄摊。而不是變換到歸一化平面勋又。
Point3d p (
x.at<float>(0,0),
x.at<float>(1,0),
x.at<float>(2,0)
);
points.push_back( p );
}
}
int main ( int argc, char** argv )
{
if ( argc != 3 )
{
cout<<"usage: triangulation img1 img2"<<endl;
return 1;
}
//-- 讀取圖像
Mat img_1 = imread ( argv[1], CV_LOAD_IMAGE_COLOR );
Mat img_2 = imread ( argv[2], CV_LOAD_IMAGE_COLOR );
vector<KeyPoint> keypoints_1, keypoints_2;
vector<DMatch> matches;
find_feature_matches ( img_1, img_2, keypoints_1, keypoints_2, matches );
cout<<"一共找到了"<<matches.size() <<"組匹配點(diǎn)"<<endl;
//-- 估計(jì)兩張圖像間運(yùn)動(dòng)
Mat R,t;
pose_estimation_2d2d ( keypoints_1, keypoints_2, matches, R, t );
//-- 三角化
vector<Point3d> points;
triangulation( keypoints_1, keypoints_2, matches, R, t, points );
//-- 驗(yàn)證三角化點(diǎn)與特征點(diǎn)的重投影關(guān)系
Mat K = ( Mat_<double> ( 3,3 ) << 520.9, 0, 325.1, 0, 521.0, 249.7, 0, 0, 1 );
for ( int i=0; i<matches.size(); i++ )
{
Point2d pt1_cam = pixel2cam( keypoints_1[ matches[i].queryIdx ].pt, K );
Point2d pt1_cam_3d(
points[i].x/points[i].z,
points[i].y/points[i].z
);
cout<<"point in the first camera frame: "<<pt1_cam<<endl;
cout<<"point projected from 3D "<<pt1_cam_3d<<", d="<<points[i].z<<endl;
// 第二個(gè)圖
Point2f pt2_cam = pixel2cam( keypoints_2[ matches[i].trainIdx ].pt, K );
Mat pt2_trans = R*( Mat_<double>(3,1) << points[i].x, points[i].y, points[i].z ) + t;
pt2_trans /= pt2_trans.at<double>(2,0);
cout<<"point in the second camera frame: "<<pt2_cam<<endl;
cout<<"point reprojected from second frame: "<<pt2_trans.t()<<endl;
cout<<endl;
}
return 0;
}
//其它函數(shù)略
其中,cv::triangulatePoints
函數(shù)接受的參數(shù)是兩個(gè)相機(jī)位姿和特征點(diǎn)在兩個(gè)相機(jī)坐標(biāo)系下的坐標(biāo)换帜,輸出三角化后的特征點(diǎn)的3D坐標(biāo)楔壤。但需要注意的是,輸出的3D坐標(biāo)是齊次坐標(biāo)膜赃,共四個(gè)維度挺邀,因此需要將前三個(gè)維度除以第四個(gè)維度以得到非齊次坐標(biāo)xyz。這個(gè)坐標(biāo)是在相機(jī)坐標(biāo)系下的坐標(biāo)跳座,以輸入的兩個(gè)相機(jī)位姿所在的坐標(biāo)系為準(zhǔn)端铛。
在主函數(shù)中,通過把3D坐標(biāo)重投影到兩個(gè)相機(jī)的歸一化平面上疲眷,從而計(jì)算重投影誤差禾蚕。因此需要再次對(duì)xyz坐標(biāo)同時(shí)除以z,以得到歸一化平面上的坐標(biāo)狂丝。
以下是一部分三角化結(jié)果换淆,可以看到誤差很小,大約在小數(shù)點(diǎn)后第3位的量級(jí)几颜。d的值是特征點(diǎn)到第一個(gè)相機(jī)光心O1的距離倍试,但這個(gè)距離沒有單位,只能表示相對(duì)大小蛋哭。
...
point in the first camera frame: [-0.153772, -0.0742802]
point projected from 3D [-0.153832, -0.0754351], d=16.6993
point in the second camera frame: [-0.180649, -0.0589251]
point reprojected from second frame: [-0.1806478467687954, -0.05780866898967527, 1]
point in the first camera frame: [-0.468612, 0.119578]
point projected from 3D [-0.468523, 0.118851], d=14.046
point in the second camera frame: [-0.499328, 0.1119]
point reprojected from second frame: [-0.4994513059407403, 0.1125883903930561, 1]
...
完整代碼的下載地址:https://github.com/jingedawang/FeatureMethod
三县习、參考資料
《視覺SLAM十四講》第7講 視覺里程計(jì)1 高翔