自習室

こもります

RealSense SDK 2.0 を自力でビルドして、C++やUnityで使う@Windows

基本的な導入に関してやったことのメモ。

これを試したかったので、最新の development ブランチをビルドすることを目的とします。 unagi (@UnagiHuman) | Twitter さんありがとうございます!

筆者利用環境

Quick Start

まずは自分でビルドせず、動作確認をします。Windows Distribution に従う。

  • GitHub Releases から最新の Intel.RealSense.SDK.exe をインストール。
    • これでカメラ精度を見る Depth.Quality.Tool.exe も、 可視化ツール Intel.RealSense.Viewer.exe もインストールされます
    • ちなみに、Releases にある Source code (zip) は、リリース時点のコードをまとめた物。
  • Intel.RealSense.SDK.exe のインストールでドライバもインストールされるので、D415/D435 を刺してWindowsが認識することを確認。
  • Intel.RealSense.Viewer.exe を起動
    • 2D でカラー・デプス・IRなどが表示されることを確認
    • 3D でポイントクラウドが表示できることを確認
  • サンプルのプロジェクトをビルドしてみる

C++ で開発する

コードを clone してきたら development ブランチに切り替えておく。

自分で realsense2.dll をビルドする

今回の記事には二つ dll が登場します。

  • realsense2.dll は、D415/D435 などを使う為のダイナミックリンクライブラリ。 c++ でも C# でも使います
  • LibrealsenseWrapper.dll というのもあとから出てきます。こちらは Unity で使えるようにする為の、Managed のプラグイン。 Unity および C# (Windowsプログラミングとか) でRealSenseを使う時に使います。

ここは realsense2.dll の話。ここに書いてあるのをやる→ Windows 8.1 & Windows 10 Installation

注:BUILD_CV_EXAMPLE などのビルドオプションが正直よくわかってないのですが、問題なく使えています

  • CMake は利用環境向けのソリューションファイルを生成するだけなので、その後各環境でビルドして始めて dll なりが作成されます
  • Example や Tools も一緒にビルドするので、その中で realsense-viewer.exe 等が動く事を確認する。

改めてC++のサンプルを読んで、ビルドしてみる

(例)上記でビルドした結果のディレクトリの中の examples ディレクトリ中の ReleaseExamples.sln を起動して、 rs-pointcloud プロジェクト等を単体でビルド、実行。など。

Unity で開発する

ここに書いてあることをやります→ Unity Wrapper for RealSense SDK 2.0

ビルド済のUnity用のdllを使う場合は、初めにインストールした C:\Program Files (x86)\Intel RealSense SDK 2.0\bin\x64 あたりから取ってくる

LibrealsenseWrapper.dll をビルドする

今回はここが目的。 Unity ラッパー用の dll を最新の development ブランチのコードでビルドする。

VS2017でビルドする場合、.NET Core 2.x SDK のインストールを事前にお忘れ無く

VS2017 でビルドすると .net2.0 向け、 .Net3.5 向け、.Net 4.6.1 向け~みたいな感じで複数の .dll が作成されますが、現状のUnityは 3.5 だった気がするので net35 のフォルダから dll を持ってきて使います。

Unity のサンプルを動かす

以上

これで C++ と Unity で開発できることが確認出来ました。おしまい。

Kinect v2 のデプス画像をそのままの解像度で点群としてUnityで表示する

f:id:AMANE:20171227222450p:plain

できあがった物がこちら

youtu.be

はじめに

遙か遠い昔(3年前) Kinect v2 のデプス情報を Unity で描画するシリーズをやっていました

前者では、単純に画像を表示しただけでした。後者ではポイントクラウド風の描画にチャレンジしていますが、当時はUnityの仕様的に、またマシンスペック的に点数を間引かざるを得ませんでした。

2017.3 の新機能「32 bit Mesh index buffers」

この新機能の登場で、65536点以上のメッシュを一つのオブジェクトとして利用出来るようになりました。

