凹みTips

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

複数の AR マーカを利用した Oculus Rift アプリが作れる Ovrvision SDK v0.5 を試してみた

はじめに

先日は Ovrvision SDK v0.3 についての記事(Oculus Rift x Ovrvision で拡張現実(AR)を試す方法をまとめてみた - 凹みTips)を書きましたが、1ヶ月も立たないうちに次のバージョンである v0.4 がリリースされ、そして今日 4/21 にバグフィックス版であるv0.5 が続けてリリースされました。

v0.4 および v0.5 のリリースの内容は以下のとおりです。

  1. The Configuration XMLfile reading system is now available(.\ovrvision_config.xml).
    • ovrvision_config.xml でカメラの設定(コントラストや露出、樽型の歪みなど)を調整(Unity Pro 版には未同梱)
  2. Fixed a bug in the camera control of Mac OSX.
  3. The OvrvisionSDK can display multiple models using multiple AR markers in the Unity Pro.
    • Unity Pro 版で複数の AR マーカに対応(v0.5 でバグ修正)

やはり 3 がでかいですね!これによって複数の AR オブジェクトを出したり、組み合わせてロバスト性を上げたり、ポジショントラッキングに応用したりといろいろなことが可能になると思います。

また、リリースノートには載っていないのですが、Unite Japan 2014 でお会いした際にお願いさせていただいた歪みなし左右画の取得(v0.3 までは左目のみ取得)も対応されています!

以降は v0.3 の知識を前提として話を進めますので、初めての方は v0.3 の記事に先に目を通していただくと分かりやすいと思います。

デモと解説

(追記:2014/05/08)
以下、勘違いが有りました。。上下左右キーでサイズ / 左右位置が調整できるのでそこで調整してやれば視差が合わないことはなくなると思います。ただ、マーカの上にピッタリと乗る利点はあると思います。


今回後半で解説する、左右カメラ画をそれぞれ解析した際の利点について説明しています。


利用方法

上記サイトから Ovrvision SDK for Unity Pro をダウンロードします。中には前回同様 SDKovrvisionsdk.unitypackage とサンプル unity_ar_example に加え、ArUco のマーカ(64 / 56番)が入った marker_sample が同梱されています。まずは ovrvisionsdk_unity/unity_ar_example/Assets/ovrvision_sence.unity を起動してみましょう。

f:id:hecomi:20140421213447p:plain

v0.3 時同様、Hierarchy ビューを見てみると、OVRCameraController(Oculus SDK に含まれるカメラ)、OvrvisionSDK(Ovrvision SDK 本体)がありますが、今回はマルチマーカのハンドルが可能になったため、OvrvisionView OvrvisionView2 が追加されています。そしてこれらには OvrvisionTracker というスクリプトがアタッチされていて、Marker ID フィールドで対象となる ArUco のマーカ番号が設定できるようになっています。サンプルでは、付属のマーカに対応する 64 と 56 が設定されています。


そこで実行してマーカをカメラに見せてみると…、

f:id:hecomi:20140421215016p:plain

2つ同時に表示されます!

コード解説

認識は Ovrvision.cs の Update が起点になります。

Ovrvision.cs
void Update ()
{
	//camStatus
	if (!camStatus)
		return;

	byte[] undis = new byte[ovGetBufferSize()];
	GCHandle undis_handle = GCHandle.Alloc(undis, GCHandleType.Pinned);

	if (go_CamTexLeft == null || go_CamTexRight == null)
		return;

	//Get the camera image.
	ovGetCamImageForUnityColor32 (go_pixelsPointerLeft, go_pixelsPointerRight, undis_handle.AddrOfPinnedObject(), System.IntPtr.Zero);

	//Apply
	go_CamTexLeft.SetPixels32(go_pixelsColorLeft);
	go_CamTexLeft.Apply();
	go_CamTexRight.SetPixels32(go_pixelsColorRight);
	go_CamTexRight.Apply();

	//Ex renderer
	go_ovrvisionEx.Render (undis_handle.AddrOfPinnedObject());
	undis_handle.Free();

	//Key Input
	CameraViewKeySetting ();
}

