アバター操作における手のポーズ判定

# 概要
この記事では、UnrealEngine（以下UE）でUltraLeapという深度センサーを使って手の動きをキャプチャーし、手のポーズでエフェクトを操作するという処理を実装したので紹介します。
そのなかで、手のポーズの判定の仕方について焦点を絞った内容になります。

# 経緯
自分の手から炎を出して操りたいと思ったことありませんか？
私はあります。そんな感じのやつの具現化です。

# 参考動画
最終こんな感じになります

https://youtu.be/f2f4J4YfY38

# UltaraLeap
まず、UltaraLeapの紹介です。
https://cornestech.co.jp/manufacturer/ultraleap

概要でも説明したように深度センサーを使って手の動きをキャプチャーしてくれる機械です。
UE用にプラグインも用意されているので導入も比較的容易です。

詳しく説明するのは、手に余るので必要なものだけ紹介します。
LeapMotionデバイス

https://cornestech.co.jp/product/products_ultraleap-2-2-2

UltraLeapのSDK

https://developer.leapmotion.com/

UEのプラグイン

https://www.unrealengine.com/marketplace/ja/product/ultraleap-hand-tracking-plugin

# 実装内容
とりあえず、見栄えするようにアバターを操作したかったので
Vroidでキャラクターを作成し、
VRM4Uを使ってUnrealEngine上で操作できるようにしました。
おそらくご存じかと思いますが、簡単に紹介しますと
Vroidは人型キャラクターをVRM形式で簡単に作成できるアプリケーションです
VRM4UはUEのプラグインでVRMというフォーマットをUEで扱うことを容易にしてくれるものです。
この二つのツールを使うことによって短時間でオリジナルのキャラクターをUE上に召喚できます。
ホントありがたいです。

https://vroid.com/

https://ruyo.github.io/VRM4U/

### 手のポーズの判定
デバイスとプラグインを使うことによって、キャプチャーした手の動きをキャラクターに反映させることはできますが、手のポーズの判定処理は自分で実装しなくてはいけません。
どのように、判定しようかと考えていたところありがたい記事がありましたので参考にしました。
内容としましては、各指の曲げ具合で、開いているか閉じているかの二値の状態をとり、それぞれの指の状態の組み合わせでポーズを判定するというものです。

https://dev.classmethod.jp/articles/mediapipe-recognize-hand-pose-with-multi-hand-tracking/

ということで、指の曲げ具合の取得は各指のボーンのベクトルの角度計算で取得できると考えました。

- まず、始めに指のボーンを再帰的に処理するためMapオブジェクトに格納します。

1. テーブルにキーとなるボーン名と付属情報を登録
![スクリーンショット_20221204_011432.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/dd82531c-7fd3-c997-9357-557b8e235977.png)


2. 処理しやすくするためテーブル情報から構造体のリストに変換
![スクリーンショット_20221204_011305.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/550c57c9-40b1-0ee9-f2c0-9cc80670e56d.png)
![スクリーンショット_20221204_010811.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/d1072ae8-fd1f-34eb-b2fe-646f426319e1.png)

3. 構造体のリストのキー情報から各指のボーンツリーを構造体のリストに変換
![スクリーンショット_20221204_012759.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/8a95f4fd-fca7-97a5-976d-604ec01354ae.png)
![スクリーンショット_20221204_012821.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/448d621a-4075-0e79-a5a2-c26be3f5ecd3.png)


ボーン名のマップの方法ですが、ブループリントでスケルタルメッシュのツリー情報から取得できれば手首の骨をルートにして再帰的に取得するなどもっと簡潔にできると思うのですが、指定のソケット名のチャイルドオブジェクトを取得するような処理が見つけられなかったのでテーブルで処理しています。
良い方法知っていたら、ぜひ知りたいところです。

- 次に手のポーズのテーブルを定義してマップします
例えば、グーならすべてClose、サムズアップなら親指がOpenでその他の指がCloseといった具合です。
1. ポーズのテーブルを定義
![スクリーンショット_20221204_015041.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/78e37cc5-0845-1fac-636a-9d803b4504b7.png)
![スクリーンショット_20221204_015101.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/8d332119-060b-6bf6-e63a-3f112f7e0e50.png)
![スクリーンショット_20221204_015133.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/79aa86d3-9e7a-33fb-cfab-a633e4d48eb4.png)

2. マップに格納
ここではすべての指の状態をひとまとめにしてキーとしています
![スクリーンショット_20221204_015237.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/86c52082-7170-4051-401f-179ad94a13b4.png)
![スクリーンショット_20221204_015618.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/f1272a67-ba03-36b3-9896-bdc162b48d7a.png)

- そして本題のポーズの判定処理
Tickでの処理となります
指の各状態を取得し、前段階で取得したポーズマップから手のポーズを判定します。
指の状態の取得は、各指の関節毎に角度計算といえばおなじみの内積で行います。
内積は各指の関節が同じ方向を向いている場合、最大値の1となり逆方向を向くと-1となります。
よって、1～-1の間で閾値をとり閾値以上であればOpen、以下ならCloseとします。
親指は他の指より判定が甘くなるので、閾値を調整してCloseになりやすくします。
1. 指のボーン名の構造体のリストで各指をForLoopで処理し、指の角度計算メソッド（CalcFingerPose）で指の開閉状態をポーズの状態として取得します。
![スクリーンショット_20221204_021328.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/34a471d8-44b4-03f0-c034-5d79f262d6d7.png)
CacFingerPoseメソッド
![スクリーンショット_20221204_020827.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/199b8641-dbbf-da28-5366-fe0f011b215c.png)
1.1. CacFingerPoseメソッド詳細
各指の関節のベクトルを取得
![スクリーンショット_20221204_022835.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/60c073ea-13e7-abde-a730-22d9f40e238f.png)
取得した各指の関節のベクトルの内積から角度差を計算
![スクリーンショット_20221204_022852.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/782a63c0-0347-42c3-6ca4-6fd723ba6f30.png)
角度差の閾値で開閉状態を判定
![スクリーンショット_20221204_022915.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/c230679f-ac40-3162-c1a7-e649158231f0.png)
![スクリーンショット_20221204_023924.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953463/8da6f23e-31dc-9fdd-59b6-0ef0d09e05b5.png)

ポーズ判定の処理に関しては以上となります。
要点をポーズ判定に絞りエフェクト出力の処理などは紹介できず申し訳ないです。
現在、実装中でノードが整理できず説明できる状態でないので割愛しました。いい感じにできてきたら記事にしたいと思います。

ではみなさん、少し早いですがメリークリスマス＆ハッピーニューイヤー