空を映写幕に、街を映画館に。

コードも書けるブロガーの桂です。

さて、最近はすっかり新卒採用シーズンでして、イベントなどで自社の~~お金のかかった~~手間のかかった動画を見る機会が増えました。[こちら](https://www.youtube.com/watch?v=WMtBh_o8WEA)から見ることができますので、よろしければぜひご覧になってから次にお進みください。（宣伝）

ムービーは弊社のビジョンである「あたりまえを、発明しよう。」を描いたものですが、その中にこんな一場面があります。

![空](https://qiita-image-store.s3.amazonaws.com/0/47722/0950b160-b078-d9cf-4b87-07f9a304cbcb.png)

「ぼくたちはもっと空を自由に使えないのだろうか？」

そう、空って空間的にも視覚的にも活用余地が大きいですよね。というわけで、今回は**空をスクリーンに見立てて映像を投影する**、というのを行ってみたいと思います。これで外歩きながら映画見たり、公園ででっかいスクリーン(空)で映画を見たりできます。すばらしい。空自由に使いたい。

## どうやって映すか

とはいっても、もちろん単純に照射しても写ってくれません。

なので、今回はカメラとヘッドマウントディスプレイを使い、

1. カメラから映像を取得
2. システム側で空を識別して、動画を挿入
3. ヘッドマウントディスプレイに投影

という流れで、投影を行います。
正確には「空部分を映像に差し替える」という感じですかね。

## 小道具

#### ヘッドマウントディスプレイ

このご時世、ふつうのヘッドマウントディスプレイじゃ面白くありません。時代は3Dです。3D。
そこで登場するのが、[Oculus Rift](https://www.oculus.com/ja/)。解説は不要ですね。
これで3D没入空間が作れます。今回はオフィスに落ちてたDK2を使います。

#### カメラ

Oculusは両眼のディスプレイなので、カメラもステレオカメラが必要になります。
今回はOculus用に開発されたステレオカメラ[Ovrvision](http://ovrvision.com/)を使います。
Oculusは本体上部にUSBポートが付いていて、ここにLeap Motionなど様々なデバイスをつなげて連携出来るようになっていますが、このカメラもここにアタッチメントすることができます。
大阪のベンチャーが開発してるようですが、なかなか面白い代物です。
これでカメラとディスプレイが揃いました。

ちなみに装着後はこんな感じ

![Ovrvision](https://qiita-image-store.s3.amazonaws.com/0/47722/92788f4c-fea6-4711-e874-abaf1ec51b7a.jpeg)


#### 言語とフレームワーク

ある程度素早く処理してくれないとVR酔いが激しくなるので、C++で[openFrameworks](http://openframeworks.jp/)を利用します。
高速かつ簡便にクリエイティブコーディングが行えるフレームワークです。
後述する画像のブラーなどにはOpenCVのライブラリ(ofxOpenCV)を利用します。

#### 開発環境

外への持ち歩きを考えてMacBookProを使い、Xcodeで開発します。

## 下拵え時点でのトラブル

2つだけ。(時間的にはここで50%くらい使ってますが...)

#### 1. Ovrvisionが認識されない

なぜか認識されない。組み立ても自分でやったため、故障か？と疑い続ける。とりあえずPCのUSBポートに直接接続する→認識！というわけでOculusのUSBポートの故障を疑いつつ、説明書を読むと衝撃の事実が発覚。
ふだんOculusを使うためにはUSBとHDMIの接続だけなのですが、**USBポートを使うにはOculus本体の電源接続が必須**となるのでした。
そちらをつないだら無事に認識。USBポートの隣のLEDランプが目印です。

#### 2. oFが32bitなのにOvrvisionのSDKが64bit

[oF公式twitter](https://twitter.com/openframeworks/status/485929699831328768)によると次期メジャーバージョンアップで64bit化するらしいのですが、なんといっても現状は32bit。64bit魔改造版もいくつか在野で出てるものの、あまりきれいに動かず。

こうなったらOvrvisionの方を32bit化するしかない、と決意し運営に要望を出したら翌日に32bit対応版(Universal)出してくれました(感謝

こちらも無事にビルド完了。

## 開発の工夫

ここまで来ればあとは簡単です。
右目と左目それぞれのカメラから画像を取得し、空部分を映像に置換し、ディスプレイに出力するだけ。
いくつか工夫した点を書いていきます。

#### 空の認識

空の認識は大変で、結局妥協して明度を中心に検出することにしました。
というのも、ふつう屋外で歩いてるとき、フォーカスすべき視界（下界の建物とか風景とか）で露出を調整すると、空はほとんど白飛びします。
この辺カメラの性能にも寄るのですが、さいわい（？）Ovrvisionはそこまで幅広いダイナミックレンジを持っていないので、空は白飛びさせてしまって、その白に対しクロマキー合成を行う、という手法を採用しました。

#### アルファブレンディング

空を認識した後は、その部分に映像を合成するのですが、このとき単純に合成すると、空のエッジの部分がきれいに合成できません。
そこで、いったん二値のアルファチャンネルを作成し、それをグレースケール画像としてブラーし、その画像をアルファチャンネルとして用いてブレンディングを行いました。
これによりエッジ付近の合成がよりスムーズに行えました。

### 空検出＋ブレンディングのコード

こんな感じです。
ちなみに自分は利き目が右目なので右カメラの映像で検出してます。
~~マジックナンバー？何それ~~

```cpp:
    ofxCvGrayscaleImage alpha;
    alpha.allocate(cam_width, cam_height);
    ofPixelsRef alpha_pixels = alpha.getPixelsRef();
    for (int w = 0; w < cam_width; w++) {
        for (int h = 0; h < cam_height; h++) {
            int base = h * cam_height + w;
            int r = right_pixels[base * 3];
            int g = right_pixels[base * 3 + 1];
            int b = right_pixels[base * 3 + 2];
            if (r + g + b > 600) {
                alpha_pixels[base] = 255;
            } else {
                alpha_pixels[base] = 0;
            }
        }
    }
    alpha.blur(5);
    alpha.resize(window_half_width, window_height);
    alpha_pixels = alpha.getPixelsRef();
```

意外とシンプルですね。rとgとb足して600以上ならフラグ立てて、その画像をblurしてるだけです。あとはこれを透明度にして映像を合成します。

## 出来上がり

というわけであとはゴリゴリ書いて、できました！

コードはこんな感じです。

https://gist.github.com/dkatsura/12a16e6a70341f82be40

いちいち右目と左目で処理がDRY違反するのが難点ですね。

キャプチャはこんな感じ。

![キャプチャ](https://qiita-image-store.s3.amazonaws.com/0/47722/800086f2-f8bf-d1b7-96be-b0a52b0dba92.png)

今回は開発中ということで、書斎の壁にPVを投影しています。
(左の画像ズレてるように見えますが、利き目が右で平面映像なので、これでうまくいきます。)
外で行うと空の部分に映画が合成される塩梅です。

## 最後に

#### 今後の課題

まだ屋外に持ち出せてないので、当初の目的を果たせず。
たぶんこのまま装着して屋外歩くと国家権力に声を掛けられるので、公園とかで使うのが妥当ですかね。なんだかんだ視界が遅延して危ないですし。

ちなみに先述したようにOculus側で電源が必要になるのでDC出力のあるモバイルバッテリーを一緒に背負う必要があります。

一通り出来上がったら[視聴覚交換マシン](http://www.petworks.co.jp/~hachiya/works/shi_ting_jue_jiao_huanmashin.html)のOculus版とかもやってみたいですねー。

#### 感想

二日目に弊社 @ma2saka が「正しく作るより、作りたいものを作るという感覚は久しぶりで超楽しい。」という名言を残されてましたが、まさに。

あとこの作品は開発合宿とか社内イベントとかの機会を利用して少しずつ作りこんでいったので(まだ完成してませんが)、こういう機会は大事だなーとつくづく思いました。というわけでこのLivesense Advent Calendarの仕掛人 @masahixixi にも感謝。

あしたは腹黒い新卒で有名な @kekekenta です！