ARToolKit基本プログラム

ar_basic_view

ARToolKitのプログラムは基本がおさえられていれば、あとはほぼ問題ないです。なので、少し詳しめに紹介しようと思います。 なお、ARToolKitに的を絞るので、OpenGL関係の部分は省略します。

ソース構成
  • ARTK_Basic.cpp
使うマーカ
  • ARToolKit付属の「Hiro」
ソースダウンロード
ARTK_Basic.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
実行結果のイメージ
ar_basic_view

① ヘッダのインクルード

ARTK_basic.cpp
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include <AR/ar.h>
#include <AR/param.h>
#include <AR/video.h>
#include <AR/gsub.h>

ヘッダについてはARToolKitのヘッダファイルにまとめておきました。 必要なヘッダをインクルードしてください。

“Windows以外ならwindows.hはいらない”とか、”stdio.hじゃなくてiostream使ってます”とか若干の違いはありますが、基本的に一緒です。

② グローバル変数

ARTK_basic.cpp
// グローバル変数
/* カメラ構成 */
char *vconf_name  = "Data/WDM_camera_flipV.xml";	// ビデオデバイスの設定ファイル
int  xsize;											// ウィンドウサイズ
int  ysize;											// ウィンドウサイズ
int  thresh = 100;									// 2値化の閾値
int  count = 0;										// 処理フレーム数

/* カメラパラメータ */
char *cparam_name = "Data/camera_para.dat";			// カメラパラメータファイル
ARParam cparam;										// カメラパラメータ

/* パターンファイル */
char   *patt_name   = "Data/patt.hiro";				// パターンファイル
int    patt_id;										// パターンのID
double patt_trans[3][4];							// 座標変換行列
double patt_center[2] = { 0.0, 0.0 };				// パターンの中心座標
double patt_width     = 80.0;						// パターンのサイズ(単位:mm)

カメラ構成

カメラ、ビデオデバイス系の設定変数です。 ビデオデバイスの設定ファイル(WDM_camera_flipV.xml)には、使用するビデオデバイスの情報が入っています。このファイルへの相対パスを設定します。

2値化の閾値についてですが、これはARToolKit内部で行われている処理のためのものです。 ARToolKitでは画像を2値化して検出を行っているので、そのための閾値になります。 環境によって変えると認識率が上がったり下がったりします。

カメラパラメータ

カメラパラメータファイル(camera_para.dat)には、カメラのレンズ設定のようなものが入っています。 キャリブレーションをすると出てくるやつです。 後々説明しますが、こいつの中身は射影行列です。

パターンファイル

パターンファイル(patt.hiro)には、マーカのパターン情報が入っています。 「patt.hiro」はARToolKitに標準で付いている「HIRO」と書いてあるマーカです。

パターンのIDはパターンを作った時に割り振られるIDを格納しておく変数です。 座標変換行列、パターンの中心座標は描画の際に使います。 パターンのサイズは、マーカの外枠の実寸(単位:mm)です。 ARToolKitでは正方形のパターンなので、幅だけで大丈夫です。

③ main関数

ARTK_basic.cpp
//=======================================================
// main関数
//=======================================================
int main( int argc, char **argv )
{
	// GLUTの初期化
	glutInit( &argc, argv );

	// ARアプリケーションの初期化
	Init();

	// ビデオキャプチャの開始
	arVideoCapStart();

	// メインループの開始
	argMainLoop( MouseEvent, KeyEvent, MainLoop );

	return 0;
}

まず、GLUTの初期化を行います。
初期化関数内では、↑でパスを設定した外部ファイルを読み込んで初期化を行います。

ARTK_basic.cpp
//=======================================================
// 初期化関数
//=======================================================
void Init(void)
{
	ARParam wparam;		// カメラパラメータ

	// ビデオデバイスの設定
	if( arVideoOpen( vconf_name ) < 0 ){
		printf("ビデオデバイスのエラー\n");
		exit(0);
	}

	// ウィンドウサイズの取得
	if( arVideoInqSize( &xsize, &ysize ) < 0 ) exit(0);
	printf("Image size (x,y) = (%d,$d)\n", xsize, ysize);

	// カメラパラメータの設定
	if( arParamLoad( cparam_name, 1, &wparam ) < 0 ){
		printf("カメラパラメータの読み込みに失敗しました\n");
		exit(0);
	}

	// カメラパラメータのサイズ調整
	arParamChangeSize( &wparam, xsize, ysize, &cparam );
	// カメラパラメータの初期化
	arInitCparam( &cparam );
	printf("*** Camera Parameter ***\n");
	arParamDisp( &cparam );

	// パターンファイルのロード
	if( (patt_id = arLoadPatt(patt_name)) < 0){
		printf("パターンファイルの読み込みに失敗しました\n");
		exit(0);
	}

	// gsubライブラリの初期化
	argInit( &cparam, 1.0, 0, 0, 0, 0 );

	// ウィンドウタイトルの設定
	glutSetWindowTitle("ARTK_basic");
}

