TouchDesignerでOpenCVをはじめーる（Windows10、Python3.7、OpenCV4.0）

# はじめに
TouchDesignerでOpenCVやっていきます。導入からMobileNetSSDまで。

# 開発環境
- Windows10（RTX2080 Max-Q、i7-8750H、RAM16GB）
- TouchDesigner 2020.24520
- Anaconda 2020.02
- Python 3.7
- OpenCV 4.0

# 導入
[TouchDesigner](https://derivative.ca/download)からインストールします。
サインインして、Keyを作成しましょう。

開いたらこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/0bedc214-22cd-c401-12a2-beb07e623671.png)

## OpenCVを入れる

メニューのDialog->Textport and DATsを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/730bcb51-fabe-5c59-e9ea-6f3bad8548bd.png)

下記プログラムを実行して、PythonとOpenCVのバージョンと場所を確認しましょう。

~~~
python >>> import sys
python >>> sys.version
'3.7.2 (heads/3.7-Derivative:052feb9e72, Mar  7 2019, 16:29:45) [MSC v.1900 64 bit (AMD64)]'
python >>> import cv2
python >>> cv2.__version__
'4.0.0'
python >>> cv2
<module 'cv2.cv2' from 'C:\\Program Files\\Derivative\\TouchDesigner\\bin\\lib\\site-packages\\cv2\\cv2.cp37-win_amd64.pyd'>
~~~

同じ環境をanacondaで作成しましょう。opencv-contribも入れます。

~~~
conda create -n td python=3.7
conda activate td
pip install opencv-python
pip install opencv-contrib-python
~~~

anaconda promptを開いて、pythonインタプリタを開き、OpenCVの場所を確認します。

~~~
(td) C:\Users\good_\Documents\PythonProjects>python
Python 3.7.7 (default, May  6 2020, 11:45:54) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> cv2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'cv2' is not defined
>>> import cv2
>>> cv2
<module 'cv2.cv2' from 'C:\\Users\\gachimoto\\anaconda3\\envs\\td\\lib\\site-packages\\cv2\\cv2.cp37-win_amd64.pyd'>
~~~

~~anacondaで作成したpydファイルをC:\Program Files\Derivative\TouchDesigner\bin\lib\site-packages\cv2\へ上書きします。~~

[TouchDesignerで機械学習（ObjectDetection）動かしてみた](https://qiita.com/sumita_v09/items/0b38ed21a28fa43e74c4)を参考に、メニューのEdit->Preferencesを開き、Python 64-bit Module Pathにanacondaで作成した環境のsite-packagesへのパスを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/858a6d58-6e28-e7e7-17a1-9472ac25fe25.png)

~~~
C:\Users\gachimoto\anaconda3\envs\td\lib\site-packages
~~~

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/cfc3c6df-a387-5bd6-684f-4b2c71c93259.png)

これで、OpenCVが使えるようになります。
Increment Filename on SaveをOffにしておくと、ファイルを保存した時に数字が付かなくなります。
（※[TouchDesigner のパフォーマンスチューニング & TIPS #バージョン管理は他でやる事が多いのでバックアップは作らない](https://qiita.com/satoruhiga/items/33dac6b6faaff3cf210a#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%AE%A1%E7%90%86%E3%81%AF%E4%BB%96%E3%81%A7%E3%82%84%E3%82%8B%E4%BA%8B%E3%81%8C%E5%A4%9A%E3%81%84%E3%81%AE%E3%81%A7%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97%E3%81%AF%E4%BD%9C%E3%82%89%E3%81%AA%E3%81%84)）

## 画像サイズを取得してみる

VideoDeviceIn[TOP]、Null[TOP]、Script[CHOP]を配置します。script1_callbacksを削除し、OP Execute[DAT]を配置し、opexec1のMonitor OPsにnull1を指定、Post CookをONにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/80789900-ca57-7e6f-f2c1-8d92896c7088.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/c7a22a53-7624-acda-fa29-4cc4ba69b605.png)

[TouchDesigner で Python を使ってみよう！(２) #外部エディタの使い方](https://qiita.com/joe0hara/items/19c229c3d0e790006581#%E5%A4%96%E9%83%A8%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BF%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)を参考に、スクリプトをVSCodeで編集するように設定しましょう。

~~~
C:\Users\gachimoto\AppData\Local\Programs\Microsoft VS Code\Code.exe
~~~

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/800f111f-e66b-a1a3-ed83-c67a75483e95.png)


opexec1のEditからscriptを以下のように編集します。

~~~python
import cv2
import numpy as np

def onPostCook(changeOp):
    image = changeOp.numpyArray(delayed=True)
    image = np.array(image)
    image = np.uint8(image * 255.0)
    image = np.flipud(image)
    gray = image[:, :, 0]

    out = op('script1')
    out.clear()
    out.numSamples = 1
    w = out.appendChan('w')
    h = out.appendChan('h')
    ch = out.appendChan('ch')
    h[0], w[0] = gray.shape
    ch[0] = 1
    return
~~~

script1に画像サイズ（幅、高さ、チャンネル数）が表示されたら完了です。

## DNNモジュールを用いて人検出してみる

OpenCVのDNNモジュールを用いて、MobileNetSSDで人検出した値（BoundingBoxの左上の座標(x, y)と幅と高さ(w,h)）をScript[CHOP]に表示してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/5dcc03ca-6cb2-cd2d-c620-73007dc649ec.png)

