OculusQuestのハンドトラッキングについて色々調べてみた

#初めに
先日OculusIntegrationのアップデートがされ、QuestのハンドトラッキングがUnityでも試せるようになったので、色々調べてみました。

Oculus公式も言ってますが、**ハンドトラッキング機能は現在developer preview版**となっているので、本リリースの時にはこの記事が役に立たなくなってる可能性もありますが悪しからず
#検証環境
- Unity2019.1.14f1
- OculusIntegration ver 12.0
- OculusQuest ver 12.0
- Windows10

余談ですが、Integraitonはver1.43からver12.0に一気に上がりました。
おそらく、Questのバージョン番号と合わせるためかと
#公式リファレンス
https://developer.oculus.com/documentation/quest/latest/concepts/unity-handtracking/
これみたら大体書いてあります。

以降、リファレンスの主だった部分をまとめつつ足りない部分を補ったり、試してみての所感を書いていきます。

#公式サンプル
- `Oculus/SampleFramework/Usage/HandInteractionTrainScene`
- `Oculus/VR/Scenes/HandTest`

などのシーンは参考になると思うので、一度見ておくといいと思います。


また、デバック用に`OVRHandTest.cs`が用意されており、今回紹介した諸々の値を見ることが出来ます。



#とりあえず動かしてみる
`OVRCameraRig`の`HandAnchor`以下に`OVRHandPrefab`を入れる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422708/fc678b06-597a-de75-1310-8cff456ff1b6.png)
なお、`OVRManager/HandTrackingSuppport`が`ControllersOnly`の時は動作しないので、切り替えてあげる必要があります(自分の環境ではサンプルのHandTestシーンではオフになってました)
![OVRManager.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422708/088339a9-4e98-4fef-7adf-4ec8351f415b.png)

#OVRHandPrefabの各種設定

- `OVRHand`
      - HandType：対応する手を設定
- `OVRMesh`
    - MeshType：同上
- `OVRSkelton`
    - SkeltonType：同上
    - UpdateRootPose：オンにするとOVRCameraRigとの位置の依存性が無くなる。普通に使う分にはオフのままでいいでしょう
    - UpdateRootScale：現実の手の大きさから、表示される手のモデルの大きさを補正する
    - EnablePhysicsCapsules：コライダーを手につける（Hand_Thumb1_CapsuleColliderとか、Hand_Pinky3_CapsuleColliderなど結構細かくコライダーが分けられている模様）
- `OVRSkeltonRenderer`：デフォルトではオフになっているが、オンにすることで手の骨格が表示される





#OVRHand
よく使いそうな関数、パラメータをピックアップしていきます


###GetFingerIsPinching() と GetFingerPinchStrength()
読んで字のごとくですが、引数として渡した指がピンチしているか否かのbool値とfloat値を返してくれます。
引数には、Thumb, Index, Middle, Ring, Pinky　が設定できる

```
var hand = GetComponent<OVRHand>();

bool isIndexPinching = hand.GetFingerIsPinching(OVRHand.HandFinger.Index);
float  middlePinchStrength = hand.GetFingerPinchStrength(OVRHand.HandFinger.Middle);
```

**Thumb（親指）を指定した場合には、親指と任意の指においてのピンチを見る**ようです（おそらく親指の先端と一番近い指が選ばれている）

###IsTracked
手のトラッキングがとれているかどうか
###HandConfidence
トラッキングの信用性
`HandConfidence.High`と`HandConfidence.Low`があり、手を高速で動かしたり重ねたりするとLowになりました。

#手が重なったときに消える件について
これはOculusホーム画面でも同様の処理が行われてましたが、両手が重なったときには手の表示が消えます。
**実際には手の表示が消えていても手の座標等の情報自体は生きている**(かなり汚いですが)ので、拍手とかの判定くらいには使えそうです。

`OVRMeshRenderer.cs`にて、**トラッキングの精度が下がってるとき**(`IsDataValid && IsDateHighConfidence == false`)には**手のメッシュを消す**という処理が行われてようなので、これをごにょごにょすれば両手が重なったときにも表示を残せそうです。

・・・が、ハンドトラッキングがまだテスト版で、本来はアクセスすることを想定していない部分を弄ることになるのでよほどのことがない限りおすすめしません。


自分も試しにやってみたんですが、カメラ側の手はほとんどぶれませんが、隠れている方の手は結構飛ぶのでメッシュ消したくなるのもわかる感じでした。