先に keijiro さんがこの機能を利用して、PCLのデータをインポートするエディタ拡張を作られていて、これが出来るなら Kinect V2 のデプスデータも似たような感じで扱えるのではと思い、今回のネタに着手しました。

github.com

大まかな方策

以下の作戦で行けると踏んで作業を始めました。結果、おおよそ行けました。

  • Kinect v2のデプスデータは画像の形式で得られる。各ピクセル輝度値(= 奥行き)に基づいてVertex Shader で点を動かす
  • Kinect v2 は 512x424 = 217088 点のデータなので、32bit のインデックスに収まる。これを一つのメッシュとして描画する

イメージは下の図のような感じです。大きな値ほど、遠くに頂点が移動します。

f:id:AMANE:20171226210541p:plain

実装 - C#の部分

作成したプロジェクトを公開しています。

github.com

ビルド・実行

ビルド・実行の仕方については、上記リポジトリの Readme.md に記載しています

512x424 頂点のメッシュを用意する

f:id:AMANE:20171226214001j:plain

初めBlender でそのようなデータを用意しようかと思ったのですが、柔軟で良いし勉強になると思いコードで作ることにしました。こちらの記事を参考にさせて頂きました。

上を参考に書いたコードはこちらになります

詳細はコードを読んでいただきたいのですが、ポイントだけ整理しておきます。

32 bit Mesh index buffers を有効にする

2017.3 から追加された 32 bit Mesh index buffers を有効にするために、以下の指定を行います。これで、頂点数が32bit… 4,294,967,296 まで拡張されます。

mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
テクスチャ座標を 0~1 ではなく、半ピクセル分シフトさせる。

これにより、すべての頂点とすべてのデプス画像の画素を1対1で対応づけます。

Vector2[] uv = new Vector2[w * h];
for (var y = 0; y < h; y++)
{
  for (var x = 0; x < w; x++)
  {
    uv[y * w + x] = new Vector2(((float)x + 0.5f) / (float)w, ((float)y + 0.5f) / (float)h);
  }
}
mesh.uv = uv;

ただしこの工夫、やらなくてもぱっと見差がありません。シフトをやらない方の関数も一応用意しました Mesh MeshGenerateNormal(int w, int h, float pitch)

頂点のみ描画する(ポイント指定)

こちらの記事を参考にさせて頂きました。 この記事のスクリプトを、メッシュを含む GameObject に貼り付けるだけです

Kinect v2 のデプス画像データを、16bit の画像として上記メッシュに貼る

f:id:AMANE:20171226214142p:plain

デプスのデータを取ってくる部分は、 Kinect for Windows SDK 2.0 Unity Pro Add-in のサンプルままですので説明は割愛します。できあがりのコードを見つつポイントだけ説明します。

ushort 配列としてデプス画像を取得する

デプス画像は ushort の値を1ピクセルとして、512x424 要素の配列で表現されます。ushort は 2byte = 16bit です。

// L29
_Data = new ushort[frameDesc.LengthInPixels];

// L53
frame.CopyFrameDataToArray(_Data);
Unity のテクスチャデータにする

Unity には GRAY 1ch 16bit 深度のテクスチャとして TextureFormat.R16 があるので、これを使います。

// L30
_RawData = new byte[frameDesc.LengthInPixels * 2]; // ushort 配列を byte 配列として扱う
_Texture = new Texture2D(frameDesc.Width, frameDesc.Height, TextureFormat.R16, false); // R16 の形式でテクスチャを用意する
// L58
Buffer.BlockCopy(_Data, 0, _RawData, 0, _Data.Length * 2); // ushort 配列をそのまままるっと byte 配列にはめ込む
// L62
_Texture.LoadRawTextureData(_RawData); // 毎フレーム、byte配列を元にテクスチャを更新する
_Texture.Apply();

実装 - Shaderの部分

これで、メッシュを動かすのに必要な情報は、シェーダ側に渡されました。ここからはシェーダでの記述になります。できあがりのコードは以下です。

