【Unity】Mediapipeでハンドトラッキング + 人物切り抜き（ONNX or TouchDesignerからSpout）

# 概要
ハンドトラッキングと人物切り抜きをUnity単体で完結させる方法と、TouchDesignerからSpoutで送受信する方法を試したのでその記録。

### 目次
1. `Mediapipe`でハンドトラッキング
2. `keijiro/NNCam`で人物切り抜き
3. TouchDesignerの`Nvidia Background TOP`で切り抜き、`Spout`でやり取り

最終的には`Nvidia Background TOP`が優秀で、Unityで動かすよりディレイが生じないTouchDesigner単体でやるほうが良さそうとなった。
コード全文載せてるので長いです。

# 環境
OS：Windows 10 Home
CPU：i7-8750H
GPU：NVIDIA GeForce RTX 2070 Max-Q

Unity 2021.3.4f1

### 使用したプラグイン

- ハンドトラッキング
[MediaPipeUnityPlugin](https://github.com/homuler/MediaPipeUnityPlugin)


- 人物切り抜き
[keijiro/NNCam](https://github.com/keijiro/NNCam)

- UnityでSpout
[keijiro/KlakSpout](https://github.com/keijiro/KlakSpout)


### 


# 1. Mediapipeでハンドトラッキング
https://haruton3301.com/articles/unity-mediapipe-sample

↑こちらの記事を参考に、MediaPipeUnityPluginを使用した。

### 準備

プロジェクトファイルをZipでダウンロードした後、[Releases/v0.10.3](https://github.com/homuler/MediaPipeUnityPlugin/releases/tag/v0.10.3)の`MediaPipeUnity.0.10.3.unitypackage`をimportして`Assets/MediaPipeUnity/Samples/Scenes`が動くことを確認する。

サンプルシーンは中身をいじるには複雑な構造になっていたので、Wikiの[Tutorial/Official Solution](https://github.com/homuler/MediaPipeUnityPlugin/wiki/Getting-Started#official-solution)を順番に真似ていくことにした。

`Face Tracking`部分を一通りなぞった後、以下2つから`hand_landmark_full.bytes`や`sidePacket`を参考にして`Hand Tracking`に対応させていった。

https://github.com/homuler/MediaPipeUnityPlugin/issues/754

https://github.com/homuler/MediaPipeUnityPlugin/issues/780

### 結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/5ece3123-f41b-e511-fe55-2525195d2d2b.png)

### マーカーを表示するコード全文
`Inspector`はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/5c430b59-ede6-8cba-d4bf-a0763b0c6533.png)

```HandtrackingAddLayer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Mediapipe.Unity.CoordinateSystem;
using Stopwatch = System.Diagnostics.Stopwatch; // for Timestamp

namespace Mediapipe.Unity.Tutorial
{
	public class HandtrackingAddLayer : MonoBehaviour
	{
		[SerializeField] private RawImage _screen;
		[SerializeField] private int _width;
		[SerializeField] private int _height;
		[SerializeField] private int _fps;
		// ----以下追加-----
		// ハンドトラッキング用
		[SerializeField] private TextAsset _configAsset;
		private CalculatorGraph _graph;
		private ResourceManager _resourceManager;
		private enum ModelComplexity { Lite = 0, Full = 1,}
		[SerializeField] private ModelComplexity _modelComplexity = ModelComplexity.Full;
		[SerializeField] private int _maxNumHands = 2;
		// カメラ入力用
		private WebCamTexture _webCamTexture;
		private Texture2D _inputTexture;
		private Color32[] _pixelData;
		// 重ねるマーカー用
		[SerializeField] private MultiHandLandmarkListAnnotationController _annotationController;

		private IEnumerator Start()
		{
			if (WebCamTexture.devices.Length == 0)
			{
				throw new System.Exception("Web Camera devices are not found");
			}
			// 使用するカメラを選択
			var webCamDevice = WebCamTexture.devices[0];
			_webCamTexture = new WebCamTexture(webCamDevice.name, _width, _height, _fps);
			_webCamTexture.Play();

			yield return new WaitUntil(() => _webCamTexture.width > 16);
			_screen.rectTransform.sizeDelta = new Vector2(_width, _height);

			// ----以下追加-----
			// MediaPipeでは、CPU 上の画像データがImageFrameクラスに格納される
			_inputTexture = new Texture2D(_width, _height, TextureFormat.RGBA32, false);
			_pixelData = new Color32[_width * _height];

			// WebCamの映像を表示
			_screen.texture = _webCamTexture;

			_resourceManager = new LocalResourceManager();
			if (_modelComplexity == ModelComplexity.Lite)
			{
				yield return _resourceManager.PrepareAssetAsync("hand_landmark_lite.bytes");
				yield return _resourceManager.PrepareAssetAsync("hand_recrop.bytes");
				yield return _resourceManager.PrepareAssetAsync("handedness.txt");
				yield return _resourceManager.PrepareAssetAsync("palm_detection_lite.bytes");
			}
			else
			{
				yield return _resourceManager.PrepareAssetAsync("hand_landmark_full.bytes");
				yield return _resourceManager.PrepareAssetAsync("hand_recrop.bytes");
				yield return _resourceManager.PrepareAssetAsync("handedness.txt");
				yield return _resourceManager.PrepareAssetAsync("palm_detection_full.bytes");
			}

			// configtextは外部から読み込む
			_graph = new CalculatorGraph(_configAsset.text);
			// ランドマークの取得
			var handLandmarksStream = new OutputStream<NormalizedLandmarkListVectorPacket, List<NormalizedLandmarkList>>(_graph, "hand_landmarks");
			handLandmarksStream.StartPolling().AssertOk();

			// sidePacketを作ってStartRunに渡す
			var sidePacket = new SidePacket();
			sidePacket.Emplace("model_complexity", new IntPacket((int)_modelComplexity));
			sidePacket.Emplace("num_hands", new IntPacket(_maxNumHands));
            // カメラ映像とマーカーの座標は以下で合わせられる
			sidePacket.Emplace("input_rotation", new IntPacket(0));
			sidePacket.Emplace("input_horizontally_flipped", new BoolPacket(false));
			sidePacket.Emplace("input_vertically_flipped", new BoolPacket(true));

			_graph.StartRun(sidePacket).AssertOk();

			var stopwatch = new Stopwatch();
			stopwatch.Start();

			// ランドマーク用
			var screenRect = _screen.GetComponent<RectTransform>().rect;

			while(true)
			{
				_inputTexture.SetPixels32(_webCamTexture.GetPixels32(_pixelData));
				var imageFrame = new ImageFrame(ImageFormat.Types.Format.Srgba, _width, _height, _width*4, _inputTexture.GetRawTextureData<byte>());
				var currentTimestamp = stopwatch.ElapsedTicks / (System.TimeSpan.TicksPerMillisecond / 1000)
				_graph.AddPacketToInputStream("input_video", new ImageFramePacket(imageFrame, new Timestamp(currentTimestamp))).AssertOk();

				yield return new WaitForEndOfFrame();

				if (handLandmarksStream.TryGetNext(out var multiLandmarks))
				{
                    // マーカーの表示
					_annotationController.DrawNow(multiLandmarks);

					// 位置の表示
					if (multiLandmarks != null && multiLandmarks.Count > 0)
					{
						foreach (var landmarks in multiLandmarks)
						{
							// landmarks.Landmark[9]：中指の付け根の位置を取得
							var posTarget = landmarks.Landmark[9];
							Debug.Log($"Unity Local Coordinates: {screenRect.GetPoint(posTarget)}, Image Coordinates: {posTarget}");
						}
					}
				}
				else
				{
					_annotationController.DrawNow(null);
				}
			}
		}

		private void OnDestroy()
		{
			if (_webCamTexture != null)
			{
				_webCamTexture.Stop();
			}

			if(_graph != null)
			{
				try
				{
					// InputStreamを閉じてCalculatorGraphを処分する
					_graph.CloseInputStream("input_video").AssertOk();
					_graph.WaitUntilDone().AssertOk();
				}
				finally
				{
					_graph.Dispose();
					Debug.Log("Done");
				}
			}
		}
	}
}
```

取得したい各LandMarkのIDは[ここ](https://google.github.io/mediapipe/solutions/hands.html#hand-landmark-model)から。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/c2ba6ab0-f2bd-df71-84eb-58739ef43bcc.png "Hand Landmarks")


# keijiro/NNCamで人物切り抜き
### 準備
[keijiro/NNCam](https://github.com/keijiro/NNCam)のプロジェクトファイルをZipでダウンロードした後、`Room`フォルダを`NNCam`フォルダ下に移動して、`NNCam`フォルダを`unitypackage`としてExport。
Mediapipeのハンドトラッキングを動かしていたプロジェクトにimportする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/779d794c-88cd-894a-1b54-c0eb6eb38ba3.png)

`NNCam`の`README`に書いてあるとおり、[Releases/v0.0.1](https://github.com/keijiro/NNCam/releases/tag/v0.0.1)から`BodyPixOnnx.zip`をダウンロードし、作成した`ONNX`フォルダ下に置く。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/47400878-c73d-4855-12a7-3cba21e7da66.png)
`NNCam/Room`のシーンを再生しようとすると、`MediaPipeUnityPlugin`と`NNCam`で使用する`Barracuda`で、`Google.Protobuf.dll`というが被ってるぞというエラーが出るので、
```
Multiple precompiled assemblies with the same name Google.Protobuf.dll included on the current platform.
Only one assembly with the same name is allowed per platform.
```
`Library/PackageCache/com.unity.barracuda@80909e3320/Barracuda/Runtime/Plugins/ProtoBuffer/Google.Protobuf.dll`のほうを削除する。

プロジェクトを立ち上げ直すとエラーが解消されているはず。

### 結果
`NNCam`の`Model`は`Res Net 50-stride32`を使用。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/6bf95392-0497-1c22-696c-a0c905e98986.png)

### 人物切り抜きとハンドトラッキングを合わせたコード全文
`Inspector`はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/dfaa2134-48eb-3e5a-c20e-0af0263c5695.png)


```HandtrackingNNCam.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Stopwatch = System.Diagnostics.Stopwatch; // for Timestamp
using NNCam; // from keijiro

namespace Mediapipe.Unity.Tutorial
{
	public class HandtrackingNNCam : MonoBehaviour
	{
		// カメラ入力用
		private WebCamTexture _webCamTexture;
		[SerializeField] private int _width;
		[SerializeField] private int _height;
		[SerializeField] private int _fps;
		private Texture2D _inputTexture;
		private Color32[] _pixelData;
		[SerializeField] private RawImage _screen;
		// ハンドトラッキング用
		[SerializeField] private TextAsset _configAsset;
		private enum ModelComplexity { Lite = 0, Full = 1,}
		[SerializeField] private ModelComplexity _modelComplexity = ModelComplexity.Full;
		[SerializeField] private int _maxNumHands = 2;
		private CalculatorGraph _graph;
		private ResourceManager _resourceManager;
		// 重ねるマーカー用
		[SerializeField] private MultiHandLandmarkListAnnotationController _annotationController;

		// for NNCam
		[SerializeField] Texture2D _background = null;
		[SerializeField, Range(0.01f, 0.99f)] float _threshold = .5f;
		[SerializeField] ResourceSet _resources = null;
		[SerializeField] Shader _shader = null;
		private SegmentationFilter _filter;
		private Material _material;

        // 人物切り抜きのマスク
		void Update()
		=> _filter.ProcessImage(_webCamTexture);

		private IEnumerator Start()
		{
			// for NNCam
			_filter = new SegmentationFilter(_resources);
			_material = new Material(_shader);
			// NNCamのマテリアルをRawImageにあてる
			_screen.material = _material;

			if (WebCamTexture.devices.Length == 0)
			{
				throw new System.Exception("Web Camera devices are not found");
			}
			// 使用するカメラを選択
			var webCamDevice = WebCamTexture.devices[0];
			_webCamTexture = new WebCamTexture(webCamDevice.name, _width, _height, _fps);
			_webCamTexture.Play();

			yield return new WaitUntil(() => _webCamTexture.width > 16);

			_screen.rectTransform.sizeDelta = new Vector2(_width, _height);

			// MediaPipeでは、CPU 上の画像データがImageFrameクラスに格納される
			_inputTexture = new Texture2D(_width, _height, TextureFormat.RGBA32, false);
			_pixelData = new Color32[_width * _height];

			// WebCamの映像を表示
			_screen.texture = _webCamTexture;

			_resourceManager = new LocalResourceManager();
			if (_modelComplexity == ModelComplexity.Lite)
			{
				yield return _resourceManager.PrepareAssetAsync("hand_landmark_lite.bytes");
				yield return _resourceManager.PrepareAssetAsync("hand_recrop.bytes");
				yield return _resourceManager.PrepareAssetAsync("handedness.txt");
				yield return _resourceManager.PrepareAssetAsync("palm_detection_lite.bytes");
			}
			else
			{
				yield return _resourceManager.PrepareAssetAsync("hand_landmark_full.bytes");
				yield return _resourceManager.PrepareAssetAsync("hand_recrop.bytes");
				yield return _resourceManager.PrepareAssetAsync("handedness.txt");
				yield return _resourceManager.PrepareAssetAsync("palm_detection_full.bytes");
			}

			// configtextは外部から読み込む
			_graph = new CalculatorGraph(_configAsset.text);
			// ランドマークの取得
			var handLandmarksStream = new OutputStream<NormalizedLandmarkListVectorPacket, List<NormalizedLandmarkList>>(_graph, "hand_landmarks");
			handLandmarksStream.StartPolling().AssertOk();

			// sidePacketを作ってStartRunに渡す
			var sidePacket = new SidePacket();
			sidePacket.Emplace("model_complexity", new IntPacket((int)_modelComplexity));
			sidePacket.Emplace("num_hands", new IntPacket(_maxNumHands));
            // カメラ映像とマーカーの座標は以下で合わせられる
			sidePacket.Emplace("input_rotation", new IntPacket(0));
			sidePacket.Emplace("input_horizontally_flipped", new BoolPacket(false));
			sidePacket.Emplace("input_vertically_flipped", new BoolPacket(true));

			_graph.StartRun(sidePacket).AssertOk();

			var stopwatch = new Stopwatch();
			stopwatch.Start();

			// ランドマーク用
			var screenRect = _screen.GetComponent<RectTransform>().rect;

			while(true)
			{
				_inputTexture.SetPixels32(_webCamTexture.GetPixels32(_pixelData));
				var imageFrame = new ImageFrame(ImageFormat.Types.Format.Srgba, _width, _height, _width*4, _inputTexture.GetRawTextureData<byte>());
				var currentTimestamp = stopwatch.ElapsedTicks / (System.TimeSpan.TicksPerMillisecond / 1000);
				_graph.AddPacketToInputStream("input_video", new ImageFramePacket(imageFrame, new Timestamp(currentTimestamp))).AssertOk();

                // RawImageにあてる用のマテリアルをセット
				_material.SetTexture("_MainTex", _webCamTexture);
				_material.SetTexture("_Background", _background);
				_material.SetTexture("_Mask", _filter.MaskTexture);
				_material.SetFloat("_Threshold", _threshold);

				yield return new WaitForEndOfFrame();

				if (handLandmarksStream.TryGetNext(out var multiLandmarks))
				{
                    // マーカーの表示
					_annotationController.DrawNow(multiLandmarks);
				}
				else
				{
					_annotationController.DrawNow(null);
				}
			}
		}

		private void OnDestroy()
		{
			if (_webCamTexture != null)
			{
				_webCamTexture.Stop();
			}

			if(_graph != null)
			{
				try
				{
					// InputStreamを閉じてCalculatorGraphを処分する
					_graph.CloseInputStream("input_video").AssertOk();
					_graph.WaitUntilDone().AssertOk();
				}
				finally
				{
					_graph.Dispose();
					Debug.Log("Done");
				}
			}
			// for NNCam
			_filter.Dispose();
			Destroy(_material);
		}
	}
}
```

`Compositor.shader`をカメラの入力に対応させるため、`_CameraFeed`を`_MainTex`で置き換える。
```CompositorForMediapipe.shader
Shader "Hidden/NNCam/CompositorForMediapipe"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Background("", 2D) = ""{}
        _Mask("", 2D) = ""{}
    }

    CGINCLUDE

    #include "UnityCG.cginc"

	sampler2D _MainTex;
    sampler2D _Background;
    sampler2D _Mask;
    float _Threshold;

	struct Input
	{
		float2 uv_MainTex;
	};

    void Vertex(float4 position : POSITION,
                float2 uv : TEXCOORD0,
                out float4 outPosition : SV_Position,
                out float2 outUV : TEXCOORD0)
    {
        outPosition = UnityObjectToClipPos(position);
        outUV = uv;
    }

    float4 Fragment(float4 position : SV_Position,
                    float2 uv : TEXCOORD0) : SV_Target
    {
        float3 bg = tex2D(_Background, uv).rgb;
        float3 fg = tex2D(_MainTex, uv).rgb;
        float mask = tex2D(_Mask, uv).r;
        float th1 = max(0, _Threshold - 0.1);
        float th2 = min(1, _Threshold + 0.1);
        return float4(lerp(bg, fg, smoothstep(th1, th2, mask)), 1);
    }

    ENDCG

    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex Vertex
            #pragma fragment Fragment
            ENDCG
        }
    }
}
```

# TouchDesignerで切り抜き、Spoutでやり取り

### 準備

https://labs.1-10.com/2022/10/unity%E3%81%A8touchdesigner%E9%96%93%E3%81%AEspout%E9%80%9A%E4%BF%A1%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%91%E3%83%BC%E3%83%86%E3%82%A3%E3%82%AF%E3%83%AB%E3%82%92%E6%8F%8F%E7%94%BB%E3%81%99/

こちらを参考に`Spout`の環境を整える。
[keijiro/KlakSpout](https://github.com/keijiro/KlakSpout)のREADMEの手順に従って、Scoped Registries（スコープ付きレジストリ）を利用して`KlakSpout`をimportする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/e44b2ebc-5b0c-b54e-6084-87ef9abb5230.png)
うまくいくと、`Window/Package Manager/My Registries`にkeijiro作のプラグインがたくさん表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/69faf62c-0548-19be-4d04-ccc932de8336.png)

スコープ付きレジストリとは、な記事。

https://tips.hecomi.com/entry/2021/12/19/003344

### 結果
`Nvidia Background TOP`が優秀だけど、ディレイあり。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/44517a31-5217-8629-98cd-28362873b54c.png)

### Spoutの受信とハンドトラッキングを合わせたコード全文
`Inspector`はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/8e675c69-3d9e-64b6-1716-6a331e86244b.png)

`Target Texture`にSpoutを受ける用の`RenderTexture`を作成してセットしたらすぐに受け取れた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/d5477248-6044-756d-048c-b80b5cccd03e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/e380a0c1-d86f-77b0-a4dd-9567a610264a.png)

入力画像をハンドトラッキングの推論に渡すところは、
```csharp
_inputTexture.SetPixels32(_webCamTexture.GetPixels32(_pixelData));
```
`Color32[]`で入っていれば良さそうなので、以下の記事を参考に`RenderTexture`を変換してみる。

https://light11.hatenadiary.com/entry/2018/04/19/192905

```HandtrackingSpout.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Stopwatch = System.Diagnostics.Stopwatch; // for Timestamp

namespace Mediapipe.Unity.Tutorial
{
	public class HandtrackingSpout : MonoBehaviour
	{
		// 入力用
		[SerializeField] RenderTexture _spoutIn;
		[SerializeField] private int _width;
		[SerializeField] private int _height;
		private Texture2D _inputTexture;
		private Color32[] _pixelData;
		[SerializeField] private RawImage _screen;
		// ハンドトラッキング用
		[SerializeField] private TextAsset _configAsset;
		private enum ModelComplexity { Lite = 0, Full = 1,}
		[SerializeField] private ModelComplexity _modelComplexity = ModelComplexity.Full;
		[SerializeField] private int _maxNumHands = 2;
		private CalculatorGraph _graph;
		private ResourceManager _resourceManager;
		// 重ねるマーカー用
		[SerializeField] private MultiHandLandmarkListAnnotationController _annotationController;

		private IEnumerator Start()
		{
			// MediaPipeでは、CPU 上の画像データがImageFrameクラスに格納される
			_inputTexture = new Texture2D(_width, _height, TextureFormat.RGBA32, false);
			_pixelData = new Color32[_width * _height];

			_screen.rectTransform.sizeDelta = new Vector2(_width, _height);

			// Spoutの映像を表示
			_screen.texture = _spoutIn;

			_resourceManager = new LocalResourceManager();
			if (_modelComplexity == ModelComplexity.Lite)
			{
				yield return _resourceManager.PrepareAssetAsync("hand_landmark_lite.bytes");
				yield return _resourceManager.PrepareAssetAsync("hand_recrop.bytes");
				yield return _resourceManager.PrepareAssetAsync("handedness.txt");
				yield return _resourceManager.PrepareAssetAsync("palm_detection_lite.bytes");
			}
			else
			{
				yield return _resourceManager.PrepareAssetAsync("hand_landmark_full.bytes");
				yield return _resourceManager.PrepareAssetAsync("hand_recrop.bytes");
				yield return _resourceManager.PrepareAssetAsync("handedness.txt");
				yield return _resourceManager.PrepareAssetAsync("palm_detection_full.bytes");
			}

			// configtextは外部から読み込む
			_graph = new CalculatorGraph(_configAsset.text);
			// ランドマークの取得
			var handLandmarksStream = new OutputStream<NormalizedLandmarkListVectorPacket, List<NormalizedLandmarkList>>(_graph, "hand_landmarks");
			handLandmarksStream.StartPolling().AssertOk();

			// sidePacketを作ってStartRunに渡す
			var sidePacket = new SidePacket();
			sidePacket.Emplace("model_complexity", new IntPacket((int)_modelComplexity));
			sidePacket.Emplace("num_hands", new IntPacket(_maxNumHands));
			sidePacket.Emplace("input_rotation", new IntPacket(0));
			sidePacket.Emplace("input_horizontally_flipped", new BoolPacket(false));
			sidePacket.Emplace("input_vertically_flipped", new BoolPacket(true));

			_graph.StartRun(sidePacket).AssertOk();

			var stopwatch = new Stopwatch();
			stopwatch.Start();

			// ランドマーク用
			var screenRect = _screen.GetComponent<RectTransform>().rect;

			while(true)
			{
				_inputTexture.SetPixels32(GetPixels(_spoutIn));
				var imageFrame = new ImageFrame(ImageFormat.Types.Format.Srgba, _width, _height, _width*4, _inputTexture.GetRawTextureData<byte>());
				var currentTimestamp = stopwatch.ElapsedTicks / (System.TimeSpan.TicksPerMillisecond / 1000);
				_graph.AddPacketToInputStream("input_video", new ImageFramePacket(imageFrame, new Timestamp(currentTimestamp))).AssertOk();

				yield return new WaitForEndOfFrame();

				if (handLandmarksStream.TryGetNext(out var multiLandmarks))
				{
					_annotationController.DrawNow(multiLandmarks);
				}
				else
				{
					_annotationController.DrawNow(null);
				}
			}
		}

		// Spoutで受け取ったRenderTextureをMediapipeのためにColor32[]に変換する
		private Color32[] GetPixels(RenderTexture rt)
		{
			var currentRT = RenderTexture.active;
			RenderTexture.active = rt;

			// ピクセル情報をテクスチャに格納する
			var texture = new Texture2D(rt.width, rt.height);
			texture.ReadPixels(new UnityEngine.Rect(0, 0, rt.width, rt.height), 0, 0);
			texture.Apply();

			// ピクセル情報を取得する
			var colors = texture.GetPixels32();

			// 元に戻す
			RenderTexture.active = currentRT;
			return colors;
		}

		private void OnDestroy()
		{
			if(_graph != null)
			{
				try
				{
					// InputStreamを閉じてCalculatorGraphを処分する
					_graph.CloseInputStream("input_video").AssertOk();
					_graph.WaitUntilDone().AssertOk();
				}
				finally
				{
					_graph.Dispose();
					Debug.Log("Done");
				}
			}
		}
	}
}
```

# 余談
`Nvidia Background TOP`については、このチュートリアルがわかりやすい。

<iframe width="560" height="315" src="https://www.youtube.com/embed/rajV8p9bdQE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

あとkeijiroはすごい。