卒業研究報告書 題目 3DCG と Web カメラの合成を表現するプログラムの制作 指導教員 綿森道夫准教授 報告者 学籍番号 : 氏名 : 尾川景子 平成 23 年 2 月 8 日 高知工科大学電子 光システム工学科 1

Similar documents
ARToolKit プログラムの仕組み 1: ヘッダファイルのインクルード 2: Main 関数 3: Main Loop 関数 4: マウス入力処理関数 5: キーボード入力処理関数 6: 終了処理関数 3: Main Loop 関数 1カメラ画像の取得 2カメラ画像の描画 3マーカの検出と認識

Microsoft Word - 卒業論文 docx

Tekutama AR ~ 拡張現実感によるオーバーレイ表示と動作 ~ 情報物理研究室 渡部 修平 1

コンピュータグラフィックスS 演習資料

2 2 2 OpenGL Linux Linux Video for Linux(Video4Linux, v4l ) API Video4Linux USB IEEE1394 API Linux Video for Linux 2(Video4Linux2, v4l2 ) OpenCV API U

問 1 図 1 の図形を作るプログラムを作成せよ 但し ウィンドウの大きさは と し 座標の関係は図 2 に示すものとする 図 1 作成する図形 原点 (0,0) (280,0) (80,0) (180,0) (260,0) (380,0) (0,160) 図 2 座標関係 問 2

スライド 1

Microsoft PowerPoint - 講義資料-mlib

memo

Microsoft PowerPoint - kougi2.ppt

Microsoft PowerPoint - [150421] CMP実習Ⅰ(2015) 橋本 CG編 第1回 幾何変換.pptx

C プログラミング演習 1( 再 ) 2 講義では C プログラミングの基本を学び 演習では やや実践的なプログラミングを通して学ぶ

#include <stdio.h> 2 #include <stdlib.h> 3 #include <GL/glut.h> 4 Program 1 (OpenGL GameSample001) 5 // 6 static bool KeyUpON = false; // 7 sta

Fair Curve and Surface Design System Using Tangent Control

コンピュータグラフィックス第8回

Microsoft PowerPoint - info_eng3_05ppt.pptx

Mapmakerfor の手順下絵を準備 作者の設定した大きさで作成する場合 下絵にする地図を挿入 トリミングと大きさの調整 大きさを調整した画像を保存 下絵を背景に設定 作成画面の大きさを調整 1 自分で用意した下絵を背景にする場合 下絵を背景に設定 作成画面の大きさを調整 画像が大きい場合シート

演算増幅器

プログラミング実習I

画像ファイルを扱う これまでに学んだ条件分岐, 繰り返し, 配列, ファイル入出力を使って, 画像を扱うプログラムにチャレンジしてみよう

Microsoft PowerPoint P演習 第10回 関数.ppt [互換モード]

/*p7-1-1*/

(4) モデルの消去 mqodeletemodel( model ); (5) 終了処理 ( プログラム終了時にやってください ) mqocleanup(); 3. 使い方 (2) 連番ファイルを読み込んで表示する場合 (1) 初期化 (ARToolKit の場合,argInit() の後に使用 )

アクション講座 第1回目

AR技術を用いたグリーティングカード作成ソフトの開発

C#の基本

< F2D D E6A7464>

PowerPoint プレゼンテーション

Field Logic, Inc. 標準モード 3D モデル作成 配置編 Field Logic, Inc. 第 1 版

