7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FirstVRのジェスチャー認識システムを作る

Last updated at Posted at 2018-10-30

#はじめに
この記事はFirstVR(筋変位センサ搭載コントローラ)を使ってジェスチャー認識システムを作るよ!という記事です。
「FirstVRのサンプルは一通り触ってみたけど、ジェスチャー認識とかどうやってゲームに組み込んでいったらいいかわからない><」という方が、自分でジェスチャー認識のシステムを作り、ゲームに組み込めるようになるのを目的としています。
AGDRec_20181029_1959422.gif

↑のシステムを作っていきたいと思います。

##前準備
SDKをダウンロードしていない方は https://dev.first-vr.com/downloads?locale=ja からダウンロードしてください。
sdk.PNG
SDKの中身はプロジェクトファイルになっているので、Unityでそのまま開いて大丈夫です。
開けたらScript,Sprite,Sceneフォルダをそれぞれ作り、新しいSceneを1つ追加します。
Scene名は何でも良いですが、こちらではGestureSettingという名前にしておきます。
newScene.PNG
それが出来たら、ProjectのFVR > Prefab フォルダの中から ConnectionCheckFVRContainer の2つをHierarchyにドラッグ&ドロップします。
prefab.PNG
ConnectionCheckのRenderModeはデフォルトではWorldSpaceになっていますが、今回はScreenSpace-Overlayに変えておきます。
ついでにUIScaleModeもScaleWithScreenSizeに変えておきます。
connection.PNG

変えた直後はImageサイズが小さくて画面に表示されないと思うので、ConnectionCheck以下のvisualとTextを自分が見やすいサイズ、位置に調整してください。

visualの設定だけ目安として載せておきます。
visual.PNG

続いて、ジェスチャー認識を確認するための画像を用意します。
画像は何でも良いですが、記事ではこの画像を使用します。
hand_tex.png
上の画像をプロジェクトに入れたら、SpriteEditorで画像をいい感じに分割しましょう。
SpriteModeをMultipleにしたらSpriteEditorを押します。
SpriteEditor.PNG
hand_texSprite.png
Spriteの範囲でクリックしてからドラッグすると範囲選択ができるので、この黒枠のように一つ一つのSpriteを囲み、Applyを押すと、画像が区切られているはずです。

それが出来たら新しくCanvasを作り、UIScaleModeをScaleWithScreenSizeに変えます。
Screen_2.PNG
作ったCanvasの下に
1.Button2つ
2.グー、パーのImageを2つずつ
3.ジェスチャーチェック用のImageを1つ
をそれぞれ作ってください。
Canvasika.PNG

RockButton…ボタンです。押したらキャリブレーションを開始させます。
RockImg…白い手のImageです。キャリブレーションの時間を計るのに使います。
RockSettingCalibImg…色が付いた方の手のImageです。キャリブレーションの時間を計るのに使います。

Paperの方も同じような意味で名付けています。

CheckImg…一番したにある手です。ジェスチャー認識ができているかをチェックします。

(Canvasのすぐ下の階層のRock、Paperには特に意味はなく、それぞれのジェスチャーに関連しているオブジェクトをまとめて管理しやすくしているだけなので、気にしなくても大丈夫です)
Screen_3.PNG
2番のグー、パーのImageは下に白い手、上に色が付いた手の画像がくるように、ぴったりと重ねて配置してください。
SettingCalibImg.PNG
SettingCalibImg(色が付いている方のImage)のImageTypeをSimpleからFilledに変えます。
すると色々とFill~という項目が出てきたと思いますが、そのうちのFillMethodをRadial360からVerticalに変更してください。
AGDRec_20181029_130054.gif
FillAmountのバーをいじった時に、こんな感じになれば成功です。
この設定をもう片方にもしておいてください。

ここまでで前準備は終わりです。
##本題
ではさっそくジェスチャー認識をするコードを書いていきます。
先ほど作ったScriptフォルダの中に、C#スクリプトを作ってください。名前はSetGestureとしておきます。
作れたらダブルクリックでスクリプトを開きます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using FVRlib;

