AzureKinectの赤外線画像をUnityで見る

#概要
小ネタです。
AzureKinectから得られた赤外線画像をUnityで見れるようにしようとしたけど、
UnityのTextureに変換する所に一瞬つまづいたので、その解決策の忘備録も含めています。

#結果
以下のようにUnityで赤外線画像が見れるようになります。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/unity?src=hash&amp;ref_src=twsrc%5Etfw">#unity</a> <a href="https://twitter.com/hashtag/AzureKinect?src=hash&amp;ref_src=twsrc%5Etfw">#AzureKinect</a><br>UnityでAzureKinectの赤外線画像を表示してみた。 <a href="https://t.co/BD1X4PVpOV">pic.twitter.com/BD1X4PVpOV</a></p>&mdash; unagi (@UnagiHuman) <a href="https://twitter.com/UnagiHuman/status/1209326165959135233?ref_src=twsrc%5Etfw">December 24, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

#AzureKinectから赤外線画像を取得してUnityで見れる形にする

https://github.com/microsoft/Azure-Kinect-Sensor-SDK/issues/490
IRデータの取り扱い方は上のリンク先の投稿を参考にしました。

IR画像のサイズはdepthのwidth,heightと同じで、データ的には16bitでIRの輝度が書かれています。
このデータをUnityのRenderTextureに変換するにはデータを変換する必要があります。
この変換はshader内でやってしまうのが一番速度が速いです。

以下に、Unity上でKinectからIRデータを取得してUnityのRenderTextureに変換するコードを乗っけます。

また、あくまでサンプルなので簡潔に書いているため実用的なソースではありません。本来はAzureKinect側からデータを受け取る側を別スレッドにすべきだし、またKinectは最高30FPSなのでUpdateでデータの処理をするのは無駄なのでそこら辺の考慮もすべきです。

```csharp

using UnityEngine;
using UnityEngine.Experimental.Rendering;
using Device = Microsoft.Azure.Kinect.Sensor.Device;

/// <summary>
/// IR画像取得サンプル
/// </summary>
public class TestIR : MonoBehaviour
{
    /// <summary>
    /// ColorFormatをR8_UNormに設定したRenderTexture（width,heightはDepth画像に合わせる）
    /// </summary>
    [SerializeField] private RenderTexture _irTexture = null;
    [SerializeField] private Shader _unprojectIRshader = null;
    [SerializeField] private float _maxIRValue = 10000;
    [SerializeField] private float _minIRVaue = 0;

    private Material _material;
    private Texture2D _tempIR;
    private Device _kinectDevice;


    // Start is called before the first frame update
    void Start()
    {
        //GraphicsFormat.R8_UNorm
        _material = new Material(_unprojectIRshader);
        _tempIR = new Texture2D(_irTexture.width, _irTexture.height, GraphicsFormat.R16_UNorm, 0);
        _kinectDevice = Device.Open();
    }

    // Update is called once per frame
    void Update()
    {
        //irのデータ配列を取得
        var capture = _kinectDevice.GetCapture();
        var irData = capture.IR.Memory;

        //シェーダーに放り込むソース用のテクスチャにロード
        _tempIR.LoadRawTextureData(irData.ToArray());
        _tempIR.Apply();

        capture.Dispose();

        //変換元のソースのテクスチャをセット
        _material.SetTexture("_IrTexture", _tempIR);

        //IR輝度の最大値（画像の見え方の調整）
        _material.SetFloat("_MaxValue", _maxIRValue);

        //IR輝度の最小値（画像の見え方の調整）
        _material.SetFloat("_MinValue", _minIRVaue);

        var prevRT = RenderTexture.active;
        //変換先のテクスチャをセット(ColorFormatをR8_UNorm)
        Graphics.SetRenderTarget(new RenderBuffer[1]{ _irTexture.colorBuffer }, _irTexture.depthBuffer);
        Graphics.Blit(null, _material, 0);

        RenderTexture.active = prevRT;
    }

    private void OnDestroy()
    {
        if (_material != null) Destroy(_material);
        if (_tempIR != null) Destroy(_tempIR);
    }
}

```

以下はshader側の処理です。

```csharp

Shader "UnprojectIR"
{
	CGINCLUDE
	#include "UnityCG.cginc"

	texture2D _IrTexture;

	float _MaxValue;
	float _MinValue;

	//16bitをR8_Normフォーマットに変換(0～1.0)
	inline float GetIR(texture2D source, int tx, int ty, int vmax, int vmin)
	{
		int v = source[uint2(tx, ty)] * 65535;
		float colorValue = 0.0f;
		if (v <= vmin)
		{
			colorValue = 0.0f;
		}
		else if (v >= vmax)
		{
			colorValue = 1.0f;
		}
		else
		{
			colorValue = (float)(v - vmin) / (float)(vmax - vmin);
		}
		return colorValue;
	}


	void Vertex(
		float4 position : POSITION,
		out float4 positionOut : SV_Position,
		inout float2 texCoord : TEXCOORD0
	)
	{
		positionOut = UnityObjectToClipPos(position);
	}

	void Fragment(
		float4 position : SV_Position,
		float2 texCoord : TEXCOORD0,
		out float colorOut : SV_Target0
	)
	{
		uint w, h;
		_IrTexture.GetDimensions(w, h);

		// Texture index
		uint tx = texCoord.x * w;
		uint ty = texCoord.y * h;

		float ir = GetIR(_IrTexture, tx, ty, _MaxValue, _MinValue);
		//output write
		colorOut = ir;
	}

	ENDCG

    SubShader
	{
		Cull Off ZWrite Off ZTest Always
		Pass
		{
			CGPROGRAM
			#pragma vertex Vertex
			#pragma fragment Fragment
			ENDCG
		}
	}
}

```

#まとめ
UnityでAzureKinectの赤外線を見る方法を調べた。これを利用して[以前の別記事](https://qiita.com/UnagiHuman/items/f877da98d7600e3d579c)でやったような再帰性反射材をつかったトラッキングをやってみたい。