複数のマーカを扱う【ARToolKit】

ar_multi_marker

ARToolKitで複数のマーカを扱う方法を紹介したいと思います。マーカ間の距離と角度の差も求めてみます。

ARToolKit基本プログラムで普通にCGモデルを描画することはできたと思います。

ですが、マーカは1つしか認識できていません。

そこで、複数のマーカを認識してCGモデルを描画させてみます。 ついでに、座標変換行列の理解を深めるためにマーカ間の距離と角度の差も表示させてみました。

ソース構成
  • ARTK_MultiMarker.cpp
ソースダウンロード
ARTK_MultiMarker.zip
使うマーカ
  • ARToolKit付属の「Hiro」
  • ARToolKit付属の「Sample1」
  • ARToolKit付属の「Kanji」
プロジェクトの設定
プロジェクトの種類:Win32コンソールアプリケーション
Debug時ライブラリ:libARd.lib libARgsubd.lib libARvideod.lib opengl32.lib glu32.lib glut32.lib
Release時ライブラリ:libAR.lib libARgsub.lib libARvideo.lib opengl32.lib glu32.lib glut32.lib
実行結果のイメージ
ar_multi_marker

基本的にはモデル表示を安定化させたプログラムと一緒です。 違うのはパターンやマーカに関する処理の部分です。

① グローバル変数

ARTK_MultiMarker.cpp(グローバル変数)
/* パターンファイル */
#define MARK_NUM        3                        // 使用するマーカの個数
//-----
#define MARK1_MARK_ID    1                        // マーカID
#define MARK1_PATT_NAME    "Data\\patt.hiro"        // パターンファイル名
#define MARK1_SIZE        80.0                    // パターンの幅(80mm)
//-----
#define MARK2_MARK_ID    2                        // マーカID
#define MARK2_PATT_NAME    "Data\\patt.sample1"    // パターンファイル名
#define MARK2_SIZE        80.0                    // パターンの幅(80mm)
//-----
#define MARK3_MARK_ID    3                        // マーカID
#define MARK3_PATT_NAME    "Data\\patt.kanji"        // パターンファイル名
#define MARK3_SIZE        80.0                    // パターンの幅(80mm)
//-----
typedef struct {
    char   *patt_name;            // パターンファイル
    int    patt_id;                // パターンのID
    int    mark_id;                // マーカID
    int    visible;                // 検出フラグ
    double patt_width;            // パターンのサイズ(単位:mm)
    double patt_center[2];        // パターンの中心座標
    double patt_trans[3][4];    // 座標変換行列
} MARK_T;
//-----
MARK_T   marker[MARK_NUM] = {
    {MARK1_PATT_NAME, -1, MARK1_MARK_ID, 0, MARK1_SIZE, {0.0, 0.0}},
    {MARK2_PATT_NAME, -1, MARK2_MARK_ID, 0, MARK2_SIZE, {0.0, 0.0}},
    {MARK3_PATT_NAME, -1, MARK3_MARK_ID, 0, MARK3_SIZE, {0.0, 0.0}}
}; 

定義部分(define)

プログラム中で使うので、defineで定義してみました。

使用するマーカの個数使うマーカの数です。今回は3個なので3。
マーカID一つ一つのマーカに割り振るIDです。
パターンファイル名パターンファイルへのパスです。
パターンの幅パターンの幅(単位:mm)

構造体(MARK_T)およびその初期化

この構造体は初期化時にマーカの数だけ確保されます。 プログラム内部で結構使うので、しっかり理解しておいてください。

patt_nameパターンファイル名を格納する変数です。
初期化時にdefineで定義したパターンファイル名を割り振ります。
patt_idパターンを作った時に割り振られるIDを格納しておく変数です。マーカIDと混同しやすいですが、こっちはファイルから読み込んだパターン情報がARToolKitの内部関数にセットされるときに生成されるIDです。このIDを使ってマーカパターンの識別を行います。基本プログラムからあったpatt_idと同じものです。
初期化時には-1にしておきます。
mark_id一つ一つのマーカに割り振ったIDです。パターンIDと混同しやすいですが、こっちは「どのマーカでどんな処理をするか」といった時に使う変数です。
初期化時にdefineで定義したマーカIDを割り振ります。
visibleそのマーカを検出したかどうかというフラグ(0:検出してない、1:検出した)。基本的にbool型でも構わないのですが、プログラム内部的にやりにくい&わかりにくいのでint型です。
初期化時には検出してない状態にしておきます。
patt_widthパターンのサイズ(単位:mm)。ARToolKit基本プログラムからあったpatt_widthと同じものです。
初期化時にdefineで定義したパターンの幅を割り振ります。
patt_centerパターンの中心座標。ARToolKit基本プログラムからあったpatt_centerと同じものです。
初期化時に0で初期化します。
patt_trans座標変換行列。ARToolKit基本プログラムからあったpatt_transと同じものです。
初期化はしません。