public class SetGesture : MonoBehaviour {

    //-------------------------------------------
    // public
    //-------------------------------------------

    // キャリブレーションの待機時間表示用Image
    [Tooltip("0:グー,1:パー")]
    public Image[] calibHandImg;

    // ジェスチャーをチェックするときのImage
    [Space(10)]
    public Image checkImg;

    // ジェスチャーをチェックするときに使うSprite
    [Tooltip("0:グー,1:パー")]
    public Sprite[] checkHandSp;

    // ジェスチャー登録のボタン
    [Space(10),Tooltip("0:グー,1:パー")]
    public Button[] setGestureBtn;

    //-------------------------------------------
    // private
    //-------------------------------------------

    // FVR
    FVRGesture gesture;
    FVRConnection fvr;

    // キャリブレーションの時間
    float roundLength = 0;

    //-------------------------------------------
    // 関数
    //-------------------------------------------
    void Start ()
    {
        // FVRConnectionを取得
        fvr = FindObjectOfType(typeof(FVRConnection)) as FVRConnection;

        // 新しいカスタムジェスチャーを作成
        gesture = fvr.gestureManager.RegisterCustomGesture("RockGesture");

        // キャリブレーションの時間を設定
        roundLength = fvr.gestureManager.calibrationRoundLength;

    }
	
	void Update ()
    {
        if (gesture.held) checkImg.sprite = checkHandSp[0];
        else checkImg.sprite = checkHandSp[1];
	}

    /// <summary>
    /// グーを設定
    /// </summary>
    public void SetRockGesture()
    {
        StartCoroutine(Calibrate(true));
    }

    /// <summary>
    /// パーを設定
    /// </summary>
    public void SetPaperGesture()
    {
        StartCoroutine(Calibrate(false));
    }

    /// <summary>
    /// 登録したジェスチャーをリセット
    /// </summary>
    public void ResetGesture()
    {
        fvr.gestureManager.ResetPatternData(gesture);
    }

    /// <summary>
    /// キャリブレーション
    /// </summary>
    /// <param name="isTarget">ターゲットジェスチャーかどうか</param>
    /// <returns></returns>
    IEnumerator Calibrate(bool isTarget)
    {
        //キャリブレーション
        if (isTarget) fvr.gestureManager.SetTargetData(gesture);
        else fvr.gestureManager.SetNonTargetData(gesture);

        //キャリブレーション中は他ボタンを押せないようにする
        for (int i = 0; i < setGestureBtn.Length; i++) setGestureBtn[i].interactable = false;

        // キャリブレーションしている間の時間を計測
        float calibTime = 0;

        // キャリブレーションの時間を計算、視覚化
        while(gesture.registering)
        {
            calibTime += Time.deltaTime;

            if (isTarget) calibHandImg[0].fillAmount = calibTime / roundLength;
            else calibHandImg[1].fillAmount = calibTime / roundLength;

            yield return null;
        }

        // 終わったらボタンを再度押せるようにする
        for(int i = 0; i < setGestureBtn.Length; i++)
        {
            calibHandImg[i].fillAmount = 0;
            setGestureBtn[i].interactable = true;
        }
    }
}

中にはこのコードを書いてください。

順に説明していきます。

###FVRlib

using FVRlib;

FVRの接続やジェスチャ認識の判定で今回使っているFVRConnectionやFVRGestureは、FVRlibという名前空間のメンバーです。
そのため、こいつをusingしておかないと誰お前?と怒られます。

###RegisterCustomGesture

gesture = fvr.gestureManager.RegisterCustomGesture("RockGesture");

