凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Maker Faire Tokyo 2013 でレゴへのプロジェクションマッピングネタで出展してきた

はじめに

11/3、4 で開催されていた Maker Faire Tokyo 2013 に、id:jonki凸P(Mont Blanc Pj.)というネタで出展してきました。レゴで作ったステージにゲーム画面をプロジェクションマッピングし、レゴを動かすとゲーム内のブロックも動的に変更されたり、そのゲーム内の物体とハードウェアが連動したりする、というネタです。ものづくり系のイベントに出展するのは今回が初めてで完成が直前になってしまいましたが、何とか形にすることが出来ました。そして2日間大きな問題も起こることなく多くの方に体験していただくことが出来て、とても楽しい時間を過ごすことが出来ました。
出展中は時間の都合上ざっくりとしか説明出来なかったのですが、私は主にソフト面を担当したので、その詳細な仕組みやコードを本エントリで紹介させていただければと思います。

概要

展示で再生していた紹介動画が以下になります。

コンセプトは、

  1. 白色のレゴ(立体)で作ったステージにプロジェクションマッピングしたゲーム
  2. ゲーム中のオブジェクトとハードウェアが連動
  3. レゴを組み替えるとゲームの世界も変化

の 3 点です。2x2 のレゴをゲーム画面のどこかにつけると、ゲーム内でもそこにブロックが出てきて当たり判定が発生し、上を歩けるようになったり、乗るとジャンプするようになったりする、というところを担当しました。

認識の仕組み

一番多かった質問が「どうやってブロックが追加されていることを認識しているのか?」というものでした。これは上の動画でも説明しているのですが、より詳細に図解します。




上図のような形になります。一番のミソは、背景には灰色のレゴを利用して、追加するブロックには白色のレゴを使用していることです。これにより 2 日間のデモにも耐えうる安定性を確保出来ました(取れている白黒画は動画参照)。
はじめは不可視ARマーカのように再帰性反射材を使おうと思ったのですが、白レゴの赤外線の反射が強く、常に白く撮れてしまうため余りうまく行きませんでした。次は投光機を斜めから当てて影を作って認識することを考えました。が、これは段差を検知するには上からと下から両方から(もしくは左右両方から)光を交互に当てて当てて影を取らないと、どこで段差が下がったかが撮れず仕組みが複雑になるため、あきらめました。そして最後に行き着いたのが今回採用した手法です。たまたま背景に用いるレゴが灰色しか売っておらず、仕方なく買っていろいろ試していたところ、たまたま天井に赤外線ライトを当てた状態で赤外線カメラ見てみるといい感じに白黒が撮れており、コレだ!と採用しました。

ソフト構成

ゲームは Unity で作成しており、そこに対してブロックの追加/削除指示を行う専用のキャリブレ&画像解析ツールを OpenCV + Qt*1で作成しました。また、動画にはないのですが本番ではブロックの種類をタブレットから変更できるようにしました。これは前日作ることになり、時間がなかったので HTMLNode.js で作成しました。お互いにどう動いているかは下記になります。

基本的に信号は OSC で送っています。

ゲーム

ゲームは Unity で作成しています。実は斜めから見ると3次元になっていたりします。単純に組み込みの物理エンジンを利用して、手抜きで作成しています。が、素の物理エンジンは余り 2D ゲームでは衝突判定まわりでうまく動いてくれないので、結局横方向の当たり判定などは Raycast したりしています。Unity 用の 2D の UI フレームワークを使えばもっと簡単だったのかもしれないので次回機会があれば何か利用してみたいと思います。
通信周りは UnityOSC を利用しています。

画像解析ツール

ゲームよりも主に力を入れたのがこちらになります。MFT に間に合わせるために大分適当になっていますが、コードは以下で公開しています。

主に以下の様な処理を行っています。

歪み補正は、カメラ画が結構歪曲しているため、レゴをグリッドで区切るとズレてしまうという問題がありました。そこで OpenCVcv::undistort を使用してカメラの歪みを補正しています。

