OculusQuestのハンドトラッキングとUDP通信でライトの色を変えよう

# 今回作ったもの
まず今回作ったものを動画で示します。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Oculus Questのハンドトラッキングで色の頭文字を書いてその色にライトを変えるの出来た!!<br>QuestからUDPでラズパイに色を何にするか送ってラズパイからライトにirMagicianでデータの送信<a href="https://twitter.com/hashtag/Oculus?src=hash&amp;ref_src=twsrc%5Etfw">#Oculus</a> <a href="https://twitter.com/hashtag/RaspberryPi?src=hash&amp;ref_src=twsrc%5Etfw">#RaspberryPi</a> <a href="https://t.co/z05tpje7VB">pic.twitter.com/z05tpje7VB</a></p>&mdash; ryo (@Ah92082778) <a href="https://twitter.com/Ah92082778/status/1463018002673135618?ref_src=twsrc%5Etfw">November 23, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
このように、まず色の頭文字を書いてから指パッチンをするとライトの色を変えられるシステムを作りました。

# 利用したものの概要
- Unity 2019.4.18f1
- Oculus Quest
- ラズパイ model2B(記憶があいまいです)
- Python 2.7(ラズパイでアップデートしてなかったです。今後改善したいと思ってます。)
- IrMagician ラズパイで用いる赤外線リモコンシステムのためのパーツです。
- リモコン操作が出来るライト　IrMagicianから赤外線リモコンで色を変えます。

# 概要図
今回はOculus QuestにアプリをビルドせずOculus Linkを用いて動作させています。ビルドすれば図のデスクトップPC部分に関しては必要がなくなると思っています。（今後改良したいと思っています。）
Oculus Questのハンドトラッキングを用いて手の動きと指パッチンを認識しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199067/f5511641-20ea-19b3-91e0-2d4002a6dbb4.png)

# アイデア
今回OCRを用いて文字を認識しようと考えていました。その際に以下の二点が思いつきました。 
 
1. Unity側でOCRを行ってUDPで文字をラズパイに送る。 
2. Unity側で書いたものを画像として保存しラズパイに送り、画像からOCRを行って文字を認識する。 

しかし、1では簡単に利用できそうなものを見つけられませんでした。OpenCVのアセットやTesseractを用いれば出来るかもしれません。2に関しては画像からOCRを行うのがラズパイ2では性能不足に感じ速度が出なかったため断念しました。
そのため、今回は精確な文字認識ではなく複数の点から文字を識別するようにしました。
画像のようにUnityで9つの点を置き、それぞれの当たり判定を用いてR,B,Yのどれかということ認識するようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199067/e3d7e25b-7137-8002-ba6b-b5f3bc21804d.png)


# コードなどの説明
今回作成したものは大きく分けるとUnity側とラズパイ側二つに分かれます。
## Unity(OculusQuest側）
- ハンドトラッキングをして文字を認識する
- UDP通信する

まずはUnity側ではどのように見えているのかを示します。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199067/bdcec746-8130-a0f9-e0a1-89829b0ada26.png)
このようにハンドトラッキングの指の先に当たり判定があるオブジェクトを子オブジェクトとして設定します。そして、目の前に9つの当たり判定がついているSphereを表示します。
Unityのヒエラルキーは以下の画像のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199067/ebb09169-7d0a-243c-ddd6-0c26ae78def7.png) 

実際に書いたコードについての説明をします。 ハンドトラッキングについてはこの記事がとても参考になりました。

https://qiita.com/divideby_zero/items/4949fadb2c60f810b3aa

```csharp
using UnityEngine;
using UnityEngine.UI;

public class YubiPatchin : MonoBehaviour
{
    //ラズパイのIPアドレスとポート番号を指定します。
    public string remoteHost = "";
    public int remotePort = 60000;
   
    //タッチするSphereを取得します。
    [SerializeField]
    GameObject[] touchSphereObj;
    //Udp通信を行うためのコードです。
    UdpSender udpSender;
    [SerializeField]
    OVRSkeleton oVRSkeleton;
    Vector3 oyayubi;
    Vector3 nakayubi;
    Vector3 hitosashi;
    [SerializeField]
    Text text;

    float stepTime = 0f;
　　//これに関しては配列を用いればよかったと後悔しています。今後修正予定です。
    public bool touchSphereFlag1 = false;
    public bool touchSphereFlag2 = false;
    public bool touchSphereFlag3 = false;
    public bool touchSphereFlag4 = false;
    public bool touchSphereFlag5 = false;
    public bool touchSphereFlag6 = false;
    public bool touchSphereFlag7 = false;
    public bool touchSphereFlag8 = false;
    public bool touchSPhereFlag9 = false;

      
    // Start is called before the first frame update
    void Start()
    {
        udpSender = new UdpSender(remoteHost, remotePort);
    }

    // Update is called once per frame
    void Update()
    {
        //ここで指パッチンの動作を判定するために親指と中指と人差し指の先のpositionを取得します。
        oyayubi = oVRSkeleton.Bones[(int)OVRSkeleton.BoneId.Hand_Thumb2].Transform.position;
        nakayubi = oVRSkeleton.Bones[(int)OVRSkeleton.BoneId.Hand_MiddleTip].Transform.position;
        hitosashi = oVRSkeleton.Bones[(int)OVRSkeleton.BoneId.Hand_IndexTip].Transform.position;
        //UDPで通信する際に連続して何度も送らないように五秒ごとに送れるようにしました。
        stepTime += Time.deltaTime;
        if (stepTime > 5.0f) {
            //指パッチンをしているかどうかの判定です。
            if ((oyayubi - nakayubi).magnitude < 0.03f && (hitosashi - oyayubi).magnitude > 0.05f)
            {
                text.text = "yubiPatchin";
                //タッチされていないSphereごとになんの文字化を判定しています。
                //udpSender.SendData()でラズパイに文字データとして送信しています。
                if (touchSphereFlag8 == false)
                {
                    udpSender.SendData("RED");
                    text.text = "RED";
                }
                else if (touchSphereFlag2 == false)
                {
                    udpSender.SendData("YELLOW");
                    text.text = "YELLOW";
                }
                else
                {
                    udpSender.SendData("BLUE");
                    text.text = "BLUE";
                }
                for (int i = 0; i < 9; i++)
                {
                    touchSphereObj[i].GetComponent<Renderer>().material.color = Color.white;
                }
                touchSphereFlag1 = false;
                touchSphereFlag2 = false;
                touchSphereFlag3 = false;
                touchSphereFlag4 = false;
                touchSphereFlag5 = false;
                touchSphereFlag6 = false;
                touchSphereFlag7 = false;
                touchSphereFlag8 = false;
                touchSPhereFlag9 = false;
                stepTime = 0f;
            }
        }

    }
}

```

