Comments
Description
Transcript
卒業研究報告書 3DCG と Web カメラの合成を表現するプログラムの制作
卒業研究報告書 題目 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章 4-1 Windows プログラミング 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章 5-1 3DCG と Web カメラの合成を表現するプログラム制作 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 関数> ①GLUT の初期化 ②ビデオデバイスの初期化 ③カメラパラメータの設定 ④パターンファイルのロード ⑤ウィンドウの設定 ⑥3D オブジェクトのロード ⑦ビデオキャプチャの開始 ⑧メインループの呼び出し ①GLUT の初期化 GLUT というのは OpenGL Utility Toolkit の略で、OpenGL の補助ライブラリである。 OpenGL と GLUT で 2 次元と 3 次元の描画処理などをする。glutInit()関数を使って初期化す る。 28 ②ビデオデバイスの設定 ここで Web カメラを使えるように arVideoOpen()関数を使って準備をする。 ③カメラパラメータの設定 グローバル変数でセットしているカメラパラメータのファイルを使って設定する。このフ ァイルには焦点距離やレンズの歪みなどの特性が記録されており、使うカメラによって違う ので、プログラムを作る前にカメラキャラブレーションをして作成しておく。ここで行う処 理は次の通りである。 ・arVideoInqSize()関数で画像のサイズを取得する ・arParamLoad()関数でカメラパラメータのファイルをロードする ・arParamChangeSize()関数で画像のサイズに合わせてカメラパラメータを変更する ・arInitCParam()関数でカメラパラメータをセットする ④パターンファイルのロード マーカーとして使うパターンをロードする。arLoadPatt()関数を使い、ロードが成功すると、 パターンに割り当てられた ID 番号が返り値で返ってくる。この ID 番号はマーカーを識別す るときに使われる。 ⑤ウィンドウの設定 ここで取得したカメラパラメータを使う。使う関数は argInit()関数である。ここではウィ ンドウをフルスクリーンに設定したり、ウィンドウ内に複数の表示領域に分ける設定などが できる。 ⑥3D オブジェクトのロード Metaseqoia で作った 3D オブジェクトをここでロードする。使う関数は mqoCreateModel() であるが、ロードする前に mqoInit()関数で初期化する必要がある。そして、これら は GLMetaseq ライブラリの関数なので、最初にインクルードしておかなければならない。 ⑦ビデオキャプチャの開始 設定が終わったので、arVideoCapStart()関数を使ってビデオキャプチャを開始する。 ⑧メインループの呼び出し arMainLoop()関数でマウス入力処理関数、キーボード入力処理関数、メインループ関数を 指定し、メインループを呼び出す。 29 5-2-2 メインループ関数 メインループ関数の流れは以下のようである。終了処理が行われるまで何度も繰り返し処 理をする。 <メインループ関数> ①カメラ画像の取得 ②カメラ画像の描画 ③マーカーの検出・認識 ④次の画像のキャプチャ指示 ⑤マーカーの信頼度の比較 ⑥マーカーの位置・姿勢の計算 ⑦3D オブジェクトの描画 ⑧バッファの内容を画面に表示 ①カメラ画像の取得 arVideoGetImage()関数を使って Web カメラから画像を取得する。この関数は成功すれば 画像データへのポインタを返してくるが、画像の準備ができていないときには NULL が返っ てくる。この場合、これ以降の処理が行えないので、arUtilSleep()関数で適当な休止時間を おいた後に、メインループを抜ける必要がある。取得された画像は次の画像に切り替わると き、または終了処理が行われるまでバッファに保持される。 ②カメラ画像の描画 取得した画像を出力するために argDrawMode2D()関数と argDispImage()関数を使う。 argDrawMode2D()関数は 2 次元画像を描画するための準備をする関数で、argDispImage() 関数は取得した画像を出力する関数である。また、複数の表示領域を設けていた場合、 argDispImage()関数で描画する場所を指定できる。AR アプリケーションではカメラ画像を描 画した後に、3D オブジェクトを重ねて描画するため、先にカメラ画像そ描画しておく必要が ある。 ③マーカーの検出・認識 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; ④次の画像のキャプチャ指示 arVideoCapNext()関数で次の画像をキャプチャする。arVideoGetImage()関数で取得した 画像はこの関数を実行するとなくなるので、この間にマーカーを検出しておく必要がある。 ⑤マーカーの信頼度の比較 マーカーの検出の時には、マーカーではない領域も含まれているので、検出されたマーカ ーの中から、指定のパターン 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 に格納される。また、パターンが複数ある場合は、そのパターンごとに 処理を行う必要がある。 ⑥マーカーの位置・姿勢の計算 arGetTransMat()関数を使って、検出されたマーカーの情報からマーカー・カメラ間の座標 変換行列を計算する。この関数の引数は次の通りである。 31 ARMarkerInfo *marker_info (マーカー情報を格納した変数へのポインタ) double center[2] (マーカーの原点位置) double width (マーカーのサイズ) double conv[3][4] (マーカー・カメラ間の座標変換行列) center はマーカー上のどこに原点を置くか決めるパラメータである。値の単位はミリメー トルで、例えば、マーカーの中央に原点を置きたい場合は次のように指定する。 double center[2] = { 0.0, 0.0 }; width はマーカーのサイズで、マーカーの一辺の長さを設定する。この値の単位もミリメー トルである。 conv はマーカー座標系をカメラ座標系に変換する行列が入る。 ⑦3D オブジェクトの描画 3D オブジェクトの描画は DrawObject()関数にまとめる。その中で使われている関数は、 以下のような関数である。 ・3D 描画モードの設定をする argDrawMode3D()関数 ・射影変換を設定する argDraw3dCamera()関数 ・行列のフォーマットを ARToolKit 形式から OpenGL 形式に変換する argConvGlPara()関数 ・モデルビュー行列の指定をする glMatrixMode()関数 ・モデルビュー行列の値を変更をする glLoadMatrixd()関数 ⑧バッファの内容を画面に表示 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; int n_frame; //MQO モデル //フレーム数 } 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 音 場面によって異なる音楽が流れるようにもした。流れる場面は以下の通りである。 ①1 のマーカーの下からモデルが登場する時 ②2 のマーカーの方向へ回転中 ③1 のマーカーから 2 のマーカーへの移動中 ④2 のマーカーの下へモデルが退場する時 それぞれ場面によって異なるフラグを付けているので、それに合わせて音楽を流すように した。例えば、①の部分のプログラムは以下の通りである。 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; //パターンID int mark_id; //マーカーID int visible; //検出フラグ double width; //パターンサイズ(単位:mm) double center[2]; //パターンの中心座標 double trans[3][4]; //座標変換行列 } OBJECT_T; 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"; //MQOファイル char *seq_name = "Sequence/walk_%d.mqo"; //歩くMQO int thresh = 100; 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 char img1_width, img1_height; *bmpfile1 = "Data/idou.bmp"; GLubyte *image2; // ビットマップ画像が入る配列 int char img2_width, img2_height; *bmpfile2 = "Data/sagasu.bmp"; GLubyte *image3; // ビットマップ画像が入る配列 int char img3_width, img3_height; *bmpfile3 = "Data/shutsugen.bmp"; GLubyte *image4; // ビットマップ画像が入る配列 int char img4_width, img4_height; *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 ); //Zバッファの初期化 glEnable( GL_DEPTH_TEST ); //陰面処理の適用 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); glPopMatrix(); // Draw the cube. // Restore world coordinate system. //フレーム番号のカウント k++; if( k >= n_frame ) k = 0; } LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 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; if ( gPatt_found ) { arglCameraFrustumRH(&gARTCparam, VIEW_DISTANCE_MIN, VIEW_DISTANCE_MAX, p); 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