OpenCVのfindEssentialMatでの内部パラメータの用途

5-point algorithm (5点アルゴリズム)を使いたくて、OpenCVに確か実装されてたよなーって思って調べてみると、なぜか関数の入力に内部パラメータが必要とされていました。
OpenCVの5-point algorithmはこの論文を実装してるはずなので、軽く読んでみたけどやっぱり焦点距離は必要ない気がしたので、OpenCVのコードを見てみました。

Nistér, D. An efficient solution to the five-point relative pose problem, CVPR 2003.

findEssentialMat

OpenCVで5-point algorithmを扱っているのはfindEssentialMatという関数で、中身はこんな感じになっています。

// Input should be a vector of n 2D points or a Nx2 matrix
cv::Mat cv::findEssentialMat( InputArray _points1, InputArray _points2, InputArray _cameraMatrix,
                              int method, double prob, double threshold, OutputArray _mask)
{
    CV_INSTRUMENT_REGION();

    Mat points1, points2, cameraMatrix;
    _points1.getMat().convertTo(points1, CV_64F);
    _points2.getMat().convertTo(points2, CV_64F);
    _cameraMatrix.getMat().convertTo(cameraMatrix, CV_64F);

    int npoints = points1.checkVector(2);
    CV_Assert( npoints >= 0 && points2.checkVector(2) == npoints &&
                              points1.type() == points2.type());

    CV_Assert(cameraMatrix.rows == 3 && cameraMatrix.cols == 3 && cameraMatrix.channels() == 1);

    if (points1.channels() > 1)
    {
        points1 = points1.reshape(1, npoints);
        points2 = points2.reshape(1, npoints);
    }

    double fx = cameraMatrix.at<double>(0,0);
    double fy = cameraMatrix.at<double>(1,1);
    double cx = cameraMatrix.at<double>(0,2);
    double cy = cameraMatrix.at<double>(1,2);

    points1.col(0) = (points1.col(0) - cx) / fx;
    points2.col(0) = (points2.col(0) - cx) / fx;
    points1.col(1) = (points1.col(1) - cy) / fy;
    points2.col(1) = (points2.col(1) - cy) / fy;

    // Reshape data to fit opencv ransac function
    points1 = points1.reshape(2, npoints);
    points2 = points2.reshape(2, npoints);

    threshold /= (fx+fy)/2;

    Mat E;
    if( method == RANSAC )
        createRANSACPointSetRegistrator(makePtr<EMEstimatorCallback>(), 5, threshold, prob)->run(points1, points2, E, _mask);
    else
        createLMeDSPointSetRegistrator(makePtr<EMEstimatorCallback>(), 5, prob)->run(points1, points2, E, _mask);

    return E;
}

29行目から32行目のこの部分で内部パラメータを使っています。どうにも画像座標をカメラ座標へ変換するのに使っているようです。

points1.col(0) = (points1.col(0) - cx) / fx;
points2.col(0) = (points2.col(0) - cx) / fx;
points1.col(1) = (points1.col(1) - cy) / fy;
points2.col(1) = (points2.col(1) - cy) / fy;

また、38行目で焦点距離でRANSACで使う閾値を除算しています。これは29~32行目で焦点距離で除算してるのに合わせてるだけっぽいです。

threshold /= (fx+fy)/2;

なのでおそらく、ここでは焦点距離の値は何でもよくて、画像座標系の原点を画像の左上から画像の中心(光学中心)に移動させるっていう意味合いが強いのだと思います。
ですので、あらかじめ各特徴点を光学中心を原点にしたものに変換してから_points1, _points2に与えてやれば、内部パラメータは焦点距離=1.0, 光学中心=(0.0, 0.0)としても多分問題ないです。

だからこんな感じでデフォルトパラメータの焦点距離が1.0で光学中心が(0.0, 0.0)になっているのでしょうね。

CV_EXPORTS_W Mat findEssentialMat( InputArray points1, InputArray points2,
                                 double focal = 1.0, Point2d pp = Point2d(0, 0),
                                 int method = RANSAC, double prob = 0.999,
                                 double threshold = 1.0, OutputArray mask = noArray() );

findEssentialMatは2枚の画像で同じ内部パラメータを使う前提なのですが、あらかじめカメラ座標系に変換してやって_points1, _points2に渡してやることで、2枚の画像で別々の内部パラメータを使うことができるはずです。

※ 論文もコードも深追いしたわけではないので、間違えがあれば教えてください。

コメント