SORACOM S+ Camera Basicにアルゴリズムをデプロイしてみました

前回の[S+cameraを使用して簡単な画像処理をローカル環境でやってみた](https://qiita.com/shinfrom/items/c99726ee0b60029b9f67)の続きになりますが、S+ Camera Basicにシンプルなアルゴリズムをデプロイしてみました。

今回の流れです。

・SORACOM Beamの接続確認
・計算した結果をエッジカメラからクラウドに送信する方法
・エッジカメラで動かすためのアルゴリズムの変更
・作成したアルゴリズムをエッジカメラにインストールする方法
・・soracomHarvestにアップロード
・・soracomMosaicから読み込み
・計算に利用した画像を確認する方法


計算した結果をS+ Camera Basicから自サーバーへ送信するにはSORACOM Beamという転送ツールを使用します。
SORACOM BeamとはIoTデバイスから受け取ったデータのプロトコルを変換し、クラウドや自分のサーバに転送するサービスです。

まずは、SORACOM Beamの設定から始めます。
ユーザーコンソールからSIMの設定へ入って、SORACOM Beam設定をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/60864573-2eb7-d8c9-356f-9c9fdf7b5757.png)
転送先のサーバーにはnode-redを使用しました。
httpのエントリポイントを追加します。転送先は今回の例ではhttp://ドメイン:1880/testのようになります。
エントリポイントについては変更しません。

受信側はhttpが受信できれば何でもよいのですが、今回はnode-redを使用しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/3592bf15-b36b-7141-2110-882c3036230b.png)
このようにシンプルなフローを用意すれば十分です。
メソッドはPOSTにしてURLは/testにします。

準備ができましたので、送信テストを行います。
SORACOMのSIMが刺さっているデバイスから通信する必要がありますので、S+ Camera Basicから送信テストを行います。
S+ Camera Basicにssh接続を行います。
SORACOM Mosaicコンソールにログインしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/8e781ade-aed5-7722-ddfe-1ff4b11e8425.png)
SORACOM Mosaicコンソール上の右側にあるchange device passwordで任意のパワードを設定します。
パスワードの設定が終わったら、Enable remote accessを押して接続情報を取得します。
初期設定のままでもテストすることができます。
確定すると接続用のsshが表示されますので、そのまま使用してアクセスします。パスワードは先ほど設定した任意のパスワードになります。
S+ Camera BasicからSORACOM Beamのエンドポイントにデータを送信してみてnode-redに転送ができるか確認します。

```console
curl -v -XPOST -H "Content-Type:application/json" -d "{\"key\":\"value\"}" http://beam.soracom.io:8888/
```
を実行したときにnode-redでデータを受信できると、SORACOM Beamのデータ転送はうまくいっています。

次に実際のアルゴリズムを作成していきます。
S+ Camera BasicにデプロイするためにはCameraApp0というフォルダを作っておく必要があります。
フォルダの中にはCameraApp0、PreSetup、info.jsonという３つのファイルが必要になります。この３つのファイルがアルゴリズムをデプロイするための最小構成になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/9beda536-39cb-1877-a882-ab17a3c34664.png)
CameraApp0というのは少し紛らわしいのですが、CameraApp0というフォルダの中にCameraApp0というファイルが入っています。
CameraApp0のファイルに実際の処理をPythonで書いていくことになります。
PreSetup、info.jsonについては今回は重要ではないのですが、info.jsonではバージョン情報を設定することができますので、番号くらいは変更しておいたほうがわかりやすいかもしれません。

```json
{
  "PkgApp": "CameraApp0",
  "PkgName": "beatest",
  "PkgVersion": "1.2.0",
  "PkgIdentifier": "colorRecognitionToBeam"
}
```
実際に今回テストするアルゴリズムです