② 初期化関数

ARTK_MultiMarker.cpp(初期化関数の中)
// パターンファイルのロード
for( int i=0; i<MARK_NUM; i++ ){
    if( (marker[i].patt_id = arLoadPatt(marker[i].patt_name)) < 0){
        printf("パターンファイルの読み込みに失敗しました\n");
        printf("%s\n", marker[i].patt_name);
        exit(0);
    }
} 

マーカの数分だけパターンファイルのロードを行います。 各マーカのパターンIDにarLoadPattを使ってパターンIDを割り振ります。

③ メインループ関数

ARTK_MultiMarker.cpp(メインループ関数の中)
// 3Dオブジェクトを描画するための準備
argDrawMode3D();
argDraw3dCamera( 0, 0 );
glClearDepth(1.0);                    // デプスバッファの消去値
glClear( GL_DEPTH_BUFFER_BIT );        // デプスバッファの初期化

// マーカの数分だけループ
for( i=0; i<MARK_NUM; i++ ){
    // マーカの一致度の比較
    k = -1;
    for( j=0; j<marker_num; j++ ){
        if( marker[i].patt_id == marker_info[j].id ){
            if( k == -1 ) k = j;
            else if( marker_info[k].cf < marker_info[j].cf ) k = j;
        }
    }

    // マーカーが見つからなかったとき
    if( k == -1 ){
        marker[i].visible = 0;
        continue;
    }

    // 座標変換行列を取得
    if( marker[i].visible == 0 ) {
        // 1フレームを使ってマーカの位置・姿勢(座標変換行列)の計算
        arGetTransMat( &marker_info[k], marker[i].patt_center, marker[i].patt_width,
                        marker[i].patt_trans );
    } else {
        // 前のフレームを使ってマーカの位置・姿勢(座標変換行列)の計算
        arGetTransMatCont( &marker_info[k], marker[i].patt_trans, marker[i].patt_center,
                           marker[i].patt_width, marker[i].patt_trans );
    }
    marker[i].visible = 1;

    // 3Dオブジェクトの描画
    DrawObject( marker[i].mark_id, marker[i].patt_trans );
}

3Dオブジェクトを描画するための準備

この4つの関数は基本プログラムでは描画関数で行われていました。 それをここの部分に移したのには理由があります。

それは、これらが初期化処理だからです。 今までは、

マーカ認識→座標変換行列を取得→(準備→描画)→スワップ

とやっていましたが(カッコ内が描画関数)、これをそのままにして複数マーカの描画を行うと、1つのオブジェクトを描くごとに初期化がされていくので、マーカIDが後の方のモデルだけ描画される形になります。 これではまずいので、

マーカ認識→準備→「各マーカの座標変換行列を取得→(各モデルの描画)」→スワップ

としました(「」内がマーカの数分だけループ)。こうすることで、準備→描画→スワップという流れが維持できます。

マーカの数分だけループ

それぞれのマーカについて、モデル表示を安定化させたプログラムと同じ処理を行います。

マーカ一致度の比較は、基本的に一緒です。 画像から得られたマーカと思われる部分について調べています。 MARK_NUMとmarker_numを混同しそうですが、MARK_NUMは使うマーカの数、marker_numは認識によって得られたマーカと思われる部分の数です。

マーカが見つからなかったときは、そのマーカの検出フラグを0にして、continueします。 returnではなくcontinueにしてあるのは、他のマーカについても調べなくてはならないからです。

座標変換行列の取得に関しては、最初の検出のとき(visible == 0)はarGetTransMat()、それ以外のときはarGetTransMatCont()を使うようにします。 この時点で検出できてることはわかっているので、そのマーカの検出フラグを1にしておきます。

そして、描画を行います。

④ 描画関数

