Azure Kinectを2台使った時の話

#はじめに
岐阜県にある[IAMAS](https://www.iamas.ac.jp/)という学校に通っている [@minoge1001](https://twitter.com/minoge1001) と申します。2年連続のアドカレ参加です（昨年の記事は[こちら](https://qiita.com/minoge1001/items/9532f36ee564382bfbe0)）。

10月3日に行われた[NxPC.Live vol.46 X 1/2](https://www.iamas.ac.jp/activity/nxpc-live-vol-46-x-1-2/)というライブで、Azure Kinectを2台用いて以下のような演出を行いました。今回の記事では、2台のAzure Kinectをどのように組み合わせて使ったのかというセッティング周りについて紹介します。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Realtime audio visual performance 01 (Saxophone: <a href="https://twitter.com/k_saxofocus?ref_src=twsrc%5Etfw">@k_saxofocus</a>)<a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/GLSL?src=hash&amp;ref_src=twsrc%5Etfw">#GLSL</a> <a href="https://twitter.com/hashtag/AzureKinect?src=hash&amp;ref_src=twsrc%5Etfw">#AzureKinect</a> <a href="https://twitter.com/hashtag/pointcloud?src=hash&amp;ref_src=twsrc%5Etfw">#pointcloud</a> <a href="https://twitter.com/hashtag/creativecoding?src=hash&amp;ref_src=twsrc%5Etfw">#creativecoding</a> <a href="https://t.co/HIZ7aYNDeK">pic.twitter.com/HIZ7aYNDeK</a></p>&mdash; Yugo Minomo (@minoge1001) <a href="https://twitter.com/minoge1001/status/1314847360363556865?ref_src=twsrc%5Etfw">October 10, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Realtime audio visual performance 02 (Saxophone: <a href="https://twitter.com/k_saxofocus?ref_src=twsrc%5Etfw">@k_saxofocus</a>)<a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/GLSL?src=hash&amp;ref_src=twsrc%5Etfw">#GLSL</a> <a href="https://twitter.com/hashtag/AzureKinect?src=hash&amp;ref_src=twsrc%5Etfw">#AzureKinect</a> <a href="https://twitter.com/hashtag/pointcloud?src=hash&amp;ref_src=twsrc%5Etfw">#pointcloud</a> <a href="https://twitter.com/hashtag/creativecoding?src=hash&amp;ref_src=twsrc%5Etfw">#creativecoding</a> <a href="https://t.co/Dlj0wo3LkF">pic.twitter.com/Dlj0wo3LkF</a></p>&mdash; Yugo Minomo (@minoge1001) <a href="https://twitter.com/minoge1001/status/1314885874107052034?ref_src=twsrc%5Etfw">October 10, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Realtime audio visual performance 03 (Saxophone: <a href="https://twitter.com/k_saxofocus?ref_src=twsrc%5Etfw">@k_saxofocus</a>)<a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/GLSL?src=hash&amp;ref_src=twsrc%5Etfw">#GLSL</a> <a href="https://twitter.com/hashtag/AzureKinect?src=hash&amp;ref_src=twsrc%5Etfw">#AzureKinect</a> <a href="https://twitter.com/hashtag/pointcloud?src=hash&amp;ref_src=twsrc%5Etfw">#pointcloud</a> <a href="https://twitter.com/hashtag/creativecoding?src=hash&amp;ref_src=twsrc%5Etfw">#creativecoding</a> <a href="https://t.co/7tsjINWAoF">pic.twitter.com/7tsjINWAoF</a></p>&mdash; Yugo Minomo (@minoge1001) <a href="https://twitter.com/minoge1001/status/1315090941724647424?ref_src=twsrc%5Etfw">October 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Realtime audio visual performance 04 (Saxophone: <a href="https://twitter.com/k_saxofocus?ref_src=twsrc%5Etfw">@k_saxofocus</a>)<a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/GLSL?src=hash&amp;ref_src=twsrc%5Etfw">#GLSL</a> <a href="https://twitter.com/hashtag/AzureKinect?src=hash&amp;ref_src=twsrc%5Etfw">#AzureKinect</a> <a href="https://twitter.com/hashtag/pointcloud?src=hash&amp;ref_src=twsrc%5Etfw">#pointcloud</a> <a href="https://twitter.com/hashtag/creativecoding?src=hash&amp;ref_src=twsrc%5Etfw">#creativecoding</a> <a href="https://t.co/HYBtRPg4e5">pic.twitter.com/HYBtRPg4e5</a></p>&mdash; Yugo Minomo (@minoge1001) <a href="https://twitter.com/minoge1001/status/1315100230384140288?ref_src=twsrc%5Etfw">October 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Realtime audio visual performance 05 (Saxophone: <a href="https://twitter.com/k_saxofocus?ref_src=twsrc%5Etfw">@k_saxofocus</a>)<a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/GLSL?src=hash&amp;ref_src=twsrc%5Etfw">#GLSL</a> <a href="https://twitter.com/hashtag/AzureKinect?src=hash&amp;ref_src=twsrc%5Etfw">#AzureKinect</a> <a href="https://twitter.com/hashtag/pointcloud?src=hash&amp;ref_src=twsrc%5Etfw">#pointcloud</a> <a href="https://twitter.com/hashtag/creativecoding?src=hash&amp;ref_src=twsrc%5Etfw">#creativecoding</a> <a href="https://t.co/UhGDk1bgfY">pic.twitter.com/UhGDk1bgfY</a></p>&mdash; Yugo Minomo (@minoge1001) <a href="https://twitter.com/minoge1001/status/1315111401388605440?ref_src=twsrc%5Etfw">October 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



#動機
動機は単純で「**2台のAzure Kinectで挟み込んで撮影した時にVolumetric的な演出がどのくらいできるのか試したい**」ということでした。ですので、側面方向が上手くキャプチャできない、オクルージョンに弱い、そもそも完璧な人物像は取れない、等の問題は織り込み済みで進めました。ざっくり言うと、**Azure Kinect 2台からRGB/PointCloudの2種類の画像を1台のPCに取り込んで、それぞれ種類ごとに1枚の画像にまとめた上でInstancingなどで演出をつける**という流れです。

#配置の検討
今回のライブでは、サクソフォン奏者である[@k_saxofocus](https://twitter.com/k_saxofocus)さんとのコラボなので、まずは全身（約165cm）をキャプチャするために必要な空間の大きさとAzure Kinectの配置を検討しました。

今回の条件ではキャプチャ対象である演奏者の位置はほとんど変わらないので、`Kinect Azure TOP`の`Depth Mode`は`Narrow FOV - Unbinned(640x576)`としました。演奏中の動作としては

- 下手側のテーブル上にあるMacbook Proを操作する
- 演奏中に体が揺れる（主に前後方向）

の2種類がありましたが、映像のアスペクト比的には後者（縦方向）の方が厳しいので、**前後50cm程度動いても頭と足が切れない**ようなラインを狙いました。Azure KinectはRGBカメラ・Depthカメラともに[画角が光軸から上下対称](https://docs.microsoft.com/ja-jp/azure/kinect-dk/media/resources/hardware-specs-media/camera-fov.png)なので、理論的には身長の半分の高さに設置するのが（解像度的には）ベストですが、生カメラとしてテイクする演出も考えられたので身長の半分より少し高めに設定しています。

なお、2台のカメラはなるべく水平にした上で光軸や高さも極力一致させるのが理想ですが、[微調整はTouchDesigner上で行う](#位置調整)ので、そこまでシビアに頑張らなくても大丈夫です。結果、以下のような配置になりました。

![setting diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/78e7d8c1-0f4d-172c-1778-66dc6aa05d4d.png)


以降、演者正面側のAzure Kinectを**AK①**、背面側を**AK②**と呼ぶことにします。今回のライブは無観客配信だったので、VJ（私）は**AK①**の真後ろにメインPCを置いてオペレーションしています。

#データの伝送
配置が決まったところで、遠く離れた**AK②**からのデータをメインPCに取り込むための通信路を考えます。ケーブル長は迂回や立ち上げ等を考えると**8m**以上は必要なので、USBで伸ばそうとするとアクティブ延長ケーブルが必要となります（**[ホロラボの中村さんの記事](https://blog.hololab.co.jp/entry/2020/04/05/153058)が非常に参考になります**）。…が、個人で購入するには少々お高く、そもそも納期がイベントに間に合わないという事情もあり、USBでの延長は諦めて**同じネットワーク内にサブPCを用意し、それを経由させてギガビット(1Gbps)LANで伸ばす**ことにしました。

ところで、**PointCloud画像は非圧縮（もしくは可逆圧縮）で伝送**する必要があります。通常よく使われる非可逆圧縮は人間の目では変化が捉えづらい色差成分を間引いたり高周波成分のデータを削ったりすることでサイズを小さくしますが、PointCloud画像でこれをやってしまうと（映像としては問題なく見えても）位置情報が破綻します。ということで、`NDI Out/In TOP`は使えず、必然的に非圧縮で伝送できる**`Touch Out/In TOP`**を使うことになります。

`Kinect Azure TOP`の`Camera FPS`は`30`にします。PointCloud画像は`32bit RGB`なので、出力解像度(`Color Resolution`)を一番低い`1280×720`にしてもデータレートは**(32×3)×(1280×720)×30=約2.654Gbps**となり、全くギガビットのLANを通りません。そこで、`Resolution TOP`で半分の解像度(`640×360`)にします。こうすると、データ量が1/4の**約660Mbps**になるので、RGB映像を圧縮すれば1Gbpsに納まりそうです。RGB画像は`Touch Out TOP`で`HAP Q`を指定します。こうして、無事にLANで伝送することができました。なお、Resolution TOPの`Input Smoothness`を`Nearest Pixel`に変更するのを忘れずに！

以下の図が**AK②**から入力を受けてメインのPCにデータを転送するサブPCのネットワークです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/fdf7060a-01e3-4bff-24d9-2ab6a3660089.png" width=75%>

なお、本題から逸れますが、このようなケースでは

- LANケーブルのカテゴリーが**CAT 5e以上**であることをちゃんと確認する
- ネットワークインタフェースの設定をオートネゴシエーションではなく**手動で1Gbps/全二重**にする

などの細やかな気遣いをすることで、無用なトラブルを避けることができます。

#メインPCのネットワーク

メインPCのネットワークを示します。なお、もう一度同じセッティングをする時間がありませんでしたので、Azure Kinectからの入力は適当です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/48c051e5-d7f1-e03e-ce14-a2836e6018ac.png" width=100%>

以降、特に断りがない限り、**TOPの`Common`タブの`Input Smoothness`は`Nearest Pixel`に統一**してください。
##入力&解像度変更
直接接続している**AK①**の映像を`Kinect Azure TOP`と`Kinect Azure Select TOP`で取得し、`Resolution TOP`を繋げて**AK②**からの入力解像度(`640×360`)に合わせます。**AK②**からの入力は`Touch In TOP`で受け取ります。その際、`Network Port`がサブPCの`Touch Out TOP`の値と一致している必要があります。

##遅延調整①
2台のAzure KinectのPointCloud画像、RGB画像どうしのタイミングを合わせます。**直接接続されているAK①の方が早い**ので、**AK①**側のPointCloud/RGB画像にそれぞれ`Cache TOP`を接続します。ここでは、誰かに被写体に入ってもらって、頭上でパチパチと手を鳴らしてもらう等のアクションをしてもらうとスムーズに作業できます。なお、この段階ではPointCloud画像全体とRGB画像全体のタイミングは合っていませんので、最終段（[遅延調整②](#遅延調整②)）で合わせます。なお。今回の自分のセッティングでは、PointCloud画像を`-18 Fr`、RGB画像を`-4 Fr`とすることでタイミングが合いました。

##位置調整
被写体がワールド座標系の原点にくるように、`Palette`内の`PointClouds`>`pointTransform`を用いてPointCloudの位置を調節します。撮影されるPointCloud画像はカメラの位置が原点で方向が+z軸となるので、[上記の配置](#配置の検討)の場合には、z軸方向におおよそ`-2.125`、y軸方向におおよそ`+1.02`だけ平行移動させることで被写体の足元が原点に移動することになります（**TouchDesignerの単位系はメートル**）。その上で、どちらかの画像をy軸周りに180°回転させることで、被写体を前後から挟み込むような位置関係となります。また、セッティングの段階でAzure Kinect自体が正対していなかったり（パン方向のずれ）水平ではなかったり（チルト/ロール方向のずれ）した場合もここで修正します。

この工程は、次の[画像統合](#画像統合)の`Layout TOP`を繋げてから行うとやりやすいです。`Layout TOP`を`Active`にして右クリックから`View as Points`を選ぶことで点群表示にできます。先にひとつのTOPに纏めてしまうことで、PointCloud画像がきれいに繋がる位置を探ることができます。なお、ここでの被写体は動いている必要はないので、箱馬を重ねるなどある程度上背のあるダミーがあればOKです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/416e3231-d553-d19f-bff2-7093ddaa8ed1.png" width=75%>


##画像統合
PointCloud画像、RGB画像どうしをそれぞれLayout TOPで1枚の画像にします。それぞれ`640×360`の2つの画像が入力となるので、縦に繋げて1つの`640×720`の画像を作ります。

|タブ|パラメータ|値|
|:---:|:---:|:---:|
|Layout|Align|Top to Bottom|
|Common|Output Resolution|Custom Resolution|
|Common|Resolution|640, 720|
|Common|Output Aspect|Resolution|


PointCloudを格納したTOPをまとめるツールとして`Palette`内の`PointClouds`>`pointMerge`がありますが、ここでは使うことはできません。RGB画像側と全く同じ結合の仕方をさせることで、同じ位置にある画素に格納された点群の位置情報と色情報が一致するためです。

##不要な点群の削除
今回の記事では触れませんが、人物領域のみを抽出するためにPointCloud画像に対して`RGB Key TOP`を用いて不要な領域を削除しています。このあたりは、[TDSWのVol.32](https://www.patreon.com/posts/event-vol-032-39867674)で詳しく解説されているので、ご参照ください。

##遅延調整②
それぞれ1つの画像にまとまったPointCloud画像とRGB画像のタイミングを合わせます。早いRGB画像側に`Cache TOP`を入れます。今回の自分のセッティングでは`-15 Fr`でタイミングが合いました。

##Instancing用TOP
最終的に、点群の位置情報を持ったTOPと色情報を持ったTOPが出来たので、Geometry COMPを使ってインスタンシングを行うことで、リアルタイムにVolumetricっぽいポイントクラウドを扱えるようになりました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/499f41c2-7e20-0bce-60c8-78b978a90f9c.png" width=75%>

但し、[動機](#動機)で述べたように、側面のデータは取れていません。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/66f14226-8705-87f4-0d8f-348997f619db.png" width=75%>

#まとめ
今回の記事では、Azure Kinectを2台使って簡単なVolumetric的表現を行う方法について紹介しました。完全な人物像を取得することはできませんが、演出によっては低コストで何か面白いことができるかもしれません。

#補足

##ポイントクラウドが上手く表示されない場合
PointCloudに錐体状のノイズが乗るときは、解像度が変わる系のTOPで`Input Smoothness`が`Nearest Pixel`以外になっています。また、xyzの範囲が0～1にクランプされていたり、平面状や直線状に潰れされているときは、`Pixel Format`が正しくありません。ビット深度は`16 bit`以上にし、チャンネルは`RGBA`にしましょう(Instansing時の`Active`パラメータにAチャンネルを指定すると、何かと便利です）。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214923/91aaa0a4-806e-03de-a81b-cdf5d42ba716.png" width=75%>

##他のカメラについて
実際には、Azure Kinectの他に、演者正面にミラーレス一眼（バストショット用）と、演者から見て左上方にwebカメラ（全身俯瞰用）の2台のカメラを使っています。これらのカメラとの位置合わせ作業は、ポイントクラウド表示した被写体と実写映像がうまく重ね合わさるように地道に行います（かなり大変です…）。

##Azure Kinectの外部同期機能
Azure Kinectには[3.5mmオーディオケーブルを用いて同期させる仕組み](https://docs.microsoft.com/ja-jp/azure/kinect-dk/multi-camera-sync)があり、`Kinect Azure TOP`でもサポートされていますが、今回のシステムでは時間的問題もあって使用しておりません。なお、仮に使用したとしても、今回の構成では何らか同じような調整が必要だと思われます。

それでは、皆様良い年末を！