AzureKinectで肩乗りペット

クソアプリ2 Advent Calendar 2020
9日目がんばります💪

# はじめに
みなさんは肩乗りぺット好きですか？
主人への愛が感じられるとても微笑ましい行動です。
どれもとても可愛らしいですね。

![images1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/c68d871b-8966-fee3-62c1-82f15427bbbe.jpeg)
![images2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/64c95e0c-0b2a-e327-0421-cfadc40f90c1.jpeg)
![images3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/f096f7be-fa1e-92e0-5d09-d70cf8d5d04a.jpeg)
![images4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/9c9c93ee-79a4-1210-3e79-bec2a753c6ea.jpeg)

これらの事象を基に私は仮説を立てました。
「どんな忌み嫌われるモノでも人の肩に乗れば可愛くなるのではないか」

そこで皆が嫌がるモノ、「クソ」で実験してみたいと思います。（無理矢理）

# つくる
さっそくクソ（を可愛くする）アプリを作っていきましょう。

目下R&Dに勤しんでいる[AzureKinectDK](https://azure.microsoft.com/ja-jp/services/kinect-dk/)とUnityを使って実現します。
AzureKinectDKは5万円弱で、ウェアラブルセンサー無しにボディトラッキングできちゃう優れものです。
AzureKinectDKで肩の位置をトラッキングして、Unityで肩の座標にクソペットを表示してやります。

Unityのソースコードはこれだけ。

```C#
using Microsoft.Azure.Kinect.BodyTracking;
using Microsoft.Azure.Kinect.Sensor;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
namespace BodyEffect
{
    public class ShoulderPet : MonoBehaviour
    {
        [SerializeField]
        GameObject kusoPetPrefab;
        [SerializeField]
        RawImage cameraScreen;
        private Device kinect;
        private Tracker tracker;
        private Texture2D texture;
        private GameObject kusoPet;
        private void Awake()
        {
            this.kinect = Device.Open(0);
            this.kinect.StartCameras(new DeviceConfiguration
            {
                ColorFormat = ImageFormat.ColorBGRA32,
                ColorResolution = ColorResolution.R720p,
                DepthMode = DepthMode.NFOV_2x2Binned,
                SynchronizedImagesOnly = true,
                CameraFPS = FPS.FPS30
            });
            var width = kinect.GetCalibration().ColorCameraCalibration.ResolutionWidth;
            var height = kinect.GetCalibration().ColorCameraCalibration.ResolutionHeight;
            this.texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
            this.texture.Apply();
            this.cameraScreen.texture = this.texture;
            this.tracker = Tracker.Create(kinect.GetCalibration(), TrackerConfiguration.Default);
            this.kusoPet = Instantiate(this.kusoPetPrefab, this.transform);
            var t = this.KinectLoop();
        }
        private void OnDestroy()
        {
            this.kinect.StopCameras();
        }
        private async Task KinectLoop()
        {
            while (true)
            {
                using (var capture = await Task.Run(() => this.kinect.GetCapture()).ConfigureAwait(true))
                {
                    var colorImage = capture.Color;
                    var colorArray = colorImage.GetPixels<BGRA>().ToArray();
                    var colors = new Color32[colorArray.Length];
                    for (var i = 0; i < colorArray.Length; i++)
                    {
                        colors[i].b = colorArray[i].B;
                        colors[i].g = colorArray[i].G;
                        colors[i].r = colorArray[i].R;
                        colors[i].a = 255;
                    }
                    this.texture.SetPixels32(colors);
                    this.texture.Apply();
                    this.tracker.EnqueueCapture(capture);
                    var frame = tracker.PopResult();
                    if (frame.NumberOfBodies > 0)
                    {
                        var joint = frame.GetBodySkeleton(0).GetJoint(JointId.ShoulderRight);
                        // 生座標は動きすぎるので適当に調整
                        var jointPos = new Vector3(
                                -1 * joint.Position.X / 30,
                                -1 * joint.Position.Y / 30 + 6,
                                joint.Position.Z / 30
                            );
                        this.kusoPet.transform.localPosition = jointPos;
                    }
                }
            }
        }
    }
}
```

こいつを空オブジェクトにAddComponentして、各種オブジェクトをアタッチしてやります。
![image_2020_12_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/5f6efe1c-22ca-9e85-363d-da289dc7a73c.png)
注意点としては、カメラ映像をRawImageUIを利用して投影しているところ。
![image_2020_12_3 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/d857153f-4be8-7270-cfc9-600dea769368.png)
ペットにあたるGameObjectがUIより手前に表示できるようにする必要があります。
![image_2020_12_3 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/db9e7214-9871-fc29-7361-03d72c8b9e21.png)
ここまでできたらデスクトップアプリにビルドして完成！

# Play
![Videotogif (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478145/f099bdf0-581d-bde5-2bd2-8b32ebd942b1.gif)
元々可愛くデフォルメされた画像を利用してしまったので実験効果がわかりにくいですね。
次回は実写で検証してみたいと思います。（嘘）