各指のTransform情報は取れてるので、試しに各座標にSphereを生成したらこんな感じになりました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">各指関節の情報はOVRSkeltonで色々取れるみたい<br>動画は各座標に球をつけてみて手を動かしてみた様子<br>カメラ手前側の手は重なっても割と綺麗にトラックできてて、奥の手は大まかな位置はとれてるがランダムに飛ぶことがあるって感じですね<a href="https://twitter.com/hashtag/OculusQuest?src=hash&amp;ref_src=twsrc%5Etfw">#OculusQuest</a> <a href="https://t.co/ntConGwmN9">pic.twitter.com/ntConGwmN9</a></p>&mdash; ほるかす@めぐるーまー (@pwhrhs) <a href="https://twitter.com/pwhrhs/status/1208686631319789568?ref_src=twsrc%5Etfw">December 22, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

用途によりますが、拍手くらいならとれそうな感じなので割と実用圏内くらいにはトラッキング出来てる気がします。


#ハンドジェスチャーについて
例の**Oculusマークのハンドジェスチャーは**`OVRHand.cs`の`IsSystemGestureInProgress`でとれるようです。
内部的には手のひらがカメラ側を向いたときに人差し指のピンチを見ているらしい。

基本的なハンドジェスチャーはOculus側で用意してくれてないかな～とか思ったのですが、自分の調べた範囲ではなさそうなので**自作する必要がありそう**です。
といってもあまり凝ったものでなければ、前述の**`GetFingerIsPinching()` と `GetFingerPinchStrength()`**を使えば簡単に実装できそうです。

とりあえずグー、チョキ、パーを作ったので載せときます。閾値はかなり適当に決めたので調整しつつ使ってもらえればと思います。

```
public bool Guu
    {
        get
        {
            //薬指と小指は値が0になることが多いのであきらめた
            return (hand.GetFingerPinchStrength(OVRHand.HandFinger.Middle) >= 0.5f &&
                    hand.GetFingerPinchStrength(OVRHand.HandFinger.Index) >= 0.1f);
        }
    }

    public bool Choki
    {
        get
        {
            return ((hand.GetFingerPinchStrength(OVRHand.HandFinger.Pinky) >=0.05f) && (hand.GetFingerPinchStrength(OVRHand.HandFinger.Ring) >= 0.2f));
        }
    }

    public bool Paa
    {
        get
        {
            return (hand.GetFingerPinchStrength(OVRHand.HandFinger.Thumb) <= 0.01f);
        }
    }

void Update()
    {

        if (Guu)
        {
            Debug.Log("GUU");
        }
        else if (Choki)
        {
            Debug.Log("CHOKI");
        }
        else if (Paa)
        {
            Debug.Log("PAA");
        }
        else
        {
            Debug.Log("NO SIGN");
        }

    }
```

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">OVRHandのGetFingerPinch Strengthを使って、ハンドジェスチャーの判定してみた<br>これでキャラクターとジャンケンできるのでは？！<br>意地悪に変なポーズしたりしてもtrueにしちゃう時あるので、精度求めるなら各指関節のtransformにアクセスして距離とか諸々で判定してやる必要がありそう<a href="https://twitter.com/hashtag/OculusQuest?src=hash&amp;ref_src=twsrc%5Etfw">#OculusQuest</a> <a href="https://t.co/C6yyiP93XP">pic.twitter.com/C6yyiP93XP</a></p>&mdash; ほるかす@めぐるーまー (@pwhrhs) <a href="https://twitter.com/pwhrhs/status/1208411292983119872?ref_src=twsrc%5Etfw">December 21, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

中指と人差し指は割ときれいな値をとれるんですが、薬指小指は若干精度悪い印象でした。

また、グーの判定ですが、実際にグーポーズすればわかると思いますが、指の先端ではなく各指の第1,2関節と親指の先端が交わるため、GetFingerPinchStrength()では満足な値が得られません。

なので、**より精度を求める場合には、各指関節の値をとってそれと判定を行った方がいい**気がします。（`OVRSkelton.Bones`を使って指のボーン情報をとることが出来る）

今回は記事が長くなりそうなので、またの機会にハンドジェスチャーについては書こうと思います。

#その他細かな点
- ハンドトラッキングするためには、Questにビルドする前にQuestのホーム画面の設定で「手を使用」に切り変える必要がある。（アプリ内で切り替える方法があるかは現在未検証）
- コントローラーと手を同時にトラックすることはできない（今のところは）