なおしのこれまで、これから

学んだこと・感じたこと・やりたいこと

アイトラッキングのSDKで使用できるメソッドについて

始めに

前回、Vive pro eyeを簡単に試してみました。

shitakami.hatenablog.com


今回は解説しきれなかったSDKに用意されているメソッドについてまとめようと思います。 バージョンはv2です。

途中、だらだら書いているので簡単に見たい方はまとめをご覧ください。

目次



SRanipal_Eye_v2.UpdateData(private)

このメソッドはprivateであり外部からは呼び出すことは出来ませんが、SRanipal_Eye_v2のメソッドからは頻繁に使われています。

このメソッドではSRanipal_Eye_API.GetEyeData_v2を呼び出しトラッキング情報を取得します。また、1フレーム内で複数回トラッキング情報をAPIから取得しないようになっていおります。

private static bool UpdateData()
{
    if (Time.frameCount == LastUpdateFrame) return LastUpdateResult == Error.WORK;
    else LastUpdateFrame = Time.frameCount;
    LastUpdateResult = SRanipal_Eye_API.GetEyeData_v2(ref EyeData_);

    return LastUpdateResult == Error.WORK;
}



SRanipal_Eye_v2.GetVerboseData

こちらはVerboseData(左右それぞれの情報と両目合わせた情報)を取得することができます。コードを見るに返り値はtrueで固定のようです。

こちらは2つ用意されており、1つは引数にEyeData_v2を渡してそこから取得するものとUpdateData()を呼び出してから取得するものがあります。

public static bool GetVerboseData(out VerboseData data, EyeData_v2 eye_data)
{
    data = eye_data.verbose_data;
    return true;
}

public static bool GetVerboseData(out VerboseData data)
{
    UpdateData();
    return GetVerboseData(out data, EyeData_);
}



SRanipal_Eye_v2.GetEyeOpenness

こちらは引数で右目、左目を指定してその目がどれだけ開いているかを取得します。取り出している値は SingleEyeData.eye_opennessになります。こちらも返り値は必ずtrueのようです。

こちらのメソッドも2つ用意されており、1つはEyeData_v2を渡してそこから取得するものとUpdateData()を呼び出してから取得するものです。

public static bool GetEyeOpenness(EyeIndex eye, out float openness, EyeData_v2 eye_data)
{
    if (SRanipal_Eye_Framework.Status == SRanipal_Eye_Framework.FrameworkStatus.WORKING)
    {
        SingleEyeData eyeData = eye == EyeIndex.LEFT ? eye_data.verbose_data.left : eye_data.verbose_data.right;
        bool valid = eyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_EYE_OPENNESS_VALIDITY);
        openness = valid ? eyeData.eye_openness : 0;
    }
    else
    {
        // If not support eye tracking, set default to open.
        openness = 1;
    }
    return true;
}

public static bool GetEyeOpenness(EyeIndex eye, out float openness)
{
    UpdateData();
    return GetEyeOpenness(eye, out openness, EyeData_);
}



SRanipal_Eye_v2.TryGaze

こちらは引数でSingleEyeDataValidityを渡して、指定されたデータが正しく取得できているかを確かめるメソッドです。左右それぞれの目と両目すべてが正しい場合にtrueを返します。

SingleEyeDataValidityenum型で、目の位置や方向などを指定できます。

こちらも1つはeye_dataから取得するものとUpdateData()を呼び出すものの2種類あります。

public static bool TryGaze(SingleEyeDataValidity validity, out GazeIndex gazeIndex, EyeData_v2 eye_data)
{
    bool[] valid = new bool[(int)GazeIndex.COMBINE + 1] { eye_data.verbose_data.left.GetValidity(validity),
                                                          eye_data.verbose_data.right.GetValidity(validity),
                                                          eye_data.verbose_data.combined.eye_data.GetValidity(validity)};
    gazeIndex = GazeIndex.COMBINE;
    for (int i = (int)GazeIndex.COMBINE; i >= 0; --i)
    {
        if (valid[i])
        {
            gazeIndex = (GazeIndex)i;
            return true;
        }
    }
    return false;
}

public static bool TryGaze(SingleEyeDataValidity validity, out GazeIndex gazeIndex)
{
    UpdateData();
    return TryGaze(validity, out gazeIndex, EyeData_);
}



SRanipal_Eye_v2.GetGazeRay

このメソッドは目線の出発点と方向、もしくは目線を表すRayを求めるメソッドになります。 

引数で右目、左目、両目を指定し、取得が成功すればtrueを返します。(API自体が動いていなかった場合もtrueを返す点に注意)

EyeData_v2を引数に与えた場合はEyeData_v2からRayを求め、そうでない場合はUpdateData()を呼び出してから求めています。

// 出発点、方向を求める
public static bool GetGazeRay(GazeIndex gazeIndex, out Vector3 origin, out Vector3 direction, EyeData_v2 eye_data);
public static bool GetGazeRay(GazeIndex gazeIndex, out Vector3 origin, out Vector3 direction);

// Rayを求める
public static bool GetGazeRay(GazeIndex gazeIndex, out Ray ray, EyeData_v2 eye_data)
public static bool GetGazeRay(GazeIndex gazeIndex, out Ray ray)


こちらのメソッドの処理を詳しく見ると、トラッキング情報のみでRayを求めていることがわかりました。 なので、こちらはHMDの位置や方向は考慮されていません。