ovGetCamImageForUnityColor32() が今回アップデートされ、引数には、歪み有左カメラ画、歪み有右カメラ画、歪み無左カメラ画、歪み無右カメラ画(New!)のポインタを突っ込みます。こうして得られた歪み無カメラ画を、OvrvisionEx.Render() に突っ込むことでマーカの解析を行っています。OvrvisionEx 側のコードを見てみます。

OvrvisionEx.cs
private const int MARKERGET_MAXNUM10 = 100; 
private const int MARKERGET_ARG10 = 10; 

public int Render(System.IntPtr pImgSrc)
{
	float[] markerGet = new float[MARKERGET_MAXNUM10];
	GCHandle marker = GCHandle.Alloc(markerGet, GCHandleType.Pinned);

	ovExSetImage (pImgSrc, 640, 480);
	ovExRender ();

	//Get marker data
	int ri = ovExGetData(marker.AddrOfPinnedObject(), MARKERGET_MAXNUM10);

	OvrvisionTracker[] otobjs = GameObject.FindObjectsOfType(typeof(OvrvisionTracker)) as OvrvisionTracker[];
	foreach (OvrvisionTracker otobj in otobjs) {
		for (int i=0; i < ri; i++) {
			if(otobj.markerID == (int)markerGet[i*MARKERGET_ARG10]) {
				otobj.UpdateTransform(markerGet,i);
				break;
			} else {
				otobj.UpdateTransformNone();
			}
		}
	}

	marker.Free ();

	return ri;
}

おおまかな流れは以前と同じですが、ovExGetData による認識の結果、返ってくる値が以前は 0/1 だったのが、v0.4 から見つかったマーカの個数になりました。また v0.5 より、バッファとして渡している第 1 引数、コードで言うところの marker に、複数個分のマーカの情報が入ってくるようになりました!1 つのマーカあたりの情報は 10 個(MARKERGET_MAXNUM10)なので、10 個おきにバッファの中身を見ていけば複数個のマーカ情報がハンドル出来るわけです。そして OvrvisionTracker をアタッチされたスクリプトの番号を順番に見ていって設定された番号を見つけるとオブジェクトに UpdateTransform、見つからなければ UpdateTransformNone で隠すようにしています。これらのコードを見てみます。

OvrvisionTracker.cs
public void UpdateTransform (float[] markerGet, int elementNo) {
	int i = elementNo * MARKERGET_ARG10;
	this.transform.localPosition = new Vector3 (markerGet[i+1],markerGet[i+2],markerGet[i+3]);
	this.transform.localRotation = new Quaternion (markerGet[i+4],markerGet[i+5],markerGet[i+6],markerGet[i+7]);
}

public void UpdateTransformNone () {
	this.transform.localPosition = new Vector3 (-10000.0f, -10000.0f, -10000.0f);
}

marker[0 + 10*n] はマーカ番号が入っていて、marker[1~3 + 10*n] は位置、marker[4~7 + 10*n] は傾きの情報が入っていることが分かります。ここまで分かれば、後は自分のやりたいことに応じてカスタマイズしていけば色々出来そうですね。

左右それぞれの画を別々に認識

前回のエントリでも書いたのですが、現在の SDK では左目のカメラ画を利用して AR オブジェクトの位置と傾きを決定しています。そして Oculus SDK のカメラでこの物体を視ることで立体視を行っています。しかしながら実際の世界を見る画角とバーチャルの世界を見る画角が合わなかったり(2014/05/08 追記:上下左右キーでサイズ/位置が調整できるため画角の調節は可能です)、マーカの上にぴったり乗っている表現が苦手だったりします。そこで以前やった方法(Oculus Rift でミクさんに画面の中から出てきていただいた:解説 - 凹みTips)のように、左右の目それぞれを解析することでこの問題を解決してみます。

スレッド切ったりとまじめにやると長くなるので、最小限の手順だけ紹介します。

まず、左目の映像と右目の映像を分けるためにレイヤを作成します。「OvrArObjLeft」「OvrArObjRight」などと適当に名前をつけて、11 番目 / 12 番目のレイヤとしておきます。次に、OvrvisionView をコピーして同じものを 2 つ作成し、OvrvisionViewLeft / OvrvisionViewRight などとリネームしてそれぞれを OvrArObjLeft / OvrArObjRight なレイヤにしておきます。