[MobilNet_SSD_opencv](https://github.com/djmv/MobilNet_SSD_opencv)をダウンロードし、MobileNetSSD_deploy.prototxt、MobileNetSSD_deploy.caffemodelを、TouchDesignerのプロジェクトと同じフォルダに置いてください。

opexec1のスクリプトを以下のように編集してください。

~~~python
import cv2
import numpy as np


def onPostCook(changeOp):
    image = changeOp.numpyArray(delayed=True)
    image = np.array(image)
    image = np.uint8(image * 255.0)
    image = np.flipud(image)
    image = cv2.cvtColor(image, cv2.COLOR_RGBA2BGR)

    out = op('script1')
    out.clear()

    inWidth = 300
    inHeight = 300
    inScaleFactor = 0.007843
    meanVal = 127.5
    classNames = ('background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
                  'diningtable', 'dog', 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor')
    net = cv2.dnn.readNetFromCaffe(
        "MobileNetSSD_300x300.prototxt", "MobileNetSSD_train.caffemodel")
    blob = cv2.dnn.blobFromImage(
        image, inScaleFactor, (inWidth, inHeight), meanVal, swapRB=True, crop=False)
    net.setInput(blob)
    detections = net.forward()

    cols = image.shape[1]
    rows = image.shape[0]
    persons = []
    for i in range(detections.shape[2]):
        class_id = int(detections[0, 0, i, 1])
        if classNames[class_id] == 'person':
            confidence = detections[0, 0, i, 2]
            if confidence > 0.2:
                xLeftBottom = int(detections[0, 0, i, 3] * cols)
                yLeftBottom = int(detections[0, 0, i, 4] * rows)
                xRightTop = int(detections[0, 0, i, 5] * cols)
                yRightTop = int(detections[0, 0, i, 6] * rows)
                w = float(xRightTop - xLeftBottom)
                h = float(yRightTop - yLeftBottom)
                x = float((xRightTop + xLeftBottom)/2.0)
                y = float(rows - (yRightTop + yLeftBottom)/2.0)
                persons.append([x, y, w, h])
                # cv2.rectangle(image, (xLeftBottom, yLeftBottom),
                #               (xRightTop, yRightTop), (0, 255, 0))
                # label = classNames[class_id] + ": " + str(confidence)
                # labelSize, baseLine = cv2.getTextSize(
                #     label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
                # cv2.rectangle(image, (xLeftBottom, yLeftBottom - labelSize[1]), (
                #     xLeftBottom + labelSize[0], yLeftBottom + baseLine), (255, 255, 255), cv2.FILLED)
                # cv2.putText(image, label, (xLeftBottom, yLeftBottom),
                #             cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
                # cv2.imwrite("dnn.png", image)

    if len(persons) > 0:
        out.numSamples = len(persons)
    else:
        out.numSamples = 1

    x = out.appendChan('x')
    y = out.appendChan('y')
    w = out.appendChan('w')
    h = out.appendChan('h')

    if len(persons) > 0:
        for index, person in enumerate(persons):
            x[index] = person[0]
            y[index] = person[1]
            w[index] = person[2]
            h[index] = person[3]
    else:
        x[0] = 0.
        y[0] = 0.
        w[0] = 0.
        h[0] = 0.

    return
~~~

[TouchDesignerでOpenCVを使った簡単な顔認識について](https://qiita.com/komakinex/items/5b84b88d537d393afc98)を参考にして、ごにょごにょするとrenderで人検出結果を切り取り表示することができます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">人検出して切り取り表示<a href="https://twitter.com/hashtag/MobileNetSSD?src=hash&amp;ref_src=twsrc%5Etfw">#MobileNetSSD</a> <a href="https://twitter.com/hashtag/TouchDesigner?src=hash&amp;ref_src=twsrc%5Etfw">#TouchDesigner</a> <a href="https://twitter.com/hashtag/OpenCV?src=hash&amp;ref_src=twsrc%5Etfw">#OpenCV</a> <a href="https://twitter.com/hashtag/Python?src=hash&amp;ref_src=twsrc%5Etfw">#Python</a> <a href="https://t.co/wf6BEvNDUV">pic.twitter.com/wf6BEvNDUV</a></p>&mdash; 藤本賢志(ガチ本)@pixivFANBOXはじめました (@sotongshi) <a href="https://twitter.com/sotongshi/status/1274028619841409025?ref_src=twsrc%5Etfw">June 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

お疲れ様でした。

# 追記

~~~
def onPostCook(changeOp):
    image = changeOp.numpyArray(delayed=True)
    image = np.array(image)
    image = np.uint8(image * 255.0)
    image = np.flipud(image)
~~~

この画像入力の逆をやってTOPに画像をreturnしたいのですが、どうやるんでしょうか？

# 参考文献
- [TouchDesignerでOpenCVを使った簡単な顔認識について](https://qiita.com/komakinex/items/5b84b88d537d393afc98)
- [TouchDesignerで機械学習（ObjectDetection）動かしてみた](https://qiita.com/sumita_v09/items/0b38ed21a28fa43e74c4)
- [TouchDesigner で Python を使ってみよう！(２) #外部エディタの使い方](https://qiita.com/joe0hara/items/19c229c3d0e790006581#%E5%A4%96%E9%83%A8%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BF%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)
- [MobileNet SSD Object Detection using OpenCV 3.4.1 DNN module](https://ebenezertechs.com/mobilenet-ssd-using-opencv-3-4-1-deep-learning-module-python/)