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描画すれば、この動画の方みたいなこともできると思います。








