三角测量

2017-06-08  本文已影响2420人  金戈大王

本文紧接着前一篇文章《2D-2D相机位姿估计》展开。在得到了两张图片对应的相机位姿之后,就可以通过三角测量的方法计算出配对特征点的3D位置。

一、三角测量原理

想必三角测量这个词大家并不陌生,因为地理课上我们就学过它的原理。在两个不同地方观测同一个点,只要知道两个观测地点的距离和观测角度,就可以确定观测点到两地的距离。这是因为一条边和两个内角可以完全确定一个三角形,仅此而已。

如下图所示,已知O1和O2点的坐标、O1P的方向和O2P的方向,就可以唯一确定P点的位置。但是在SLAM中,问题稍微复杂一些。由于噪声的影响,O1P和O2P有可能根本没有交点,这个时候就只能用最小二乘法求取使误差最小的P点的坐标了。

另外,O1和O2之间的距离对三角测量的误差影响很大。距离太短,也就是说相机的平移太小时,对P点观测的角度误差会导致较大的深度误差。而距离太远,场景的重叠部分会少很多,使特征匹配变得困难。因此相机的平移需要把握一个度,既不能太大也不能太小,这是三角测量的一对矛盾。

无论怎样,OpenCV都提供了非常方便的函数供我们调用。只需要调用cv::triangulatePoints就可以实现特征点的三角化。

二、代码实现

下面给出关键部分代码,包括三角化函数和主函数。

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 )
    {
        // 将像素坐标转换至相机坐标
        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 );
    
    // 转换成非齐次坐标
    for ( int i=0; i<pts_4d.cols; i++ )
    {
        Mat x = pts_4d.col(i);
        x /= x.at<float>(3,0); // 归一化   //金戈大王注:此处的归一化是指从齐次坐标变换到非齐次坐标。而不是变换到归一化平面。
        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() <<"组匹配点"<<endl;

    //-- 估计两张图像间运动
    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 );
    
    //-- 验证三角化点与特征点的重投影关系
    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;
}

//其它函数略

其中,cv::triangulatePoints函数接受的参数是两个相机位姿和特征点在两个相机坐标系下的坐标,输出三角化后的特征点的3D坐标。但需要注意的是,输出的3D坐标是齐次坐标,共四个维度,因此需要将前三个维度除以第四个维度以得到非齐次坐标xyz。这个坐标是在相机坐标系下的坐标,以输入的两个相机位姿所在的坐标系为准。

在主函数中,通过把3D坐标重投影到两个相机的归一化平面上,从而计算重投影误差。因此需要再次对xyz坐标同时除以z,以得到归一化平面上的坐标。

以下是一部分三角化结果,可以看到误差很小,大约在小数点后第3位的量级。d的值是特征点到第一个相机光心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讲 视觉里程计1 高翔

上一篇下一篇

猜你喜欢

热点阅读