【Unity MediaPipe Plugin】ハンドトラッキングを使ったショートカットアプリを作る

# このアプリを作ろうと思った経緯
以前Oculus Questのハンドトラッキングを利用してラズパイからライトの色を変更するシステムを作成しました。このシステムではOculus Questをかぶり、ラズパイをUDPサーバとして起動しなければなりませんでした。そこで今回はより簡単に、利用することが出来るものを作成しようと考えました。

https://qiita.com/Ah_/items/c6689292d3b8bc218bbb

### おしゃれかつ便利なショートカットを作りたい
自分がよくアクセスするURLを楽に開くことが出来るショートカットを作成したいと思いました。そのために、Mediapipe を利用して手で特定のポーズをした際にURLをブラウザで開くことの出来るアプリが欲しいと思いました。

# 利用した技術とバージョン
- Unity 2021.3.3f1
- Media Pipe Unity Plugin

https://github.com/homuler/MediaPipeUnityPlugin

# MediaPipeとは
MediaPipeとはGoogle が主導しているクロスプラットフォーム対応されている機械学習ライブラリです。MediaPipeでは今回利用したハンドトラッキングだけでなく、顔の向きや眼球の光彩、ポーズ特定などを動画などにおいて利用することが出来ます。プラットフォームとしてAndroid, iOS, C++, Python, JSで利用することが出来ます。（プラットフォームによっては眼球の光彩など利用できないものもあります。）今回はこのMediaPipeをUnityで利用することが出来るようにしてくださった、UnityPluginを利用してアプリを作成しました。

https://google.github.io/mediapipe/


# 目標
1. Unity MediaPipe Pluginのハンドトラッキングを利用して、手で特定のポーズをした際に指定したURLを開けるようにする。
1. InputFieldを設置してポーズから開くことの出来るURLを簡単に変更することが出来るようにする。

# 作成したアプリの動画
ポーズにそれぞれURLを設定して、ブラウザで開かれるようにしました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Unity MediaPipe Pluginを使わせてもらって、ハンドトラッキングの指先の距離を判定して指定したURLをブラウザで開くアプリ出来た。 <a href="https://t.co/ZYq9aRpjw2">pic.twitter.com/ZYq9aRpjw2</a></p>&mdash; ryo (@Ah92082778) <a href="https://twitter.com/Ah92082778/status/1611571907438215168?ref_src=twsrc%5Etfw">January 7, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 今回の実装を行ったところ
必要な情報として指の位置ごとのTransform.positionを取得し、指先ごとの距離を比較することで三つのポーズを取れるようにしました。
指のどの位置がMediapipeの番号はどこにあたるのかについては以下のMediapipeのリンクを参考にします。

https://google.github.io/mediapipe/solutions/hands.html

今回利用させていただいたPluginの中において指の位置を取得している部分を探し、その変数を利用して手のポーズを判定する関数を実行しています。比較に使用している数値は自分の環境で数値を確認しながら設定しました。MultiHandLandmarkListAnnotation.cs内において下記の関数と [SerializeField]を利用して登録したURLをブラウザで開くことが出来るように実装しました。シーンについてはUnity MediaPipePluginのSampleシーンを変更させて使わせていただきました。

下記コードのclass名が異なっていたので修正しました。(2023/07/01)

```c#:MultiHandLandmarkListAnnotation.cs
 public sealed class MultiHandLandmarkListAnnotation : ListAnnotation<HandLandmarkListAnnotation>
  {
    // アプリのURLを入力した値を利用するためのText
    [SerializeField] Text _Pose1;
    [SerializeField] Text _Pose3;
    [SerializeField] Text _PoseFox;
    // 手のポーズをとった際に一度だけ実行するためのbool値
    private bool checkFlag = false;
    // ... 略

    public void SetHandedness(IList<ClassificationList> handedness) {
        // ... 略
        checkHandDistance();
    }
    // 手のポーズを判定するための関数
    public void checkHandDistance() {
      //人差し指と小指の先が親指の先から離れているかどうかを確認
      if (!checkFlag && Vector3.Distance(children[0][4].transform.position, children[0][8].transform.position) >= 30.0 
                     && Vector3.Distance(children[0][4].transform.position, children[0][20].transform.position) >= 30.0)
      {
        // ポーズのInputFieldで入力したURLを開く実装
        var uri = new Uri(_PoseFox.text, UriKind.Absolute);
        Application.OpenURL(uri.AbsoluteUri);
        checkFlag = true;
      }
      // 人差し指と中指と薬指の先が親指の先から離れているかどうかを確認
      else if (!checkFlag && Vector3.Distance(children[0][4].transform.position, children[0][8].transform.position) >= 30.0 
                          && Vector3.Distance(children[0][4].transform.position, children[0][12].transform.position) >= 30.0
                          && Vector3.Distance(children[0][4].transform.position, children[0][16].transform.position) >= 30.0)
      {
        var uri = new Uri(_Pose3.text, UriKind.Absolute);
        Application.OpenURL(uri.AbsoluteUri);
        checkFlag = true;
      }
      // 人差し指が親指の先から離れているかを確認
      else if (!checkFlag && Vector3.Distance(children[0][4].transform.position, children[0][8].transform.position) >= 30.0)
      {
        var uri = new Uri(_Pose1.text, UriKind.Absolute);
        Application.OpenURL(uri.AbsoluteUri);
        checkFlag = true;
      }
      // ポーズをリセットして次のポーズを出来るようにするためのリセット処理　
      if (checkFlag && Vector3.Distance(children[0][4].transform.position, children[0][8].transform.position) < 10.0)
      {
        checkFlag = false;
      }
    }
}

```

# 今後の展望
- まずアプリを閉じるとリンクが消えてしまうので、ファイル形式などで保存してアプリを起動し直した際にもリンクが残っているようにしたいと考えています。
- ポーズの数が三つのみになってしまっているので、種類を増やせるようにしたいと考えています。
- ポジションを取得することが出来るところに判定の処理を挟んでいるのですが、より効率よく判定出来るところがあるのではと思っているので修正したいと思っています。
- 現状はURLを登録するだけですが、アプリケーションの起動や以前の記事のようにUDPを利用してネットワーク越しに家電を動かすなどしていきたいです。

# 今後記事にしていきたいもの
MediaPipeとAndroidアプリを組み合わせてUDPを利用してスクロールするシステムも作成したので今後記事にしていきたいと思います。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">PCでMediaPipe使ってハンドトラッキングして、UDPでどっちにスクロールするかを送信、スマホ側でUDPのデータからスクロール出来た。<br>もうちょっとスムーズに動かせるようにしたい。<br>(動画撮ってからスクロール向き逆だと気づいた) <a href="https://t.co/mNzdvkkNUF">pic.twitter.com/mNzdvkkNUF</a></p>&mdash; ryo (@Ah92082778) <a href="https://twitter.com/Ah92082778/status/1614575875441451011?ref_src=twsrc%5Etfw">January 15, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 参考にさせて頂いたリンク

https://github.com/homuler/MediaPipeUnityPlugin

https://learn.microsoft.com/ja-jp/dotnet/api/system.uri?view=net-7.0

https://github.com/google/mediapipe

https://google.github.io/mediapipe/solutions/hands.html