1 1. Program 1 OpenCV (OpenCV Sample001) 1 /* 2 - > - > - >VC++ 3 ( ) 4 C:\opencv\build\include 5 ( ) 6 C:\opencv\build\x86\vc10\lib 7 - > - > - > - >

Microsoft Word - Cプログラミング演習(10)

やさしくPDFへ文字入力 v.2.0

PowerPoint プレゼンテーション

プログラミングI第10回

PowerPoint Presentation

コマンドラインから受け取った文字列の大文字と小文字を変換するプログラムを作成せよ 入力は 1 バイトの表示文字とし アルファベット文字以外は変換しない 1. #include <stdio.h> 2. #include <ctype.h> /*troupper,islower,isupper,tol

Wordでアルバム作成

PowerPoint プレゼンテーション

3. 文字の入力 文字 ボタンをクリックします 文字入力したい範囲をドラックし 文字枠を作成します 文字を入力します この作業を繰り返します マウスポインタの形 4. 文字枠のサイズ変更 拡大 ボタンをクリックします 大きさを変えたい文字枠をクリックします マウスポインタを文字枠の右下のハンドル (

コンピュータグラフィックス基礎              No

平成 29 年度卒業研究 初心者のためのゲームプログラミング用 教材の開発 函館工業高等専門学校生産システム工学科情報コース 5 年 25 番細見政央指導教員東海林智也

3Dプリンタ用CADソフト Autodesk Meshmixer入門編[日本語版]

Word2007 Windows7 1 パンジーを描こう 暮らしのパソコンいろは 早稲田公民館 ICT サポートボランティア

プログラミング基礎

コンピューターグラフィックスS

PowerPoint プレゼンテーション

Taro-ファイル処理(公開版).jtd

2. 印刷対象のサイズの確認 大判印刷を行う場合 まず 印刷をする文書のサイズを確認する必要があります サイズの確認の方法はアプリケーションによって異なるので ここでは PowerPoint(2010/2013) と Adobe Acrobat を例に説明します PowerPoint2010 の場合

. フォントを OS にインストールする インターネット等で入手したフリーのフォントをインストールすることにより Windows に標準でインストールされているフォント以外のものを利用することができます 多数のフォントをインストールするとパソコンの動作が遅くなります 必要なフォント以外はインストール

スライド 1

線を描く 線ツールをクリックする 原点 ( 青 緑 赤の 3 つの軸が交わるところ ) をクリックする 水平方向 ( 赤い軸と緑の軸がある面 ) にカーソルを動かしクリックする 原点とクリックした点の間に黒い線が描画される 垂直方向にカーソルを動かす 青い線が表示され 青い軸上 と表示される 青い線

第32回_プレゼン資料_菅原(Unityはじめるよ~上半身だけ動かす2~)

円筒面で利用可能なARマーカ

ポストカード

基本作図・編集

Microsoft Word - VB.doc

基本作図・編集

情報システム設計論II ユーザインタフェース(1)

Microsoft PowerPoint - chap10_OOP.ppt

Microsoft PowerPoint - kougi4.ppt

Microsoft Word - 92.doc

デバッグの工夫

Transcription:

卒業研究報告書 題目 3DCG と Web カメラの合成を表現するプログラムの制作 指導教員 綿森道夫准教授 報告者 学籍番号 :1110157 氏名 : 尾川景子 平成 23 年 2 月 8 日 高知工科大学電子 光システム工学科 1

第 1 章序論... 4 1-1 研究の背景... 4 1-2 研究の概要... 4 1-3 研究の新規性... 4 第 2 章 2 次元イラストの習作... 5 2-1 写真を参考にしてイラスト風にトレース... 5 2-1-1 顔をトレース... 5 2-1-2 全身をトレース... 6 2-1-3 自分をトレース... 7 2-1-4 より細かいトレース... 9 2-2 リアルイラスト... 10 2-2-1 犬のイラスト... 10 2-2-2 マニピュレータのイラスト... 12 第 3 章 3DCG モデルの習作... 13 3-1 技術習得のための習作... 13 3-1-1 クマの 3DCG モデル制作... 13 3-1-2 鳥のようなクリーチャーの 3DCG モデル制作... 15 3-1-3 トイカーの 3DCG モデル制作... 16 3-1-4 おもちゃの機関車の 3DCG モデル制作... 17 3-1-5 戦闘機の 3DCG モデル制作... 18 3-1-6 ポットの 3DCG モデル制作... 19 3-2 プログラムで使うクリーチャーの 3DCG モデルの作成... 20 3-2-1 デザインを考える... 20 3-2-2 カボチャのクリーチャーの 3DCG モデル制作... 21 3-2-3 テクスチャで仕上げ... 22 第 4 章 Windows プログラミング... 23 4-1 DX ライブラリを用いてプログラミング... 23 4-2 DX ライブラリを用いて制作した習作... 24 4-3 Windows プログラミング... 25 第 5 章 3DCG と Web カメラの合成を表現するプログラム制作... 26 5-1 AR 技術とは... 26 5-2 ARToolKit を用いた合成プログラムの骨格... 27 5-2-1 初期化処理... 28 5-2-2 メインループ関数... 30 5-3 マーカー上にキャラクターを表示させ アニメーションさせる ( 第 1 段階 )... 33 5-3-1 アニメーション制作... 33 2

5-3-2 アニメーションの表示... 34 5-4 マーカーを 2 種類用意してキャラクターを表示させる ( 第 2 段階 )... 36 5-5 キャラクターがマーカー間を移動する ( 第 3 段階 )... 37 5-6 移動中に 1 つのマーカーが消えた時の処理 ( 第 4 段階 )... 39 5-6-1 2 のマーカーが消えた場合... 39 5-6-2 1 のマーカーが消えた場合... 40 5-7 3DCG モデルが登場する場面 退場する場面の追加 ( 第 5 段階 )... 42 5-8 移動中のアニメーション 音 説明のテロップを作り込む ( 第 6 段階 )... 43 5-8-1 移動中のアニメーション... 43 5-8-2 音... 43 5-8-3 説明のテロップ... 44 5-9 完成したプログラムについて... 45 5-10 Windows 対応バージョンへの試み... 47 第 6 章まとめ... 48 参考文献... 49 謝辞... 50 付録... 51 3

第 1 章序論 1-1 研究の背景 地上デジタル放送の普及にともない 3D テレビも商品化され 3D が身近になってきた そして 3D メガネなどの利用により 立体的な映像を楽しむことができ 3D の制作技術の向上も望まれている 近年 AR 技術が急速に発展し 3DCG とカメラ映像との合成が可能になってきていることもあり この AR 技術を研究して オリジナルの Windows プログラムに導入することを目的とする 1-2 研究の概要 本研究ではまず 3DCG のモデルを製作し そのモデルを AR のプログラムによって Web カメラの映像と合成させる Web カメラにマーカーが 2 つ映るとモデルが 2 つのマーカー間の歩行しながら移動するアニメーションの機能も追加している また 完成ではないがそのプログラムを Windows 対応へすることも試みた 1-3 研究の新規性 本研究は 2 つのマーカーを認識して移動するプログラムである 移動中にマーカーが 1 つになった時の処理をしているような例をインターネット上で探すことができなかったので 本研究にはマーカーが 1 つになった時の処理も取り入れた また 場面によって異なる音楽が流れるように工夫した それから部分的ではあるが ネイティブな Windows 対応プログラムを試作してみた 4

第 2 章 2 次元イラストの習作 テスクチャ作成の時に活かす技術習得のために Illustrator を使い 以下のような 2 次元 イラストの習作を作った 2-1 写真を参考にしてイラスト風にトレース 2-1-1 顔をトレース 図 2-1 の左の写真を参考に 右のような顔のイラストを作成した 人の顔のパーツの中でも一番特徴がある大事な部分が目なので 目は特に細かく慎重にトレースをした 髪は写真を見ると細かいがイラスト風に描くため 大まかな流れだけトレースした その他にも細かい影は付け過ぎるとリアルになるので あまり描かないように気を付けた このイラストを作成するときに苦労したのは口の部分で バランスよく笑ったようなラインを描くのが難しかったのと あと歯もバランスよく描くのが難しかった 図 2-1 顔のイラスト風トレース 5

2-1-2 全身をトレース 次は図 2-2 の左の写真を参考に 右のようなイラストを作成した 顔の部分は このままトレースすると細かい部分が描きづらいので 先ほどの図 2-1 のイラストを縮小して合成させた ポーズは写真と同じだが 尐しオリジナリティを出すために 服やブーツなどは写真とは違うように描いてみた このときに苦労したのは スカートのデニムの質感を出すことと ブーツのくしゃっとなった雰囲気出すことある 特にブーツは不自然にくしゃくしゃにし過ぎないよう もとの写真を参考に足のラインをトレースし その上からブーツを描いた また ブーツのシワの影も質感を出すために尐し多めに描いた 図 2-2 全身のイラスト風トレース 6

2-1-3 自分をトレース 2-1-2 と同じ方法で 自分自身の全身をイラスト風に作成した まず 図 2-3 の右の写真を参考に顔をトレースした その次に 左の写真を参考に全身をトレースし 先ほどトレースした顔のイラストを縮小して合成した 完成したイラストは図 2-4 である 今回もあまりリアルになり過ぎないように 影は最低限のものしか付けなかった しかし 上着とジーパンのくしゃくしゃ具合は残したかったので シワの線は尐し多めに描いた 今まで自分自身をじっくり見たり イラスト風に描いたりしたことが無かったので このイラストを描いている途中に自分の顔の特徴などを発見し 描き終わった後には いい経験になったと感じた 図 2-3 参考にした全身の写真 ( 左 ) と顔の写真 ( 右 ) 7

図 2-4 自分の写真を参考にしたイラスト 8

2-1-4 より細かいトレース 次は図 2-5 の左の写真を参考に より細かい部分までトレースしたイラストを作成した 2-1-1 の時に参考にした写真と同じ写真を使った しかし 今回はできるだけもとの写真に近くなるように細かくトレースしてみた このイラストを描く時に苦労したのは目と髪である 最初の方でも述べたとおり 顔のパーツの中で目は特に大事なパーツなので 瞳の光り具合やまつ毛などを細かくトレースをするのに時間が大変かかり苦労した それから 髪もできるだけ写真に忠実に細かくトレースしたので 時間がかかった また もとの写真は白黒画像なので 色の変化が分かりにくく 髪の反射している部分の色から影になっている部分への中間の色をどれだけ分けて着色するか どのくらいの色を指定するか など調整する部分に苦労した 図 2-5 より細かいトレース 9

2-2 リアルイラスト 2-1 で描いたイラストとは違い Illustrator のグラデーションメッシュやぼかしなどの機能 を使ってよりリアルな色塗りを学びながら イラストを作成した 2-2-1 犬のイラスト テキストに載っていた見本の犬のイラストを参考にして 図 2-6 のような犬のイラストを作成した 顔や手足などのパーツは簡易な形をしているが グラデーションメッシュ機能を使って色を塗ったので 光の当たり具合や影などのグラデーションがきれいに表現でき 立体的なイラストに仕上がった 背景の色も同様に塗ったので 尐し丘の様な雰囲気がでた また犬の下の影には ぼかし機能を使い より自然な影を表現できた 図 2-6 犬のイラスト 10

グラデーション選択したオブジェクトの色をグラデーションに出来る機能 グラデーションの向きや度合をグラデーションパレットによって指定できる 線形グラデーションと円形グラデーションがあり 図 2-7 は選択されたオブジェクト ( 青い線で囲まれた部分 ) を円形グラデーションで色付けしている 図 2-7 グラデーション グラデーションメッシュグラデーション機能では表現できない複雑なグラデーションが表現できる機能 選択したオブジェクトをメッシュ化し 細かく部分分けすることで それぞれの点に色が指定できる このため 先ほどのグラデーション機能とは違う 線形でも円形でもない複雑なグラデーションを作ることができる 図 2-8 グラデーションメッシュ 11

2-2-2 マニピュレータのイラスト 次もテキストに載っていたイラストを参考にマニピュレータのイラストを作成した ( 図 2-9) このイラストは細かいパーツが多く それを組み立てるように描いたので 全体的なバランスをとるのに苦労した また色塗りは 2-2-1 の犬とは違い 金属的な雰囲気を出すためにグラデーション機能を使った 細かい部分の色塗りにはグラデーション機能と乗算機能を重ねて より暗い質感を出した さらに金属に映る映り込みの部分も黒い線で表現した それから 左下のシリアルナンバーの部分は凹んだ感じを出すために ぼかし機能とグラデーション機能を重ねて表現した 図 2-9 マニピュレータのイラスト 12

第 3 章 3DCG モデルの習作 Web カメラの映像と合成させる 3DCG モデルを製作する技術習得のために Metasequoia を使って いくつか習作を作った そして それを活かし 最終的に自分のオリジナルのクリーチャーを作った 3-1 技術習得のための習作 3-1-1 クマの 3DCG モデル制作 まず 最初に比較的作りやすいクマの 3DCG モデルを作った ( 図 3-1) クマの体のパーツはそれぞれ基本図形の立方体を丸く加工し 合体させた 耳や手足は左右同じ形なので 片方だけ作り ミラーリング機能を使って複製した また オリジナリティを出すために 色は自分の好きな色に指定し 顔も自分でテクスチャを作ってみた ここで テクスチャの貼り方も学ぶことができた 図 3-1 クマの 3DCG モデル 図 3-2 顔のテクスチャ 13

ミラーリング同じ形のオブジェクトを境界軸の反対側に複製できる機能 今回は耳と手足を作る時にこのミラーリングを使用した 例えば図 3-3 と図 3-4 はクマの足を作る時の様子である まず右足を作っておいて オブジェクト設定で X 軸方向 ( クマの横方向 ) に同じ形の左足を複製した 図 3-3 ミラーリング前 図 3-4 ミラーリング後 14

3-1-2 鳥のようなクリーチャーの 3DCG モデル制作 次に図 3-5 のような鳥のようなクリーチャーの 3DCG モデルを作った 3-1-1 のクマと同様で 基本図形の立方体から作り始めたが 手足などをパーツ分けせずに一つのオブジェクトを変形させて作った そのため オブジェクトは全部で目と体全身の 2 つだけである クチバシや手足は押し出し機能を使って元の立方体から変形させた それから 左右対称の体にするために 今回も体の半分をもとにミラーリング機能を使った このモデルを作る時に苦労したのは指の部分で 人の手のように指 5 本がきれいに並んでいるように配置するのが難しかった 図 3-5 鳥のようなクリーチャーの 3DCG モデル 15

3-1-3 トイカーの 3DCG モデル制作 次は図 3-6 のようなトイカーの 3DCG モデルを作った 積み木のようなもので作ったイメージだったので 今まで作ってきたような丸さではなく 角を削ったようなようにした これは角を丸める機能とスムージング機能を使って表現した また ここでもライトとタイヤはミラーリング機能で複製している 最後に色を付ける時にペンキで塗ったような雰囲気を出したかったので 尐しだけではあるが光が反射しているように光の当たり具合を調整した 図 3-6 トイカーの 3DCG モデル 図 3-7 光の調整 16

3-1-4 おもちゃの機関車の 3DCG モデル制作 3-1-3 のトイカーとほぼ同じ方法で図 3-8 のようなおもちゃの機関車の 3DCCG モデルも作った タイヤは全部で 8 つあるがミラーリング機能で複製している 色もトイカーと同様で光の当たり具合を尐し調整している また この機関車も積み木のイメージで角を尐し丸くしているが 角を取ったものよりももう尐し研磨して丸い感じにした これを表現するのに 曲面制御機能とナイフツールを使った 図 3-8 おもちゃの機関車の 3DCG モデル 図 3-9 曲面制御 図 3-10 ナイフツール使用 17

3-1-5 戦闘機の 3DCG モデル制作 次はもう尐し複雑な形をした図 3-11 のような戦闘機の 3DCG モデルを作った このモデルは基本図形の立方体を押し出し機能など使って変形させたので 1 つのオブジェクトで出来ている これも左右対称な形をしているが 今回はミラーリング機能を使わずに作った 左右歪みなく作ることも難しかったが 一番苦労したのは戦闘機の裏の部分である ( 図 3-12) 噴射孔が 2 つあるが この大きさを調整したり 上手く押し出し機能を使って凹みを作ったりする作業が難しかった 図 3-11 戦闘機の 3DCG モデル 図 3-12 戦闘機の裏 18

3-1-6 ポットの 3DCG モデル制作 次は複雑かつ曲面を活かした図 3-13 のようなポットの 3DCG モデルを作った このモデルも基本図形の立方体から始まり 曲面制御やナイフツールなどを使って作ったので 1 つのオブジェクトで出来ている 苦労したのは取っ手の部分と注ぎ口である 両方とも押し出し機能を使って作ったが 上手く真っ直ぐに押し出しできなかったり 取っ手の部分は薄くなりすぎたりして 調整が難しかった ( 図 3-14) 図 3-13 ポットの 3DCG モデル 図 3-14 ポットを横から見た図 19

3-2 プログラムで使うクリーチャーの 3DCG モデルの作成 3-2-1 デザインを考える オリジナリティを出すために まず 自分でクリーチャーをデザインした ハロウィンのキャラクターのようなクリーチャーが作りたかったので 思いつくままにいくつかキャラクターを描いてみた ( 図 3-15) そして やはり作るなら一番複雑なものが作り甲斐がある と思い 真ん中にいるカボチャのクリーチャーを作ることにした 図 3-15 デザイン画 20

3-2-2 カボチャのクリーチャーの 3DCG モデル制作 3-1 の習作で学んだ技術を活かして 図 3-16 のようなカボチャのクリーチャーの 3DCG モデルを作った できるだけデザイン画のキャラクターを忠実に作りたかったので 服のフードや袖の折り返した部分までこの時点で細かく作った このモデルを作る時に苦労したのはクリーチャーの頭の部分で カボチャのようなデコボコ感を出すのに苦労した また 手足などは左右対称ではあるが ミラーリング機能はこの時には使わなかった 図 3-16 カボチャのクリーチャーの 3DCG モデル 21

3-2-3 テクスチャで仕上げ 最後にテクスチャを作成してモデルに張り付けた テクスチャは SAI というペイントソフトを使い 顔や服などの全部で 5 枚作成した 作成したテクスチャの一部は図 3-17 である 立体感を出すために影を尐し多めに描いた そして そのテクスチャを張り付けて完成したのが図 3-18 の 3DCG モデルである このモデルを本研究のプログラムで使用する 図 3-17 作成したテクスチャ 図 3-18 完成した 3DCG モデル 22

第 4 章 Windows プログラミング 4-1 DX ライブラリを用いてプログラミング Windows プログラミングを学ぶにあたり いきなりではなく まず DX ライブラリを用いて Windows プログラミングを学んだ ちなみに本研究のプログラミングで使用したソフトは Microsoft Visual Studio 2008 であるで 言語は VC++ である DX ライブラリは C 言語でゲームを比較的簡単に作れるようにするライブラリである Windows API と DirectX というライブラリの機能を利用し それを簡単に使える関数の形にしている 習作 (4-2) として 2 章と 3 章で作成した作品を画像として表示するプログラムを作った 図 4-1 はプログラムの一部である このプログラムでは画像を表示する DrawGraph 関数や押されたキーを読む GetJoypadInputState 関数などの DX ライブラリの関数を使った 図 4-1 DX ライブラリを用いた Windows プログラミング 23

4-2 DX ライブラリを用いて制作した習作 DX ライブラリを用いた Windows プログラミングの習作の実行画面は図 4-2 と図 4-3 である 図 4-2 は実行して一番最初に出る画面で 2 章で作成した自分のイラストと Illustrator で作成したロゴを表示している キーボードの Z キーを押すと次の画面へ切り替わる 切り替わった後の画面は図 4-3 である 矢印キーを押せば画像が左右上下へ動き X キーを押せば画像の拡大 C キーを押せば画像の縮小ができるようにプログラムしている Space キーを押せば 次の画面へ切り替わる この図 4-3 のようなものを全部で 3 パターン用意した 表示する画像は PNG 形式にしたので 背景が透き通ってモデルだけが動いているように表現できた 図 4-2 実行画面 : 最初 図 4-3 実行画面 : 拡大縮小 移動 24

4-3 Windows プログラミング 次に Windows 対応のプログラムを作ることができるように書籍などで勉強した その結果 Windows 対応のプログラムの大まかな流れは次のようになっていることがわかった まず WinMain 関数でウィンドウを作ったり そのウィンドウのメニューバーを作ったり ウィンドウの初期設定などを行う そして その際に発行されるメッセージを CALLBACK 関数に渡す CALLBACK 関数では与えられたメッセージによってそれぞれ処理をする 図 4-4 CALLBACK 関数 メッセージには ウィンドウが作られる際に発行される WM_CREATE ウィンドウ内に画像を配置したり 色を塗ったりする WM_PAINT ウィンドウを閉じる時に発行される WM_DESTROY などがある また ウィンドウ内に絵を描く場合 先に WM_CREATE の時に描く為のブラシなどを準備して WM_PAINT で描くブラシを指定し どこからどこまで描くか指定する 第 5 章の最後に ARToolKit とこの Windows 対応プログラムを合体させることを試みた 完成品とまでは言えないが 一応動作可能なプログラムを作ることができた 25

第 5 章 3DCG と Web カメラの合成を表現するプログラム制作 5-1 AR 技術とは AR とは Augmented Reality の略で 日本語では拡張現実感や強調現実感などと呼ばれている AR 技術を使うと Web カメラでキャプチャした画像の中に 3 次元オブジェクトを重畳して表示することができる つまり 図 5-1 のように実際にそこには存在しないが カメラを通した映像を見ると カメラの映像の中に存在するはずの無い 3 次元オブジェクトが映っている ということが表現できる このように デジタルの情報を現実の世界に融合させることができるので AR 技術は作業支援や情報提示に使われており 医療や教育などの分野への応用が期待されている 図 5-1 赤い立方体が表示されている 3 次元オブジェクトを表示するにあたって どこに表示するかの位置情報を取得しなければならない 図 5-1 はマーカーと呼ばれる黒い四角のパターンを使用したものである これは専用マーカー方式といい マーカーをカメラに認識させることによって位置情報を得ている この他にもマーカーを使わないマーカーレス方式や 精密なセンサを用いたセンサを使う方式 GPS などの位置情報を得てセンサと併用する方式などがある 本研究では専用マーカー方式でプログラムを作った 26

5-2 ARToolKit を用いた合成プログラムの骨格 本研究では ARToolKit というライブラリを使って専用マーカー方式のプログラムを作った これは AR アプリケーションの実装を手助けする C/C++ 用のプログラミングライブラリである ARToolKit が提供してくれる主な機能には次のようなものがある カメラからの画像取得 マーカーの検出 パターンの認識 マーカーの位置や角度などの計測 カメラ画像と 3 次元オブジェクトの合成表示マーカーとは図 4-2 のような黒い四角で囲まれたパターンのことである これをカメラで認識させ 例えば このマーカー上に 3 次元オブジェクトを表示する場合は どの方向が X 軸方向なのか Y 軸方向なのかを計測したり 大きさをみて遠くにあるのか近くにあるのか計測したりする 黒い四角は絶対必要だが 中の部分は自分の好きなようにデザインできる ただ 座標系の反転を防ぐために 点対称の図柄は避けた方がいい 図 5-2 マーカー 関数は大きく分けて 5 つある 初期化処理 マーカーの検出などを行うメインループ マ ウスの入力処理 キーボードの入力処理 そして終了処理である 次に初期化処理とメインループについて示す 27

図 5-3 プログラム画面 5-2-1 初期化処理 まず 初期化処理は main 関数で行う 以下のようないろいろな初期化が行われる <main 関数 > 1GLUT の初期化 2ビデオデバイスの初期化 3カメラパラメータの設定 4パターンファイルのロード 5ウィンドウの設定 63D オブジェクトのロード 7ビデオキャプチャの開始 8メインループの呼び出し 1GLUT の初期化 GLUT というのは OpenGL Utility Toolkit の略で OpenGL の補助ライブラリである OpenGL と GLUT で 2 次元と 3 次元の描画処理などをする glutinit() 関数を使って初期化する 28

2 ビデオデバイスの設定 ここで Web カメラを使えるように arvideoopen() 関数を使って準備をする 3カメラパラメータの設定グローバル変数でセットしているカメラパラメータのファイルを使って設定する このファイルには焦点距離やレンズの歪みなどの特性が記録されており 使うカメラによって違うので プログラムを作る前にカメラキャラブレーションをして作成しておく ここで行う処理は次の通りである arvideoinqsize() 関数で画像のサイズを取得する arparamload() 関数でカメラパラメータのファイルをロードする arparamchangesize() 関数で画像のサイズに合わせてカメラパラメータを変更する arinitcparam() 関数でカメラパラメータをセットする 4パターンファイルのロードマーカーとして使うパターンをロードする arloadpatt() 関数を使い ロードが成功すると パターンに割り当てられた ID 番号が返り値で返ってくる この ID 番号はマーカーを識別するときに使われる 5ウィンドウの設定ここで取得したカメラパラメータを使う 使う関数は arginit() 関数である ここではウィンドウをフルスクリーンに設定したり ウィンドウ内に複数の表示領域に分ける設定などができる 63D オブジェクトのロード Metaseqoia で作った 3D オブジェクトをここでロードする 使う関数は mqocreatemodel() であるが ロードする前に mqoinit() 関数で初期化する必要がある そして これらは GLMetaseq ライブラリの関数なので 最初にインクルードしておかなければならない 7 ビデオキャプチャの開始 設定が終わったので arvideocapstart() 関数を使ってビデオキャプチャを開始する 8 メインループの呼び出し armainloop() 関数でマウス入力処理関数 キーボード入力処理関数 メインループ関数を 指定し メインループを呼び出す 29

5-2-2 メインループ関数 メインループ関数の流れは以下のようである 終了処理が行われるまで何度も繰り返し処理をする < メインループ関数 > 1カメラ画像の取得 2カメラ画像の描画 3マーカーの検出 認識 4 次の画像のキャプチャ指示 5マーカーの信頼度の比較 6マーカーの位置 姿勢の計算 73D オブジェクトの描画 8バッファの内容を画面に表示 1カメラ画像の取得 arvideogetimage() 関数を使って Web カメラから画像を取得する この関数は成功すれば画像データへのポインタを返してくるが 画像の準備ができていないときには NULL が返ってくる この場合 これ以降の処理が行えないので arutilsleep() 関数で適当な休止時間をおいた後に メインループを抜ける必要がある 取得された画像は次の画像に切り替わるとき または終了処理が行われるまでバッファに保持される 2カメラ画像の描画取得した画像を出力するために argdrawmode2d() 関数と argdispimage() 関数を使う argdrawmode2d() 関数は 2 次元画像を描画するための準備をする関数で argdispimage() 関数は取得した画像を出力する関数である また 複数の表示領域を設けていた場合 argdispimage() 関数で描画する場所を指定できる AR アプリケーションではカメラ画像を描画した後に 3D オブジェクトを重ねて描画するため 先にカメラ画像そ描画しておく必要がある 3マーカーの検出 認識 ardetectmarker() 関数で 取得した画像の中からマーカーと思われる部分を検出し そのマーカーのパターンを認識する ARToolKit では画像を 2 値化してマーカーの候補領域を探すので この関数を使うときには 0~255 の範囲で閾値を指定する このときカメラ画像に含まれるマーカーらしき部分はすべて取得され その情報は ARMarkerInfo 型の構造体にセットされる 30

ARMarkerInfo 構造体 typepdef struct{ int area: // マーカー領域の画素数 int id; // パターン ID int dir; // 方向 (0,1,2,3 のいずれか ) double cf; // 信頼度 (0.0~1.0) double pos[2]; // マーカーの中心座標 (x,y) double line[4][3]; // 直線の式 (4 辺 ) double vertex[4][2]; // 頂点座標 (4 点 ) ARMarkerInfo; 4 次の画像のキャプチャ指示 arvideocapnext() 関数で次の画像をキャプチャする arvideogetimage() 関数で取得した 画像はこの関数を実行するとなくなるので この間にマーカーを検出しておく必要がある 5マーカーの信頼度の比較マーカーの検出の時には マーカーではない領域も含まれているので 検出されたマーカーの中から 指定のパターン ID の最も高い信頼度をもつものを探し出さなければならない この部分は関数ではなく 以下の for ループを使って信頼度を比較する 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; k と j は int 型の変数でカウンタである marker_num は検出されたマーカーの個数なので 検出されたマーカー分 for ループを回し 比較していく 中の if 文では パターンが一致しているか 一致していればその信頼度が大きさを比べている 最終的には一番信頼度の大きいマーカーの番号が k に格納される また パターンが複数ある場合は そのパターンごとに処理を行う必要がある 6 マーカーの位置 姿勢の計算 argettransmat() 関数を使って 検出されたマーカーの情報からマーカー カメラ間の座標 変換行列を計算する この関数の引数は次の通りである 31

ARMarkerInfo *marker_info ( マーカー情報を格納した変数へのポインタ ) double center[2] ( マーカーの原点位置 ) double width ( マーカーのサイズ ) double conv[3][4] ( マーカー カメラ間の座標変換行列 ) center はマーカー上のどこに原点を置くか決めるパラメータである 値の単位はミリメー トルで 例えば マーカーの中央に原点を置きたい場合は次のように指定する double center[2] = { 0.0, 0.0 ; width はマーカーのサイズで マーカーの一辺の長さを設定する この値の単位もミリメー トルである conv はマーカー座標系をカメラ座標系に変換する行列が入る 73D オブジェクトの描画 3D オブジェクトの描画は DrawObject() 関数にまとめる その中で使われている関数は 以下のような関数である 3D 描画モードの設定をする argdrawmode3d() 関数 射影変換を設定する argdraw3dcamera() 関数 行列のフォーマットを ARToolKit 形式から OpenGL 形式に変換する argconvglpara() 関数 モデルビュー行列の指定をする glmatrixmode() 関数 モデルビュー行列の値を変更をする glloadmatrixd() 関数 8バッファの内容を画面に表示 ARToolKit のグラフィックス処理系ではダブルバッファが使われている これは画面を連続で描画する際のちらつきを防ぐための方法で 表画面 ( フロントバッファ ) と裏画面 ( バックバッファ ) という 2 つのバッファを用意して 表画面を表示している間に裏画面に描画し 表と裏を入れ替える これにより 描画の過程が表に出ない 関数は argswapbuffer() 関数を使う この処理以前に行った描画処理が反映されるので 画像や 3D オブジェクトの描画処理はこれより前にする必要がある 32

5-3 マーカー上にキャラクターを表示させ アニメーション させる ( 第 1 段階 ) まず マーカー上に第 3 章で作成したカボチャのクリーチャーの 3DCG モデルを表示させ アニメーションさせるプログラムを作った モデルは Metasequoia で作ったので GLMetaseq というライブラリを用いた このライブラリには Metasequoia の MQO 形式を扱える関数が入っている 表示させるモデルは初期化処理の時に読み込む必要がある 5-3-1 アニメーション制作 今回はただモデルを表示するだけではなく 歩くアニメーションで表示する これには RokDeBone2 というモーション作成ソフトを使って連番 MQO ファイルを作った 図 5-4 RokDeBone2 作業画面 まず 自分の作ったカボチャの 3DCG モデルを読み込み 図 5-4 のように骨組みを作る 肘や膝なども曲げられるように 関節を作った そして フレーム毎にどこをどの方向にどれだけ動かすか それぞれの関節に付いている軸をマウスで動かす 今回のフレーム数は 42 で 手足の動きを指定し 歩くアニメーションを作った そして これを連番 MQO ファイルの形式で出力する 33

5-3-2 アニメーションの表示 次に この連番 MQO ファイルを読み込むプログラムを作る GLMetaseq では連番 MQO ファイルから作るアニメーションデータをシーケンスをいう そこで シーケンスを作成す る シーケンスを扱うためのデータ型は MQO_SEQUENCE 型である MQO_SEQUENCE 型 typedef struct{ MQO_MODEL model; //MQO モデル int n_frame; // フレーム数 MQO_SEQUENCE; そして シーケンスを作成する関数は mqocreatesequence() 関数である この関数を使っ てシーケンスを作成しているプログラムは以下の通りである char *seq_name = "Sequence/walk_%d.mqo"; MQO_SEQUENCE mqo_seq; // シーケンス int n_frame = 42; // フレーム数 // 連番 MQO ファイル名 printf(" シーケンスの読み込み中..."); mqo_seq = mqocreatesequence( seq_name, n_frame, 0.03 ); if( mqo_seq.n_frame <= 0 ){ printf(" シーケンスの読み込みに失敗しました n"); return -1; printf(" 完了 n"); 特にモデルの大きさが大きすぎて画面に収まらなかったので スケールは 0.03 倍にした 連番 MQO ファイル名は walk_ 数字.mqo にしたので これを 42 フレーム分読み込んでいる そして ここで作成したシーケンスを 3 次元オブジェクトを描画する関数 DrawObjecct() 関数の中で呼び出す それには mqocallseaquence() 関数を使う 引数はシーケンスと描画するフレーム番号で 表示スピードはこのフレーム番号のカウントの仕方を工夫することで調整できる そして このシーケンスは終了処理の時に mqodeleteseaquence() 関数で削除する必要がある しかし この mqocallseaquence 関数を用いて そのまま表示すると尐し問題がある Metasequoia の 3 次元の軸と ARToolKit のマーカーの 3 次元の軸が違うだめ 図 5-5 の右図のように横になってしまう 34

図 5-5 Metasequoia の軸 ( 左 ) とマーカーの軸 ( 右 ) これを解決するために DrawObject() 関数の描画処理の時に OpenGL の関数を使う gltranslatef( 0.0, 0.0, 40.0 ); // モデルの平行移動 glrotatef( 90.0, 1.0, 0.0, 0.0); // モデルを立たせる glrotatef() 関数はモデルを回転させる関数で 引数は回転角度と XYZ の軸のどの方向を回転させるかである (1.0 を入れて指定 ) 今回は X 軸方向に 90.0 度回転させている そして モデルの中心がマーカーの中心に来てしまうので これだけだとモデルがマーカーにめり込んでしまう そのため gltranslatef() 関数を使って Z 軸方向へ移動させている その処理をして完成した画面が次の画面 ( 図 5-6) である 図 5-6 アニメーション画面 35

5-4 マーカーを 2 種類用意してキャラクターを表示させる ( 第 2 段階 ) 最終段階では 2 つのマーカーを使うので まず それぞれのマーカーにモデルを表示させるプログラムを作った 図 5-7 のような左側の 1 のマーカーが認識できれば手を下したカボチャのモデルを表示 右側の 2 のマーカーが認識できれば手を横に広げたカボチャのモデルを表示させる そのため マーカーのパターン 3DCG モデルは 2 つずつ読み込み マーカーの検出処理も 1 の場合と 2 の場合で 2 回行っている 図 5-7 2 つのマーカーにモデル表示 2 つのマーカーのそれぞれに処理を行っているので 片方のマーカーが認識されなくても もう一方のマーカーに対応するモデルは正しく表示される ( 図 5-8) 図 5-8 認識できないマーカー上には表示されない 36

5-5 キャラクターがマーカー間を移動する ( 第 3 段階 ) 次にモデルが 1 のマーカーから 2 のマーカーへ移動するプログラムを作った アニメーションはまだつけていない まず 1 のマーカーが認識できた場合は 1 のマーカー上にカボチャの 3DCG モデルが表示される 次に 1 のマーカーと 2 のマーカーが両方とも認識できた場合 1 のマーカーから 2 のマーカーまでの距離を計測し その距離を移動していく マーカーを 2 つとも認識したときの処理が以下である // マーカーを 2 つとも認識したとき if( object[0].visible > 0 && object[1].visible > 0 ){ // マーカー 1 の座標系でカメラの位置を取得 arutilmatinv( object[0].trans, wmat1 ); // マーカー 1 から見たマーカー 2 の位置を取得 arutilmatmul( wmat1, object[1].trans, wmat2 ); // 移動量 extx = extx + ( wmat2[0][3] * 0.01 ); exty = exty + ( wmat2[1][3] * 0.01 ); // マーカー 2 まで行ったらマーカー 1 へ戻る if( wmat2[0][3] > 0 ){ if( extx >= wmat2[0][3] ){ extx = 0.0; exty = 0.0; else{ if( extx <= wmat2[0][3] ){ extx = 0.0; exty = 0.0; else{ // マーカー 1 に表示 extx = 0.0; exty = 0.0; 37

object 配列はマーカーの情報を格納した OBJECT_T 型の構造体の配列である typedef struct{ char *patt_name; // パターン名 int patt_id; // パターン ID int mark_id; // マーカー ID int visible; // 検出フラグ double width; // パターンサイズ ( 単位 :mm) double center[2]; // パターンの中心座標 double trans[3][4]; // 座標変換行列 OBJECT_T; object[0] は 1 のマーカーに対応しており object[1] は 2 のマーカーに対応している 各マー カーが認識できればそれぞれの visible が 1 になる arutilmatinv( object[0].trans, wmat1 ); これはカメラから見た 1 のマーカーの座標変換行列の逆行列を求めている ( 逆行列は wmat1 へ格納される ) つまり 1 のマーカーから見たカメラの位置を求めている arutilmatmul( wmat1, object[1].trans, wmat2 ); そして この関数は先ほど求めた行列を使って 1 のマーカーと 2 のマーカーの距離の行列を求めている ( その行列は wmat2 へ格納される ) wmat2 行列の wmat2[0][3] は X 座標 wmat2[1][3] は Y 座標 wmat2[2][3] は Z 座標を表す この時にはまだ高さ方向の変位は考えておらず X と Y 方向の変位だけ考えている 0.01 をかけているのは 距離を 100 分割して尐しずつ移動させるためである X の変位がマーカー間の距離を超えた場合 1 のマーカーの位置 (0,0) へ戻り それが繰り返される 図 5-9 マーカー間を移動中 38

5-6 移動中に 1 つのマーカーが消えた時の処理 ( 第 4 段階 ) 5-5 のままでは 両方のマーカーが認識できた場合のみ移動をするので 片方のマーカーが認識できなければ移動をしない 2 のマーカーが消えれば 3D モデルは 1 のマーカーへ戻り 1 のマーカーが消えるとモデル自体が消えてしまう 片方のマーカーが消えてもモデルは残り 移動中であればモデルはその場で停止するようにプログラムを改良した 5-6-1 2 のマーカーが消えた場合 1 のマーカーがあると描画する というのを 3D オブジェクトを描画する際の条件としているので このままでも 2 のマーカーが消えてもカボチャの 3D モデルは消えない ただし 5-5 のときに示したプログラムの後半にあるが 2 つのマーカーが認識できなかった場合 1 のマーカーへ戻る としている これにより 2 のマーカーが消えた時に 1 のマーカーへ戻る そこで この部分をなくすことにより 問題は解決した 2 のマーカーが消えた場合 何も処理が行われないのでその場で止まったままになる ( 図 5-10) 図 5-10 2 のマーカーを隠すとその場で止まる 図 5-10 で モデルの向きが変わっているが これについては 5-8 で説明する 39

5-6-2 1 のマーカーが消えた場合 この部分が一番苦労した部分である 1 のマーカーを基準にして 3D オブジェクトを描画しているので 1 のマーカーがなくなるとモデル自体が消えてしまう そして マーカー間の距離を求める時にも 1 のマーカーを基準にして求めているのでうまくいかなくなる この解決策として 2 のマーカーを基準にして距離を求める処理も同時に行い 1 のマーカーが消えた場合にはその距離を使って モデルを表示させた <1 のマーカーを基準 > // マーカー 1 の座標系でカメラの位置を取得 arutilmatinv( object[0].trans, wmat1 ); // マーカー 1 から見たマーカー 2 の位置を取得 arutilmatmul( wmat1, object[1].trans, wmat2 ); <2 のマーカーを基準 > // マーカー 2 の座標系でカメラの位置を取得 arutilmatinv( object[1].trans, wmat3 ); // マーカー 2 から見たマーカー 1 の位置を取得 arutilmatmul( wmat3, object[0].trans, wmat4 ); 図 5-11 マーカー間の距離を求める 図 5-11 のような処理を行っている wmat2 は 1 のマーカーを基準に求めたマーカー間の距離で wmat4 は 2 のマーカーを基準に求めたマーカー間の距離である 2 つは完全とは言えないが同じ大きさになるはずである 移動をさせる際も同じように移動量を 100 分割して求めるのだが 2 のマーカーが消えた場合と同じように増やすと逆方向へ進んでしまうので この場合は減らしていかなければならない 40

<2 のマーカーが消えた時 両方のマーカーがある時使う移動量 > extx = extx + ( wmat2[0][3] * 0.01 ); exty = exty + ( wmat2[1][3] * 0.01 ); extz = extz + ( wmat2[2][3] * 0.01 ); <1 のマーカーが消えた時に使う移動量 > extx2 = extx2 - ( wmat4[0][3] * 0.01 ); exty2 = exty2 - ( wmat4[1][3] * 0.01 ); extz2 = extz2 - ( wmat4[2][3] * 0.01 ); 5-5 の時点では X と Y 方向しか考えていなかったが ここで高さ方向も付け加えた 図 5-12 1 のマーカーを隠してもその場で止まる 41

5-7 3DCG モデルが登場する場面 退場する場面の追加 ( 第 5 段階 ) モデルがパッと現われて パッと消えるのではなく 登場する場面と退場する場面にもアニメーションをつけた どういうアニメーションかというと 登場はマーカーの下から回りながら上がってくる 退場は逆にマーカーの下へ回りながら降りていく というものである モデルの回転には 5-3-2 の時にモデルの回転で使った glrotatef() 関数を使った glrotatef( angle, 0.0, 1.0, 0.0 ); // モデルを回転させる angle は double 型の変数でこれを尐しずつ増やすことにより モデルを回転しているよう に表示する 深さも同じような考えで gltranslatef() 関数の Z 軸方向を増減させた 図 5-13 登場 ( 左 ) と退場 ( 右 ) マーカーより下の部分のモデルの体が消えているのはマーカーの下に透明の箱の 3DCG モデルを設置しているからである ( 図 5-14 の左図 ) 透明の箱の中に入っていくので モデルの姿が消えていくように見える この透明の箱も Metasequoia で作成し 色をつけずに透明にした 図 5-14 マーカーを横から見た図 ( 左 ) と透明の箱 ( 右 ) 42

5-8 移動中のアニメーション 音 説明のテロップを作り込む ( 第 6 段階 ) 5-8-1 移動中のアニメーション 確かに歩行のアニメーションはついているが そのままだとモデルはそっぽを向いたまま 1 のマーカーから 2 のマーカーへ移動してしまう これでは不自然なので 1 のマーカーの上にモデルが登場した後 その場でモデルの正面が 2 のマーカーのある方向まで回転するようにした その処理は以下のようなものである // マーカーへの方向を取得 angle2 = atan2(wmat2[1][3], wmat2[0][3]) * 180 / 3.14 + 90.0; これは三角関数のアークタンジェントを利用して 1 のマーカーと 2 のマーカーとの角度を 求めている 単位はラジアンにしている 5-7 の登場した時の向きからここで求めた角度の方 向を向くように 回転させる 5-8-2 音 場面によって異なる音楽が流れるようにもした 流れる場面は以下の通りである 11 のマーカーの下からモデルが登場する時 22 のマーカーの方向へ回転中 31 のマーカーから 2 のマーカーへの移動中 42 のマーカーの下へモデルが退場する時 それぞれ場面によって異なるフラグを付けているので それに合わせて音楽を流すように した 例えば 1 の部分のプログラムは以下の通りである if ( dflag == 0 ) { if( snd1_first ){ PlaySound( TEXT("Data/A1_02033.wav"), NULL, SND_ASYNC SND_FILENAME ); snd1_first = 0; snd4_first = 1; 43

dflag が 0 の時は一番最初の状態である つまり 1 のマーカーの下にモデルがいる状態で ある snd1_first は音楽が流れたか流れていないかを判断するフラグで 音楽が流れ終わると 0 となる その後で snd4_first が 1 になっているのは次の音楽を鳴らすための準備である 5-8-3 説明のテロップ 音楽と同様で 場面によって動きを説明するテロップも出るようにした 処理もほぼ同じ方法で それぞれの場面によって付けているフラグで場合分けをしている 説明の画像はペイントで作成した 保存形式はビットマップ形式で 読み込み処理は画像を読み込む関数を別に作り行っている 説明のテロップを描画しているプログラムの部分は以下の通りである // ビットマップ画像の表示 if( dflag == 1 ){ glrasterpos2i( (int)(scaling * xsize/12), (int)(scaling * ysize/15) ); glpixelzoom( scaling, scaling ); gldrawpixels(img1_width, img1_height, GL_RGB, GL_UNSIGNED_BYTE, image1); これは 2 次元の画像の表示なので カメラの画像を表示した後に記述している dflag が 1 の時は モデルがマーカー間を移動している状態である glrasterpos2i() 関数は画像を表示する場所の指定 glpixelzoom() 関数は画像の拡大縮小 gldrawpixels() 関数は画像の描画をしている 図 5-15 説明テロップの表示 44

5-9 完成したプログラムについて 完成したプログラムについて説明する プログラムを起動すると コマンドプロンプトとフレーム率などを変更できるウィンドウが出てくる ( 図 5-16 の左図 ) OK ボタンを押すと 次にカメラ画像の表示倍率を変更できるウィンドウが出てくる ( 図 5-16 の右図 ) 表示倍率は 1.0 倍 ~2.0 倍まで変えられる 図 5-16 起動すると出てくるウィンドウ 表示倍率のウィンドウの OK ボタンを押すと カメラ画像のウィンドウが出てくる そし て 1 のマーカーと 2 のマーカーをカメラが認識すると カボチャの 3DCG モデルが 1 のマ ーカーから出てくる ( 図 5-17) 図 5-17 カボチャ出現 45

全身が 1 のマーカーから出ると カボチャは 2 のマーカーを探し出す ( 図 5-18 の左図 ) 2 のマーカーを見つけると 2 のマーカーへ向かって移動する ( 図 5-18 の右図 ) 図 5-18 移動先探し中 ( 左 ) と移動中 ( 右 ) 2 のマーカーへ到着すると 2 のマーカーの中へ消えていく ( 図 5-19) 図 5-19 カボチャ退場 また 1 のマーカーと 2 のマーカーが逆でも ( 図 5-20 の左図 ) 2 つのマーカー間に高さの違 いがあっても ( 図 5-20 の右図 ) カボチャは移動する 終了するにはウィンドウの ボタンを クリックするか Esc キーを押す 図 5-20 マーカーの配置場所を変えてみた 46

5-10 Windows 対応バージョンへの試み AR のプログラムを Windouw 対応のプログラムにすることを試みる もともとのプログラムが MS-DOS 上で動いてはいても ウィンドウは作っている そのため Windows 対応のプログラムにする積極的な意味はないのであるが リサイズが可能なことと 大きな一つの Windows プログラムの一部として AR 機能を搭載するといった場合には Windows にネイティブに対応したプログラムの方が便利である また インターネット上でも ARToolKit の Windows 対応プログラムの作成方法が見当たらなかったので この点においても多尐の意味はあるかもしれない 図 5-21 のようにプログラムすることで 完全ではないが カボチャのモデルがマーカー上で歩行するアニメーションのプログラムができた ( 図 5-22) 図 5-21 プログラム画面 図 5-22 実行画面 5-9 で説明したプログラムと違う機能は 起動時にコマンドプロンプトが出ないこと ウィンドウの大きさを自由に変えられることなどが挙げられる 終了する場合はウィンドウの ボタンをクリックする 47

第 6 章まとめ 初めて AR の動作画面を見た時は とても驚いたことを覚えている 実際にはそこには存在しないが カメラを通すと物体があったり キャラクターがいたり とても面白そうに感じた プログラムを作り始めると 苦労をする部分も多々あったり 理解をするのが難しい部分もあったが 徐々に自分の作りたいものに近付いているのが実行画面で確認できたので 楽しみながら制作することができた また もう尐し付け加えたかったのが キーボードの入力の操作である Esc キーを押すとウィンドウが閉じる という処理は入れた その他に 例えば Space キーを押せばカボチャが違うアニメーションをしたり カボチャが他のキャラクターに変わったりするような処理もプログラムを作り始めた当初は考えていた しかし 時間の都合上 これは追加しないことにした 本作品を作ることによって プログラミングの大変さや楽しさを体感することができた また 私の数ある夢のひとつ 自分の作ったキャラクターを 3DCG 化し アニメーションを作る ということを実現することができたので満足している また機会があれば こういうプログラムを作ってみたい 48

参考文献 *AR 入門身近になった拡張現実 著者 : 佐野彰 発行所 : 株式会社工学社 *ARToolKit 拡張現実感プログラミング入門 著者 : 橋本直 発行所 : 株式会社アスキーメディアワークス * 拡張現実感を実現する ARToolKit プログラミングテクニック 著者 : 谷尻豊寿 発行所 : 株式会社カットシステム *14 歳からはじめる C 言語オンラインゲームプログラミング教室 著者 : 大槻有一郎 発行所 : 株式会社ラトルズ *Windows ゲームプログラミング第 2 版 著者 : 赤坂玲音 発行所 : ソフトバンククリエイティブ株式会社 *Illustrator リアルイラスト制作テクニック 著者 : 増井浩司 発行所 : 株式会社技術評論社 * メタセコイアからはじめよう! 著者 : 原田大輔 発行所 : 株式会社技術評論社 *Metasequoia ではじめる 3D-CG モデリング ~ 高機能フリー 3DCG をマスターしよう ~ 著者 : 鴨杜健児 発行所 : 株式会社工学社 * Illustrator で学ぶ トレース習熟ドリル 著者 : 大和宣明 発行所 : 有限会社ラピュータ 49

謝辞 今回の卒業研究および卒業論文の作成にあたり 終始丁寧で熱心なご指導とご教示を賜りました高知工科大学工学部電子 光システム工学科綿森道夫准教授に 学生生活面で特に支えてくださいました矢野政顕教授に心から感謝を申し上げます また 高知工科大学工学部電子 光システム工学科在学中に本研究実験遂行や学生生活面 その他各過程で終始ご厚意頂きました 高知工科大学電子 光システム工学科長岩下克教授 木村正廣教授 神戸宏教授 河東田隆教授 真田克教授 橘昌良教授 成沢忠教授 八田章光教授 野中弘二教授 星野孝総准教授 山本真行准教授 植田和憲講師 高崎敬雄教育講師 杉田彰久教育講師 安岡文子秘書 中山愛秘書の皆様には重ねて感謝の意を述べさせていただきます 50

付録 プログラムリスト 51

ARToolKit を用いたプログラム #include <windows.h> #include <stdio.h> #include <math.h> #include "simple.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> #include "GLMetaseq.h" // パターン #define OBJ_NUM 2 #define MARK1_ID 1 #define MARK2_ID 2 #define PATT1_NAME "Data/patt.ichi" // マーカー #define PATT2_NAME "Data/patt.ni" // マーカー #define OBJ1_SIZE 66.0 // パターンサイズ ( 単位 :mm) #define OBJ2_SIZE 66.0 // パターンサイズ ( 単位 :mm) typedef struct{ char *patt_name; int patt_id; int mark_id; int visible; double width; double center[2]; double trans[3][4]; OBJECT_T; // パターン名 // パターンID // マーカー ID // 検出フラグ // パターンサイズ ( 単位 :mm) // パターンの中心座標 // 座標変換行列 52

OBJECT_T object[obj_num] = { { PATT1_NAME, -1, MARK1_ID, 0, OBJ1_SIZE, {0.0, 0.0, { PATT2_NAME, -1, MARK2_ID, 0, OBJ2_SIZE, {0.0, 0.0 ; char *vconf_name = "Data/WDM_camera_flipV.xml"; char *cparam_name = "Data/camera_para.dat"; char *mqo_box = "Data/box2.mqo"; char *seq_name = "Sequence/walk_%d.mqo"; int thresh = 100; // ビデオデバイスの設定ファイル // カメラパラメータファイル //MQOファイル // 歩くMQO MQO_SEQUENCE mqo_seq; int n_frame = 42; MQO_MODEL box; // シーケンス // フレーム数 // 透明の箱 //3 次元オブジェクトの移動量 GLfloat extx = 0.0; GLfloat exty = 0.0; GLfloat extz = 0.0; GLfloat extx2 = 0.0; GLfloat exty2 = 0.0; GLfloat extz2 = 0.0; double depth = -150; double angle = 0.0; double angle2 = 0.0; int dflag = -1; int vflag = -1; int Flag = 0; int aflag = 0; double wmat1[3][4], wmat2[3][4]; double wmat3[3][4], wmat4[3][4]; double scaling = 1.0; int xsize, ysize; // 向き // 深さフラグ // 変換行列 // 変換行列 // 画像サイズ bool snd1_first = 1; bool snd2_first; 53

bool snd3_first; bool snd4_first; GLubyte *image1; // ビットマップ画像が入る配列 int img1_width, img1_height; char *bmpfile1 = "Data/idou.bmp"; GLubyte *image2; // ビットマップ画像が入る配列 int img2_width, img2_height; char *bmpfile2 = "Data/sagasu.bmp"; GLubyte *image3; // ビットマップ画像が入る配列 int img3_width, img3_height; char *bmpfile3 = "Data/shutsugen.bmp"; GLubyte *image4; // ビットマップ画像が入る配列 int img4_width, img4_height; char *bmpfile4 = "Data/kieru.bmp"; // プロトタイプ宣言 static void MainLoop(void); static void DrawObject(double patt_trans[3][4], int markv ); static void MouseEvent(int button, int state, int x, int y); static void KeyEvent(unsigned char key, int x, int y); static void Cleanup(void); static void mysetlight(void); BOOL CALLBACK dlgproc( HWND, UINT, WPARAM, LPARAM ); void Bitmapread(GLubyte **img_ptr, int* width_ptr, int* height_ptr, char *filename); int main(int argc, char **argv) { ARParam cparam; // カメラパラメータ ARParam wparam; // カメラパラメータ ( 作業用変数 ) int i; // カウンタ //GLUT の初期化 glutinit(&argc, argv); 54

// ビデオデバイスの設定 if( arvideoopen( vconf_name ) < 0 ){ printf(" ビデオデバイスのエラー "); return -1; // カメラパラメータの設定 if( arvideoinqsize( &xsize, &ysize ) < 0 ){ printf(" 画像サイズを取得できませんでした \n"); return -1; if( arparamload( cparam_name, 1, &wparam ) < 0 ){ printf(" カメラパラメータの読み込みに失敗しました \n"); return -1; arparamchangesize( &wparam, xsize, ysize, &cparam ); arinitcparam( &cparam ); // パターンファイルのロード for( i=0; i < OBJ_NUM; i++ ){ if( ( object[i].patt_id = arloadpatt(object[i].patt_name) ) < 0 ){ printf(" パターンファイルの読み込みに失敗しました \n"); return -1; // ビットマップ画像の読み込み Bitmapread(&image1, &img1_width, &img1_height, bmpfile1); if ( img1_width == 0 img1_height == 0 ) { printf(" 表示文字データの読み込みに失敗しました \n"); return -1; // ビットマップ画像の読み込み Bitmapread(&image2, &img2_width, &img2_height, bmpfile2); if ( img2_width == 0 img2_height == 0 ) { printf(" 表示文字データの読み込みに失敗しました \n"); 55

return -1; // ビットマップ画像の読み込み Bitmapread(&image3, &img3_width, &img3_height, bmpfile3); if ( img3_width == 0 img3_height == 0 ) { printf(" 表示文字データの読み込みに失敗しました \n"); return -1; // ビットマップ画像の読み込み Bitmapread(&image4, &img4_width, &img4_height, bmpfile4); if ( img4_width == 0 img4_height == 0 ) { printf(" 表示文字データの読み込みに失敗しました \n"); return -1; // 表示倍率のダイアログボックス HINSTANCE hi = ( HINSTANCE )GetWindowLong( HWND_DESKTOP, GWL_HINSTANCE ); DialogBox( hi, "DIALOG1", HWND_DESKTOP, ( DLGPROC ) dlgproc ); // ウィンドウの設定 arginit( &cparam, scaling, 0, 0, 0, 0 ); //GLMetaseq の初期化 mqoinit(); // シーケンスの読み込み printf(" シーケンスの読み込み中..."); mqo_seq = mqocreatesequence( seq_name, n_frame, 0.03 ); if( mqo_seq.n_frame <= 0 ){ printf(" シーケンスの読み込みに失敗しました \n"); return -1; printf(" 完了 \n"); 56

// 透明の箱の読み込み if( ( box = mqocreatemodel( mqo_box, 0.25 ) ) == NULL ){ printf(" 透明の箱の読み込みに失敗しました \n"); return -1; // ビデオキャプチャの開始 arvideocapstart(); // メインループの開始 argmainloop( MouseEvent, KeyEvent, MainLoop ); return 0; void MainLoop(void) { ARUint8 *image; ARMarkerInfo *marker_info; int marker_num; int j, k, i; // カメラ画像 // マーカー検出用の情報 // マーカーらしく部分の個数 // カメラ画像の取得 if( ( image = arvideogetimage() ) == NULL ){ arutilsleep(2); return; // カメラ画像の描画 argdrawmode2d(); argdispimage( image, 0, 0 ); // ビットマップ画像の表示 if( dflag == 1 ){ glrasterpos2i( (int)(scaling * xsize/12), (int)(scaling * ysize/15) ); glpixelzoom( scaling, scaling ); gldrawpixels(img1_width, img1_height, GL_RGB, GL_UNSIGNED_BYTE, image1); 57

if( dflag == 0 ){ // ビットマップ画像の表示 if( aflag == 1 ){ glrasterpos2i( (int)(scaling * xsize/12), (int)(scaling * ysize/15) ); glpixelzoom( scaling, scaling ); gldrawpixels(img2_width, img2_height, GL_RGB, GL_UNSIGNED_BYTE, image2); else{ // ビットマップ画像の表示 glrasterpos2i( (int)(scaling * xsize/12), (int)(scaling * ysize/15) ); glpixelzoom( scaling, scaling ); gldrawpixels(img3_width, img3_height, GL_RGB, GL_UNSIGNED_BYTE, image3); // ビットマップ画像の表示 if( dflag == 2 ){ glrasterpos2i( (int)(scaling * xsize/12), (int)(scaling * ysize/15) ); glpixelzoom( scaling, scaling ); gldrawpixels(img4_width, img4_height, GL_RGB, GL_UNSIGNED_BYTE, image4); // マーカの検出と認識 if( ardetectmarker( image, thresh, &marker_info, &marker_num ) < 0 ){ Cleanup(); exit(0); // 次の画像のキャプチャ指示 arvideocapnext(); //3D オブジェクトを描画するための準備 argdrawmode3d(); argdraw3dcamera( 0, 0 ); // マーカの信頼度の比較 for( i = 0; i < OBJ_NUM; i++ ){ k = -1; 58

for( j = 0; j < marker_num; j++){ if( object[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 ){ object[i].visible = 0; // 座標変換行列を取得 else{ argettransmat( &marker_info[k], object[i].center, object[i].width, object[i].trans ); object[i].visible = 1; // マーカーをつとも検出したとき if( object[0].visible > 0 && object[1].visible > 0 ){ // マーカーの座標系でカメラの位置を取得 arutilmatinv( object[0].trans, wmat1 ); // マーカーから見たマーカーの位置を取得 arutilmatmul( wmat1, object[1].trans, wmat2 ); // マーカーへの方向を取得 angle2 = atan2(wmat2[1][3], wmat2[0][3]) * 180 / 3.14 + 90.0; // マーカーの座標系でカメラの位置を取得 arutilmatinv( object[1].trans, wmat3 ); // マーカーから見たマーカーの位置を取得 arutilmatmul( wmat3, object[0].trans, wmat4 ); vflag = 0; // 音再生 if ( dflag == 0 ) { if(!snd3_first ){ 59

PlaySound( NULL, NULL, SND_PURGE ); snd3_first = 1; if( snd1_first ){ PlaySound( TEXT("Data/A1_02033.wav"), NULL, SND_ASYNC SND_FILENAME ); snd1_first = 0; snd4_first = 1; if( depth >= 0 ){ if( snd4_first ){ PlaySound( TEXT("Data/A1_09176.wav"), NULL, SND_ASYNC SND_FILENAME SND_LOOP ); snd4_first = 0; if( dflag == 1 ){ if(!snd1_first ){ PlaySound( NULL, NULL, SND_PURGE ); snd1_first = 1; snd2_first = 1; if( snd2_first ){ PlaySound( TEXT("Data/A1_05107.wav"), NULL, SND_ASYNC SND_FILENAME SND_LOOP ); snd2_first = 0; if( dflag == 2 ){ if(!snd2_first ){ PlaySound( NULL, NULL, SND_PURGE ); snd2_first = 1; snd3_first = 1; if( snd3_first ){ PlaySound( TEXT("Data/A1_02034.wav"), NULL, SND_ASYNC SND_FILENAME ); 60

snd3_first = 0; if( Flag == 0 ){ dflag = 0; Flag = 1; DrawObject( object[0].trans, vflag ); else if( object[0].visible == 1 ){ vflag = 1; DrawObject( object[0].trans, vflag ); // マーカーだけ検出 else if( object[1].visible == 1 ){ vflag = 2; DrawObject( object[1].trans, vflag ); // マーカーだけ検出 // バッファの内容を画面に表示 argswapbuffers(); void mysetlight(void) { GLfloat light_diffuse[] = { 0.9, 0.9, 0.9, 1.0 ; GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 ; GLfloat light_ambient[] = { 0.3, 0.3, 0.3, 0.1 ; GLfloat light_position[] = { 100.0, -200.0, 200.0, 0.0 ; // 拡散反射光 // 鏡面反射光 // 環境光 // 位置と種類 // 光源の設定 gllightfv( GL_LIGHT0, GL_DIFFUSE, light_diffuse ); gllightfv( GL_LIGHT0, GL_SPECULAR, light_specular ); gllightfv( GL_LIGHT0, GL_AMBIENT, light_ambient ); 61 // 拡散反射光の設定 // 鏡面反射光の設定 // 環境光の設定

gllightfv( GL_LIGHT0, GL_POSITION, light_position ); // 拡散反射光の設定 // glshademodel( GL_SMOOTH ); // シェーディングの種類の設定 glenable( GL_LIGHT0 ); // 光源の有効化 void DrawObject( double patt_trans[3][4], int markv ) { double gl_para[16]; static int k = 0; // 描画するフレームの番号 //3D オブジェクトを描画するための準備 argdrawmode3d(); argdraw3dcamera( 0, 0 ); // 座標変換行列の適用 argconvglpara( patt_trans, gl_para ); glmatrixmode( GL_MODELVIEW ); glloadmatrixd( gl_para ); //3D オブジェクトの描画 glclear( GL_DEPTH_BUFFER_BIT ); glenable( GL_DEPTH_TEST ); //Z バッファの初期化 // 陰面処理の適用 mysetlight(); glenable( GL_LIGHTING ); // 光源の設定 // 光源の適用 if( markv == 0 ){ if( dflag == 0 ){ // 上がる extx2 = wmat4[0][3]; exty2 = wmat4[1][3]; extz2 = wmat4[2][3]; if( depth < 0.0 ){ depth += 1.0; angle += 1.0; extz = depth; extz2 = depth; 62

else{ if( angle2 < angle ){ aflag = 1; angle -= 1.0; else{ aflag = 0; dflag = 1; extx2 = wmat4[0][3]; exty2 = wmat4[1][3]; extz2 = wmat4[2][3]; else if( dflag == 1 ){ extx = extx + ( wmat2[0][3] * 0.01 ); exty = exty + ( wmat2[1][3] * 0.01 ); extz = extz + ( wmat2[2][3] * 0.01 ); // マーカーの方向へ向く // 移動 extx2 = extx2 - ( wmat4[0][3] * 0.01 ); exty2 = exty2 - ( wmat4[1][3] * 0.01 ); extz2 = extz2 - ( wmat4[2][3] * 0.01 ); else if( dflag == 2 ){ depth = extz; if(depth > -150){ depth -= 1.0; angle += 1.0; extz = depth; extz2 = depth; else{ extx = 0.0; exty = 0.0; extx2 = wmat4[0][3]; exty2 = wmat4[1][3]; dflag = 0; // 沈む 63

移動 // マーカー 2まで行ったらマーカー 1へ戻る if( wmat2[0][3] > 0 ){ if( extx >= wmat2[0][3] ){ dflag = 2; else{ if( extx < wmat2[0][3] ){ dflag = 2; if( dflag == 0 ){ // 透明の箱 glpushmatrix(); if( markv == 2 ) gltranslatef( extx2, exty2, wmat2[2][3] ); glrotatef( 90.0, 1.0, 0.0, 0.0); // モデルを立たせる mqocallmodel( box ); // モデルの描画 glpopmatrix(); else if( dflag == 2 ){ // 透明の箱 glpushmatrix(); if( markv == 2 ) gltranslatef( extx2, exty2, wmat2[2][3] ); else gltranslatef( extx, exty, wmat2[2][3] ); // モデルの平行 glrotatef( 90.0, 1.0, 0.0, 0.0); // モデルを立たせる mqocallmodel( box ); // モデルの描画 glpopmatrix(); // カボチャ glpushmatrix(); gltranslatef( 0.0, 0.0, 40.0 ); // モデルの平行移動 if ( markv == 2 ) gltranslatef(extx2, exty2, extz2); else 64

gltranslatef(extx, exty, extz); glrotatef( 90.0, 1.0, 0.0, 0.0); // モデルを立たせる glrotatef( angle, 0.0, 1.0, 0.0 ); // モデルを回転させる mqocallsequence( mqo_seq, k ); // 指定フレームの描画 glpopmatrix(); gldisable( GL_LIGHTING ); gldisable( GL_DEPTH_TEST ); // フレーム番号のカウント k++; if( k >= n_frame ) k = 0; void MouseEvent( int button, int state, int x, int y ) { // 入力状態を表示 printf(" ボタン :%d 状態 :%d 座標 :(x,y)=(%d, %d) \n",button, state, x, y); void KeyEvent( unsigned char key, int x, int y ) { //ESCキーを入力したらアプリケーション終了 if( key == 0x1b ){ Cleanup(); exit(0); void Cleanup(void) { arvideocapstop(); // ビデオキャプチャの停止 arvideoclose(); // ビデオデバイスの終了 argcleanup(); // グラフィック処理の終了 if( image1!= NULL ) free(image1); if( image2!= NULL ) free(image2); if( image3!= NULL ) free(image3); if( image4!= NULL ) free(image4); 65

mqodeletemodel( box ); // モデルの削除 mqodeletesequence( mqo_seq ); // シーケンスの削除 mqocleanup(); //GLMetaseqの終了処理 void Bitmapread(GLubyte **img_ptr, int* width_ptr, int* height_ptr, char *filename) { FILE *fp; int x, y, d; int width, height, position = 0; long t; GLubyte *image; fp = fopen(filename, "rb"); if ( fp == NULL ) { *width_ptr = 0; *height_ptr = 0; return; fseek(fp, 18, SEEK_SET); fread(width_ptr, 4, 1, fp); fread(height_ptr, 4, 1, fp); width = *width_ptr; height = *height_ptr; image = (GLubyte*)malloc(3 * width * height * sizeof(glubyte) ); if ( image == NULL ) { *width_ptr = 0; *height_ptr = 0; return; d = 4-3 * width % 4; if ( d == 4 ) d = 0; fseek(fp, 54, SEEK_SET); for ( y = height - 1; y >= 0; y-- ) { for ( x = 0; x < width; x++ ) { fread((image + position + 2), 1, 1, fp); fread((image + position + 1), 1, 1, fp); fread((image + position), 1, 1, fp); position += 3; fread(&t, d, 1, fp); 66

fclose(fp); *img_ptr = image; BOOL CALLBACK dlgproc( HWND hwnddlg, UINT msg, WPARAM wparam, LPARAM lparam) { switch( msg ){ case WM_INITDIALOG: SetDlgItemText( hwnddlg, IDC_EDIT1, "1.0" ); break; case WM_COMMAND: switch( wparam ){ case IDOK: char cbuf[20]; GetDlgItemText( hwnddlg, IDC_EDIT1, cbuf, 20 ); scaling = atof( cbuf ); if( scaling < 0.5 ) scaling = 1.0; if( scaling > 5.0 ) scaling = 2.0; EndDialog( hwnddlg, FALSE ); return TRUE; case IDCANCEL: EndDialog( hwnddlg, FALSE ); return TRUE; break; case WM_CLOSE: EndDialog( hwnddlg, FALSE ); return TRUE; return FALSE; 67

Windows 対応にしたプログラム #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <gl/gl.h> #include <gl/glu.h> #include <gl/glut.h> #include <AR\video.h> #include <AR\param.h> #include <AR\ar.h> #include <AR/config.h> #include <AR/gsub_lite.h> #include "GLMetaseq.h" #define APP_NAME TEXT("ARwindows") /* カメラ構成 */ char *vconf = TEXT("Data\\WDM_camera_flipV.xml"); // 既定のカメラ構成 /* カメラパラメータ */ char *cparam_name = TEXT("Data\\camera_para.dat"); // 既定のカメラパラメータ /* パターンファイル */ char *patt_name = TEXT("Data\\patt.ichi"); // パターンファイル名 /* モデル */ char *seq_name = TEXT( "Sequence\\walk_%d.mqo" ); // カボチャ歩く // ============================================================================ // Constants // ============================================================================ #define VIEW_SCALEFACTOR 0.025 // 1.0 ARToolKit unit becomes 0.025 of my OpenGL units. #define VIEW_DISTANCE_MIN 0.1 // Objects closer to the camera than this will not be displayed. 68