ビデオデバイスの設定

arVideoOpen()では、ビデオデバイス用パスを使ってビデオデバイスを使える状態にします。 そして、arVideoInqSize()でウィンドウサイズを取得します。 これは実行時に出てくるカメラの設定のやつです。

カメラパラメータの設定

arParamLoad()では、カメラパラメータ用パスを使ってカメラパラメータを取得します。 arParamChangeSize()を用いて、それを画面のサイズに合わせて変更します。 そして、arInitCParam()でカメラパラメータをARToolKitにセットします。 ARToolKitでは、計算に用いるパラメータをグローバル変数によって内部で管理しており、カメラパラメータもその一つなので、これをセットします。

arParamDisp()では、ARParam変数の中身を見ることができます。

ar_basic_console_proj

パターンファイルの設定

arLoadPatt()では、パターンファイル用パスを使ってパターン情報を取得します。 この時、ファイルから読み込んだパターン情報がARToolKitの内部変数にセットされ、IDが生成されます。 このIDを使ってマーカパターンの識別を行います。

ウィンドウの設定

argInit()はgsubモジュールの初期化を行う関数です。

void argInit( ARParam* cparam, double zoom, int fullFlag, int xwin, int ywin, int hmd_flag )

【引数】
cparam:カメラパラメータ
zoom:表示画像サイズの拡大率(入力画像サイズに対して)
fullFlag:フルスクリーンモード(1:有効、0:無効)
xwin:横方向の表示領域の追加数(ウィンドウ内に複数の表示領域を作りたいときに使う、普通は0)
ywin:縦方向の表示領域の追加数(ウィンドウ内に複数の表示領域を作りたいときに使う、普通は0)
hmd_flag:ステレオディスプレイモード(1:有効、0:無効)

初期化が終わったら、ビデオキャプチャを開始してからメインループに入ります。 argMainLoop()についてですが、これは内部的にマウス入力などの関数を登録してから、glutMainLoop()を行っています。(ARToolKitルートフォルダ/lib/SRC/Gl/gsub.cの中身を見るとわかる) なので、もしマウス入力などを使わないならNULLにしておけばOKです。 ARToolKitはglutを使って実装されているので、その前にglut系の関数を使うと色々できます。 例えば、ウィンドウのタイトルを設定するにはglutSetWindowTitle()を使うと設定することができます。(ただし、argInit()の後に書いてください)

④ メインループ関数