このシェーダでは、メッシュの頂点数とテクスチャの画素数が一致していることを前提として、その画素数 = 頂点数分だけ vertex shader がまわり、各頂点を画素のデータに基づき奥に動かす、と言うことをしていきます。ここも重要な箇所だけ見ていきます。

奥行きデータを再構成する

_Displacement は奥行き感を調整するための係数です。用意したメッシュのサイズなどと照らし合わせて適宜調整して下さい。(実際はMaterialの inspector から調整出来ます)

// KinectDepthBasic.shader L72
float d = col.x * 4000 * _Displacement;

4000 という定数がありますが、これは今回他のテクスチャの使い方も試したときの名残ですのであまり気にしないでください

デプス画像座標系から、空間に展開する

CoordinateMapper.MapDepthFrameToCameraSpace が使えない件

本来 Kinect v2 SDK には、 CoordinateMapper.MapDepthFrameToCameraSpace という関数があり、これを使うことでカメラの正確な諸元によって深度画像を3次元上の点に変換出来ます。(以前書いた記事ではそれを使いました

しかし今回は、1ch 16bit テクスチャとしてシェーダに送り込みたいので、事前に変換してしまうと、以下の様な問題が起きます

  • 1点につき xyz それぞれ float の値として表現されてしまう
    • これはデータをchar形式とかに圧縮させることで回避可能だが…
  • CoordinateMapper.MapDepthFrameToCameraSpace の結果はそもそも、 CameraSpacePoint という実質 Vector3 形式の配列で与えられるため、圧縮作業を行うためには要素数だけ for 文を回す必要がある。for文を回した時点で、高速化は見込めない。

というわけで、今回は C# …というかSDKの層での3次元展開は諦めます。

Vertex Shader で模擬して展開する

正確では無いかも知れませんが、「それっぽく見せたい」程度で良ければ、自力で変換してしまえば良いです。図を見ながら説明します。

f:id:AMANE:20171226223839p:plain

水平面でまず考えますが、垂直面も同じです。 Kinect V2 のデプス画像の水平視野角は 70deg なので、画像の左端のx座標を 256 (=デプス画像横幅512の半分) と置いたとき、デプス画像は D= 365.6 の高さの四角錐の底面に投影されている、と表現できます。

各画素について、Kinect v2 から得られるデータとして、画像上でのX軸の座標を x , 奥行きを d とすると、実際のX軸座標は x' = x * d / D で表すことが出来ます。

これをy座標についても行います。実際のシェーダの記述は以下の様になります

// KinectDepthBasic.shader L57
v.vertex.x = v.vertex.x * d / 3.656;
v.vertex.y = v.vertex.y * d / 3.656;
v.vertex.z = d;

(おまけ)奥行きに合わせてHueを回して着色する

f:id:AMANE:20171227212959p:plain

このままだとすべての点が単色で表示されます。空間中に点があるのでこれでもわかりますが、せっかくなのでより奥行きがわかりやすくしてみます。

hsv 表色系の hue の値を奥行きに連動させるようにします。 0~1 でhue が虹色のように一周し元の色に戻ってきます。

// L25 vertex shader で位置情報をもとに色を計算して、fragment shaderに渡せるよう構造体に color の情報を加える
struct v2f
{
  float2 uv : TEXCOORD0;
  float4 vertex : SV_POSITION;
  float4 col : COLOR;
};

// 中略

// L40 HSV から RGB に変換する関数
// 参照→ http://www.chilliant.com/rgb2hsv.html

float3 HUEtoRGB(in float  H)
{
  float R = abs(H * 6 - 3) - 1;
  float G = 2 - abs(H * 6 - 2);
  float B = 2 - abs(H * 6 - 4);
  return saturate(float3(R, G, B));
}

float3 HSVtoRGB(in float3 HSV)
{
  float3 RGB = HUEtoRGB(HSV.x);
  return ((RGB - 1) * HSV.y + 1) * HSV.z;
}

// 中略
// L86 
// 距離に応じた色をつけてあげる
float3 hsv = float3(1, 1, 1);
hsv.x = v.vertex.z % 1.0;
float3 rgb = HSVtoRGB(hsv);
o.col = float4(rgb.xyz, 1);

実装は以上です。

最後に

制約

今回の実装にはいくつか制約がありますので、ご注意下さい。

  • Kinect SDK の公式な Coordinate mapping をしていないので、点群の位置が不正確
  • Color 情報と点群のマッピングもしたいけど、それができる設計になっていない(自力でシェーダ内で実装できるか?)

感想

しかし、結果としては高速に大量の点を描くことが出来るようになったので満足です。メディアアート的なアプリにはもってこいなのではないでしょうか。

上記制約を解消したり、実装面でイケてない所とか見つけた人、これを使ってなにかおもしろいことをやった人がいたらぜひおしえてください〜

OpenCV 3.3.1 で Yolo v2 for object detection を動かしてみる (Windows)

わーい!

というわけで、とりあえずサンプルを動かしてみました。今回は速報版ってことでご容赦下さい。

環境構築

環境

インストール

  • Windows (vc14) 用にビルドされた物を落としてくる
  • .exe だけど自己解凍するだけなので、適当な場所に置く
    • 私の場合は D:\develop\opencv331 となるように置いた
  • OpenCVのいつもの儀式、パス通し。こちらの記事に習って行う

とりあえずサンプルを動かす

sources\samples\dnn\yolo_object_detection.cpp を動かす。

サンプルのためのプロジェクトを作成
  • Visual Studio 2015 > ファイル > 新規作成 > プロジェクト
  • テンプレート > Visual C++ > 「空のプロジェクト」 を選択
サンプルコードの取り込み
  • $(ProjectDir) 直下に、上記 sources\samples\dnn\yolo_object_detection.cpp を配置
  • Visual Studio の ソリューションエクスプローラー上で、ソースファイル フィルタに、上記ファイルを投げ込む

f:id:AMANE:20171028110435p:plainf:id:AMANE:20171028110439p:plain

インクルードとライブラリのリンク

Debug 時と Release 時で使う .lib ファイルが異なる事に注意。

ビルド
  • ビルド > ソリューションのビルド。 とりあえず Debug でやってみる。
コンフィグとモデルの用意
  • 今回はとりあえず動けば良いので、用途や適正は考えずに、yolo 公式から最もベタっぽいやつを取ってくる。
    • yolo.cfg
    • yolo.weights
  • Visual Studio 上でデバッグ実行する場合、作業ディレクトリは $(ProjectDir) となる。ので、そこに、yoloの cfg ファイルと weights ファイルを置く。
  • ついでに、テスト画像も用意。Yolo の学習にも使っている COCO から適当に。

f:id:AMANE:20171028112003p:plainf:id:AMANE:20171028112141p:plain

実行
  • cfg と model が読まれるようコマンド引数を設定。たとえば以下の様な感じ。
    • -cfg=yolo.cfg -model=yolo.weights -image=3320359009_059880900a_z.jpg
    • これは OpenCVCommandLineParser の書式です。
  • F5 デバッグ実行。

f:id:AMANE:20171028113621p:plain

結果

f:id:AMANE:20171028114308p:plain

見つけたオブジェクトに枠がついています。学習に使われたラベルはこちらです。

奥の車が見つかって無くて、ショルダーバッグだけで無く買い物袋も認識されていて、と言う結果です。良いのか悪いのかはわかりません。

それと、今のところ、提示されたクラスのIDと、その名称の紐付け方がよくわかっていません。他の結果も見た感じ、 Class:0 が人、 Class:2 が車っぽいって事はわかりました(笑)

f:id:AMANE:20171028114953p:plain

次にやりたいこと

  • Class の id から、クラス名を引く方法は?
  • サンプルを元に、動画ファイルやウェブカムを食わせる
  • Tiny YOLO はどうやら高速に動きそう、などモデルと設定の特性について学びたい

Raspberry Pi に VNC でアクセスする (2017/9版)

やりたいこと

  • Raspberry Pi に毎回 HDMI・キーボ・マウスをつなぐのが面倒なので、MacとかからGUIで触れるようにしたい。
  • ChromiumVisual Studio Code を、VNC アクセス中に起動したい。★ここがポイント

f:id:AMANE:20170903180625p:plain

結論

最近のラズパイ…というか raspbian OS には、 RealVNC のモジュールがプリインストールされているので、サーバのサービスを入れてあげれば即VNCサーバ化する。

Raspberry Pi | RealVNC

手順

ラズパイ側

RealVNC 公式に書いてあるとおり→ Docs | VNC Connect and Raspberry Pi | RealVNC

  • 最新の Raspbian Jessie にする sudo apt-get update && sudo apt-get upgrade
  • RealVNC のサーバをインストール。 sudo apt-get install realvnc-vnc-server
  • ラズパイの設定からVNCを利用可能に設定。これだけで、ラズパイ起動時のサービス自動起動設定までされます。

f:id:AMANE:20170903172256p:plain

  • ラズパイの ip を調べる。 ifconfig または、RealVNC Server をインストールしたらタスクバーに出てくる RealVNC のアイコンをクリックすると表示される。例えば 192.168.11.11

f:id:AMANE:20170903172612p:plain

クライアント側(今回はMac

というわけで簡単です。

事の顛末(チラ裏)

この件は誰でもまとめてそうだし、入門書の類に載ってそうなのでまとめるほどでは無い記事だと思ったのですが、自分がちょいとハマったのでまとめることにしました。以下その顛末メモ。なのである意味ここからが本編。

古い情報を鵜呑みにしない

特に最新のラズパイがどんなことになっているかをあまり調べないまま「TightVNC を入れて、Systemd に自動起動を登録しよう」という記事を読んでそのままやってみたところ、以下のような問題が起きました。

あげつらうのは感じ悪いけど、メモとしてはじめ参考にしていた記事を残しておく

Visual Studio Code が VNC 経由だと表示できない

VNC経由で Visual Studio Code (Code-oss) のインストールを済ませて、いざ起動しようと思ったら起動しない。インストール手順間違えたかな、とか思いながらラズパイに直接ログインして操作したら起動できる。あっれ~となって調べる。と、以下の記事を発見

www.hanselman.com

TIghtVNC サーバを使うと、 Visual Studio Code だけじゃなく、Chromium ブラウザ他、 Electron っぽい奴らが全滅するらしい。 セキュリティとグラフィックの掛け算でのなんか問題なのでしょう。

代替手段の調査

インストールしてしまった TightVNC を消す

RealVNC サーバの構成

この記事の先頭に戻る。

TCPSpout + KlakSpout で Unity ソフト間動画転送

なにをやるのか

  • 複数のウェブカメラや動画を、同期したり切り替えたりしながら、ネットワーク内の複数のコンピュータに渡して再生したい
    • つまり VJ ソフトみたいなことしたい
  • Unity で自作したい
  • Windows

概念図は以下の様な感じ

f:id:AMANE:20170825131105p:plain

  • マルチソースの録画再生機 兼MIXERとして、左側のマシンがあり、そこから、複数のクライアントに動画を送り出します。
  • 複数のクライアントにどの映像をどのタイミングでどんな組み合わせで送るか、を左側のマシンで一括で管理します。
  • クライアント側は受け取った動画を使ってよしなにやります。たとえば、VR-HMD を使ったりします。

調査

Spout

Mac でのプロジェクションマッピングとか VJ の界隈で Syphon という規格で動画データを渡すことは以前から知っていたのですが、その Windows 版が Spout です。Syphon と互換性があり、相互に送受信することも出来るみたいです。これを使いたい。

keijiro/KlakSpout

github.com

keijiro さんのこれを使うと、Unity で手軽に Spout が利用出来ます。使い方は GitHub に充分に書かれているので省略。

異なるカメラに二つ三つと SpoutSender コンポーネントをつけていくと、複数の映像を送り出すことも可能です。この様子を図に表すと以下の感じ。

f:id:AMANE:20170826212625p:plain

ただし、このアセットだけだと、同一マシン内でのビデオデータ送受信しかできません。そこで次に続きます。

TCPSpout

techlife.sg

Spout を、LAN内でPCをまたいで利用可能にしてくれるのが TCPSpoutです。

  • Spout 準拠の映像出力を捕まえて送り出すサーバ
  • 別PCのサーバを見つけて映像を受け取り内部で Spout の映像として利用可能としてくれるクライアント

のセットで利用します。図に示すと以下の感じ。この図の中で青い四角で描いてあるところを、Unityの KlakSpout にすれば、今回狙っている構成のできあがりです。Bonjour を使って、IP等打たなくてもLAN内のサーバを自動検出してくれるのが素晴らしいです。

f:id:AMANE:20170826143050p:plain

開発

実際の利用法を見ていきましょう。

1. Sender スクリプトのアタッチ

送りたい映像を撮る Unity の Camera に、 SpoutSender スクリプトをアタッチする

f:id:AMANE:20170826144131j:plain

2. Sender の確認

1.でSenderのスクリプトをアタッチしたことで、 Window/Klak/Spout Sender List に、Sender の名前が表示されるので確認する。この名称は、Senderスクリプトをアタッチしたゲームオブジェクトの名称になる。

f:id:AMANE:20170826143755j:plain

3. TCPSpout サーバの立ち上げ

TCPSpoutServer.exe を起動して、Spout Sender List から、上記名称の Sender を選択する。ここではSenderとして上記 “MainCamera” を選択しています。

f:id:AMANE:20170826144608p:plain

ここから先は、映像を受ける側(クライアント)側の設定です

4. TCPSpout クライアント立ち上げ

TCPSpoutClient.exe を起動して、Server List から、対象のサーバを選択します。Bonjour が自動でサーバを検出してくれるのが本当に素晴らしいです。私の例では名称が “TCPSpout FLAUROS” となっていますが、これは、TCPSpout が決めた、“TCPSpout” + “サーバマシン名” という形式で命名されており、Server を立ち上げているPCに私が “FLAUROS” と名付けているのから来ています。

f:id:AMANE:20170826150103p:plain

5. Sender の確認

ここでTCPSpout がつないでくれたSenderが見えていることを確認しておきましょう。確認の仕方は Server 側でやったときと同じです。名称は、 “MainCamera” ではなく、ここでは 一個前で選んだ “TCPSpout FLAUROS” になります。

f:id:AMANE:20170826150212p:plain

6. Receiver スクリプトのアタッチ

受け取るためのオブジェクトを作って、SpoutReceiver スクリプトをアタッチします。Sender として、先ほど確認した名称を記入します。受け取ったテクスチャを書き込む RenderTexture または、直接書き込む Renderer もここで登録します。詳細は公式のドキュメントを参照ください。

f:id:AMANE:20170826150431p:plain

ここで映像を受け取った RenderTexture をよしなに活用出来るようになります。

複数ストリーム用意する

KlakSpout の SpoutSender を、Unity シーン内で複数用意したカメラにそれぞれアタッチすることで、複数の映像を送り出すことができます。その数に合わせて、TCPSpoutServer.exe を複数起動すれば、複数の映像を同時に TCP 上に送り出すことが可能です。

受け取り側も同様に、 TCPSpoutClient.exe を複数起動することで、複数の Server から送り出されている Spout 映像を受け取ることが出来ます。

この際に注意点が、 TCPSpout のクライアントがつける、Sender の名前が、 “TCPSpout” + “マシン名” + “ (n)” <nはサーバの起動数> となることです。例えば、上記の私の例で言うと、 “TCPSpout FLAUROS (2)” という名前で検出されます。あんまりイケてませんが、気をつければ実用上は問題無いでしょう。

所感

同じLAN内で有線で繋いで居ると、FullHD の動画でも結構安定して送れています。自分はウェブカムの分割兼配信機、のように使いたいのですが、遅延に関しては、TCPSpout による物より、ウェブカムそのもの撮影から表示までの遅延の方が大きいです。

それと、人によっては、サーバから出す映像をブロードキャスト/マルチキャスト したいと言う要望もあるかと思います。今回の TCPSpout を利用するケースだと対応出来ません。 サーバでカメラを必要数作って、 SpoutSender を必要個数アタッチすることで、ワークラウンド的には対処出来るかと思います。

いずれにせよなかなか便利なので、積極的に使っていきたいと思います。

Ovrvision Pro を VIVE で使う

つかいかた

Unity 標準の VR 対応 + OvrvisionProSDK for Unity

  • 新しいプロジェクトを作る
  • VR セットアップにする (OpenVR)
  • Ovrvision Pro SDK for Unity を入手する
  • OvrvisionPro/Resources/Prefabs/OvrvisionProCamera.prefab をつっこむ
  • LeftCamera, RightCamera の Target Displayを、Game Window の Display 番号とあわせる(なぜかずれていることが多い)
  • なぜかLayerを何も設定しなくても左右の目に正しく描かれるけど、気持ち悪いので以下の設定をする (*1)
    • Layer 8 を “LeftEye”, Layer 9 “RightEye” という名前で登録する
    • OvrvisionProCamera/LeftCamera/LeftImagePlane を、 Left Eye に登録する
    • OvrvisionProCamera/RightCamera/RightImagePlane を、 Right Eye に登録する
    • OvrvisionProCamera/LeftCamera の CullingMask から、 RightEye レイヤーを外す(左目には左カメラの映像が見える)
    • OvrvisionProCamera/RightCamera の CullingMask から、 LeftEye レイヤーを外す(右目には右カメラの映像が見える)

蛇足ですが、私の環境だと、OvrvisionProCamera の Ovrsition コンポーネント中、 Camera Mode を 960x950@60:Default のままにしておくと、盛大にジャダります。 640x480@90 にするとスムースに見ることができます。

SteamVR Plugin を利用する

上のままだとVIVEコンが使えないので、SteamVR Plugin を利用します。

  • Asset Store から StreamVR Plugin をインポートする
  • StreamVR/Prefabs/[CameraRig].prefab をつっこむ
  • [CameraRig]/Camera (head)/Camera (eye) の Target Display を Game Window の Display 番号と合わせる (なぜかずれていることが多い)
  • [CameraRig]/Camera (head)/Cemera (eye) に、 SteamVR_UpdatePoses スクリプトをアタッチする(こりんさん情報

これでVIVEコンも表示されるようになります。

OvrvisionPro SDK 内の camimage_demo サンプルを動かす

上記でも何度も同じことをやっていますが、このサンプルだけを動かしたいなら、以下のあたりを気をつけましょう

  • VR対応せずにただカメラ映像を正しく取れることを確認したいなら、MainCamera のTargetDisplayを、Game Window の Display 番号と合わせるだけでOK
  • VR対応させるなら、追加で以下の手続きが必要
    • LeftCamera, RightCamera の Target Displayを、Game Window の Display 番号とあわせる(なぜかずれていることが多い)
    • LeftImagePlane, RightImagePlane を適切なレイヤにあてて、各目のカメラの Culling Mask を正しく設定する(*1 の手続き)

ビデオシースルーとCGを組み合わせる難しさ

まだあまり追っていないのですが、Unity で描画する3Dの世界と、Ovrvision のカメラ映像の世界が、頭を振っているとずれる感じがします。

Ovrvision 公式としては、 ArUco を使ったビジョン(マーカ)ベースのAR開発環境を提供しています。これはカメラ映像を基準としてCGを描くのでずれないのですが、できればマーカベースじゃないARをやりたいです

ちょっと解決法を模索したいです。(未来の自分に期待)

HTC VIVE で頭の位置を固定する

どういうこと?

向きは固定しません。振り向くことはできます。

言い換えると、ヘッドトラックはしているのだけど、毎フレーム必ず、頭の座標を指定の位置に戻します。

なにがしたいの?

THETA などの全天球動画を、撮影位置からぴったり見たいです。

実際やってみてわかったこと

残念なことにこの処方は酔うことが確認できました。THETA を見るようなケースの場合は、動画像を Skybox に貼り付けるなど、十分に大きくしてしまえば実質同じなので、そのほうが良いと思います。

やりかた

前調査

こちらの記事様でやられていましたが、 2017年5月時点の SteamVR SDK を利用した場合、これだけでは不充分であることがわかりました。

monopocket.net

SteamVR Plugin を利用する場合

SteamVR/Scenes に入っている Example シーンの頭の位置を固定してみます。今回は、原点正方向を基準位置とします。

この example シーンのカメラでは、Main Camera (origin) のなかに Main Camera (head) さらにその中に Main Camera (eye) という構造になっていますが、Play してみると、Main Camera (eye)(origin) の直下に展開されます。

この (eye) の座標を0に固定すれば良いと思うのですが、どこかのタイミングで強制的にヘッドトラッキングに基づく座標に上書きされてしまうので、ここでは 親のcameraの座標を相対的に戻るようずらす という解決策を取ります。

f:id:AMANE:20170517213744p:plain Play前

f:id:AMANE:20170517213757p:plain Play中

もう2工夫必要です。

  1. example シーンでは、Main Camera(head) が Scale 0.1 倍にされています。そこで、Main Camera (origin) を相対的にずらす際に、そのスケール分戻して上げる必要があります。
  2. カメラがY軸中心で180度回っているので、これを戻して上げる必要があります。(これはEditor上で行います)

以上のことをやるためのスクリプトは以下のもので、これを Main Camera (origin) に貼り付ければOKです。

// FixHMDPosition.cs
using UnityEngine;
using UnityEngine.VR;

public class FixPosition : MonoBehaviour
{
    void LateUpdate()
    {
        Vector3 trackingPos = InputTracking.GetLocalPosition(VRNode.CenterEye);
        transform.parent.position = -trackingPos * 0.1f;
    }
}

カメラの方向をY軸180度戻したので、それに合わせてシーンも修正する必要があります。

弊害

このハックで頭の位置を固定すると、 一見 Unity で作った世界に対して頭部位置が固定されたように描画されますが、 HTC VIVE で言うところの「シャペロン境界」は、メインカメラとの位置関係で決められているらしく、頭を動かした結果シャペロン境界が表示されるエリアに入ると、表示されて、結果自分が動いていることが露見します。これは、世界の中に動いているものと動いていないものが混在する状態になるので、気持ち悪さの大きな原因になります。

f:id:AMANE:20170517215408j:plain

そこで、今回の手法で頭部位置を 「Unityで作る世界の中で」固定したい場合は、別の方法でシャペロン境界を非表示にする必要があります。

こりんさんが過去にまとめてくださっていたのですが、今やってみると少し仕様が変わっていたようで、steamvr.vrsettings にもう一行追加する必要があります。

{
   "collisionBounds" : {
      "CollisionBoundsColorGammaA" : 0,
      "CollisionBoundsStyle": 4 // Developer モードにしないと非表示にはできない
   },
   ...
}

言うまでもないことですが、これに加えてまずはじめに、体験者がシャペロン境界に届かないよう、プレイエリアの中心に座ってもらって始めるのが前提になります。

SteamVR の [Camera Rig] を使わない場合

基本は SteamVR Plugin を利用する場合と同じなのですが、以下の手順となります。

  • Player Settings で Virtual Reality Supported にチェックを入れ、 OpenVR 対応とします
  • Main Camera を、空のゲームオブジェクトで一層包みます。仮にこれを CameraParent とします。
  • その Main Camera に対し、先程と 「ほぼ」 同様の FixHMDPosition.cs をアタッチしますが、先程のサンプルとは異なり CameraParent はスケールが等倍のままなので、FixHMDPosition.cs でスケールをかけた部分を消します。
// 前略
      transform.parent.position = -trackingPos; // * 0.1f しない
// 後略

さいごに

最初に書きましたが、 THETA の全天球動画を見る、という用途には向いていない気がしました。ヘッドトラッキングに慣れた体からはもう戻れないのね。。。。