読者です 読者をやめる 読者になる 読者になる

VR道を往く!

VRとかUnityとかそういう話中心に。

PixPro360 4Kを使ったリアルタイムなパノラマビューワーをUnityで作ってみる

リコーのTHETAのようなパノラマ撮影用カメラ、今やいろんな会社が割とリーズナブルな価格で出してて本当にいい時代になりましたよね。 ちょっと前までは数百万するようなカメラを買わないといけなかったのに。

今回はそんなパノラマカメラを使ってリアルタイムなパノラマビューワを作っていこうと思います。 THETA Sでも可能なのですが、これを使います。

f:id:kaninabe:20160424162423j:plain

まあどうせTHETA Sは使ってる人がいっぱいいて今さらですし。

PIXPRO360 4KはTHETAと違って全周は撮れませんが、 動画の解像度はPIXPROの方が高い(THETA Sが全周分で1920x1080に対しPIXPRO360 4Kは半球分で2880x2880)。 取り付け方法、取り付け場所によってはマウントする側などが死角になってしまうケースもあるので、 それならば一方向を高い解像度で撮った方がいい、という考え方もありますよね。

接続方法について

ではさっそくやっていきましょう。 PIXPRO360 4Kでリアルタイムに動画をPCへ入力するには、
1. USBでWebカムとして認識する(UVC)
2. HDMIで接続しキャプチャデバイス経由で入力する
この2つの方法があります。これはTHETA Sと一緒ですね。
ただTHETA SではUSBだと解像度が1280x720に制限されてしまっていましたが、 PIXPROの場合はUSBの場合でもHDMIと同じ2880x2880まで出力可能です。 fpsは遅い(5fps)ですが。

UVCの場合、UnityではWebCamTextureを使うことで簡単に入力映像をテクスチャ化できます。
HDMIを使った方がfpsが高くなるのですが、別途キャプチャデバイスが必要になってしまうし、 Unityに取り込む際に有料のアセットを使う必要が出てきます。
詳しくは解説されている方がいますのでご参照を。
izm-11.hatenablog.com

という訳でとりあえず簡単にできるUSB接続で行きたいと思います。

まず予めPIXPROのUSB接続モードをストレージモードでなくWebカムモードにしておきます。 (撮影待機状態で下ボタン(設定ボタン)2回押してシャッターボタン、もう2回下ボタン押してシャッターボタン、 これでUSB接続モードの選択になるので上下で選んでシャッターボタンで決定)

f:id:kaninabe:20160424170659j:plain:h300

こうなってればOK。これで電源入れてUSBで接続すればWebカムとして認識されます。

f:id:kaninabe:20160424165939j:plain:w300
ちなみにUSB接続すると三脚用ネジ穴が使えないので↓こういう別売りケースとマウントが必要になります。この辺の仕様はいけてないですねー。

Unityでテクスチャに映像を表示

UnityでUVCのWebカム映像をテクスチャに使うにはWebCamTextureを使えばできます。
こんな風に。

using UnityEngine;
using System.Linq;

[RequireComponent(typeof(Renderer))]
public class WebcamTexPlayer : MonoBehaviour {

void Start () {
        //接続されているデバイスの中にPIXPROがあるか探す
        var devices = WebCamTexture.devices.Select(d => d.name);
        var cdevice = devices.FirstOrDefault(devname => devname.Contains("PIXPRO"));

        if(cdevice != null)
        {
            //PIXPROを解像度2880x2880、fpsは5として接続。
            var wcamtex = new WebCamTexture(cdevice, 2880, 2880, 5);

            //現在のオブジェクトのテクスチャに置き換える。
            var mat = GetComponent<Renderer>().sharedMaterial;
            if (mat != null)
                mat.mainTexture = wcamtex;

            //再生開始
            wcamtex.Play();
        }
    }
}

