はじめに
リコーのYuuki_Sです。
弊社ではRICOH THETAという全周囲360度撮れるカメラを出しています。
RICOH THETA VやTHETA Z1は、OSにAndroidを採用しており、Androidアプリを作る感覚でTHETAをカスタマイズすることもでき、そのカスタマイズ機能を「プラグイン」と呼んでいます。(詳細は本記事の末尾を参照)。
THETAで撮影した360度画像をVR機器で見たことはありますか?
360度見渡せてその場の雰囲気は実感できる一方で、少し奥行き感が物足りなく感じるかもしれません。
これは、左目と右目に同じ映像を表示しているためです。
人間の目は、左右の目に映る映像の違い(視差)で奥行きを感じ取っています。
しかし、THETAに代表される360度カメラではある1点から見た映像を撮影するので、それをVR機器で見ても左右の目に映る映像には視差が生じず奥行き感が損なわれてしまいます。
これを解消する方法はいくつかあり、全方向に視差が生じるようにレンズを大量に配置する方法や人間の目と同じく左右にレンズを配置して前方180度のみを撮影する方法が存在します。
特に後者は仕組みもシンプルで、撮影画像の形式も既に規格化されています。
それがGoogleが2017年に発表したVR180です。
この画像の様にVR180対応のカメラは、レンズが2つ並んだ配置しています。
これで右目用と左目用の映像を撮影でき、撮影画像も180度の画角の正方形の画像が2枚並んだシンプルな状態となります。
そして、この画像を対応したVR機器で見れば右目と左目にそれぞれ異なる映像が入り奥行き感が出る仕組みです。
このようにVR180規格の画像は、180度画角の画像を2枚、位置をズラして撮影し組み合わせれば生成できる訳です。
勿論、360度撮影可能なTHETAなら180度画像も撮影できるわけで...
と、言うわけで今回はプラグインを利用してTHETA1台でVR180規格の画像を撮影してみました。
仕組み
今回のプラグインの仕組みは至ってシンプル。
THETAで1枚画像を撮影し、次にTHETAを水平移動させてもう1枚画像を撮影、それをVR180規格の画像に合成するだけです。
どれだけ水平移動させるかの条件は人間の目の間隔、瞳孔間距離(IPD)の平均値に合わせて今回は65mmとしました。
また、2枚の画像の合成方法はTHETAで撮影されるEquirectangular画像の中央部を正方形で切り抜き、左右に並べる方法を取ります。
文字だけだと分かりにくいので全体像は下図を参照ください。
実装
今回は、開発のベースとしてTHETA Plug-in SDKを利用しました。
実装は、撮影後呼ばれるonTakePicture関数にて撮影画像を正方形に切り抜き、2枚撮影されたらそれを合成するというものです。
繰り返しの記述になるため、1枚目の処理のみ記載します。
また、今回はTHETA Z1を利用したため撮影状態をOLEDに表示するようにしています。
if (pic_num == 0) {
Intent textIntent = new Intent("com.theta360.plugin.ACTION_OLED_TEXT_SHOW");
textIntent.putExtra("text-middle", "Captured:1/2"); // 真ん中の領域に表示される文字列
textIntent.putExtra("text-bottom", "Next : Left Side"); //一番下の領域に表示される文字列
sendBroadcast(textIntent);
bitmap_R = BitmapFactory.decodeStream(inputStream);
cropBitmap_R = Bitmap.createBitmap(bitmap_R, (bitmap_R.getWidth() - bitmap_R.getHeight()) / 2, 0, bitmap_R.getHeight(), bitmap_R.getHeight(), null, true);
bitmap_R.recycle();
bitmap_R = null;
pic_num++;
2枚目の撮影が終わったら、合成処理をおこないます。
これも前述の通り、1枚目と2枚目から切り抜いた画像を並べ新規画像を生成、保存するだけです。
ただし、新規生成したJPEG画像にはExif情報が無いため、画像処理を含むTHETAプラグインの実装方法【THETAプラグイン開発で紹介されているExifをコピーする関数を利用して元の撮影画像からExif情報をコピーしています。
FileOutputStream fos = null;
String targetFileName = fileUrl_match.substring(fileUrl_match.length() - 11);
targetFileName = targetFileName.substring(0, 7);
int addNum = Integer.parseInt(targetFileName) + 1;
String outputFileName = fileUrl_match.substring(0, fileUrl_match.length() - 11) + String.format("%07d", addNum) + ".JPG";
fos = new FileOutputStream(outputFileName);
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.close();
Log.d(TAG,outputFileName);
Exif.copyMetadata(fileUrl_match,outputFileName);
String[] fileUrls = new String[]{"DCIM/100RICOH/R"+String.format("%07d", addNum) + ".JPG","DCIM/100RICOH/R"+String.format("%07d", addNum) + "_tmp.JPG"};
notificationDatabaseUpdate(fileUrls);
Intent textIntent = new Intent("com.theta360.plugin.ACTION_OLED_TEXT_SHOW");
textIntent.putExtra("text-middle", "Captured : 0/2"); // 真ん中の領域に表示される文字列
textIntent.putExtra("text-bottom", "Next : Right Side"); //一番下の領域に表示される文字列
sendBroadcast(textIntent);
以上で実装は完了です。それでは撮影してみましょう。
撮影と作例
撮影時の注意点として、1枚目と2枚目を正しく水平移動した位置関係で撮らなければならない点があります。
これがズレてしまうと、VR HMDで見た際に正しい奥行き感が得られないだけではなく、酔いの原因にもなります。
とは言え手動で正しく水平移動させることは困難なため、今回は以下のような置き台を3Dプリンタで印刷しました。
THETAを収める穴が65mmズレた位置関係で存在する置き台です。
そして、これを用いて撮影した結果が以下になります。
※映ってしまった人物の顔には別途ぼかしをかけています。
左右それぞれの目で見る用の画像が並んでいます。
今回の仕組みでは2枚の撮影に時間差が生じるため、動的なシーンが撮影できないという弱点はありますが、風景においては十分に奥行き感のあるVR180規格画像が取得できました。
Oculus Quest2で観る
せっかくVR180規格の画像ができたので、VR HMDで視聴しましょう。
今回はOculusQuest2を利用しました。
まずは、撮影した画像をPC等でOculusQuest2の内部ストレージに移動します。
次にプリインストールされているOculusTVアプリを起動させ、移動させた画像を表示します。
そして右下のメニューからVR180(右端のアイコン)を選択します。
これでVR180に対応した表示となります。
実際に見てみると、向上した奥行き感を実感できます。
(キャプチャ画像では分からないのが、残念です)
おわりに
VR180規格の画像をTHETAで撮影するプラグインを作ってみました。
今回は1台のTHETAで撮影できる仕組みにしましたが、2台のTHETAを連携させれば動く被写体も撮影できるようになると思います。
皆さんもぜひ、いろいろプラグインを作ってみて下さい。
RICOH THETAプラグインパートナープログラムについて
THETAプラグインをご存じない方はこちらをご覧ください。
パートナープログラムへの登録方法はこちらにもまとめてあります。
QiitaのRICOH THETAプラグイン開発者コミュニティ TOPページ「About」に便利な記事リンク集もあります。
興味を持たれた方はTwitterのフォローとTHETAプラグイン開発コミュニティ(Slack)への参加もよろしくおねがいします。