本文緊接著前一篇文章《2D-2D相機(jī)位姿估計(jì)》展開。在得到了兩張圖片對應(yīng)的相機(jī)位姿之后,就可以通過三角測量的方法計(jì)算出配對特征點(diǎn)的3D位置。
一、三角測量原理
想必三角測量這個詞大家并不陌生,因?yàn)榈乩碚n上我們就學(xué)過它的原理。在兩個不同地方觀測同一個點(diǎn),只要知道兩個觀測地點(diǎn)的距離和觀測角度,就可以確定觀測點(diǎn)到兩地的距離。這是因?yàn)橐粭l邊和兩個內(nèi)角可以完全確定一個三角形,僅此而已。
如下圖所示,已知O1和O2點(diǎn)的坐標(biāo)、O1P的方向和O2P的方向,就可以唯一確定P點(diǎn)的位置。但是在SLAM中,問題稍微復(fù)雜一些。由于噪聲的影響,O1P和O2P有可能根本沒有交點(diǎn),這個時候就只能用最小二乘法求取使誤差最小的P點(diǎn)的坐標(biāo)了。

另外,O1和O2之間的距離對三角測量的誤差影響很大。距離太短,也就是說相機(jī)的平移太小時,對P點(diǎn)觀測的角度誤差會導(dǎo)致較大的深度誤差。而距離太遠(yuǎn),場景的重疊部分會少很多,使特征匹配變得困難。因此相機(jī)的平移需要把握一個度,既不能太大也不能太小,這是三角測量的一對矛盾。
無論怎樣,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)動
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;
// 第二個圖
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ù)是兩個相機(jī)位姿和特征點(diǎn)在兩個相機(jī)坐標(biāo)系下的坐標(biāo),輸出三角化后的特征點(diǎn)的3D坐標(biāo)。但需要注意的是,輸出的3D坐標(biāo)是齊次坐標(biāo),共四個維度,因此需要將前三個維度除以第四個維度以得到非齊次坐標(biāo)xyz。這個坐標(biāo)是在相機(jī)坐標(biāo)系下的坐標(biāo),以輸入的兩個相機(jī)位姿所在的坐標(biāo)系為準(zhǔn)。
在主函數(shù)中,通過把3D坐標(biāo)重投影到兩個相機(jī)的歸一化平面上,從而計(jì)算重投影誤差。因此需要再次對xyz坐標(biāo)同時除以z,以得到歸一化平面上的坐標(biāo)。
以下是一部分三角化結(jié)果,可以看到誤差很小,大約在小數(shù)點(diǎn)后第3位的量級。d的值是特征點(diǎn)到第一個相機(jī)光心O1的距離,但這個距離沒有單位,只能表示相對大小。
...
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 高翔