そしてタッチされるSphereには以下のスクリプトをアタッチします。

```csharp
using UnityEngine;

public class TouchSphere : MonoBehaviour
{
    //このnumberはSphereの場所を示しています。
    //123
    //456
    //789
    //で対応しています。
    [SerializeField]
    int number;
    [SerializeField]
    GameObject HandTrack;
    YubiPatchin script;

    // Start is called before the first frame update
    void Start()
    {
        script = HandTrack.GetComponent<YubiPatchin>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    private void OnTriggerEnter(Collider other)
    {
        gameObject.GetComponent<Renderer>().material.color = Color.yellow;
        switch (number)
        {
            case 0:
                script.touchSphereFlag1 = true;
                break;
            case 1:
                script.touchSphereFlag2 = true;
                break;
            case 2:
                script.touchSphereFlag3 = true;
                break;
            case 3:
                script.touchSphereFlag4 = true;
                break;
            case 4:
                script.touchSphereFlag5 = true;
                break;
            case 5:
                script.touchSphereFlag6 = true;
                break;
            case 6:
                script.touchSphereFlag7 = true;
                break;
            case 7:
                script.touchSphereFlag8 = true;
                break;
            case 8:
                script.touchSPhereFlag9 = true;
                break;
        }
    }
}

```

次にUdpSenderについてなのですが、今回はこの記事を参考にさせていただいたのでコード自体は記事を参照してください。

https://yubeshicat.hatenablog.com/entry/2019/10/31/163435

そしてそれらをGameObjectのHandTrakingにアタッチします。そしてラズパイのIPアドレスとポート番号を入力してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199067/1dc0c29e-807f-bd31-dda6-5dc7e75332d0.png)

## ラズパイ
- UDPで受け取る 
- IrMagicianを使って赤外線をライトに送る

まずUDPでの受け取りに関してもこのサイトを参考にさせていただきました。

https://yubeshicat.hatenablog.com/entry/2019/10/31/163435

IrMajicianのコードをsudo で実行するためにSubProcessを用いました。

IrMagicianについてはここで利用方法について書くと長くなってしまうので割愛します。
以下のサイトがとても分かりやすいので、このサイトを見ながら赤外線リモコンを使って設定をしてください。

https://itdecoboconikki.com/2017/05/01/raspberry-pi-irmagician/

そして設定したファイルを用いてライトを変更するための以下のコードを実行します。

```python
#coding:utf-8

import threading
import time
import signal
import sys
import serial
import subprocess
from socket import socket, AF_INET, SOCK_DGRAM

HOST = ''   
PORT = 60000


is_running = True
angle_data = bytes(b'')
   
# UDP通信部
sock = socket(AF_INET, SOCK_DGRAM)
def udp_communication ():
	First = False
        First2 = False
	global is_running
	global sock
	global angle_data
	sock.bind((HOST, PORT))
	try:
		while is_running:
			msg, address = sock.recvfrom(64)
			angle_data = msg
			if msg == "RED":
                subprocess.call(["sudo","python","irmcli/irmcli.py", "-p", "-f","irmcli/RED.json"])
			if msg == "BLUE":
                subprocess.call(["sudo","python","irmcli/irmcli.py", "-p", "-f","irmcli/BLUE.json"])
            if msg == "YELLOW":
                subprocess.call(["sudo","python","irmcli/irmcli.py", "-p", "-f","irmcli/YELLOW.json"])
	except KeyboardInterrupt:
		print ("Keyboard Interrupt Exception")

if __name__ == "__main__":
	thread = threading.Thread(target=udp_communication)
	thread.start()

```

# 終わりに
自分ではとても面白いものを作れたと思っています。
今後はこれをもっと改良しながらいろいろと出来るようにしていきたいと思っています。今後はラズパイのGPIOピンを使ってをサーボモータなどと組み合わせたりして何か作りたいと思っています。

# 参考資料
UnityとラズパイをUDPでつなぐ際に参考にさせていただいたサイト

https://yubeshicat.hatenablog.com/entry/2019/10/31/163435

Oculus Questのハンドトラッキングにおいて参考にさせていただいたサイト

https://qiita.com/divideby_zero/items/4949fadb2c60f810b3aa

IrMagicianの使い方についてのとても分かりやすい記事

https://itdecoboconikki.com/2017/05/01/raspberry-pi-irmagician/