#はじめに
前回はVIVE Pro Eyeの目玉であるアイトラッキングのセットアップを行ったので今回は実際にプログラミングしていく.
環境一覧
SteamVR 1.16.10
Unity 2019.4.27f1 or 2020.1.17f1(動作確認済み,非対応なSDKあり)
SteamVR Plugin 2.7.3
Vive SRanipalRuntime Plugin 1.3.2.0
追加アクセサリー
VIVEトラッカー2018
Ovrvision Pro(ステレオカメラ)
Leap Motion
使用PCスペック
インテル® Core™ i7-7700K
NVIDIA GeForce GTX 1070
- 環境作成編
#1 Unityのセットアップをする 2021年 1月更新
#2 プロジェクトを作ってみる 2021年 1月更新
#3-α Unity2019でVR開発のための環境を整える(SteamVR Plugin2.7.Xをインストールする):推奨 2021年 6月更新
#3-β Unity2020でVR開発のための環境を整える(SteamVR Plugin2.7.Xをインストールする) 2021年 5月更新
#3-おまけ UnityでVR開発のための環境を整える(SteamVR Pluginの最新版をインストールする):非推奨 2021年 2月更新
#4 SteamVRのダウングレードと自動アップデートの停止(上級者向け) 2021年 5月更新
- コントローラの入力やトラッカー,HMDの位置座標,回転座標を得る+α
#5 VIVEコントローラのボタン入力を取得する 2021年 2月更新
#6 トリガーの押し具合やトラックパッドの位置情報の入力を取得する 2021年 2月更新
#6-おまけ VIVEコントローラを使った開発をする時のおすすめ設定(独断と偏見) 2021年 5月更新
#7 ヘッドマウントディスプレイとコントローラの位置座標と回転を取得する 2021年 2月更新
#8 VIVEトラッカー2018を有効化してUnity上で位置座標と回転を取得 2021年 5月更新
#9 VIVEコントローラの振動機能を開発してみる 2021年 4月更新
- VIVE Pro Eyeのアイトラッキングを使ってみる
#10 VIVE Pro Eyeの視線トラッキングを有効化(セットアップ)する 2021年 4月更新
#11 VIVE Pro Eyeのアイトラッキングを使ってUnity上で瞬きや視線のデータを取得する(この記事) 2021年 4月更新
#11-おまけ VIVE Pro Eyeのアイトラッキングで計測できるデータについて(適宜更新) 2021年 4月更新
- VIVE Pro Eyeのカメラを使ってみる
#12 VIVE Pro Eyeのフロントカメラの解像度(性能)とAR機能を有効化について 2021年 5月更新
- 小技
プレイエリアの境界線(シャペロン境界)が表示されないように設定を変更する 2021年 6月更新
Unity上でカメラを複数台追加してHMDやコントローラの動きを見る 2021年 6月更新
- Ovrvision Pro(ステレオカメラ)を使ってAR開発してみる
$1-1 Ovrvision Pro(ステレオカメラ)のセットアップをして実際に動かしてみる 2021年 6月更新
$1-2 Ovrvision Pro(ステレオカメラ)とHMDを使ってARアプリ開発する 2021年 6月更新
- Leap Motionを使ってハンドトラッキングしてみる
$2-1 Leap Motion(ハンドトラッキング)のセットアップをして実際に動かしてみる 2021年 6月更新
$2-2 Leap Motion(ハンドトラッキング)とHMDを使ったインタラクティブなアプリ開発する 2021年 6月更新
-
アプリを作ってみる
VR(仮想空間)上に曲面ディスプレイを作成して大画面で広角WEBカメラ映像を見る 2021年 5月更新
-
実際にアプリを作成してみる
仮想空間上に曲面ディスプレイを作成して大画面で広角WEBカメラ映像を見る 2021年 5月更新
今回の記事では,
1.Unityでアイトラッカーのプログラムをするための準備をする.
2.実際に値を取得してみる(関数を使った方法).
3.実際に値を取得してみる(データの中身を参照する方法).
4.焦点情報を使ったサンプルプログラムを動かしてみる.
という流れ.
#1.Unityでアイトラッカーのプログラムをするための準備をする.
いつもどおりSteamVR Plugin ,Vive SRanipalRuntime Pluginを追加したプロジェクト上でVR用のカメラを追加しておきます.(この辺りの設定は過去記事を参照してください)
今回はスクリプト落とし込む為のオブジェクトをタブ上で右クリックし「Create Empty」で作成します.
当たり判定などがないプログラムを動かすためだけのGameObjectを作成されます.
/ViveSR/Scripts/Eyeフォルダ内にある"SRanipal_Eye_Framework"を先ほど作成したGameObject内にドラックアンドドロップで追加します.
このスクリプトから色々と呼び出していく形なのでGameObjectじゃなくてもいいので絶対にオブジェクトに追加しておく.
この後プログラムをしていく中で焦点情報を出すときにメインカメラを起点に計測を行うのでVR用に追加したカメラの設定からTagを参照し,もしタグが「MainCamera」になっていなければ設定しておく.
もしTagになければAdd Tag...から追加して変更しておく.
これで下準備は完了です.
#2.実際に値を取得してみる(関数を使った方法).
前回記事の最後に紹介した解凍したフォルダ内の..SDK(v1.3.3.0等)/SDK/2_Unity/Document/EyeにあるDocument_Unityから開発のためのドキュメントを使用してプログラムを書いていきます.
取得できるデータや内容はバージョンによって大きく変わるのでもしコピペで動かなかったり変な値が出力されるときにはドキュメントを参照してください.
まずは関数を使ってプログラムを書いていきます.
取得できるデータは,
①瞳孔位置
②まぶたの開き具合
③視線情報
④焦点情報
になっています.
※焦点情報に関しては当たり判定のあるオブジェクトがないといけないので3Dオブジェクトを適宜追加しておいてください.(焦点情報にはゲームによく使われるRayという当たり判定を使用しているため)
いつも通りC#スクリプトを作成していきます.
今回はとりあえずVIVEsampleというスクリプト作成します.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ViveSR
{
namespace anipal
{
namespace Eye
{
public class VIVEsample : MonoBehaviour
{
//⓪取得呼び出し-----------------------------
//呼び出したデータ格納用の関数
EyeData eye;
//-------------------------------------------
//①瞳孔位置--------------------
//x,y軸
//左の瞳孔位置格納用関数
Vector2 LeftPupil;
//左の瞳孔位置格納用関数
Vector2 RightPupil;
//------------------------------
//②まぶたの開き具合------------
//左のまぶたの開き具合格納用関数
float LeftBlink;
//右のまぶたの開き具合格納用関数
float RightBlink;
//------------------------------
//③視線情報--------------------
//origin:起点,direction:レイの方向 x,y,z軸
//両目の視線格納変数
Vector3 CombineGazeRayorigin;
Vector3 CombineGazeRaydirection;
//左目の視線格納変数
Vector3 LeftGazeRayorigin;
Vector3 LeftGazeRaydirection;
//右目の視線格納変数
Vector3 RightGazeRayorigin;
Vector3 RightGazeRaydirection;
//------------------------------
//④焦点情報--------------------
//両目の焦点格納変数
//レイの始点と方向(多分③の内容と同じ)
Ray CombineRay;
/*レイがどこに焦点を合わせたかの情報.Vector3 point : 視線ベクトルと物体の衝突位置,float distance : 見ている物体までの距離,
Vector3 normal:見ている物体の面の法線ベクトル,Collider collider : 衝突したオブジェクトのCollider,Rigidbody rigidbody:衝突したオブジェクトのRigidbody,Transform transform:衝突したオブジェクトのTransform*/
//焦点位置にオブジェクトを出すためにpublicにしています.
public static FocusInfo CombineFocus;
//レイの半径
float CombineFocusradius;
//レイの最大の長さ
float CombineFocusmaxDistance;
//オブジェクトを選択的に無視するために使用されるレイヤー ID
int CombinefocusableLayer = 0;
//------------------------------
//1フレーム毎に実行
void Update()
{
//おまけ------------------------------------
//エラー確認ViveSR.Error.がWORKなら正常に動いている.(フレームワークの方に内蔵済みだからいらないかも)
if (SRanipal_Eye_API.GetEyeData(ref eye) == ViveSR.Error.WORK)
{
//一応機器が正常に動いてる時の処理をここにかける
}
//-------------------------------------------
//⓪取得呼び出し-----------------------------
SRanipal_Eye_API.GetEyeData(ref eye);
//-------------------------------------------
//①瞳孔位置---------------------(HMDを被ると検知される,目をつぶっても位置は返すが,HMDを外すとと止まる.目をつぶってるときはどこの値返してんのか謎.一応まぶた貫通してるっぽい???)
//左の瞳孔位置を取得
if (SRanipal_Eye.GetPupilPosition(EyeIndex.LEFT, out LeftPupil))
{
//値が有効なら左の瞳孔位置を表示
Debug.Log("Left Pupil" + LeftPupil.x + ", " + LeftPupil.y);
}
//右の瞳孔位置を取得
if (SRanipal_Eye.GetPupilPosition(EyeIndex.RIGHT, out RightPupil))
{
//値が有効なら右の瞳孔位置を表示
Debug.Log("Right Pupil" + RightPupil.x + ", " + RightPupil.y);
}
//------------------------------
//②まぶたの開き具合------------(HMDを被ってなくても1が返ってくる??謎)
//左のまぶたの開き具合を取得
if (SRanipal_Eye.GetEyeOpenness(EyeIndex.LEFT, out LeftBlink, eye))
{
//値が有効なら左のまぶたの開き具合を表示
Debug.Log("Left Blink" + LeftBlink);
}
//右のまぶたの開き具合を取得
if (SRanipal_Eye.GetEyeOpenness(EyeIndex.RIGHT, out RightBlink, eye))
{
//値が有効なら右のまぶたの開き具合を表示
Debug.Log("Right Blink" + RightBlink);
}
//------------------------------
//③視線情報--------------------(目をつぶると検知されない)
//両目の視線情報が有効なら視線情報を表示origin:起点,direction:レイの方向
if (SRanipal_Eye.GetGazeRay(GazeIndex.COMBINE, out CombineGazeRayorigin, out CombineGazeRaydirection, eye))
{
Debug.Log("COMBINE GazeRayorigin" + CombineGazeRayorigin.x + ", " + CombineGazeRayorigin.y + ", " + CombineGazeRayorigin.z);
Debug.Log("COMBINE GazeRaydirection" + CombineGazeRaydirection.x + ", " + CombineGazeRaydirection.y + ", " + CombineGazeRaydirection.z);
}
//左目の視線情報が有効なら視線情報を表示origin:起点,direction:レイの方向
if (SRanipal_Eye.GetGazeRay(GazeIndex.LEFT, out LeftGazeRayorigin, out LeftGazeRaydirection, eye))
{
Debug.Log("Left GazeRayorigin" + LeftGazeRayorigin.x + ", " + LeftGazeRayorigin.y + ", " + LeftGazeRayorigin.z);
Debug.Log("Left GazeRaydirection" + LeftGazeRaydirection.x + ", " + LeftGazeRaydirection.y + ", " + LeftGazeRaydirection.z);
}
//右目の視線情報が有効なら視線情報を表示origin:起点,direction:レイの方向
if (SRanipal_Eye.GetGazeRay(GazeIndex.RIGHT, out RightGazeRayorigin, out RightGazeRaydirection, eye))
{
Debug.Log("Right GazeRayorigin" + RightGazeRayorigin.x + ", " + RightGazeRayorigin.y + ", " + RightGazeRayorigin.z);
Debug.Log("Right GazeRaydirection" + RightGazeRaydirection.x + ", " + RightGazeRaydirection.y + ", " + RightGazeRaydirection.z);
}
//------------------------------
//④焦点情報--------------------
//radius, maxDistance,CombinefocusableLayerは省略可
if (SRanipal_Eye.Focus(GazeIndex.COMBINE, out CombineRay, out CombineFocus/*, CombineFocusradius, CombineFocusmaxDistance, CombinefocusableLayer*/))
{
Debug.Log("Combine Focus Point" + CombineFocus.point.x + ", " + CombineFocus.point.y + ", " + CombineFocus.point.z);
}
//------------------------------
}
}
}
}
}
今回はGameObject内にスクリプトを追加します.
適宜コメントアウトをしながら動作確認をしみてください.
#3.実際に値を取得してみる(データの中身を参照する方法).
次にデータの中身を参照する方法を使ってプログラムを書いていきます.
取得できるデータは,
①どのぐらいまぶたを開いてるか
②視線の起点の座標(角膜の中心)mm単位
③瞳孔の位置
④瞳孔の直径
になっています.
前回と同様にC#スクリプトを作っていきます.
とりあえずVIVEsample2というスクリプト作成します.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ViveSR
{
namespace anipal
{
namespace Eye
{
public class VIVEsample2 : MonoBehaviour
{
//⓪取得呼び出し-----------------------------
//呼び出したデータ格納用の関数
EyeData eye;
//-------------------------------------------
//①どのぐらいまぶたを開いてるか-----------------
//呼び出したデータ格納用の関数
float LeftOpenness;
float RightOpenness;
//-------------------------------------------
//②視線の起点の座標(角膜の中心)mm単位------
//呼び出したデータ格納用の関数
Vector3 LeftGazeOrigin;
Vector3 RightGazeOrigin;
//-------------------------------------------
//③瞳孔の位置-------------------------------
//呼び出したデータ格納用の関数
Vector2 LeftPupilPosition;
Vector2 RightPupilPosition;
//-------------------------------------------
//④瞳孔の直径-------------------------------
//呼び出したデータ格納用の関数
float LeftPupiltDiameter;
float RightPupiltDiameter;
//-------------------------------------------
//1フレーム毎に実行
void Update()
{
//⓪取得呼び出し-----------------------------
SRanipal_Eye_API.GetEyeData(ref eye);
//-------------------------------------------
//①どのぐらいまぶたを開いてるか-----------------
//左目を開いてるかが妥当ならば取得 なぜかHMD付けてなくてもTrueがでる,謎.
if (eye.verbose_data.left.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY))
{
LeftOpenness = eye.verbose_data.left.eye_openness;
Debug.Log("Left Openness:" + LeftOpenness);
}
//右目を開いてるかが妥当ならば取得 なぜかHMD付けてなくてもTrueがでる,謎.
if (eye.verbose_data.right.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY))
{
RightOpenness = eye.verbose_data.right.eye_openness;
Debug.Log("Right Openness:" + RightOpenness);
}
//-------------------------------------------
//②視線の起点の座標(角膜の中心)mm単位------ -
//左目の眼球データ(視線原点)が妥当ならば取得 目をつぶるとFalse 判定精度はまあまあ
if (eye.verbose_data.left.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_ORIGIN_VALIDITY))
{
LeftGazeOrigin = eye.verbose_data.left.gaze_origin_mm;
Debug.Log("Left GazeOrigin:" + LeftGazeOrigin.x + ", " + LeftGazeOrigin.y + ", " + LeftGazeOrigin.z);
}
////右目の眼球データ(視線原点)が妥当ならば取得 目をつぶるとFalse 判定精度はまあまあ
if (eye.verbose_data.right.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_GAZE_ORIGIN_VALIDITY))
{
RightGazeOrigin = eye.verbose_data.right.gaze_origin_mm;
Debug.Log("Right GazeOrigin:" + RightGazeOrigin.x + ", " + RightGazeOrigin.y + ", " + RightGazeOrigin.z);
}
//-------------------------------------------
//③瞳孔の位置-------------------------------
//左目の瞳孔の正規化位置が妥当ならば取得 目をつぶるとFalse 判定精度は微妙
if (eye.verbose_data.left.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_PUPIL_POSITION_IN_SENSOR_AREA_VALIDITY))
{
LeftPupilPosition = eye.verbose_data.left.pupil_position_in_sensor_area;
Debug.Log("Left Pupil Position:" + LeftPupilPosition.x + ", " + LeftPupilPosition.y);
}
////右目の瞳孔の正規化位置が妥当ならば取得 目をつぶるとFalse 判定精度は微妙
if (eye.verbose_data.right.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_PUPIL_POSITION_IN_SENSOR_AREA_VALIDITY))
{
RightPupilPosition = eye.verbose_data.right.pupil_position_in_sensor_area;
Debug.Log("Right GazeOrigin:" + RightPupilPosition.x + ", " + RightPupilPosition.y);
}
//-------------------------------------------
//④瞳孔の直径-------------------------------
//左目の瞳孔の直径が妥当ならば取得 目をつぶるとFalse 判定精度はまあまあ
if (eye.verbose_data.left.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_PUPIL_DIAMETER_VALIDITY))
{
LeftPupiltDiameter = eye.verbose_data.left.pupil_diameter_mm;
Debug.Log("Left Pupilt Diameter:" + LeftPupiltDiameter);
}
////右目の瞳孔の直径が妥当ならば取得 目をつぶるとFalse 判定精度はまあまあ
if (eye.verbose_data.right.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_PUPIL_DIAMETER_VALIDITY))
{
RightPupiltDiameter = eye.verbose_data.right.pupil_diameter_mm;
Debug.Log("Right Pupilt Diameter:" + RightPupiltDiameter);
}
//-------------------------------------------
}
}
}
}
}
今回はGameObject内にスクリプトを追加します.
適宜コメントアウトをしながら動作確認をしみてください.
VIVEsampleと同時に動かすこともできますがデータの呼び出しが2つになってしまうのでうまく⓪以外の部分を組み合わせてください.
次回の記事で取得したデータについて細かく解説していきます.暇なときに適宜更新していきます...
#4.焦点情報を使ったサンプルプログラムを動かしてみる.
今回抜き出したデータを使って焦点位置を表示するサンプルを作成していきます.
今回の記事で作成したスクリプトのVIVEsample.csの④にある焦点情報を使っていきます.
まずは3つの3Dオブジェクトと視線位置を示すために赤く色づけした小さな玉のオブジェクトを作成します.
焦点位置は当たり判定を応用して計測しているのでオブジェクトにコライダーと呼ばれる当たり判定があることを確認します.
自作のオブジェクト作成するさいにはしっかりと当たり判定を入れておきましょう.
なぜか赤の球が白になっていますが実際に動かすとこんな感じで焦点位置が表示されます.
今回はとりあえずの動作確認なので今後はこれを活用したシステムを作ってみたいなと思います.取得しているデータについては「#11-おまけ VIVE Pro Eyeのアイトラッキングで計測できるデータについて(適宜更新)」の方に書いていきたい思っています.
次回はVIVE pro eyeについてるフロントカメラの性能に関する話や,実際にAR機能を触ってみたいと思います.