#define VIEW_DISTANCE_MAX 100.0 // Objects further away from the camera than this will not be displayed. // Marker detection. static int gartthreshhold = 100; // Transformation matrix retrieval. static double gpatt_width = 80.0; // Per-marker, but we are using only 1 marker. static double gpatt_centre[2] = {0.0, 0.0; // Per-marker, but we are using only 1 marker. double gpatt_trans[3][4]; // Per-marker, but we are using only 1 marker. int gpatt_found = FALSE; // Per-marker, but we are using only 1 marker. int gpatt_id; // Per-marker, but we are using only 1 marker. // Drawing. ARParam gartcparam; ARGL_CONTEXT_SETTINGS_REF garglsettings = NULL; MQO_SEQUENCE mqo_seq; int n_frame = 42; // シーケンス // フレーム数 int xsize, ysize; // ============================================================================ // Functions // ============================================================================ // Something to look at, draw a rotating colour cube. static void DrawFigure(void) { static int k=0; // フレーム番号 glpushmatrix(); // Save world coordinate system. glscalef( 0.04, 0.04, 0.04 ); 69

gltranslatef(0.0, 0.0, 40.0); // Place base of cube on marker surface. glrotatef( 90.0, 1.0, 0.0, 0.0 ); mqocallsequence(mqo_seq, k); // Draw the cube. glpopmatrix(); // Restore world coordinate system. // フレーム番号のカウント k++; if( k >= n_frame ) k = 0; LRESULT CALLBACK WindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) { HDC hdc; 24, static PIXELFORMATDESCRIPTOR pfd = { sizeof(pixelformatdescriptor), 1, PFD_DRAW_TO_WINDOW PFD_SUPPORT_OPENGL PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, PFD_MAIN_PLANE, 0, 0, 0, 0; int pfdid; BOOL bresult; static HGLRC m_hglrc; RECT winsize; ARParam wparam; // カメラパラメータ static ARUint8 *gartimage; // カメラ画像 static ARMarkerInfo *marker_info; // マーカー検出用の情報 static int marker_num; // マーカーらしき部分の個数 static int isfirst = 1; GLdouble p[16], m[16]; PAINTSTRUCT ps; int j, k; 70