```python
#!/opt/soracom/python/bin/python
# 緑色が濃いところを見つけて四角で囲むというサンプルプログラムです

import os
import sys
from PIL import Image
import time
import io
import requests
import cv2
import numpy as np

print("START: Color recognition system", flush=True)


# カメラの画像を取得する準備
requests.get("http://localhost:8080/v1/stopCameraCapture")      # httpのGETメソッド
print("START: waiting for cameraState(10s)", flush=True)
time.sleep(10)
requests.put("http://localhost:8080/v1/cameraState", json={"width": 1280, "height": 720})
r = requests.get("http://localhost:8080/v1/startCameraCapture")

# 画像を取得できているか確認する
if r.status_code != 200:
    print(f"Failed /v1/startCameraCapture (HTTP {r.status_code}), exited.", flush=True)
    exit
print("START: waiting for camera(5s)", flush=True)
time.sleep(5)
# 画像を取得できることを確認したらループに入る
print("Initialized. Go the loop.", flush=True)

#PILのデータをopencvで扱えるようにする関数
# https://qiita.com/derodero24/items/f22c22b22451609908ee
def pil2cv(image):
    ''' PIL型 -> OpenCV型 '''
    new_image = np.array(image, dtype=np.uint8)
    if new_image.ndim == 2:  # モノクロ
        pass
    elif new_image.shape[2] == 3:  # カラー
        new_image = cv2.cvtColor(new_image, cv2.COLOR_RGB2BGR)
        print("cv2 change", flush=True)
    # elif new_image.shape[2] == 4:  # 透過
    #     new_image = cv2.cvtColor(new_image, cv2.COLOR_RGBA2BGRA)
    return new_image

#opencvのデータをPILで扱えるようにする関数
def cv2pil(image):
    ''' OpenCV型 -> PIL型 '''
    new_image = image.copy()
    if new_image.ndim == 2:  # モノクロ
        pass
    elif new_image.shape[2] == 3:  # カラー
        new_image = cv2.cvtColor(new_image, cv2.COLOR_BGR2RGB)
    elif new_image.shape[2] == 4:  # 透過
        new_image = cv2.cvtColor(new_image, cv2.COLOR_BGRA2RGBA)
    new_image = Image.fromarray(new_image)
    return new_image

# 処理を行うための無限ループ
while True:
   
    r = requests.get("http://localhost:8080/v1/cameraJpegImage")    #画像を取得
    
    if r.status_code != 200:        #200以外のステータスの時は画像取得失敗
        print(f"Failed /v1/cameraJpegImage (HTTP {r.status_code}), next.", flush=True)
        break
    
    #画像ファイルからファイルを読み込む　バイナリで扱う 
    image = Image.open(io.BytesIO(r.content))
    img = pil2cv(image)     #opencvで扱える形に変換する

    # 確認用の画像を作成する
    frame = img

    #矩形のサイズ　矩形の中心からの距離で考える
    RECT_SIZE = 15

    # HSV変換
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV_FULL)
    
    # みどり色を指定する
    low_color = np.array([50, 75, 75])
    upper_color = np.array([70, 255, 255])

    # 色を抽出する
    ex_img = cv2.inRange(img_hsv,low_color,upper_color)

    # 縦，横方向の和の中で最も大きいインデックスを抽出
    x = np.argmax(np.sum(ex_img,axis=0))
    y = np.argmax(np.sum(ex_img,axis=1))
     
    #周辺を矩形で囲む
    cv2.rectangle(frame,pt1=(x+RECT_SIZE, y+RECT_SIZE),pt2=(x-RECT_SIZE, y-RECT_SIZE),color=(0, 0, 255),thickness=3,lineType=cv2.LINE_4,shift=0)
    

    # soracom beamに送信するデータの作成
    sendobj = "{'X': %d,'Y':%d }" % (x,y)
    # soracom beamにデータを送信する
    response = requests.post(' http://beam.soracom.io:8888/', data=sendobj)
    print(response.status_code,flush=True)      # HTTPのレスポンスの確認
    print(response.text,flush=True)             

    # 処理に使用した画像を送信するためにバイナリに変換
    output = io.BytesIO()
    p_image = cv2pil(frame)  
    p_image.save(output, format='JPEG')
    r = requests.post("http://harvest-files.soracom.io", data=output.getvalue(), headers={"Content-Type":"image/jpeg"})
    print("Save to SORACOM Harvest Files", flush=True)
    if r.status_code != 200:
        print(f"Failed (HTTP {r.status_code}), next.", flush=True)

    print('finish', flush=True)

    time.sleep(120)
```
緑色が濃いところを探して四角で囲むというシンプルなアルゴリズムです。
このアルゴリズムをデプロイしていきます。

ここから先の作業は、自分の場合はlinuxのほうがやりやすかったのでwslで行います。
Windowsで行っても問題はないと思います。
先ほど作成したCameraApp0フォルダをlinuxに移します。
まずはCameraApp0を入れるための任意のディレクトリを作成して移動しておきます。

