三角測量

本文緊接著前一篇文章《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 高翔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 高級鉗工應(yīng)知鑒定題庫(858題) ***單選題*** 1. 000003難易程度:較難知識范圍:相關(guān)4 01答案:...
    開源時代閱讀 6,306評論 1 9
  • 第十五章 隧道與橋梁測量 1.隧道測量概述 隧道自兩端洞口相對開挖,在洞內(nèi)預(yù)定位置挖通,稱為貫通,因?yàn)?,隧道測量也...
    1915萬憑麓閱讀 1,722評論 0 3
  • 世間世事 誰能料 苦短人生 難磨多 孤行不如八十難 沒有圣僧 菩薩心
    李子柚閱讀 297評論 0 0
  • 1. 孤獨(dú)這兩個字拆開來看,有孩童,有瓜果,有走獸,有飛蟲,足以撐起一個盛夏的傍晚,人情味兒十足?!爸蓛呵婀狭i下...
    尚夏sunny閱讀 444評論 9 8
  • 早上培訓(xùn)的時間太多,不夠休息,每天都累。 今晚回家吃飯了。和家人一起,加上公眾號里面理念。累s了 最近做單比較少,...
    DeathKnightR閱讀 357評論 0 0

友情鏈接更多精彩內(nèi)容