f:id:hecomi:20140421234115p:plain

そして OVRCameraController の CameraLeft / CameraRight のカリングマスクに OvrArObjLeft / OvrArObjRight を追加します。

f:id:hecomi:20140422004455p:plain

これでそれぞれのカメラから左右それぞれのオブジェクトしか見えないようになります。

次に、スクリプト側で左目の映像だけでなく右目の映像も解析するようにします。

Ovrvision.cs
void Update ()
{
	//camStatus
	if (!camStatus)
		return;

	byte[] undis_left  = new byte[ovGetBufferSize()];
	byte[] undis_right = new byte[ovGetBufferSize()];
	GCHandle undis_left_handle  = GCHandle.Alloc(undis_left,  GCHandleType.Pinned);
	GCHandle undis_right_handle = GCHandle.Alloc(undis_right, GCHandleType.Pinned);

	if (go_CamTexLeft == null || go_CamTexRight == null)
		return;

	//Get the camera image.
	ovGetCamImageForUnityColor32 (
		go_pixelsPointerLeft, go_pixelsPointerRight, undis_left_handle.AddrOfPinnedObject(), undis_right_handle.AddrOfPinnedObject());

	//Apply
	go_CamTexLeft.SetPixels32(go_pixelsColorLeft);
	go_CamTexLeft.Apply();
	go_CamTexRight.SetPixels32(go_pixelsColorRight);
	go_CamTexRight.Apply();

	//Ex renderer
	go_ovrvisionEx.Render (undis_left_handle.AddrOfPinnedObject(),  11);
	go_ovrvisionEx.Render (undis_right_handle.AddrOfPinnedObject(), 12);
	undis_left_handle.Free();
	undis_right_handle.Free();

	//Key Input
	CameraViewKeySetting ();
}

右カメラ画用のバッファを追加して、ovGetCamImageForUnityColor32() の第 4 引数で渡すようにします。そして、OvrvisionEx.Render() にそれぞれのカメラ画を投げるようにして、それと共にどのレイヤのオブジェクトを処理するかを第 2 引数で伝えられるようにしています。

OvrvisionEx.cs

レイヤを受け取れるように改造した Render を見てみます。

public int Render(System.IntPtr pImgSrc, int layer)
{
	float[] markerGet = new float[MARKERGET_MAXNUM10];
	GCHandle marker = GCHandle.Alloc(markerGet, GCHandleType.Pinned);

	ovExSetImage (pImgSrc, 640, 480);
	ovExRender ();

	//Get marker data
	int ri = ovExGetData(marker.AddrOfPinnedObject(), MARKERGET_MAXNUM10);

	OvrvisionTracker[] otobjs = GameObject.FindObjectsOfType(typeof(OvrvisionTracker)) as OvrvisionTracker[];
	foreach (OvrvisionTracker otobj in otobjs) {
		if (otobj.gameObject.layer == layer) {
			for (int i=0; i < ri; i++) {
				if(otobj.markerID == (int)markerGet[i*MARKERGET_ARG10]) {
						otobj.UpdateTransform(markerGet,i);
					break;
				} else {
					otobj.UpdateTransformNone();
				}
			}
		}
	}

	marker.Free ();

	return ri;
}

レイヤが一致するときだけ処理するようにしただけです。

これで冒頭のデモ動画の後半のような感じになります。上記処理は Transform を更新する以外は全て Unity のアップデートと非同期で行えるので、Thread を使ってかなり高速化出来ると思います(別途記事を書きます)。

おわりに

簡単に視野角全体を使った AR の立体視が出来るようになりました。触った感じですが、Ovrvision の解像度が高い点と ArUco が優秀なおかげか、以前やっていた PS Eye x ARToolKit とくらべて綺麗で追従性の高い立体視が出来るように感じます。マーカも複数個ハンドル出来るため、マーカ毎に色々なコンテンツを出したりロバスト性を上げたりと色々なことが出来そうです!