新しいジェスチャーインスタンスを作成しています。
RegisterCustomGesture.PNG
List.PNG
API( http://reference.first-vr.com/ja/annotated.html )を見ると、カスタムジェスチャーを作成し、それぞれのListに追加してくれているようです。
また複数のジェスチャーを認識したい場合は、nameの引数を変えてそれぞれを識別させるみたいですね。

###SetTargetData、SetNonTargetData

fvr.gestureManager.SetTargetData(gesture);
fvr.gestureManager.SetNonTargetData(gesture);

ジェスチャーデータの登録をしてくれます。
setData.PNG
気になるのがターゲットデータ、非ターゲットデータという部分。一体なんぞや?という方が多いと思います。
こちらは機械学習でよく使われる単語らしく、その意味としては**”対象となっているもの/予測したい内容/答”**です。
なので、ターゲット=狙って行うジェスチャー、ノンターゲット=それ以外のジェスチャーという意味ですね。
上のコードだと 狙って行うジェスチャーはグー、それ以外のジェスチャーとしてパーを登録しています。

今回はグー、パーのみですが、これがもしグー、チョキ、パーになった場合はどう実装するかというと。

FVRGesture[] gesture = new FVRGesture[3];

void Start()
{
    gesture[0] = fvr.gestureManager.RegisterCustomGesture("Rock");
    gesture[0] = fvr.gestureManager.RegisterCustomGesture("Paper");
    gesture[0] = fvr.gestureManager.RegisterCustomGesture("Scissors");
}

こんな感じでRegisterCustomGestureでジェスチャーインスタンスを作成したら、そこのターゲットジェスチャーにグー、チョキ、パーをそれぞれ設定します。そのあと、それぞれのノンターゲットジェスチャーにターゲットジェスチャーで登録したもの以外のジェスチャーを登録していく、という手段をとるそうです。

例)ターゲット=グー、ノンターゲット=パー、チョキ 
ターゲット=パー、ノンターゲット=グー、チョキ などなど

ただそれをそのまま実行すると、ターゲット×3、ノンターゲット×6で合計9回もジェスチャーを登録しなければならないということになるので、ゲームに組み込む際には少し考えなければいけないかもしれないですね…。

###registering

while(gesture.registering)

キャリブレーション中かどうかをbool型で返してくれます。

##最後に
コードが書けたらあと少しで終わりです。
もう一度UnityEditorに戻り、Hierarchy > Create > CreateEmptyを作ってください。名前は何でも大丈夫ですが、こちらではGestureManagerとでもしておきます。

GestureManager.PNG
先ほど作ったSetGestureをアタッチします。

全て共通に言えることですが、配列になっているものの0番目の要素はグー、1番目はパーを設定してください。

CalibHandImg…色が付いた手のImage
CheckImg…一番下の手
CheckHandSp…手のSprite
SetGestureBtn…ボタン

を設定してください。

ここまで出来たら完成です。
ビルドして、ジェスチャー認識ができるかどうかを確かめましょう!
AGDRec_20181029_1959422.gif
こんな感じにできていたら成功です。
お疲れさまでした!

##余談
一つ余談なのですが、上のままのプログラムだとジェスチャーを設定したときに一度反応するのに、すぐにキャリブレーションが終わるという現象がおきます。

/// The first time we set a target or non-target value, the round length and samples per second are ignored and the SVM takes only one value with dummy data then
/// the dummy data is replaced with real data. 
/// After the first round the FVRGesture.calibrated flag is set to true and you are ready to start calibrating with real data

SDKに入っているCalibTestCtrlをみると、コメントでこのようなことが書かれています。

一番最初にどちらをキャリブレーションしても、ダミーデータとして取得され、一番最初はキャリブレーションの時間なども無視される。ダミーデータは実データに置き換えられる。最初のラウンドの後、GVRGesture.calibratedフラグがtrueになるため、実データでキャリブレーションの値を取得する準備が整う。

エキサイト先生に聞いてみたところ、以上のようなことが書いてあるみたいです。
なんとなく意味はわかるようなわからないようなって感じですね…。

gesture.calibrated = true;

ひとまずは、Startの中にこう書いておけば、この現象はなくなります。
どうしても気になる、という方はこの1文を追加してみてください。

2018/11/10 追記

この方法でやると
error.PNG
error_2.PNG

というようなエラーが出てしまいます。
FVRlib内のSVMで怒られているようなので、これは書かないようにしましょう。
一番最初のジェスチャー登録の際に、2回ジェスチャー登録をする、というような処理を書いてあげたほうが良いと思われます。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?