こんな感じのスクリプトを映像を貼り付けたいオブジェクトに付けておけばOKです。 ちなみにPIXPROの解像度はだいたい以下のようになっています。
GLOBAL(魚眼)モード
* 2880 x 2880 (5fps)
* 2048 x 2048 (5fps)
* 1440 x 1440 (15fps)
FRONTモード
* 3840 x 2160 (5fps)
* 1920 x 1080 (15fps)
* 1280 x 720 (30fps)
* 640 x 360 (30fps)

この辺りの違いはこちらのブログで解説されています。
yaaam.blog.jp

FRONTモードというのは前方撮影用のモードなので、今回は魚眼モードの解像度を設定します。 WebCamTextureの引数に解像度を設定しないと3840x2160で読み込まれてしまうので注意。

(注 Macだと1280x720でしか読み込めなかったので今回はWindowsでやっています。)

とりあえずこんな感じでテクスチャに出せました。 少し端が切れてしまいますが、まあ気にせず行きましょう。

f:id:kaninabe:20160424114648p:plain

魚眼映像を球面にマッピング

内側を向いた球面を作りそこにテクスチャを貼り付けます。 Unityデフォルトの球は外向きでメッシュも粗いので別に用意する必要があります。 また通常の球モデルのUVは正距円筒図法いわゆる横長パノラマのテクスチャ向けになっているので、 これを魚眼向けに変更してやる必要があります。 今回は、この魚眼画像に対応するUVへの変換はシェーダで行うことにします。

まず内向きの球メッシュを作成したのでとりあえずここに置いておきます(Unity専用)。 このメッシュに正距円筒な横長パノラマ画像を貼って使うこともできます。

PIXPROの魚眼画像に対応するにはシェーダでUV座標を正距円筒から魚眼投影に変換することで可能となります。 元の正距円筒図法のUV座標は

u = その点の経度 / 360
v = 南極点からの緯度差 / 180

となっています。

一方で魚眼テクスチャの投影方法は以下のようになります。

f:id:kaninabe:20160424002038p:plain

簡単のため、テクスチャの中心を(0, 0)、左下を(-1, -1)、右上が(1, 1)となるような座標系で考え、 赤道がちょうど端に接するものと考えると

r = 北極点からの緯度差 / 90
p = 経度

となります。ただPIXPROの場合90度よりもう少し広い角度まで収まっているので、 仮にこの角度をkとすると、

r = 北極点からの緯度差 / k

となります。 従ってこの座標系上での投影位置UV'は

UV' = (cos(p), sin(p)) * r

となり、これを左下が(0, 0)、右上が(1. 1)となる通常のテクスチャ座標系に変換すれば完成です。 フラグメントシェーダに以下のような感じで書けば正しく描画されるはずです。

fixed4 frag (v2f i) : SV_Target
{
    float p = i.uv[0] * 2.0 * UNITY_PI;
    float r = (1.0 - i.uv[1]) * 180.0 / k;
    float2 uvdash = float2(cos(p), sin(p)) * r;

    float2 new_uv = (uvdash + float2(1.0, 1.0)) * 0.5;

    return tex2D(_MainTex, new_uv);
}

ではこのシェーダを球のマテリアルに設定し、 先ほどのWebCamTexture再生スクリプトも球オブジェクトにアタッチして実行してみましょう。


Realtime PIXPRO360 4K's panorama viewing on Unity

できた。ちょっと不気味な感じの映像になっていますがご容赦を。
2880x2880だとfpsは少し遅いですが。2048x2048にすると画質と速度のバランスがよいようにも感じました。

まあこのような感じでPIXPRO360 4Kも簡単にUnityで使えますので、 THETA Sを導入するか迷ったらこちらも候補に入れてみるのもいいんじゃないでしょうか。。 値段が高いとか三脚用のネジ穴が困った場所にあるとか欠点はあるものの使い勝手は悪くないと思います。

今回作ったコード類はunitypackageにしてこちらに置いておきます。