```console
mkdir CameraToBeamTest && cd CameraToBeamTest
```
作成したディレクトリにCameraApp0を移動させます。
この移動の操作はWindowsの機能で行います。
Windowsのエクスプローラーでパスに\\wsl$を入力するとwslのファイルにアクセスすることができます。
GUIでドラッグアンドドロップで移動させることができます。

任意の場所にCameraApp0を移動させてからwslでの操作に戻ります。
公式のデプロイツールを利用してデプロイすることもできるのですが、
今回は手動でデプロイすることにします。
S+ Camera Basicにデプロイするためには圧縮をする必要があります。圧縮をする前に権限を変更することを忘れないでください。権限の変更を忘れるとプログラムがうまく動きません。
CameraApp0フォルダを圧縮します。

```console
chmod 740 CameraApp0/{CameraApp0,PreSetup}
tar czvf CameraToBeamTest.tgz CameraApp0/
```
CameraToBeamTest.tgzの部分は任意の名前に変更できますが、ディレクトリの名前と合わせておいたほうがわかりやすいと思います。

作成した圧縮ファイルをSORACOM Harvest Filesにアップロードします。
SORACOM Harvest Filesの操作画面に行ってアッロードボタンを押すと、GUIの操作でファイルをドラッグアンドドロップできますので、先ほど作成した圧縮ファイルをアプロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/81474780-d8c4-7437-805c-740172b86667.png)

アップロードしたファイルをSORACOM Mosaicから操作を行ってS+ Camera Basicにインストールします。
SORACOM MosaicのコンソールからALGORITHMをクリックしてアルゴリズムのインストール画面に入ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/9f50ef8d-576e-e1e0-e6e4-7d408d92928a.png)
install new algorithmをクリックした後にCustom Algorithmを選択します。
urlについては先ほどアップロードしたSORACOM Harvest Filesを指定します。
例えば先ほどの例でアップしたCameraToBeamTest.tgzあれば
http://private.harvest-files.soracom.io/CameraToBeam.tgz
のようになります。
urlを指定した後にinstallボタンを押すと、S+ Camera Basicにアルゴリズムがデプロイされます。

デプロイが終了すると、アルゴリズムの名前やバージョンが変更されますので、info.jsonファイルで分かりやすいものに変更しておくと便利です。

実際に動かしてみると、先ほど設定したnode-redにこのようなメッセージが入ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/002051d8-de21-37e7-cb14-13a31e047f71.png)
本当に画像処理が行われているか確認したいときには画像処理を行ったファイルを見てみます。
今回の例ではSORACOM Harvest Filesのrootディレクトリに入っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/741b8db3-964a-f6b6-2293-5414eaaf9144.png)
下向きの矢印を押すとファイルをダウンロードすることができます。
txtファイルで落ちてくるので、拡張子を.jpgに変換して開いてみると
![imagedata (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/7030e8ab-ca0a-fc91-00ab-66e7f9be43a1.jpeg)
緑色を検出できていることが確認できます。
緑色と茶色の境界が検出されているように見えますが、
緑色が一番濃いところを見つけてその周りを矩形で囲んでいるだけです。
この辺りはもっとわかりやすいようにアルゴリズムをチューニングする必要があると思われます。
httpで通信を行っていますが、node-redで受信できるところまで確認できていますので、いろいろな方法でIoTとして接続することができます。

参考情報
[エッジAIカメラ「S+ Camera Basic」でソラコムサンタを検知する](https://qiita.com/ma2shita/items/42ad9527b37495887449)
[【Python】Pillow ↔ OpenCV 変換](https://qiita.com/derodero24/items/f22c22b22451609908ee)
[ソラコムユーザードキュメント](https://users.soracom.io/ja-jp/guides/iot-devices/splus-camera-basic/api-reference/)
[独自のアルゴリズムを開発する（ソラコム公式ドキュメント）](https://users.soracom.io/ja-jp/docs/mosaic/develop-algorithm/)
[SORACOM Beam](https://users.soracom.io/ja-jp/docs/beam/?fbclid=IwAR04DLv4HfGiqXLLI4vrjD66kBAwqcv0v1kKUpQy2fC0qncT01YOHHkJXSI)
[Harvest Files でデバイスのファイルをクラウドに送信する](https://users.soracom.io/ja-jp/docs/harvest/send-files/?fbclid=IwAR26XKohvLQOD5ITYLUUMe87GIa84w-EOIKRpSdpnCKRRzPHFNxCT8LKYj4)