WHY?
すごく調子の良いNoise Glitchが手に入ったのと、SquarepusherのMVに触発されたことで、openCVオンリーでお手軽にSemanticSegmentationが出来ないかと調べ始めました。
するとEnetという手法・モデルがヒットし、試してみることにしました。
できました。
本稿では、dnnについては何も語りません(語れません)。
TouchDesignerでdnnを使う前後の部分の学びの共有になります。
サンプルはこちら↓
https://github.com/thinpedelica/touchdesigner_sample/tree/master/Segmentation
Items
- EnetのモデルをTouchDesignerで動かす
- DATにTOPの画像を入力する
- DATからTOPに画像を出力する(Spout for Python)
- 性能について
Environment
- OS: windows10
- CPU: core-i7 9750H
- GPU: GTX 1660Ti
- TouchDesigner: 2020.22080
References
http://atkg.hatenablog.com/entry/2018/12/10/215224
SemanticSegmentationいけるかも、ときっかけを与えてくれたブログhttps://www.pyimagesearch.com/2018/09/03/semantic-segmentation-with-opencv-and-deep-learning/
移植させて頂いたPythonのコードhttp://satoruhiga.com/post/extending-touchdesigner/
DATでTOPの画像を扱うための手法https://qiita.com/komakinex/items/5b84b88d537d393afc98
DATでTOPの画像を扱うための手法https://github.com/Ajasra/Spout-for-Python
DATからTOPに画像を送るために使ったSpout fo Pythonhttps://rusin.work/vjing/tools/spout-for-python/
Spout fo PythonのBuild方法
EnetのモデルをTouchDesignerで動かす
Enetモデルは、TouchDesignerに同梱されているopenCVだけで動作しました。
pyimagesearchからサンプルコード+学習済みモデルをダウンロードできます
(メールアドレス登録するとリンクを送ってくれます) 。
ローカルのPythonで動くことを確認出来たら、あとをText DATに貼り付けるだけでした。
※なお、pyimagesearchはライセンスが不明だったので、gitに登録したコードとモデルは、本家のものに変更しました。
動作確認が出来たら、実際に使用するかたちにします。
今回はOP Execute DATに実装しました。
初期化処理
net = cv2.dnn.readNet(os.path.join(ENET_BASE_PATH, MODEL_FILE))
Cookごとの処理
blob = cv2.dnn.blobFromImage(image, 1 / 255.0, (WIDTH, HEIGHT), 0, swapRB=True, crop=False) net.setInput(blob) output = net.forward() classMap = np.argmax(output[0], axis=0) mask = COLORS[classMap] mask = cv2.resize(mask, (image.shape[1], image.shape[0]), interpolation=cv2.INTER_NEAREST)
すごく短いですね。機械学習すごい。
DATにTOPの画像を入力する
過去のTouchDesignerは、DATでTOPの画像を参照することができなかったらしいですが、今はできます。
@satoruhigaさん、@komakinexさん、@v_ohjiさんの記事を参考にしました。感謝!
TOPから画像を抜く
def onPostCook(changeOp): frame = changeOp.numpyArray(delayed = True)
TouchDesignerの画像フォーマットをopenCVのフォーマットに変換する
f_arr = frame[:, :, 0:3] f_arr = f_arr * 255.0 image_invert = f_arr.astype (np.uint8) image = np.flipud(image_invert)
なお、移植元のコードではnumPyで画像のリサイズを行っていたのですが、TDSWで聞いたところ、TOPで変換(リサイズなど)したほうが速いようなので、事前にTOPで実施するようしました。
DatからTOPに画像を出力する
2020.04時点で調べた限り、DATから直接TOPに画像を出力する方法はありませんでした。
そのため、DATからSpoutで送信し、Syphon Spout In TOPで受信するという方法で、Segmentation領域の画像をTOPに渡しました。
※ただ、これみんなやりたいことだと思うので、今後いい感じの方法が追加される気がします。
調べたらSpout-for-Pythonがbuild済のpyd(dll)を提供してくれていたり、サンプルにTouchDesignerを使っていたりですごく良い感じだったのですが、私の環境ではなぜかモジュールをimportする際にエラーがでてしまいました。
諦めて、こちらを参考に自分でビルドしました。
これが思いのほか一発で出来たので、Spout-for-Pythonがコケた人は、諦めてビルドしなおしましょう!
思い出せる限りの注意点
ローカルのPythonのバージョンをTouchDesignerと合わせておきましょう(私は3.7.2)
boostのバージョンはTouchDesignerと合ってなくても大丈夫だった(私は1.72)
「create user-config.jam file and add this:」というところでは、ユーザフォルダ直下に「user-config.jam」を作成する
(C:\Users\shampagne\user-config.jam)Spout for Pythonをビルドする際に指定するincludeとlibのディレクトリは、ローカルのPythonのものでOK
これで無事にSpout for Pythonをimportできるようになりました。
サンプルコードから送信に必要な処理を抜いてくると以下のようになりました。
# setup the texture so we can load the output into it glBindTexture(GL_TEXTURE_2D, textureSendID) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) # copy output into texture glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, WIDTH, HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, output ) # Send texture to spout... # Its signature in C++ looks like this: bool SendTexture(GLuint TextureID, GLuint TextureTarget, unsigned int width, unsigned int height, bool bInvert=true, GLuint HostFBO = 0); if sys.version_info[1] == 5: spoutSender.SendTexture(textureSendID, GL_TEXTURE_2D, spoutSenderWidth, spoutSenderHeight, False, 0) else: spoutSender.SendTexture(textureSendID.item(), GL_TEXTURE_2D, spoutSenderWidth, spoutSenderHeight, False, 0)
これで、Enetで生成したSegmentationされた領域の画像をTOPに戻すことができました。
性能について
EnetモデルによるSegmentation部分の速度は、私の環境では以下のようになりました。
ただ、これバックエンドがCPUになっているようです。。
- 512x256 : 73ms (13FPS)
- 1024x512 : 354ms ( 3FPS)
精度は、後者の高解像度のほうが、見た目で分かるレベルでよかったです。
-> 2020.04.27 バックエンドをCUDAにしたところ、1024x512が80msくらいになりました。
おわりに
今回も多くの先輩たちのおかげで、調べたらすべての情報があり、
わりと難なくやりたいことができました。大変ありがたいことです。
この記事も、誰かの役に立ちますように。