歪み補正に使用するパラメータは Media Lab の以下のツールを利用しています。

ただこの数値だけでは綺麗に補正しきれなかったのでこの値を中心に微妙にパラメタをずらせる UI を Qt で作成しました。

実は直近の Qt 関連の記事は MFT 用に書いていたものでした。

Qt(Qt Quick 2)は学習コストはちょっと高いですが、一度覚えてしまえば C++ と JS をとても簡単に、具体的には 1 行書くだけで、メンバを JS にエクスポート出来たりします。

UI も QML と JS を組み合わせて強力なプロパティバインディングを利用したりしながらサクサクかけます。また、今回は画像処理だけ C++ で行って、後の数値の処理(どこが黒から白になったか判定など)は JS のレイヤで行ってみました。製品でない、こういったプロトタイプを制作する上では、如何に速くトライ&エラーするかが鍵になると思うので本構成にしたのですが、お陰で思っていたよりも早く処理を記述することが出来ました。プロトタイプを作る上で、こういったスクリプト言語を適切な箇所で利用することはとても重要だと思います。
そして同様に以下のような画像解析部を作成しました。

重要視したのは、デモを止めないように実行時に素早く再キャリブレ出来るようにしたことです。なので動的にメッシュ領域を移動 / 拡大など変更出来るようにしておきました。これでプロジェクタがズレてしまった時や、机が寄りかかられて傾いてしまったときにも、リアルタイムに(バレないように)調整出来るようにしています(キャリブレの様子は動画参照)。これも Qt で簡単に GUI が書ける利点を利用しており、メッシュ部分は HTML5 相当の Canvas で書いています(Qt すごい)。

そしてこれらの解析結果を OSC で送信するところは、自前で oscpack を QML 要素にして送るようにしました。

これは別件でも利用したため、後日エントリをまとめます。

リモコン

直前に、やっぱりブロック変えたいと思い急遽作成してタブレットで表示する形にしました。ガワは適当に jQueryjQuery.Transit を使ってボタンが押された時にアニメーションするようにしています。

ここから OSC を飛ばすには、socket.io を使ってサーバ側に情報を送り、そこからは node-osc で Unity へ OSC 経由でメッセージを送っています。

などと説明してみてますが、コード的にはクライアントから単体の WebSocket を飛ばすのは 3 行、サーバ側でメッセージを受け取って OSC を送るのも 3 行で書けます。

クライアント
// クリックされたら変更イベントをサーバへ送る
$('#button1').on('click', function() {
	socket.emit('change', kind);
});
サーバ
var client = new osc.Client('127.0.0.1', 4567);

// socket
io.sockets.on('connection', function(socket) {
	// 変更イベントを受け取って OSC 送信
	socket.on('change', function(kind) {
		client.send('/LegoAnalyzer/ChangeBlock', kind);
	});
});

Node.js はプロトタイプするのに本当に便利です。ネイティブとの連携も簡単なので是非興味が有る方は使ってみて下さい。

おわりに

何人かの方に「どうして作ろうと思ったのですか?」という質問を頂いたのですが、今回は単純に「MFT に出したい!」という思いからネタ出しをし、プロジェクションマッピングで何かしたい!から始まり、何か操作できるものにしたい、となり、このアイディアを思いついて、やろう!となった形です。MFT に出したいありきでした。このおかげで、今回 Qt のデスクトップ用アプリの GUI 周りの作成や OpenCV による画像認識は初めてだったのですが、良い題材をもとに勉強してちょっと使えるようになりました。こういった動機を与えてくださった、また素晴らしい発表や交流の場を提供してくださった MFT の関係者の方々に感謝いたします。また来年も今回の発展形や別の面白いものを思いついたら出してみたいです。取り敢えずは相方に委ねてしまったハードウェア周りも勉強して、もっと幅を広げていきたいと思っています。

*1:ここでいう Qt は主に Qt Quick 2 のことを指します