凹みTips

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

Oculus Rift x Ovrvision で拡張現実(AR)を試す方法をまとめてみた

はじめに

2014/4/3 に Ovrvision SDK v0.3 が公開されました!

v0.3 には待望の AR 機能が含まれています。まずは Unity 版を試してみました。

環境

デモ

サンプル

Ovrvision SDK for Unity Pro」をダウンロードすると ovrvisionsdk.unitypackage とサンプルプロジェクトの「unity_ar_example」が含まれています。まずはサンプルプロジェクトを開いてみます。

f:id:hecomi:20140405191727p:plain

f:id:hecomi:20140405210026p:plain

ArUco

Ovrvision SDK v0.3 に利用されている AR ライブラリは ArUco だと izm さん(@izm)と あるしおうねさん(@AmadeusSVX)が Twitter で教えて下さいました!ありがとうございます。



ArUco について

ArUco は OpenCV ベースの軽量な C++ ベース、BSD ライセンスな AR ライブラリです。1024 個までのマーカに対応しています。マーカは下記のような形になります。

f:id:hecomi:20140405221655j:plain

本家のデモは以下の様な感じです。


素晴らしい...。

マーカ生成プログラムのビルド

マーカを生成するには、あるしおうねさんが教えて下さったように aruco_create_marker を利用すると楽です。手順は以下になります(※ 今回は Windows で作業したので Windows 用の手順になってます)。

  1. 本家サイト(http://www.uco.es/investiga/grupos/ava/node/26)から Download をたどって aruco_msvc10.ziparuco-1.2.4.tgz をダウンロード
  2. OpenCV のサイト(http://opencv.org/downloads.html)から OpenCV 2.4.1 をダウンロード
  3. Visual Studio で新規に空のプロジェクトを作って追加のインクルードディレクトリに OpenCV と ArUco(aruco_msvc10/include)を追加
  4. 追加の依存ファイルに aruco124.libaruco_msvc10/lib)、opencv_core241.libopencv_highgui241.libopencv/build/x86/vc10/lib) を指定
  5. ソースに aruco-1.2.4/utils/aruco_create_maker.cpp を追加
  6. Release ビルド
  7. 出来た実行ファイルと同じディレクトリに aruco_msvc10/bin/*.dll をコピー
  8. コマンドラインから以下を実行
# ArUco.exe <markerid> <output_file_name> <pixels>
$ ArUco.exe 123 123.jpg 128

これでマーカが生成できるようになります。

Ovrvision SDK v0.3 について

いよいよ Unity のサンプルプロジェクトを見ていきます。

シーンのオブジェクトについて
  • OvrvisionSDK
    • Ovrvision.cs がアタッチされたオブジェクトで、DeviceCameraLeft(左カメラ) > CameraPlane(Ovrvision の左カメラ画テクスチャ描画用プレーン)DeviceCameraRight(同右) > CameraPlane(同右) が子としてぶら下がっています。
    • CameraPlane はレイヤが設定されており、DeviceCameraLeft は左の CameraPlane のみ、DeviceCameraRight は右の CameraPlane のみが見えるようになっています。
  • OVRCameraController
    • Ovrvision で見る世界はテクスチャ自体に歪みがかかっているため樽型でなく長方形で描画しています。一方、AR オブジェクトは通常の OVRCamera 越しに見ることで樽型の歪みをかけています。
  • OvrvisionView
    • AR で描画されるオブジェクトです。

つまり、カメラ画の現実世界 x 2、AR オブジェクト用に歪みをかけて見るバーチャルな世界 x 2 の計 4 個のカメラで世界を見ていることになります。

スクリプトについて

スクリプトは主に 4 つあります。

  • Ovrvision.cs
    • 本体。DLL 経由でカメラのテクスチャを取ってきてプレーンに貼り付けています。
  • OvrvisionEx.cs
    • ArUco を利用して DLL 経由で得た姿勢情報を元に OvrvisionView を制御するスクリプトです。
  • OvrvisionProperty.cs
    • Ovrvision のパラメタ(露出や輝度など)を DLL 経由で取得する用のスクリプト、OvrvisionEditor.cs で利用しています。
  • OvrvisionEditor.cs
    • Ovrvision 用のエディタ。スライダでパラメタを調整出来るようになっています。

AR 部分のコードについて

とてもシンプルです。

OvrvisionEx.cs

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices; 

public class OvrvisionEx
{
    [DllImport("ovrvision_ex", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    static extern void ovExSetImage(System.IntPtr pImgSrc, int w, int h);
    [DllImport("ovrvision_ex", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    static extern void ovExRender();
    [DllImport("ovrvision_ex", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
    static extern int ovExGetData(System.IntPtr mdata, int datasize);
    
    public int Render(System.IntPtr pImgSrc)
    {
        // float[] を渡して ArUco から AR オブジェクトの位置や姿勢情報などをもらう
        // DLL 経由で情報をもらうので GC 対象外にしている
        // これでアンマネージな世界へポインタを渡せるようになる
        float[] markerGet = new float[10];
        GCHandle marker = GCHandle.Alloc(markerGet, GCHandleType.Pinned);

        // 解析対象の画像をセット
        // (pImgSrc は Ovrvision.cs 側で受け取った歪みなしの左カメラ画)
        ovExSetImage (pImgSrc, 640, 480);

        // 解析
        ovExRender ();

        // 解析結果の受け取り
        // 見つからなかった場合は ri == 0、見つかったら ri == 1
        int ri = ovExGetData(marker.AddrOfPinnedObject(), 10);

        // AR オブジェクト
        Transform gb = GameObject.Find ("OvrvisionView").transform;

        if (ri > 0) {
            // 見つかった場合は位置・姿勢をセット
            gb.localPosition = new Vector3 (markerGet [1], markerGet [2], markerGet [3]);
            gb.localRotation = new Quaternion (markerGet [4], markerGet [5], markerGet [6], markerGet [7]);
        } else {
            // 見つからなかった場合は遠くへ飛ばす
            gb.localPosition = new Vector3 (-10000.0f, -10000.0f, -10000.0f);
        }

        // GCHandle の解放
        marker.Free ();

        return ri;
    }
}
複数のマーカのハンドル

現状は同時に複数のマーカのハンドルは出来ないようです。ただ、マーカの ID は見れるので、マーカ側の配置を予め決めておけばロバスト性の高いデモは作れそうです。マーカ ID は ovExGetData 経由で得た配列の 0 番目に入っています。

if (ri > 0) {
    Debug.Log(markerGet[0]); // <-- ID が分かる
}
高速化について

プロファイラを見ると、Ovrvision.Update が結構な時間を使っています(うちの PC だと 60 fps ちょっとだけ切ってました)。これは DLL 経由で Ovrvision からテクスチャを3枚分(左カメラ画、右カメラ画、ArUco に投げる歪みなし画)もらってくる処理でブロッキングしているからだと思われます。そこで以下のようにこの処理部を別スレッド化することによって高速化することが可能です。

これで 60 fps 確保出来ました(80 ~ 90 くらいになりました)。AR の姿勢の計算部分も Transform 制御する部分だけメインスレッド化して、座標情報取得を別スレッド化すれば、もう少し軽く出来ると思います。

描画順について

現状は、AR オブジェクト側のカメラの Depth が Ovrvision カメラの後ろに来ているため、影を描画したいなどの時に少し不便です。AR オブジェクト側のカメラ(OVRCameraController 下のカメラ 2 つ)の Clear FlagsDon't Clear 、Depth を 1 に設定して、Ovrvision 側のカメラの Clear Flags を Solid Color、Depth を 0 に設定すると色々と便利になると思います。冒頭のデモはこの設定で影を描画しています。

追記:
ただし、上記の手法を取るとカメラのテクスチャを貼り付けた Plane 自体が樽型に歪むため、カメラの歪みと合わせて二重に歪んでしまいます。もしくは、冒頭のデモのように OVRCameraController の Lens Collection を切るかする形になってしまいます。。

視差の問題点

現状の仕組みでは、Ovrvision を通して 2 つのカメラ画で見ている現実の世界と、左のカメラ画を解析して得られた姿勢情報を元に配置された一つのオブジェクトを OVRCamera で見ている世界の視差が異なるため、HMD で覗くと視差のズレが生じてしまっています。また、右目で見た時はマーカに対して結構ズレがあります。

f:id:hecomi:20140406005814p:plain

なので、私は OVRCameraController の CameraRight の x 座標を 0.3 から 1.0 に増やして視差を合わせています。が、その影響でマーカの上にピッタリオブジェクトが乗るような表現は難しいです。

以前 PS Eye でやった際は、左右それぞれのカメラ画を解析してマーカの視差を AR オブジェクトの視差に利用してそこそこうまく行っていたので、今回もこの方式を取ろうかなぁと思ったのですが、現状の仕組みだと左目からの姿勢情報しか取れないようなので今後のアップデートに期待したいです。

Ovrvision SDK 以外の AR の実現方法

Ovrvision は単なる WebCam として見えるので、Unity で使える WebCamTexture をそのまま使えます。なので WebCamTexture から解析可能な NyARToolKit for Unity を使うと、AR 内部のコード含めて色々改良できるので、ユースケースに合わせて使い分けると良いと思います。

おわりに

Ovrvision SDK のお陰で AR が簡単に実験出来る基盤が出来ました!現実側に色々エフェクトを掛けたりしながら複合現実も作れそうで、色々面白いコンテンツが出来そうです。