origin = eyesData[(int)gazeIndex].gaze_origin_mm * 0.001f;
direction = eyesData[(int)gazeIndex].gaze_direction_normalized;

// トラッキング情報は右手座標, Unityは左手座標系なのでx方向を反転
origin.x *= -1;
direction.x *= -1;



SRanipal_Eye_v2.Focus

こちらは実際にRayを飛ばして、その結果をFocusInfoに保存するメソッドです。

このメソッドはオーバーロードを多用していますが、基本的にRayの距離、飛ばすRayの半径、Rayが当たるLayerを指定するか、EyeData_v2を渡すかとなっており実装自体に差はありません。

public static bool Focus(GazeIndex index, out Ray ray, out FocusInfo focusInfo, float radius, float maxDistance, int focusableLayer, EyeData_v2 eye_data);
public static bool Focus(GazeIndex index, out Ray ray, out FocusInfo focusInfo, float radius, float maxDistance, int focusableLayer);
public static bool Focus(GazeIndex index, out Ray ray, out FocusInfo focusInfo, float radius, float maxDistance, EyeData_v2 eye_data)
public static bool Focus(GazeIndex index, out Ray ray, out FocusInfo focusInfo, float radius, float maxDistance)
public static bool Focus(GazeIndex index, out Ray ray, out FocusInfo focusInfo, float maxDistance, EyeData_v2 eye_data)
public static bool Focus(GazeIndex index, out Ray ray, out FocusInfo focusInfo, EyeData_v2 eye_data)
public static bool Focus(GazeIndex index, out Ray ray, out FocusInfo focusInfo)


実装としてはGetGazeRayから目線の方向を求めて、HMDの位置と方向と組み合わせてRayを飛ばします。見ている個所を取得したい場合はGetGazeRayではなくこのFocusを使うことになるでしょう。

Ray rayGlobal = new Ray(Camera.main.transform.position, Camera.main.transform.TransformDirection(ray.direction));
RaycastHit hit;
if (radius == 0) valid = Physics.Raycast(rayGlobal, out hit, maxDistance, focusableLayer);
else valid = Physics.SphereCast(rayGlobal, radius, out hit, maxDistance, focusableLayer);
focusInfo = new FocusInfo
{
    point = hit.point,
    normal = hit.normal,
    distance = hit.distance,
    collider = hit.collider,
    rigidbody = hit.rigidbody,
    transform = hit.transform
};



SRanipal_Eye_v2.GetPupilPosition

こちらは目の位置を-1 ~ 1に正規化するメソッドです。 取得した結果ではx軸は右方向が正、y軸は上方向が正となります。 もともとのデータではy軸の方向が反転しているためこちらを呼び出して正規化した方がよいでしょう。

public static bool GetPupilPosition(EyeIndex eye, out Vector2 postion, EyeData_v2 eye_data)
{
    bool valid = false;
    if (SRanipal_Eye_Framework.Status == SRanipal_Eye_Framework.FrameworkStatus.WORKING)
    {
        SingleEyeData eyeData = eye == EyeIndex.LEFT ? eye_data.verbose_data.left : eye_data.verbose_data.right;
        valid = eyeData.GetValidity(SingleEyeDataValidity.SINGLE_EYE_DATA_PUPIL_POSITION_IN_SENSOR_AREA_VALIDITY);
        postion = valid ? postion = new Vector2(eyeData.pupil_position_in_sensor_area.x * 2 - 1,
                                                eyeData.pupil_position_in_sensor_area.y * -2 + 1) : Vector2.zero;

    }
    else
    {
        // If not support eye tracking, set default in middle.
        postion = Vector2.zero;
        valid = true;
    }
    return valid;
}

public static bool GetPupilPosition(EyeIndex eye, out Vector2 postion)
{
    UpdateData();
    return GetPupilPosition(eye, out postion, EyeData_);
}



SRanipal_Eye_v2.LaunchEyeCalibration

こちらはアイトラッキングキャリブレーションを実行するメソッドになります。

public static bool LaunchEyeCalibration()
{
    int result = SRanipal_Eye_API.LaunchEyeCalibration(IntPtr.Zero);
    return result == (int)Error.WORK;
}



まとめ

SDKに用意されているメソッドは次のようにまとめられると思います。

データを取得する

関数名 解説
GetVerboseData ラッキング情報を取得
GetEyeOpenness 目をどれぐらい開けているかを取得
GetPupilPosition 目(瞳孔)の位置を取得


目線を求める

関数名 解説
GetGazeRay ローカル空間での目線を求める
Focus ワールド空間での目線を求める


その他

関数名 解説
TryGaze ラッキング情報が正しく取得できているか
LaunchEyeCalibration アイトラッキングキャリブレーションを始める



最後に

メソッドの実装を見て、個人的にはSDKは使いやすいものではないと感じています。特に、メソッドの返り値がアイトラッキングが動作していない場合でもtrueを返す点に違和感を感じます。

// GetGazeRay
if (SRanipal_Eye_Framework.Status != SRanipal_Eye_Framework.FrameworkStatus.WORKING)
{
    origin = Camera.main.transform.position;
    valid = true;  // 動作していないのにtrueを返している
}


ただし、トラッキング情報をどのように扱えばよいかの参考にはなったので一通り調べてみてよかったと感じています。

また何か興味がわけばもう少しVive pro eyeを触ってみようと思います。


追記

少し遊んでみました。

shitakami.hatenablog.com