switch ( umsg ) { case WM_DESTROY : KillTimer(hWnd, 1); if ( m_hglrc!= NULL ) wgldeletecontext(m_hglrc); arvideocapstop(); arvideoclose(); arglcleanup(garglsettings); mqodeletesequence( mqo_seq ); mqocleanup(); // シーケンスの削除 //GLMetaseq の終了処理 PostQuitMessage(0); return 0; case WM_CREATE : hdc = GetDC(hWnd); pfdid = ChoosePixelFormat(hdc, &pfd); if ( pfdid == 0 ) { MessageBox(hWnd, TEXT("pfdIDの取得に失敗しました "), TEXT("pfdID error"), MB_OK); return -1; bresult = SetPixelFormat(hdc, pfdid, &pfd); if ( bresult == FALSE ) { MessageBox(hWnd, TEXT("pfdIDの割り付けに失敗しました "), TEXT("attach error"), MB_OK); return -1; m_hglrc = wglcreatecontext(hdc); if ( m_hglrc == NULL ) { ReleaseDC(hWnd, hdc); MessageBox(hWnd, TEXT(" レンダリング コンテキストの生成に失敗しました "), TEXT("m_hGLRC error"), MB_OK); 71

return -1; /* カメラパラメータの読み込み */ if( arparamload(cparam_name, 1, &wparam) < 0 ) { MessageBox(hWnd, TEXT(" カメラパラメータの読み込みに失敗しました "), TEXT("ParamLoad error"), MB_OK); return -1; /* カメラパラメータの初期化 */ arparamchangesize( &wparam, xsize, ysize, &gartcparam ); arinitcparam( &gartcparam ); /* パターンファイルの読み込み */ if( (gpatt_id=arloadpatt(patt_name)) < 0 ) { MessageBox(hWnd, TEXT(" パターンファイルの読み込みに失敗しました "), TEXT("Load Pattern error"), MB_OK); return -1; wglmakecurrent(hdc, m_hglrc); if ((garglsettings = arglsetupforcurrentcontext()) == NULL ) { MessageBox(hWnd, TEXT("main(): arglsetupforcurrentcontext() returned error."), TEXT("setup error"), MB_OK); wglmakecurrent(null, NULL); return -1; mqoinit(); /* シーケンスの読み込み */ mqo_seq = mqocreatesequence( seq_name, n_frame, 0.03 ); if( mqo_seq.n_frame <= 0 ){ MessageBox( hwnd, TEXT(" シーケンスの読み込みに失敗しました "), TEXT("Load Sequence error"), MB_OK ); return -1; 72

/* キャプチャ開始 */ arvideocapstart(); wglmakecurrent(null, NULL); ReleaseDC(hWnd, hdc); SetTimer(hWnd, 1, 80, NULL); return 0; case WM_SIZE: hdc = GetDC(hWnd); GetClientRect(hWnd, &winsize); wglmakecurrent(hdc, m_hglrc); glclear(gl_color_buffer_bit GL_DEPTH_BUFFER_BIT); glviewport(0, 0, (GLsizei)winsize.right, (GLsizei)winsize.bottom); glmatrixmode(gl_projection); glloadidentity(); glmatrixmode(gl_modelview); glloadidentity(); wglmakecurrent(null, NULL); ReleaseDC(hWnd, hdc); isfirst = 1; return 0; case WM_PAINT : hdc = BeginPaint(hWnd, &ps); wglmakecurrent(hdc, m_hglrc); glenable(gl_depth_test); glenable(gl_cull_face); 73

glclearcolor(0.0f, 0.0f, 0.0f, 1.0f); glclear(gl_color_buffer_bit GL_DEPTH_BUFFER_BIT); gldrawbuffer(gl_back); argldispimage(gartimage, &gartcparam, 1.0, garglsettings); arvideocapnext(); gartimage = NULL; VIEW_DISTANCE_MAX, p); if ( gpatt_found ) { arglcamerafrustumrh(&gartcparam, VIEW_DISTANCE_MIN, glmatrixmode(gl_projection); glloadmatrixd(p); glmatrixmode(gl_modelview); glloadidentity(); arglcameraviewrh(gpatt_trans, m, VIEW_SCALEFACTOR); glloadmatrixd(m); DrawFigure(); SwapBuffers(hdc); gldisable(gl_depth_test); gldisable(gl_cull_face); wglmakecurrent(null, NULL); EndPaint(hWnd, &ps); return 0; case WM_TIMER: hdc = GetDC(hWnd); wglmakecurrent(hdc, m_hglrc); /* カメラ画像の取得 */ if( (gartimage = arvideogetimage()) == NULL ) { wglmakecurrent(null, NULL); ReleaseDC(hWnd, hdc); 74

return 0; // マーカの検出と認識 if ( ardetectmarker( gartimage, gartthreshhold, &marker_info, &marker_num ) < 0 ) { MessageBox(hWnd, TEXT(" マーカー検出アルゴリズムが不正です "), TEXT("AR algorythum error"), MB_OK); return -1; // マーカの一致度の比較 k = -1; gpatt_found = FALSE; for ( j = 0; j < marker_num; j++ ) { if ( gpatt_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 ) { // マーカの位置 姿勢 ( 座標変換行列 ) の計算 if ( isfirst ) { argettransmat( &marker_info[k], gpatt_centre, gpatt_width, gpatt_trans ); isfirst = 0; else { argettransmatcont( &marker_info[k], gpatt_trans, gpatt_centre, gpatt_width, gpatt_trans ); // 3Dオブジェクトの描画 gpatt_found = TRUE; wglmakecurrent(null, NULL); ReleaseDC(hWnd, hdc); InvalidateRect(hWnd, NULL, NULL); return 0; 75

return DefWindowProc(hWnd, umsg, wparam, lparam); int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow ) { WNDCLASS wc; MSG msg; /* ビデオデバイスの設定 */ if( arvideoopen( vconf ) < 0 ) { MessageBox(NULL, TEXT(" ビデオデバイスの取得に失敗しました "), TEXT("video open error"), MB_OK); return -1; /* ウィンドウサイズの取得 */ if( arvideoinqsize(&xsize, &ysize) < 0 ) { MessageBox(NULL, TEXT(" ウインドウサイズの取得に失敗しました "), TEXT("VideoInqSize error"), MB_OK); return -1; wc.style = CS_HREDRAW CS_VREDRAW; wc.lpfnwndproc = WindowProc; wc.cbclsextra = 0; wc.cbwndextra = 0; wc.hinstance = hinstance; wc.hicon = LoadIcon(NULL, IDI_APPLICATION); wc.hcursor = LoadCursor(NULL, IDC_ARROW); wc.hbrbackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszmenuname = NULL; wc.lpszclassname =APP_NAME; if (!RegisterClass(&wc) ) return 0; 76

if (CreateWindow( APP_NAME, TEXT("AR win"), WS_OVERLAPPEDWINDOW WS_CLIPCHILDREN WS_CLIPSIBLINGS WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, xsize, ysize, NULL, NULL, hinstance, NULL ) == NULL) return 0; while ( GetMessage(&msg, NULL, 0, 0) > 0 ) { TranslateMessage(&msg); DispatchMessage(&msg); return msg.wparam; 77