ARTK_MultiMarker.cpp(3Dオブジェクトの描画関数)
//=======================================================
// 3Dオブジェクトの描画を行う関数
//=======================================================
void DrawObject( int mark_id, double patt_trans[3][4] )
{
    double gl_para[16];    // ARToolKit->OpenGL変換行列

    // 陰面消去
    glEnable( GL_DEPTH_TEST );            // 陰面消去・有効
    glDepthFunc( GL_LEQUAL );            // デプステスト

    // 変換行列の適用
    argConvGlpara( patt_trans, gl_para );    // ARToolKitからOpenGLの行列に変換
    glMatrixMode( GL_MODELVIEW );            // 行列変換モード・モデルビュー
    glLoadMatrixd( gl_para );                // 読み込む行列を指定

    switch( mark_id ){
        case MARK1_MARK_ID:
            // ライティング
            SetupLighting1();            // ライトの定義
            glEnable( GL_LIGHTING );    // ライティング・有効
            glEnable( GL_LIGHT0 );        // ライト0・オン
            // オブジェクトの材質
            SetupMaterial1();

            // 3Dオブジェクトの描画
            glTranslatef( 0.0, 0.0, 25.0 );    // マーカの上に載せるためにZ方向(マーカ上方)に25.0[mm]移動
            glutSolidCube( 50.0 );            // ソリッドキューブを描画(1辺のサイズ50[mm])
            break;

        case MARK2_MARK_ID:
            // ライティング
            SetupLighting2();            // ライトの定義
            glEnable( GL_LIGHTING );    // ライティング・有効
            glEnable( GL_LIGHT0 );        // ライト0・オン
            // オブジェクトの材質
            SetupMaterial2();

            // 3Dオブジェクトの描画
            glTranslatef( 0.0, 0.0, 25.0 );        // マーカの上に載せるためにZ方向(マーカ上方)に25.0[mm]移動
            glutSolidSphere( 50.0, 10, 10 );    // ソリッドスフィアを描画(1辺のサイズ50[mm])
            break;

        case MARK3_MARK_ID:
            // ライティング
            SetupLighting1();            // ライトの定義
            glEnable( GL_LIGHTING );    // ライティング・有効
            glEnable( GL_LIGHT0 );        // ライト0・オン
            // オブジェクトの材質
            SetupMaterial2();

            // 3Dオブジェクトの描画
            glTranslatef( 0.0, 0.0, 25.0 );    // マーカの上に載せるためにZ方向(マーカ上方)に25.0[mm]移動
            glRotated( 90, 1.0, 0.0, 0.0);    // ティーポットをマーカ上に載せるために90°回転
            glutSolidTeapot( 50.0 );        // ソリッドティーポットを描画(サイズ50[mm])
            break;
    }


    // 終了処理
    glDisable( GL_LIGHTING );        // ライティング・無効
    glDisable( GL_DEPTH_TEST );        // デプステスト・無効
} 

関数の引数についてですが、基本プログラムではvoidでしたが、今回はmark_idとpatt_transという引数を設けました。 というのも、各マーカごとに保持しているものが違うからです。

あとは、マーカごとに描画の仕方を変えたくらいで、内容的には一緒です。

⑤ 距離と角度の差の表示

ARTK_MultiMarker.cpp(メインループ関数の中)
// 2つのマーカ間の距離を表示(マーカ1[Hiro]とマーカ2[Sample1]を認識した場合)
if ( marker[0].visible > 0 && marker[1].visible > 0 ){
    double wmat1[3][4], wmat2[3][4];

    // ビュー→マーカ行列(カメラ座標系を基準に考えたマーカの位置・姿勢)を取得
    arUtilMatInv( marker[0].patt_trans, wmat1 );
    // マーカ1座標系を基準に考えたマーカ2の位置・姿勢(=マーカ1とマーカ2の距離・角度の差)を取得
    arUtilMatMul( wmat1, marker[1].patt_trans, wmat2 );
    
    // 距離を表示(x, y, z)
    printf( "%5.4lf[mm] %5.4lf[mm] %5.4lf[mm]\n", wmat2[0][3], wmat2[1][3], wmat2[2][3] );
}

