聞きかじりめも

主にC++やメディア処理技術などに関して気付いたことを書いていきます.ここが俺のメモ帳だ!

OpenCVで得たカメラ内部パラメータをOpenGLの射影行列に変換

なぜかネットに正しい情報が少ないのでメモ書き

これまでのあらすじ

ARアプリケーションはARToolKitを使うのが最も楽ですが,残念ながらARToolKitGLUTに大きく依存しており,GLFW+GLEW+GLMを使ってモダンOpenGLで開発している自分にとってこれは非常にありがたくないです.特にGLSLを使う関係上,どうしてもプロジェクション行列・モデルビュー行列を自分で導かなくてはならなくなりました.モデルビュー行列はまだいいとして,プロジェクション行列は今まで適当にしか学んでなかったため,gluPerspective()arglCameraFrustumRH()以外の方法を知らず,OpenCVのカメラキャリブレーションで得た内部パラメータをOpenGLでどう使ったものか途方に暮れていました.arglCameraFrustumRH()の中身もなんかよくわかんないし...

上手くいったコード

//  OpenCVカメラパラメータからOpenGL(GLM)プロジェクション行列を得る関数
void cameraFrustumRH(Mat camMat, Size camSz, glm::mat4 &projMat, double znear, double zfar)
{
    // Load camera parameters
    double fx = camMat.at<double>(0, 0);
    double fy = camMat.at<double>(1, 1);
    double cx = camMat.at<double>(0, 2);
    double cy = camMat.at<double>(1, 2);
    double w = camSz.width, h = camSz.height;

    // 参考:https://strawlab.org/2011/11/05/augmented-reality-with-OpenGL
    // With window_coords=="y_down", we have:
    // [2 * K00 / width,   -2 * K01 / width,   (width - 2 * K02 + 2 * x0) / width,     0]
    // [0,                 2 * K11 / height,   (-height + 2 * K12 + 2 * y0) / height,  0]
    // [0,                 0,                  (-zfar - znear) / (zfar - znear),       -2 * zfar*znear / (zfar - znear)]
    // [0,                 0,                  -1,                                     0]

    
    glm::mat4 projection(
        2.0 * fx / w,      0,                     0,                                     0,
        0,                 2.0 * fy / h,          0,                                     0,
        1.0 - 2.0 * cx / w,   - 1.0 + 2.0 * cy / h, -(zfar + znear) / (zfar - znear),       -1.0,
        0,                 0,                     -2.0 * zfar * znear / (zfar - znear),  0);
    projMat = projection;
}

いろんな実装例を試しましたが,これがうまくいっています(引用元がどこだったか忘れました...).数学的にどうなってるのかまだ良く分かってないのでこれから勉強します.projectionの初期化時に転置されているように見えますが,これはGLMライブラリ(ひいてはOpenGL)の特性によるもので,実際には転置されていません.

(追記:2015/12/04)引用元はMicrosoft RoomAlive Toolkitのソースコードです.元のコードはDirectXなので左手座標系になっていますが,OpenGLの右手座標系にするためにZ軸を全て-1倍したものが上記のソースコードになります.

(修正:2015/12/21) 上手くいってるように思ってましたが,よく見ると間違ってることに気付いたので修正.参考資料は→ https://strawlab.org/2011/11/05/augmented-reality-with-OpenGL

(追記:2016/06/16) 上の透視投影行列の導き方をアップしました.

OpenCVの内部パラメータでOpenGLの透視投影行列を作成(そのに) - 聞きかじりめも

使い方

ちなみに,上のソースは次のように使います.

使用APIOpenCV, OpenGL3.3(with GLSL), GLFW, GLEW, GLM, ARToolKitPlusです.

// プロジェクション行列
    glm::mat4 Projection;
    cameraFrustumRH(cameraMatrix, cameraSize, Projection, 0.1, 5000);
// カメラビュー行列
// 光軸方向がZ軸正方向を向くカメラで,Y軸負方向がカメラの上ベクトルとする
// プロジェクション行列はそうなるように作っている
    glm::mat4 View = glm::mat4(1.0)
        * glm::lookAt(
        glm::vec3(0, 0, 0), // カメラの原点
        glm::vec3(0, 0, 1), // 見ている点
        glm::vec3(0, -1, 0)  // カメラの上方向
        );
// モデル行列
// ARマーカーの位置姿勢は,ARToolKitのarTransMat(),ARToolKitPlusのTrackerImpl::getModelViewMatrix()などで手に入れたやつを使う
    glm::mat4 markerTransMat = glm::make_mat4(tracker->getModelViewMatrix()); // ARマーカー位置姿勢
    glm::mat4 Model = glm::mat4(1.0)
        * markerTransMat; 
// モデルビュー行列,プロジェクション行列
// これをGLSLシェーダ―に転送する
    glm::mat4 MV = View * Model;
    glm::mat4 MVP = Projection * MV;

なお,カメラビュー行列をY軸正方向を上とした場合,つまり

 glm::mat4 View = glm::mat4(1.0)
        * glm::lookAt(
        glm::vec3(0, 0, 0), // カメラの原点
        glm::vec3(0, 0, 1), // 見ている点
        glm::vec3(0, 1, 0)  // カメラの上方向
        );

の場合は,cameraFrustumRH()の中身は次のようになります.

//  OpenCVカメラパラメータからOpenGL(GLM)プロジェクション行列を得る関数
void cameraFrustumRH(Mat camMat, Size camSz, glm::mat4 &projMat, double znear, double zfar)
{
    // Load camera parameters
    double fx = camMat.at<double>(0, 0);
    double fy = camMat.at<double>(1, 1);
    double cx = camMat.at<double>(0, 2);
    double cy = camMat.at<double>(1, 2);
    double w = camSz.width, h = camSz.height;

    // 参考:https://strawlab.org/2011/11/05/augmented-reality-with-OpenGL
    // With window_coords=="y_down", we have:
    // [2 * K00 / width,   -2 * K01 / width,   (width - 2 * K02 + 2 * x0) / width,     0]
    // [0,                 2 * K11 / height,   (-height + 2 * K12 + 2 * y0) / height,  0]
    // [0,                 0,                  (-zfar - znear) / (zfar - znear),       -2 * zfar*znear / (zfar - znear)]
    // [0,                 0,                  -1,                                     0]

    
    glm::mat4 projection(
        -2.0 * fx / w,     0,                     0,                                     0,
        0,                 -2.0 * fy / h,         0,                                     0,
        1.0 - 2.0 * cx / w,   - 1.0 + 2.0 * cy / h, -(zfar + znear) / (zfar - znear),       -1.0,
        0,                 0,                     -2.0 * zfar * znear / (zfar - znear),  0);
    projMat = projection;
}

気が向いたら実行結果のスクショを取りたいと思います.(今すぐ用意できる実行結果は企業秘密的にちょっとまずいファイルなので...)

(2015/12/22追記)この行列を使ってARした結果がこんな感じです.

f:id:Mzawa2:20151222163754p:plain:w120 f:id:Mzawa2:20151222163941p:plain:w120 f:id:Mzawa2:20151222163801p:plain:w120 f:id:Mzawa2:20151222163812p:plain:w120

(2016/06/12追記)この行列の導出過程をアップしました.

OpenCVの内部パラメータでOpenGLの透視投影行列を作成(そのに) - 聞きかじりめも