ARTK_basic.cpp
//=======================================================
// メインループ関数
//=======================================================
void MainLoop(void)
{
	ARUint8          *image;			// カメラキャプチャ画像
	ARMarkerInfo     *marker_info;		// マーカ情報
	int              marker_num;		// 検出されたマーカの数
	int              j, k;

	// カメラ画像の取得
	if( (image = (ARUint8 *)arVideoGetImage()) == NULL ){
		arUtilSleep( 2 );
		return;
	}
	if( count == 0 ) arUtilTimerReset();
	count++;

	// カメラ画像の描画
	argDrawMode2D();
	argDispImage( image, 0, 0 );

	// マーカの検出と認識
	if( arDetectMarker( image, thresh, &marker_info, &marker_num ) < 0 ){
		Cleanup();
		exit(0);
	}

	// 次の画像のキャプチャ指示
	arVideoCapNext();

	// マーカの一致度の比較
	k = -1;
	for( j = 0; j < marker_num; j++ ){
		if( 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 ){
		argSwapBuffers();
		return;
	}

	// マーカの位置・姿勢(座標変換行列)の計算
	arGetTransMat( &marker_info[k], patt_center, patt_width, patt_trans );

	// 3Dオブジェクトの描画
	DrawObject();	

	// バッファの内容を画面に表示
	argSwapBuffers();
}

カメラ画像の取得

arVideoGetImage()関数でカメラから画像を取得します。 この関数は画像データへのポインタを返す関数です。 ARUint8という型についてですが、中身的にはunsigned charと一緒です。 画像フォーマットはBGRAというフォーマットになっています。 新しい画像データが用意できてないとNULLを返すようになっています。

カメラ画像の描画

argDrawMode2D()関数で2次元画像を描画する準備をしてから、argDispImage()関数で画像を描画します。

argDrawMode2D()関数の内部では、射影行列を正射影(遠近感を全く考えない投影)にしています。 これによって、画面に2次元画像を描画する準備をしています。

arDispImage()関数はカメラ画像を描画する際にカメラパラメータによるレンズ歪みの補正も行っています。

void argDispImage( ARUint8 *image, int xwin, int ywin )

【引数】
image:画像データへのポインタ
xwin:表示領域の番号(ウィンドウ内に複数の表示領域を作りたいときに使う、普通は0)
ywin:表示領域の番号(ウィンドウ内に複数の表示領域を作りたいときに使う、普通は0)

引数のxwinとywinは、初期化で行ったargInit()関数で表示領域を増やした場合に使います。

マーカの検出と認識

カメラ画像の描画が終わったら、そのカメラ画像を使って今度はマーカの検出と認識を行います。 これにはarDetectMarker()関数を使います。

int arDetectMarker( ARUint8 *dataPtr, int thresh, ARMarkerInfo **marker_info, int *marker_num )

【引数】
dataPtr:画像データへのポインタ
thresh:2値化の閾値(0~255の範囲)
marker_info:マーカ情報を格納する配列へのポインタ
marker_num:検出されたマーカの数

この関数はARToolKitで重要になる関数の一つです。 画像データ(dataPtr)からマーカと思われる部分を全て取得して、マーカ情報(marker_info)にその情報をセットします。 この際に、内部で画像の2値化を使っているので閾値を指定します。 この2値化画像はar.hのグローバル変数であるデバッグ用の画像(arImage)を使えば見ることができます。

マーカと思われる部分を全て取得しているので、marker_infoは配列になっています。(例えば2つ検出したら、marker_info[0]、marker_info[1]といった感じで指定できる) marker_numには見つけたマーカの数が入っています。

このmarker_infoがとても重要です。
ARMarkerInfoという構造体でできているのですが、この中にマーカの情報が全て詰まっています。

ARMarkerInfo構造体
typedef struct {
    int     area;        // マーカ領域のピクセル数(面積)
    int     id;        // パターンID
    int     dir;        // マーカの向いている方向(3:↑、2:←、1:↓、0:←)
    double     cf;        // 信頼度(0.0~1.0の範囲を取り、構造体内のidとの一致度を示している)
    double     pos [2];    // マーカの中心座標(x,y)
    double     line [4][3];    // 正方形の枠線(直線の式(ax + by + c = 0)×4辺)
    double     vertex [4][2];    // マーカの頂点座標(x,y)×4辺
} ARMarkerInfo; 

プログラムを書くときに気にするものだけピックアップして説明します。(他はARToolKitの関数でやってくれる) idには使用するマーカと類似したパターンを検出したときに、そのID(初期化の時にarLoadPatt()で取得したpatt_id)が入ります。 それ以外の時は-1になります。 cfにはそのidとの一致度が入ります。

これを使ってマーカの識別を行うのですが、その前に、もう画像が必要なくなったので、次の画像のキャプチャ指示をしておきます。

マーカの一致度の比較

ARTK_basic.cpp(メインループ関数の中)
// マーカの一致度の比較
k = -1;
for( j = 0; j < marker_num; j++ ){
	if( 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 ){
	argSwapBuffers();
	return;
}

arDetectMarkerで得た情報を使ってマーカの一致度の比較を行います。

forループはマーカの個数分繰り返すことを示しています。 この中でarLoadPatt()で得たpatt_idと一致するidを探します。

さらにidが一致した場合、その中で信頼度cfを使用して一番一致するマーカを探しています。

最初にidが一致した場合は、k = jとしてmarker_infoの配列番号をkに記憶させておきます。 次からidが一致した場合は、cfを比較し、新しいものの方が一致度が高かった場合は新しいものをkに入れます。

こうすることで、idが一致していて、かつその中でも一致度の高いmarker_infoの配列番号(k)が得られます。

これを使ってマーカの姿勢計算をするのですが、マーカが見つからなかった場合は、このkに-1が入ったままになります。 この-1はmarker_infoの配列番号ではないので、これをそのまま見過ごすとメモリ参照エラーが起きます。

これを防ぐために、マーカが見つからなかった場合は、argSwapBuffers()関数を使ってカメラ画像の描画だけを行い、returnでまたメインループの最初からやり直します。 argSwapBuffers()関数についてですが、これはバックバッファをフロントバッファに移す処理です。 ARToolKitではダブルバッファを使用しています。 ソースの中身を見るとわかるのですが、内部的にはglutSwapBuffers()関数を呼び出しているだけです。

マーカの位置・姿勢(座標変換行列)の計算

arGetTransMat()関数を使って、マーカの位置・姿勢(座標変換行列)の計算をします。 この関数はARToolKitで重要になる関数の一つです。

double arGetTransMat( ARMarkerInfo *marker_info, double center[2], double width, double conv[3][4] )

【引数】
marker_info:マーカ情報を格納した変数へのポインタ
center:マーカの原点位置(単位:mm)
width:マーカのサイズ(単位:mm)
conv:マーカ→ビュー座標変換行列

marker_infoが変数のポインタになっていることに注意して下さい。 マーカの一致度の比較で得たkを使って、marker_infoの配列を指定します。

center、widthに関してはグローバル変数で指定したものを入れます。

convには計算されたマーカ→ビュー変換行列(ビュー座標系から見たマーカの座標系の位置・姿勢)が返ってきます。 このconvは、↓みたいな3×4の行列になっています。

patt_trans

アフィン変換の際に使う行列(移動、回転、拡大縮小をする4×4行列)を思い出して下さい。 この内の移動と回転だけを使うと↓みたいになります。

affine

convにはこの内の上部分(一番下の行以外)が入っています。 rが姿勢、tが移動量を示しています。

ARToolKit入門【処理の流れと座標変換行列】にも書きましたが、これを使って座標変換を行います。

そして、3Dオブジェクトの描画(後述)を行い、argSwapBuffers()関数でバックバッファの内容を画面に表示させます。

⑤ 描画関数

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

	// 3Dオブジェクトを描画するための準備
	argDrawMode3D();
	argDraw3dCamera( 0, 0 );

	// 陰面消去
	glClearDepth(1.0);					// デプスバッファの消去値
	glClear( GL_DEPTH_BUFFER_BIT );		// デプスバッファの初期化
	glEnable (GL_DEPTH_TEST );			// 陰面消去・有効
	glDepthFunc( GL_LEQUAL );			// デプステスト

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

	// マーカ→ビュー座標変換行列を表示
	static int h_count;
	if( h_count > 10 ){		// 大量に出てくるの防止用
		for ( int i = 0; i < 4; i++ ){
			printf(" ( ");
			for( int j = 0; j < 4; j++ ){
				printf("%10.4lf", gl_para[(j*4)+i]);
			}
			printf(" ) \n");
		}
		h_count = 0;
		printf("\n");
	}
	h_count++;

	// ライティング
	SetupLighting();			// ライトの定義
	glEnable( GL_LIGHTING );	// ライティング・有効
	glEnable( GL_LIGHT0 );		// ライト0・オン

	// オブジェクトの材質
	SetupMaterial();

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

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

まず、OpenGL用の行列gl_para[16]を用意します。 これは一次元配列ですが、中身的には4×4の行列だと思ってください。

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

3Dオブジェクトの描画をする前に、argDrawMode3D()関数とargDraw3dCamera()関数を使って、描画のための準備をします。 実際に内部的に何をやっているかというと、argDrawMode3D()はビュー行列の初期化をし、argDraw3dCamera()はカメラパラメータを使って射影行列を設定しています。

変換行列の適用

argConvGlpara()関数を使って、3×4の行列(patt_trans)から4×4の行列(gl_para)に変換します。 これは3×4の行列の下に、↑のアフィン変換の行列みたいに0,0,0,1が加えられただけです。 実際中身でもそんな処理をしています。

gl_para

そして、glMatrixMode()関数でモデルビューモードにし、gl_paraを読み込ませます。

このあと、実際の処理とは関係ないのですが、マーカ→ビュー座標変換行列(gl_para)を表示させてみました。 実行中マーカを認識すると、こんな感じ↓で出てくるはずです。

ar_basic_console_view

OpenGL描画処理

変換行列の適用が終わったら、あとはOpenGLの3D描画処理をやればOKです。 一つ注意してほしいのは、マーカ座標系が原点になっていることです。 ライトの位置とかもマーカ座標系で指定されています。 ある一定の位置にライトを置くこともできるのですが、それはちょっと難しくなるので、ここではやりません。

⑥ その他の処理

ARTK_basic.cpp
//=======================================================
// キーボード入力処理関数
//=======================================================
void KeyEvent( unsigned char key, int x, int y )
{
	// ESCキーを入力したらアプリケーション終了
	if (key == 0x1b ){
		printf("*** %f (frame/sec)\n", (double)count/arUtilTimer());
		Cleanup();
		exit(0);
	}
}


//=======================================================
// マウス入力処理関数
//=======================================================
void MouseEvent( int button, int state, int x, int y )
{
	// 入力状態を表示
	printf("ボタン:%d 状態:%d 座標:(x,y)=(%d,%d) \n", button, state, x, y );
}


//=======================================================
// 終了処理関数
//=======================================================
void Cleanup(void)
{
	arVideoCapStop();	// ビデオキャプチャの停止
	arVideoClose();		// ビデオデバイスの終了
	argCleanup();		// ARToolKitの終了処理
}

キーボード入力処理関数とマウス入力処理関数に関してはglutと全く一緒です。

終了処理についてですが、この順番を守ってください。 ビデオキャプチャを停止させ、ビデオデバイスを終了させてから、ARToolKitの終了処理を行います。

まとめ

以上で、ARToolKitの基本プログラムの説明は終了です。

OpenGLの部分なんかはDirectXでもできるので、ARToolKitの部分、特にarDetectMarker()関数とarGetTransMat()関数の意味が分かればいいと思います。

“ARToolKit基本プログラム” への6件のフィードバック

  1. muhammad ridwan より:

    you had explain the transformation matrix from a single marker.

    Now can you explain how to show the transformation matrix from the multiple marker in window command?

    thanks a lot 

    • Brays より:

      My English is not very good.
      So, I also write in Japanese.

      複数のマーカでも一つのマーカでも基本的な考え方は一緒です。
      表示の際に変換行列は必ず使うので、この記事でいうgl_paraに相当するものをコンソールに表示してあげれば、大丈夫です。
      マーカーごとに判定を付けたければ、表示の際にそのマーカのIDも表示させてあげるとわかりやすいと思います。

      The basic idea is together in a single marker at the multiple marker.
      Because the transformation matrix used whenever the display, it’ll be displayed on the console the equivalent “gl_para” referred to in this article, it’s okay.
      I think easy to understand if you want to give decision for each marker, let them also display the marker ID.

    • Brays より:

      My English is not very good.
      So, I also write in Japanese.

      複数のマーカでも一つのマーカでも基本的な考え方は一緒です。
      表示の際に変換行列は必ず使うので、この記事でいうgl_paraに相当するものをコンソールに表示してあげれば、大丈夫です。
      マーカーごとに判定を付けたければ、表示の際にそのマーカのIDも表示させてあげるとわかりやすいと思います。

      The basic idea is together in a single marker at the multiple marker.
      Because the transformation matrix used whenever the display, it’ll be displayed on the console the equivalent “gl_para” referred to in this article, it’s okay.
      I think easy to understand if you want to give decision for each marker, let them also display the marker ID.

      • muhammad ridwan より:

         I meant want to display it together at the console window not use for each marker, it means if there is 3 marker there so the window can show all of the transformation matrix for each matrix at the same time together.

        arigatou gozaimasu ^^

        • Brays より:

          I do not use ARToolKit recently, so that I do not know if it matches.
          Does it look something like this?

          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 );
              glMatrixMode( GL_MODELVIEW );
              glLoadMatrixd( gl_para );

              switch( mark_id ){
                  case MARK1_MARK_ID:
                      SetupLighting1();
                      glEnable( GL_LIGHTING );
                      glEnable( GL_LIGHT0 );
                      SetupMaterial1();

                      glTranslatef( 0.0, 0.0, 25.0 );
                      glutSolidCube( 50.0 );
                      break;

                      // マーカ→ビュー座標変換行列を表示
                      static int h1_count;
                      printf(“Mark1”);
                      if( h_count > 10 ){      // 大量に出てくるの防止用
                          for ( int i = 0; i < 4; i++ ){
                              printf(" ( ");
                              for( int j = 0; j 10 ){      // 大量に出てくるの防止用
                          for ( int i = 0; i < 4; i++ ){
                              printf(" ( ");
                              for( int j = 0; j 10 ){      // 大量に出てくるの防止用
                          for ( int i = 0; i < 4; i++ ){
                              printf(" ( ");
                              for( int j = 0; j < 4; j++ ){
                                  printf("%10.4lf", gl_para[(j*4)+i]);
                              }
                              printf(" ) n");
                          }
                          h3_count = 0;
                          printf("n");
                      }
                      h3_count++;

              }

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

      • muhammad ridwan より:

         May I request you to explain mk_patt source ?

        I difficult to understand that

        thanks :)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

PAGE TOP

Twitter