ARToolKit基本プログラムで普通にCGモデルを描画することはできたと思います。
ですが、マーカは1つしか認識できていません。
そこで、複数のマーカを認識してCGモデルを描画させてみます。 ついでに、座標変換行列の理解を深めるためにマーカ間の距離と角度の差も表示させてみました。
ソース構成 |
---|
|
ソースダウンロード |
ARTK_MultiMarker.zip |
使うマーカ |
|
プロジェクトの設定 |
プロジェクトの種類: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 |
実行結果のイメージ |
基本的にはモデル表示を安定化させたプログラムと一緒です。 違うのはパターンやマーカに関する処理の部分です。
① グローバル変数
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で得られるのは↑のようにマーカ座標系からカメラ座標系に変換する行列です。
[カメラ座標系]=[変換行列1][マーカ座標系1]
[カメラ座標系]=[変換行列2][マーカ座標系2]
これの逆行列を取るとどうなるかというと、カメラ座標系からマーカ座標系に変換する行列が得られます。 まさに逆の関係が得られるんです。
[マーカ座標系1]=[変換行列1][カメラ座標系]
カメラ座標系はどのマーカでも一緒なので(カメラは1個なので当然です)、
[マーカ座標系1]=[変換行列1][変換行列2][マーカ座標系2]
こうなります。 こいつが何を意味するかというと、マーカ座標系1からマーカ座標系2の座標変換になります。 つまりは、[変換行列1]と[変換行列2]を掛けたものは、マーカ座標系1からマーカ座標系2の座標変換行列になります。 これも当然回転成分と並進成分を含んでいるわけで、それはつまりマーカ1からマーカ2への距離と回転量になります。
ということは、これをそのまま表示させてあげれば、距離と回転量が表示できます。
・・・ですが、回転量に関しては、このまま表示させるとベクトルの差が出てきて、数字の意味がわかりにくいので、度数法(°)で表します。 角度の算出に関しては↓のサイトを参考にしてみてください。
ちなみに、実行結果はそれぞれ↓みたいな感じです。
x距離
y距離
z距離
yaw(水平回転)
pitch(縦回転)
roll(横回転)
複数マーカが扱えるようになりました。
角度計算なんかもできたので、上手に制御したり、3D描画すれば、この動画の方みたいなこともできると思います。