// 2つのマーカ間の角度の差を表示(マーカ1[Hiro]とマーカ3[Kanji]を認識した場合)
if ( marker[0].visible > 0 && marker[2].visible > 0 ){
    double wmat1[3][4], wmat2[3][4];
    double yaw, pitch, roll;

    // ビュー→マーカ行列(カメラ座標系を基準に考えたマーカの位置・姿勢)を取得
    arUtilMatInv( marker[0].patt_trans, wmat1 );
    // マーカ1座標系を基準に考えたマーカ3の位置(=マーカ1とマーカ3の距離・姿勢の差)を取得
    arUtilMatMul( wmat1, marker[2].patt_trans, wmat2 );
    
    // 姿勢の差を表示
    //for( i=0; i<3; i++ ) {
    //    for( j=0; j< 3; j++ ) printf("%5.4f ", wmat2[i][j]);
    //    printf("\n");
    //}
    //printf("\n");
    
    // 角度の差を表示(-180°~180°)
    yaw = atan2( wmat2[1][0], wmat2[0][0] );
    pitch = atan2( wmat2[2][1], wmat2[2][2] );
    roll = atan2( wmat2[2][0], sqrt(wmat2[2][1]*wmat2[2][1] + wmat2[2][2]*wmat2[2][2]) );

    printf( "yaw = %4.4lf pitch = %4.4lf roll = %4.4lf\n", 180.0*yaw/M_PI, 180.0*pitch/M_PI, 180.0*roll/M_PI );
} 

マーカの組み合わせで表示するものを変えてみました。 それぞれ、検出フラグが両者とも1になっていたら表示させるようにしました。

基本的な処理は、距離でも角度の差でも一緒です。

wmat1とwmat2についてですが、これは一時的に数を入れておくだけの変数です。 こういう「一時的に使用する」のをテンポラリと言って、tempとかいう名前でよくあったりします。

で、メインの処理となるのが、

// ビュー→マーカ行列(カメラ座標系を基準に考えたマーカの位置・姿勢)を取得
arUtilMatInv( patt_trans1, wmat1 );
// マーカ1座標系を基準に考えたマーカ2の位置・姿勢(=マーカ1とマーカ2の距離・角度の差)を取得
arUtilMatMul( wmat1, patt_trans2, wmat2 );

です。まず、各関数についてですが、

arUtilMatInvは引数1の行列の逆行列を引数2に入れる関数
arUtilMatMulは引数1の行列と引数2の行列の掛け算をした行列を引数3に入れる関数

です。各行列はARToolKitのフォーマットである4×3の行列です。 何度も書いていますが、この行列は↓の回転成分と並進成分を含んでいます。

artoolkit_trans

ARToolKitで得られるのは↑のようにマーカ座標系からカメラ座標系に変換する行列です。

[カメラ座標系]=[変換行列1][マーカ座標系1]
[カメラ座標系]=[変換行列2][マーカ座標系2]

これの逆行列を取るとどうなるかというと、カメラ座標系からマーカ座標系に変換する行列が得られます。 まさに逆の関係が得られるんです。

[マーカ座標系1]=[変換行列1][カメラ座標系]

カメラ座標系はどのマーカでも一緒なので(カメラは1個なので当然です)、

[マーカ座標系1]=[変換行列1][変換行列2][マーカ座標系2]

こうなります。 こいつが何を意味するかというと、マーカ座標系1からマーカ座標系2の座標変換になります。 つまりは、[変換行列1]と[変換行列2]を掛けたものは、マーカ座標系1からマーカ座標系2の座標変換行列になります。 これも当然回転成分と並進成分を含んでいるわけで、それはつまりマーカ1からマーカ2への距離と回転量になります。

ということは、これをそのまま表示させてあげれば、距離と回転量が表示できます。

・・・ですが、回転量に関しては、このまま表示させるとベクトルの差が出てきて、数字の意味がわかりにくいので、度数法(°)で表します。 角度の算出に関しては↓のサイトを参考にしてみてください。

ちなみに、実行結果はそれぞれ↓みたいな感じです。

x距離

ar_multi_marker_view_xmove

y距離

ar_multi_marker_view_ymove

z距離

ar_multi_marker_view_zmove

yaw(水平回転)

ar_multi_marker_view_yaw

pitch(縦回転)

ar_multi_marker_view_pitch

roll(横回転)

ar_multi_marker_view_roll

複数マーカが扱えるようになりました。
角度計算なんかもできたので、上手に制御したり、3D描画すれば、この動画の方みたいなこともできると思います。

PAGE TOP

Twitter

    人気記事

    新着まとめ