Azure Kinect の Point cloud を ムービーとして保存して使いたい

こんにちは！こちらは TouchDesigner Advent Calendar 2022 24日目の記事です。

## サンプル
今回作ったサンプルは以下にあります。

https://github.com/chimanaco/touchdesigner-advent-calendar-2022

## はじめに
動機としてはライブなどで Azure Kinect を使ってリアルタイムビジュアルを作る時に、バックアップとして事前に REC した Point Cloud のデータをムービーとして再生できるように持っておくようにしたかったというものです。

Kinect v2 の場合には Kinect for Windows SDK 2.0 の Kinect Studio を使用して REC することが可能で、Kinect Studio で REC したデータを再生することで TouchDesigner の Kinect TOP では直接 Kinect v2 を呼んだ時と同じように見えるようですが、Azure Kinect にはそのようなツールが用意されていません。

https://www.microsoft.com/en-us/download/details.aspx?id=44561

さてどうするか？ぼくが調べた感じだと方法は2つありました。

1つ目。Kinect Azure TOP または Kinect Azure Select TOP で Point cloud を表示するようにしておいて、それにつないだ Movie File Out TOP で Type を Image Sequence、Image File Type を OpenEXR として連番 exr を書き出し、書き出した連番 exr を Movie File In TOP で読み込む方法で、これでうまくいけば一番楽です。ただ、書き出しが結構重いので解像度と PC のスペックと相談になると思います。ぼくの環境（Alianware m15 R4）の場合、160x90px でやっと Azure Kinect と同じ 30FPS をキープできました。
![2022-12-23_20h10_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58558/2367d1b3-aebd-f2aa-bee6-38b3b6aa0fe7.png)
2つ目はこれに比べると大分面倒くさいですが、この記事の本題です。
以下のような手順になります。

## 手順
- Azure Kinect からのフィードを保存する
- 保存したフィードから Depth フィードを取り出す
- Depth フィードから Point Cloud を生成する

という流れになります。ではやっていきます。

## Azure Kinect からのフィードを保存する

まずは Azure Kinect Sensor SDK をダウンロードします。2022年12月23日時点で最新が v1.4.1 です。

https://github.com/microsoft/Azure-Kinect-Sensor-SDK/blob/develop/docs/usage.md

Azure Kinect Viewer を起動して Open device < Start の順にクリックして Azure Kinect が動作するとこんな感じになるはずです。
![2022-12-23_20h06_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58558/0c63dbdc-cb26-18bd-68a9-42cb38886b4f.png)
確認ができたら、Azure Kinect Viewer を閉じます。Azure Kinect SDK の tools があるディレクトリ（ぼくの環境だと `C:\Program Files\Azure Kinect SDK v1.4.1\tools` ）に移動して、コマンドラインから Azure Kinect の Record アプリである k4arecorder.exe を実行します。

最もシンプルなものだと `k4arecorder.exe E:\output.mkv` で REC を開始、`Ctrl + C` で REC を終了するというものがあります。
![2022-12-23_20h14_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58558/006d91e5-c3b5-cd10-fccd-11afb6cdbf2a.png)
他にも様々なオプションがあり、例えば10秒間 REC して止める、というコマンドは以下のようになります。
`k4arecorder.exe -l 10 E:\output.mkv`

その他のオプションについては以下のページを参照してください。

https://learn.microsoft.com/en-us/azure/kinect-dk/record-sensor-streams-file

https://learn.microsoft.com/en-us/azure/kinect-dk/azure-kinect-recorder

ちなみに mkv は様々な種類のメディアデータを格納できる Matroska（マトリョーシカ）フォーマットのファイルです。今回の Azure Kinect の場合だと COLOR, DEPTH, IR, IMU などを含めることができます。

https://learn.microsoft.com/en-us/azure/kinect-dk/record-file-format

## 保存したデータから Depth フィードを取り出す

書き出した mkv ファイルから Depth のフィードを取り出すには MKVtoolnix というツールを使うと便利です。専用の GUI である gMKVExtractGUI も DL してしまいましょう。

https://www.fosshub.com/MKVToolNix.html

http://forum.doom9.org/showthread.php?t=170249

gMKVExtractGUI を立ち上げたら MKVtoolnix のパスを指定し、書き出した mkv ファイルをドラッグ&ドロップします。
![2022-12-23_20h22_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58558/9ca6d8cc-1805-e1ef-f4db-de01b0dc77ca.png)
必要に応じて取り出すフィードの種類を選択して書き出し場所を決定し、Extract をすると avi ファイルとして書き出されます。特に何も設定しないと Depth は output_track2_[eng].avi という名前になります。これで Depth のフィードを取得することができました。

## Depth フィードから Point Cloud を作成する

この Depth の avi を TouchDesigner のネットワークにドラッグ&ドロップすると Movie File In TOP として再生できますが、今回の目的は PointCloud として使用することです。Derivative のサイトからダウンロードできる Builds には無い機能だと思いますが、以下のページでシェアされている TouchDesigner.2022.29231  Experimental Build を使うと Kinect Azure TOP が transformDepthImageToPointCloud という Depth イメージを Point Cloud に変換する関数が使えるのでこちらの Build を使います。

https://forum.derivative.ca/t/generating-pointclouds-from-depth-map-color-image/179966/35

Depth フィードを読み込んだ Movie File In TOP の後に Math TOP をつなぎます。16-bit Float で Range を 0-0.2 から 0-15 としました。

![2022-12-23_20h43_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58558/2b08ee21-6fd5-0d4f-7976-0296977b4154.png)
先程のフォーラムのページでシェアされているファイルにある以下のような python を使用しています。

```
import numpy

def onCook(scriptOp):	
	# grab the depth image from any top
	depthImg = op('null1').numpyArray()

	# send it to the kinect and get back the point cloud imaage
	pcImg = op('kinectazure1').transformDepthImageToPointCloud( depthImg )

	# upload the point cloud to the script top
	scriptOp.copyNumpyArray(pcImg)

	return
```

変換を行う Kinect Azure TOP は読み込んだ Depth フィードと解像度を同一にします。この Kinect Azure TOP を使用する際に Azure Kinect のハードを接続していなくてもこの関数は使用できるのですが、エラーを吐いているせいかめちゃくちゃ FPS が落ちるので Azure Kinect は接続して active な状態にしておいた方が好さそうです。

![2022-12-23_20h46_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58558/0633267a-7d09-7e27-f13d-7c6e4f8366dd.png)

変換ができたら RGB Key TOP を使って必要な分だけ抜き出しました。 後は COLOR の データと組み合わせたりするとよさそうですね！

![2022-12-23_20h47_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58558/3672d807-8729-607a-f7f3-a688761d797e.png)

## おわりに
一応動いたけどもっといい方法がありそうな気がするのでご存じの方おられたら是非ご指摘頂けたらうれしいです！

それでは引き続き [TouchDesigner Advent Calendar 2022](https://qiita.com/advent-calendar/2022/touchdesigner) をお楽しみください！
ありがとうございました～メリークリスマス！！

## 参考

https://forum.derivative.ca/t/mock-live-feed-into-kinect-azure-top/198363/3

https://forum.derivative.ca/t/generating-pointclouds-from-depth-map-color-image/179966/35