ARToolKit Professional (ARToolKit Version 4)
Section 4 Section 4 Simple Lite 解説
処理を追いながら仕組み、コードについて解説します。
基本的に大まかな流れとコメントでの説明となります。C固有の話やOpenGLについての説明は行いません。
(後日、大幅にアップデートの予定)
全体像
処理の全体像は、GPL版のARToolKitを解説した本【3Dキャラクターが現実世界に誕生! ARToolKit拡張現実感プログラミング入門 (amazon)】に記載れている内容と同じです。 そのため、ここでは簡単な説明に留めます。
確認中
関数の一覧
インクルード部分以外に、下記の関数があります。
- main関数
- setupCamera関数
- debugReportMode関数
- setupMarker関数
- Display関数
- DrawCube関数
- Reshape関数
- Visibility関数
- mainLoop関数
- DrawCubeUpdate関数
- Keyboard関数
- cleanup関数
ヘッダファイルのインクルード
最初にヘッダファイルの読み込み、定数の定義、グローバル変数の宣言等を行っています。
// ============================================================================
// ヘッダファイルのインクルード
// ============================================================================
#include <stdio.h> //標準入出力
#include <stdlib.h> // malloc(), free()
#ifdef __APPLE__
# include <GLUT/glut.h>
#else
# include <GL/glut.h> //GLUT
#endif
#include <AR/config.h>
#include <AR/video.h> //ARToolKitのビデオ処理系の関数
#include <AR/param.h> //ARToolKitのパラメータ管理の関数
#include <AR/ar.h> //ARToolKitの基本関数
#include <AR/gsub_lite.h> //ARToolKitのグラフィック処理系の関数
// ============================================================================
// 定数定義
// ============================================================================
#define VIEW_SCALEFACTOR (1.0/40.0) // ARとOpenGLの大きさの違い
#define VIEW_DISTANCE_MIN 0.1 // 手前側の表示範囲の限界値
#define VIEW_DISTANCE_MAX 100.0 // 奥側の表示範囲の限界値
// ============================================================================
// グローバル変数定義
// ============================================================================
// 設定用
static int prefWindowed = TRUE;
static int prefWidth = 640; // 画面の幅.
static int prefHeight = 480; // 画面の高さ.
static int prefDepth = 32; // ビットデプス
static int prefRefresh = 0; // リフレッシュレート。0 ならデフォルトのリフレッシュレートにする。
// 画像保存用
static ARUint8 *gARTImage = NULL;
static int gARTImageSavePlease = FALSE;
// マーカ検出用
static ARHandle *gARHandle = NULL;
static ARPattHandle *gARPattHandle = NULL;
static long gCallCountMarkerDetect = 0;
// 変換行列取得用
static AR3DHandle *gAR3DHandle = NULL;
static ARdouble gPatt_width = 80.0; // マーカの大きさ(1 = 1mm)
static ARdouble gPatt_trans[3][4]; // マーカの行列
static int gPatt_found = FALSE; // マーカがあるかどうかのフラグ
static int gPatt_id; // マーカのID
// 描画処理用
static ARParam gARTCparam;
static ARGL_CONTEXT_SETTINGS_REF gArglSettings = NULL;
static int gDrawRotate = FALSE; // 回転させるかどうかのフラグ
static float gDrawRotateAngle = 0; // 回転角度
main関数
各ライブラリの初期化、カメラのセットアップ、GLUTのコールバック関数の登録等の処理を行っています。
//メイン関数
int main(int argc, char** argv)
{
char glutGamemode[32];
// カメラパラメータファイルの指定
char *cparam_name = "Data/camera_para.dat";
// カメラのコンフィグ
char *vconf = "";
// マーカーパターンファイルの指定
char *patt_name = "Data/patt.hiro";
// GLUTライブラリの初期化
glutInit(&argc, argv);
// カメラのセットアップ(読み込めなければエラー)
if (!setupCamera(cparam_name, vconf, &gARTCparam, &gARHandle, &gAR3DHandle)) {
fprintf(stderr, "main(): Unable to set up AR camera.\n");
exit(-1);
}
// ライブラリのセットアップ
// ディスプレイモードの設定(ダブルバッファ、RGBAモード、デプスバッファ)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
// ウィンドウをゲームモードにするかどうか
if (!prefWindowed) {
if (prefRefresh) sprintf(glutGamemode, "%ix%i:%i@%i", prefWidth, prefHeight, prefDepth, prefRefresh);
else sprintf(glutGamemode, "%ix%i:%i", prefWidth, prefHeight, prefDepth);
glutGameModeString(glutGamemode);
glutEnterGameMode();
} else {
glutInitWindowSize(prefWidth, prefHeight); //ウィンドウサイズの指定
glutCreateWindow(argv[0]); //ウィンドウ作成
}
// ARgsub_liteのセットアップ
if ((gArglSettings = arglSetupForCurrentContext(gARHandle)) == NULL) {
fprintf(stderr, "main(): arglSetupForCurrentContext() returned error.\n");
cleanup();
exit(-1);
}
// 現在の状態を表示
debugReportMode(gARHandle, gArglSettings);
// デプステストを有効にする
glEnable(GL_DEPTH_TEST);
//タイマーのリセット
arUtilTimerReset();
//ARマーカのセットアップ
if (!setupMarker(patt_name, &gPatt_id, gARHandle, &gARPattHandle)) {
fprintf(stderr, "main(): Unable to set up AR marker.\n");
cleanup();
exit(-1);
}
//GLUTのコールバック関数の登録
//ウィンドウの再描画が必要なときに呼ばれる関数のポインタを指定
glutDisplayFunc(Display);
//ウィンドウサイズが変更されたときに呼ばれる関数のポインタを指定
glutReshapeFunc(Reshape);
//カレントウィンドウの表示/非表示の状態が変わった時に呼び出される関数のポインタを指定
glutVisibilityFunc(Visibility);
//キーボードから入力されたときに呼ばれる関数のポインタを指定
glutKeyboardFunc(Keyboard);
//イベント処理ループに入る
glutMainLoop();
return (0);
}
setupCamera関数
カメラのセットアップ、各ハンドル作成等の処理を行います。
//カメラのセットアップを行う
static int setupCamera(const char *cparam_name, char *vconf, ARParam *cparam, ARHandle **arhandle, AR3DHandle **ar3dhandle)
{
ARParam wparam;
int xsize, ysize;
AR_PIXEL_FORMAT pixFormat;
// ビデオデバイスの設定(失敗したらエラー)
if (arVideoOpen(vconf) < 0) {
fprintf(stderr, "setupCamera(): Unable to open connection to camera.\n");
return (FALSE);
}
// サイズの取得
if (arVideoGetSize(&xsize, &ysize) < 0) return (FALSE);
fprintf(stdout, "Camera image size (x,y) = (%d,%d)\n", xsize, ysize);
// ピクセルフォーマット取得
pixFormat = arVideoGetPixelFormat();
if (pixFormat < 0 ) {
fprintf(stderr, "setupCamera(): Camera is using unsupported pixel format.\n");
return (FALSE);
}
// カメラパラメータ取得
if (arParamLoad(cparam_name, 1, &wparam) < 0) {
fprintf(stderr, "setupCamera(): Error loading parameter file %s for camera.\n", cparam_name);
return (FALSE);
}
// 表示するサイズを指定
arParamChangeSize(&wparam, xsize, ysize, cparam);
// カメラパラメータの表示
fprintf(stdout, "*** Camera Parameter ***\n");
arParamDisp(cparam);
// ハンドル作成
if ((*arhandle = arCreateHandle(cparam)) == NULL) {
fprintf(stderr, "setupCamera(): Error: arCreateHandle.\n");
return (FALSE);
}
// ピクセルフォーマットをセット
if (arSetPixelFormat(*arhandle, pixFormat) < 0) {
fprintf(stderr, "setupCamera(): Error: arSetPixelFormat.\n");
return (FALSE);
}
// デバッグモードを指定
if (arSetDebugMode(*arhandle, AR_DEBUG_DISABLE) < 0) {
fprintf(stderr, "setupCamera(): Error: arSetDebugMode.\n");
return (FALSE);
}
// 3Dのハンドル作成
if ((*ar3dhandle = ar3DCreateHandle(cparam)) == NULL) {
fprintf(stderr, "setupCamera(): Error: ar3DCreateHandle.\n");
return (FALSE);
}
// ビデオキャプチャの開始
if (arVideoCapStart() != 0) {
fprintf(stderr, "setupCamera(): Unable to begin camera data capture.\n");
return (FALSE);
}
return (TRUE);
}
debugReportMode関数
現在の処理モードを取得して、表示する処理を行う関数です。
//現在の状態を表示する
static void debugReportMode(ARHandle *arhandle, const ARGL_CONTEXT_SETTINGS_REF arglContextSettings)
{
int mode;
//イメージプロセッシングモードの取得
arGetImageProcMode(arhandle, &mode);
if (mode == AR_IMAGE_PROC_FRAME_IMAGE) {
fprintf(stderr, "ProcMode (X) : FRAME IMAGE\n");
} else if (mode == AR_IMAGE_PROC_FIELD_IMAGE) {
fprintf(stderr, "ProcMode (X) : FIELD IMAGE\n");
}
//描画モード
if (arglDrawModeGet(arglContextSettings) == AR_DRAW_BY_GL_DRAW_PIXELS) {
fprintf(stderr, "DrawMode (C) : GL_DRAW_PIXELS\n");
} else if (arglTexmapModeGet(arglContextSettings) == AR_DRAW_TEXTURE_FULL_IMAGE) {
fprintf(stderr, "DrawMode (C) : TEXTURE MAPPING (FULL RESOLUTION)\n");
} else {
fprintf(stderr, "DrawMode (C) : TEXTURE MAPPING (HALF RESOLUTION)\n");
}
//パターン検出モード
arGetPatternDetectionMode(arhandle, &mode);
if (mode == AR_TEMPLATE_MATCHING_COLOR) {
fprintf(stderr, "TemplateMatchingMode (M) : Color Template\n");
} else if (mode == AR_TEMPLATE_MATCHING_MONO) {
fprintf(stderr, "TemplateMatchingMode (M) : Mono Template\n");
} else if (mode == AR_MATRIX_CODE_DETECTION) {
fprintf(stderr, "TemplateMatchingMode (M) : Matrix code detection\n");
}
}
setupMarker関数
ARマーカのセットアップを行う関数です。
//ARマーカのセットアップ
static int setupMarker(const char *patt_name, int *patt_id, ARHandle *arhandle, ARPattHandle **pattHandle_p)
{
//パターンファイルのハンドルの作成
if ((*pattHandle_p = arPattCreateHandle()) == NULL) {
fprintf(stderr, "setupMarker(): Error: arPattCreateHandle.\n");
return (FALSE);
}
//パターンハンドルにパターンファイルをロード
if ((*patt_id = arPattLoad(*pattHandle_p, patt_name)) < 0) {
fprintf(stderr, "setupMarker(): Error loading pattern file %s.\n", patt_name);
arPattDeleteHandle(*pattHandle_p);
return (FALSE);
}
//ARのハンドルと、パターンのハンドルの関連付け
arPattAttach(arhandle, *pattHandle_p);
return (TRUE);
}
Display関数
main関数のglutDisplayFunc関数で指定されるDisplay関数は、ウィンドウの再描画が必要なときに呼び出されます。
//ウィンドウが再描画されるときに呼ばれる
static void Display(void)
{
GLdouble p[16]; //射影行列
GLdouble m[16]; //モデルビュー行列
// バックバッファを指定
glDrawBuffer(GL_BACK);
// カラーバッファとデプスバッファをクリア
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// カメラの映像を表示(ズーム率 1.0)
arglDispImage(gARTImage, &gARTCparam, 1.0, gArglSettings);
gARTImage = NULL;
// 射影変換
arglCameraFrustumRH(&gARTCparam, VIEW_DISTANCE_MIN, VIEW_DISTANCE_MAX, p);
// 射影行列を指定
glMatrixMode(GL_PROJECTION);
// 行列を置き換える
glLoadMatrixd(p);
// モデルビュー行列を指定
glMatrixMode(GL_MODELVIEW);
// 単位行列のロード
glLoadIdentity();
// マーカが認識されているか
if (gPatt_found) {
// モデルビュー変換
arglCameraViewRH(gPatt_trans, m, VIEW_SCALEFACTOR);
// モデルビュー行列を置き換える
glLoadMatrixd(m);
// 立方体描画処理
DrawCube();
} else {
// マーカを認識しない場合は何もしない
}
// 2Dオーバレイはここに記述
// none
// ダブルバッファを交換する
glutSwapBuffers();
}
DrawCube関数
立方体を作成する処理を行います。
//立方体を表示する
static void DrawCube(void)
{
static GLuint polyList = 0; //ディスプレイリストの値
float fSize = 0.5f;
long f, i;
//頂点の座標
const GLfloat cube_vertices [8][3] = {
{1.0f, 1.0f, 1.0f}, {1.0f, -1.0f, 1.0f}, {-1.0f, -1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f},
{1.0f, 1.0f, -1.0f}, {1.0f, -1.0f, -1.0f}, {-1.0f, -1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f} };
//色のデータ(頂点ごと)
const GLfloat cube_vertex_colors [8][3] = {
{1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 1.0f},
{1.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f} };
GLint cube_num_faces = 6; //面の数
//面を指定
const short cube_faces [6][4] = {
{3, 2, 1, 0}, {2, 3, 7, 6}, {0, 1, 5, 4}, {3, 0, 4, 7}, {1, 2, 6, 5}, {4, 5, 6, 7} };
if (!polyList) {
//空のディスプレイリスト作成
polyList = glGenLists (1);
//ディスプレイリスト作成
glNewList(polyList, GL_COMPILE);
//描画開始
glBegin (GL_QUADS);
for (f = 0; f < cube_num_faces; f++)
for (i = 0; i < 4; i++) {
//色の指定
glColor3f (cube_vertex_colors[cube_faces[f][i]][0], cube_vertex_colors[cube_faces[f][i]][1], cube_vertex_colors[cube_faces[f][i]][2]);
//頂点の指定
glVertex3f(cube_vertices[cube_faces[f][i]][0] * fSize, cube_vertices[cube_faces[f][i]][1] * fSize, cube_vertices[cube_faces[f][i]][2] * fSize);
}
glEnd (); //描画ここまで
//辺は黒い線にする
glColor3f (0.0f, 0.0f, 0.0f);
for (f = 0; f < cube_num_faces; f++) {
glBegin (GL_LINE_LOOP);
for (i = 0; i < 4; i++)
glVertex3f(cube_vertices[cube_faces[f][i]][0] * fSize, cube_vertices[cube_faces[f][i]][1] * fSize, cube_vertices[cube_faces[f][i]][2] * fSize);
glEnd ();
}
glEndList ();
}
glPushMatrix(); // 行列をスタックに格納
glTranslatef(0.0f, 0.0f, 0.5f); // マーカの中心に立方体を置く
glRotatef(gDrawRotateAngle, 0.0f, 0.0f, 1.0f); // z座標で回転
glDisable(GL_LIGHTING); // ライティング無効
glCallList(polyList); // 立方体を描画
glPopMatrix(); // ワールド座標系の復旧
}
Reshape関数
main関数のglutReshapeFunc関数で指定されるReshape関数は、ウィンドウサイズが変更されたときに呼び出されます。
//ウィンドウがリサイズされたときに呼ばれる
static void Reshape(int w, int h)
{
// カラーバッファとデプスバッファをクリア
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// ビューポートの設定(ウィンドウサイズに合わせる)
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
// 射影行列を指定
glMatrixMode(GL_PROJECTION);
// 行列の初期化
glLoadIdentity();
// モデルビュー行列を指定
glMatrixMode(GL_MODELVIEW);
// 行列の初期化
glLoadIdentity();
}
Visibility関数
main関数のglutVisibilityFunc関数で指定されるVisibility関数は、ウィンドウの表示/非表示の状態が変わったときに呼び出される関数です。この関数では、メインループの起動・停止を行っています。
//ウィンドウの可視性が変わったときに呼び出される
static void Visibility(int visible)
{
// 画面が表示状態のとき
if (visible == GLUT_VISIBLE) {
// メインループを起動(空き状態のときに呼ばれる)
glutIdleFunc(mainLoop);
} else {
// メインループを停止
glutIdleFunc(NULL);
}
}
mainLoop関数
Visibility関数の中のglutIdleFuncで指定されているmainLoop関数は、ほかのイベント処理がないときに呼ばれる関数です。この関数がメインループ関数となり、カメラ画像の取得やマーカの検出、座標の変換を行っています。
//メインループ関数
static void mainLoop(void)
{
static int imageNumber = 0;
static int ms_prev;
int ms;
float s_elapsed;
ARUint8 *image;
ARdouble err;
int j, k;
// 経過時間
ms = glutGet(GLUT_ELAPSED_TIME);
s_elapsed = (float)(ms - ms_prev) * 0.001;
if (s_elapsed < 0.01f) return; // 100Hz間は更新しない
//経過時間を一時保存
ms_prev = ms;
//回転処理
DrawCubeUpdate(s_elapsed);
//カメラ画像の取得
if ((image = arVideoGetImage()) != NULL) {
gARTImage = image; // 取得したイメージを保存
//カメラ画像をjpgで保存する
if (gARTImageSavePlease) {
char imageNumberText[15];
sprintf(imageNumberText, "image-%04d.jpg", imageNumber++);
if (arVideoSaveImageJPEG(gARHandle, gARTImage, imageNumberText, 75) < 0) {
fprintf(stderr, "Error saving video image.\n");
}
gARTImageSavePlease = FALSE;
}
gCallCountMarkerDetect++; // FPS用のカウンターをインクリメント
// マーカの検出
if (arDetectMarker(gARHandle, gARTImage) < 0) {
exit(-1);
}
// 複数のマーカがあった場合、一致度の高いものを使用する
k = -1;
for (j = 0; j < gARHandle->marker_num; j++) {
if (gARHandle->markerInfo[j].id == gPatt_id) {
if (k == -1) k = j; // 最初のマーカを選択
else if (gARHandle->markerInfo[j].cf > gARHandle->markerInfo[k].cf) k = j; //一致度の高いほうを取得
}
}
if (k != -1) {
//マーカとカメラの座標の変換をする。
err = arGetTransMatSquare(gAR3DHandle, &(gARHandle->markerInfo[k]), gPatt_width, gPatt_trans);
gPatt_found = TRUE;
} else {
gPatt_found = FALSE;
}
// ウィンドウを再描画する
glutPostRedisplay();
}
}
DrawCubeUpdate関数
mainLoop関数の中で呼ばれているDrawCubeUpdate関数は、立方体の回転処理を行う関数です。
//回転処理を行う
static void DrawCubeUpdate(float timeDelta)
{
//回転するフラグの判定
if (gDrawRotate) {
gDrawRotateAngle += timeDelta * 45.0f; //1秒間で度回るように計算
if (gDrawRotateAngle > 360.0f) gDrawRotateAngle -= 360.0f; //360度を越さないようにする
}
}
keyboard関数
main関数の中のglutKeyboardFunc関数で指定しているkeyboard関数は、キーボードが入力されたときに呼び出されます。
//キーボード入力時の処理
static void Keyboard(unsigned char key, int x, int y)
{
int mode, threshChange = 0;
switch (key) {
//プログラム終了
case 0x1B: // Quit.
case 'Q':
case 'q':
//終了処理
cleanup();
exit(0);
break;
//回転開始
case ' ':
gDrawRotate = !gDrawRotate;
break;
//表示モードの変更
case 'C':
case 'c':
//描画モードの取得
mode = arglDrawModeGet(gArglSettings);
if (mode == AR_DRAW_BY_GL_DRAW_PIXELS) {
arglDrawModeSet(gArglSettings, AR_DRAW_BY_TEXTURE_MAPPING);
arglTexmapModeSet(gArglSettings, AR_DRAW_TEXTURE_FULL_IMAGE);
} else {
mode = arglTexmapModeGet(gArglSettings);
if (mode == AR_DRAW_TEXTURE_FULL_IMAGE) arglTexmapModeSet(gArglSettings, AR_DRAW_TEXTURE_HALF_IMAGE);
else arglDrawModeSet(gArglSettings, AR_DRAW_BY_GL_DRAW_PIXELS);
}
//fpsの表示
fprintf(stderr, "*** Camera - %f (frame/sec)\n", (double)gCallCountMarkerDetect/arUtilTimer());
gCallCountMarkerDetect = 0;
arUtilTimerReset();
debugReportMode(gARHandle, gArglSettings);
break;
//閾値を変更する
case '-':
threshChange = -5; //閾値の変更
break;
case '+':
case '=':
threshChange = +5; //閾値の変更
break;
//デバッグモードの変更
case 'D':
case 'd':
arGetDebugMode(gARHandle, &mode);
arSetDebugMode(gARHandle, !mode);
break;
//jpgで保存するフラグを立てる
case 's':
case 'S':
if (!gARTImageSavePlease) gARTImageSavePlease = TRUE;
break;
//ヘルプを表示する
case '?':
case '/':
printf("Keys:\n");
printf(" q or [esc] Quit demo.\n");
printf(" c Change arglDrawMode and arglTexmapMode.\n");
printf(" - and + Adjust threshhold.\n");
printf(" d Activate / deactivate debug mode.\n");
printf(" ? or / Show this help.\n");
printf("\nAdditionally, the ARVideo library supplied the following help text:\n");
//ビデオオプションを出力
arVideoDispOption();
break;
default:
break;
}
//閾値を変更する場合
if (threshChange) {
int threshhold;
//閾値の取得
arGetLabelingThresh(gARHandle, &threshhold);
//閾値の計算
threshhold += threshChange;
//0以上以下
if (threshhold < 0) threshhold = 0;
if (threshhold > 255) threshhold = 255;
//閾値の変更
arSetLabelingThresh(gARHandle, threshhold);
//現在の閾値の表示
printf("Threshhold changed to %d.\n", threshhold);
}
}
cleanup関数
main関数と、Keyboard関数の中で呼ばれているcleanup関数は、終了処理を行う関数です。
//終了処理
static void cleanup(void)
{
//グラフィック処理の終了
arglCleanup(gArglSettings);
//パターンファイルのハンドルとの関連付けを解除
arPattDetach(gARHandle);
//パターンファイルのハンドルを削除
arPattDeleteHandle(gARPattHandle);
//ビデオキャプチャストップ
arVideoCapStop();
//AR3Dハンドルを削除
ar3DDeleteHandle(&gAR3DHandle);
//ARのハンドルを削除
arDeleteHandle(gARHandle);
//ビデオデバイスの終了
arVideoClose();
}