21世紀もはや10分の一が過ぎ、世の中では拡張現実(AR)の研究や期待が高まっています。
そんな中で忘れ去られた感もある仮想現実(VR)の世界。
そう、かつてシュワちゃんとかが宮沢りえとTVCMに出ていたような時代にヴァーチャルリアリティと呼ばれもてはやされたあの技術。
そんな技術をいまさらながら試してみました、昭和の男、正宗です。
具体的には、WEBカメラをつなげていろいろ顔を動かしてみると中の3D空間も連動して動くというような奴です。ニンテンドーDSのアッタコレダっていうゲームをヒントに作ってみました。
さっそくみてみましょう。
(要FlashPlayer10)
モデルは毎度おなじみの、みんなの人気者愛媛県西予市げんき君です。
やってることは、顔認識を使ってWEBカメラから顔の位置を検出して、その位置を元に3Dのカメラを動かしてるだけです。
顔認識はライブラリを使っているのですが、使っているライブラリが今現在公開停止しているのか、DLできないようになっているので紹介していいものかどうか悩みます。
2011/08/26追記:今みると公開されているようです!
http://www.libspark.org/wiki/mash/Marilena
いちおうソースの説明はしてみたいと思います。
ソースの書き方はとても汚く、またとっちらかってるのでお見せするのはとても恥ずかしいんですが、何かの参考になれば嬉しいです。
ソースコードのDLはこちら
別途
- Progression4
- Papervision3D
- Marilena
が必要です。
tools.Detecto
実際に顔認識を行っているクラスは「tools.Detector」クラスになります。
顔認識はとても重い処理なので、なるべく軽くなるように工夫しています。
private function _onTimerHandler(event:TimerEvent):void { _prevTime = getTimer(); var video:Video = _tool.video; _bitmapData.draw(video, _mat); _detector.detect(new Bitmap(_bitmapData)); }
タイマーイベントを駆動して、顔認識を開始するようにしています。
カメラ映像は一度Bitmapにする必要があるんですが、ある程度縮小することによって顔認識も軽くするようにしています。
private function _onDetectionCompleteHandler(event:ObjectDetectorEvent):void { if(event.rects.length > 0){ _rect = event.rects[0] as Rectangle; } _timer.reset(); _timer.start(); dispatchEvent(new Event(RECTANGLE_UPDATE)); }
顔認識が完了するともう一度タイマーイベントを駆動して次の顔認識を始めるようにしています。
Event.ENTER_FRAMEにしなかったのは、顔認識にかかった時間を計測して、もし遅れるようであれば顔認識の更新の間隔を遅らせるなどの処理をゆくゆくははさみたかったからです。
pages.TopPage
3Dの描写は直接「pages.TopPage」で行っています。
顔認識が完了する度に、顔の位置を検出してカメラをそこに持って行っています。
private function _detectorUpdateHandler(e:Event):void { var rect:Rectangle = _detector.rect; if(!(!rect)){ var rX:Number = (rect.x / VideocameraTool.VIDEO_SCALE) - ((_tool.video.width - (rect.width / VideocameraTool.VIDEO_SCALE)) * 0.5); var rY:Number = (rect.y / VideocameraTool.VIDEO_SCALE) - ((_tool.video.height - (rect.height / VideocameraTool.VIDEO_SCALE)) * 0.5); var rZ:Number = (rect.width - _RECT_MIN) * _renge; var cX:Number = rX * _viewScaleX * _SCALE_VIEW; var cY:Number = -rY * _viewScaleY * _SCALE_VIEW; _view.camera.zoom = rZ + 40; var matX:Matrix3D = Matrix3D.rotationMatrix(0, 1, 0, cX * _PI); var matY:Matrix3D = Matrix3D.rotationMatrix(1, 0, 0, cY * _PI); var mat:Matrix3D = Matrix3D.multiply(matX, matY); _view.camera.transform = Matrix3D.multiply(mat, _cameraMatrix); } }
なんかごそごそとややこしいことをしてそうに見えますが、僕のコードの書き方が下手なだけで、実際には顔認識の位置をカメラの位置に変換しているだけです。
実は今回のソースで一番苦労したのは、
Matrix3D.rotationMatrix
で、カメラをX方向に何度、Y方向に何度動かしたときのXYZ座標への持って行き方が一番苦心しました。
Matrix3D.rotationMatrixがそれらしい処理を行ってくれそうなのはわかったんですが、引数が謎すぎるのと、X方向、Y方向、デフォルトのカメラの位置、それぞれ3つの行列を合成させるという答えに行き着くまでが、一番時間がかかりましたw
まとめ
どうでしょうか。
現実的な速度で動作させるために顔認識の精度を落としているのですが、これが将来的にもっと高速になればなかなか面白いことが出来るのではないでしょうか。
個人的には顔の位置をカメラとリンクさせるだけ、というアイデアの簡潔さがとても気に入っています。