TouchDesignerでOpenCV

この記事はTouchDesigner Advent以下略

TouchDesignerのPython環境には標準でOpenCVのcv2モジュールが入っています。
内部的には `Blob Track TOP` で使われているようなのですが、公開されている部分だけだと[ファイルの入出力を経由しないと使えません](http://blog.antymark.com/2017/02/touchdesigner099%E3%81%A7opencv%E3%82%92%E4%BD%BF%E3%81%86%E3%81%AB%E3%81%AF%EF%BC%88python%E3%81%A7%EF%BC%89/)、、、

えっ… リアルタイムで使いたい… ので、普通にTOP渡しで自由に使えるようにしましょう！

できました！

![td-imshow.png](https://qiita-image-store.s3.amazonaws.com/0/38051/92d95f25-78d8-21b8-33e0-09f9c86f8660.png)

ダウンロードはこちらから https://github.com/satoruhiga/TouchDesigner-OpenCV/releases

## 実際に使ってみる

今回はややこしい事をなるべく隠蔽したかったので `OpenCV1` にコールバックを受けるDATを指定するとデータが飛んでくるようにしました。

↓ コールバックのDATの中身

```python
import cv2

def onFrame(data):
	cv2.imshow('img', data)
```

これだけでOpenCVの画像プレビュー用のウィンドウが開きます。

あとは書籍やチュートリアルを黙々と試していくことで色々できますね！

[cv2.calcOpticalFlowPyrLKをやってみた様子](https://www.instagram.com/p/BagdfUYFkN3)

以下色々とテッキーな事が書いてありますが、使う分には何も考えなくていいのもTouchDesignerの素晴しい所だと思いますので、興味のある方のみ読んでいただければ！

---

## 方針

現在TouchDesignerでは、TOPのピクセルデータにアクセスするのに `CPlusPlus TOP` のモジュールを書く必要があります。

そして `CPlusPlus TOP` のモジュールを書いてピクセルデータを取り出したら、今度はそれをどうにかしてPythonに渡す必要があります、、

色々探してみたのですが手っ取り早くC++からPythonへデータを渡す仕組みが見当たらなかったので、ややアホっぽいですが [nanomsg](http://nanomsg.org/) を使うことにしました

## nanomsgとは？

nanomsgは、TCP、UDP、スレッド間通信、プロセス間通信など様々なプロトコルや通信方式に対応した便利なソケットライブラリです。

C++、Pythonの両方で使えるのと、Windows環境でのプロセス間通信に対応していたので試してみることにしました。

## CPlusPlus TOP用モジュールの実装

`C:\Program Files\Derivative\TouchDesigner099\Samples\CPlusPlus\CPUMemoryTOP\` あたりにあるサンプルを見ながらC++を黙々と書いていきます。

`getTOPDataInCPUMemory` 関数でTOPのピクセルデータをメインメモリに読み出すのですが、その時のオプションとして `OP_TOP_INPUT_DOWNLOAD_DELAYED` を指定しないとひどく遅いのでそこだけ注意です。

```cpp
	OP_TOPInputDownloadOptions options;
	options.downloadType = OP_TOP_INPUT_DOWNLOAD_DELAYED;
	options.cpuMemPixelType = RGBA8Fixed;
	options.verticalFlip = true;

	const uint8_t* src = (const uint8_t*)inputs->getTOPDataInCPUMemory(input, &options);

	if (!src) return;
```

送信側のソースコードは [こちら](https://github.com/satoruhiga/TouchDesigner-OpenCV/blob/master/dll/NanomsgTOP/src/main.cpp)

## Pythonの受信側

それではPython側でメッセージを受け取る部分です！

[nnpy](https://github.com/nanomsg/nnpy) というモジュールが調子よかったのですが、Windows環境でのビルドがすごい大変だったのでgithubにモジュールのバイナリも一緒に上げてあります。

ちなみに、Tipsとして ↓ のような感じのスクリプトを適当なタイミングで呼び出しておくと `.toe` と同階層にある `python` フォルダを動的にモジュールパスに追加できてひじょうに便利です

```python
import os, sys

PATH = os.path.join(os.path.abspath('.'), 'python')
if not PATH in sys.path:
	sys.path.append(PATH)
```

## Cookの制御

![cook-dep.png](https://qiita-image-store.s3.amazonaws.com/0/38051/9841eb73-117c-a4c5-ed82-d37458d83b20.png)

途中で言語をまたいだ通信が入っていたりで、内部的に結構ややこしい事になってしまっているのでそれぞれのスクリプトをどのタイミングで走らせればいいかも、だいぶこんがらがっています :disappointed_relieved:

TouchDesignerでは基本的に見えていない部分はCookをしない最適化がかかります。

今回のようにTOPに入力したものを外部のライブラリ経由で飛ばして～ というようなことをすると、オペレーターが見えなくなった時にCookが走ってくれない可能性大だったので `Null CHOP` の `Cook Type` を `Always` にして、そこから順繰りに参照していくことで全体的にCookが止まらないような依存関係を組みました。(ちょっとやりすぎかもしれない)

また `CPlusPlus TOP` の仕様？ で一番最初に強制的にCookをしないと処理が初まってくれなかったのでそこは `Execute DAT` の `onStart` を使って起動時にCookを走らせるようにしています。

## まとめ

TouchDesignerではC++で独自オペレーターが作れるので工夫次第である程度踏み込んだ所でもユーザーレベルでどうにかできる事が多々あります。

現在099のExprementalではSOPもC++で拡張できるようになっているので、Pythonではパフォーマンス的に厳しい所などがあればどんどんC++で補って便利にしていきたいですね！

それでは！:raised_hand: