SORACOMアドベントカレンダー 2020、アンカー担当の松下(ニックネーム: Max)です。
12/24 7:00pm JST 時点で、私を残すだけとなりました。そしてまだ、12/25は5時間ほど残っています。
極めて順調に毎日投稿される記事に多大なるプレッシャー感謝を感じつつ、日々生活しておりましたが、なんとかまとめ上げられそうで喜ばしい限りです!
今年もソラコムサンタがやってくるきた
IoTプラットフォームを提供しているSORACOMには、この時期になると活発になるメンバーがいます。それが ソラコムサンタ です。
ソラコムサンタは SNSやフォームにお寄せいただいた、SORACOMサービスの機能追加や改善リクエストを具現化してプレゼントしており、ソラコムメンバーの行動指針であるリーダーシップステートメントの中でも、特に "Customer Centric" 、 "Likability" そして "Deliver Results" を体現している存在です。
今年も「ソラコムサンタより愛をこめて 2020」というブログと共に、数多くのプレゼントをくれたソラコムサンタです。ありがとう!
ソラコムサンタを検知したい、カメラで。
私は2017年3月にソラコムメンバーとなりましたが、2015年の時点で既にいらっしゃったにも関わらず、ソラコムサンタ殿のご尊顔を拝見したことがありません。そもそも、彼なのか彼女なのか、そしてアカウント名も不明なのです。
※2016年って書いてたけど、2015年から在籍されていました!!
仮にも「サンタ」を名乗っているわけですから、おそらくはサンタクロースの眷属と推察できるわけですが、そうなると、もしかすると我が家にも訪れるかもしれない!?という期待が出てくるわけです。
昨今はテレワークで自宅に籠りっぱなしの私ではありますが、常に警戒するのは正直しんどい。そこで考えたのはカメラによる検知ですが、いつ現れるかわからない存在を相手に常に画像をクラウドにアップし続けるのも、テレカン用の帯域を確保しておきたい身としては気が引けました。
そこで、エッジコンピューティングですよ。
「ソラコムサンタを検知したら、その時お知らせしてくれれば、、、」
まさにエッジコンピューティングが役立つ時です。しかも画像分析は、今一番熱い!
これがアンカーである12/25のブログにふさわしい!と書き始めたわけです。ただし、いまは12/24 8:00pm JSTですが。
構成
前置きがだいぶ長くなりましたが、構成です。
Lobeでモデルを作りTensorFlow形式でexportした後、そのモデルを使ってS+ Camera Basic上で推論を行い、結果をデータ収集・蓄積サービス「SORACOM Harvest Files」で確認します。
Lobe について
私は機械学習とかそういった方面には全くスキルがありません。TensorFlowは聞いたことがありますが、TensorFlow.jsやTensorFlow Liteとの違いはわかりませんし、ハイパーほにゃららと聞けばハイパーオリンピックを連想するくらいのレベルです。
そんな私でも画像分類ができる神アプリが現れました。それがLobeです。
分類1つあたり5枚の画像を用意するだけで、それなりに動いてくれるという「ああ、ついに21世紀が来た」と感じる、そんなデスクトップアプリなのです。まあ、21世紀も20年経ちますけど。
Lobeの試用の様子はラベル当たり5枚の画像があれば始められる「Lobe」をご覧いただくとして、ここでのポイントはLobeで学習したモデルをexportできることにあります。
Exportできる形式は 2020年12月時点で6種類(うち1つはLocalでのHTTP REST API)あり、その中にTensorFlowも入っています。
S+ Camera Basic について
S+ Camera Basicは「カメラ + Linuxベースコンピュータ + セルラー通信」が一体化した、通信するエッジAIカメラです。通信にスマートフォンで使われるセルラー通信が採用されており、Wi-Fiを始めとしたネットワーク環境を用意することなく、データをクラウドとやり取りできます。
ただ単純にカメラとして動作させることも可能ですが、Linuxベースコンピュータですのでプログラムを実行することも可能です。また、このプログラムの書き換えもセルラー通信を経由して行えるので、常に最新のプログラムや機械学習のモデルを適用することができます。
S+ Camera BasicではPython 3が利用できます。また、HTTPクライアントのrequestsや画像処理のPillow、数値計算で定番のnumpy、そして機械学習ライブラリのTensorFlowも導入済みです。(すべてのライブラリはこちらをご覧ください)
TensorFlowによる推論が標準で可能という事は、Lobeでexportしたモデルを手間なくS+ Camera Basic上で動かすことができるのが、今回のポイントとなります。
今回は半分ネタでソラコムサンタ風を学習させていますが、この構成自体は汎用的に使えるのではないでしょうか。
ソラコムサンタって、どんな格好だろう?🤔
そういえばソラコムサンタってどんな格好なんでしょうね?眷属ですから、いわゆる「あの恰好」ではあるの思うのですが、色にはこだわってるんじゃないかな。青色に少し緑がかかった色 "チェレステ" (通称 #ソラコム色) かなあと。
ということで想像して衣装を用意してみました。今回はこれを基に学習させることにします。まあ、ご本人の画像が無いものですから。
Lobeでモデル作成
Lobeでのモデル作成は マイクロソフトが公開した機械学習モデルの訓練を容易にできる「Lobe」を試してみた。 に詳しく載っていますので、ごらんください。
「私(Max)」「ソラコムサンタ(Soracom Santa)」「何もなし(None)」の3つにしました。それぞれ60枚くらい撮っています。
S+ Camera Basicの開発 / 最小構成を試す
S+ Camera Basicでの開発を行ってみます。
といっても、実際はPython 3の開発を行うのと変わりありません。私は Windows 10 で WSL2 に Ubuntu 20.04 の環境を使用しました。
開発環境の準備
開発環境の構築手順はユーザードキュメントに記載されています。
この中で
Python の runtime 環境の構築完了後、SORACOM Mosaic に必要な Python のモジュールをインストールします。Python のモジュールのリストはこちらになります。こちらを pip を用いてインストールしてください。
とありますが、ここの具体的な作業としてはSORACOM Mosaic Python module listを requirements.txt
として保存したうえで
$ pip install -r requirements.txt
とします。
もしエラーが発生したら、以下の対処方法があります。
Could not find a version that satisfies the requirement tflite-runtime...
requirements.txt
から tflite-runtime==2.1.0.post1
を削除(もしくはコメントアウト)してから再度 pip install -r requirements.txt
を実行しましょう。
`tensorflow 1.14.0 has requirement tensorboard<1.15.0,>=1.14.0, but you'll have tensorboard 2.0.2 which is incompatible.
必須の対処ではありませんが requirements.txt
の tensorboard==2.0.2
を tensorboard==1.14.0
と変更してから再度 pip install -r requirements.txt
を実行しましょう。
You are using pip version 19.0.3, however version 20.3.3 is available.
こちらも必須の対処ではありませんが、気持ちの問題でアップデートしたければ pip install --upgrade pip
で解消できます。
FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated;
こちらもwarningですが気になるようでしたら numpy のバージョン調整を行えばOKです。
$ pip uninstall numpy==1.17.2
$ pip install numpy==1.16.6
その後のjqやSORACOM CLIのインストールは「あったら便利」程度であり、必須ではありません。
最終的なディレクトリ構成はこのようになりました。
mosaic_app_logging_test
はこの後で解説します。
.
├── mosaic_app_logging_test
│ └── CameraApp0
│ ├── CameraApp0
│ ├── PreSetup
│ └── info.json
├── python
└── requirements.txt
アルゴリズム開発 / まずはログに出力
いきなり画像活用をしたいところですが、ここは落ち着いてまず「ログに出力」するところから始めましょう。今後の開発の基礎知識になるからです。
ログは標準出力/標準エラー出力に出力されたものになります。 SORACOM Mosaic コンソール(WebUI)の "Download logs" で得ることができます。
フォルダ構成、ファイル作成(CameraApp0, PreSetup, info.json)
フォルダ構成は先に紹介した通りです。アルゴリズムに関わる部分のみ再度抜き出しています。
.
├── mosaic_app_logging_test
└── CameraApp0
├── CameraApp0
├── PreSetup
└── info.json
mosaic_app_logging_test
という名称は任意です。
S+ Camera Basicのアルゴリズム1開発にはユーザーサイトにも書かれているように、必須のファイルが存在しますが、必要なのは CameraApp0, PreSetup, info.json の3ファイルです。
これら3つのファイルを CameraApp0
というフォルダに置きます。この名称は固定となります。CameraApp0(フォルダ)以下の構成は自由にしていただいてOKです。後述するモデルもこの中に作っていく事になります。
※ CameraApp0 はファイルとフォルダの2つの名前で使われていますが単体で CameraApp0(フォルダ) と明示していなければファイルと読んでください。
CameraApp0
CameraApp0
がエントリーポイントです。
ユーザーサイトに書かれている CameraApp0.py
はエントリーポイントから呼び出されているだけなので CameraApp0
だけで済んでしまう場合は不要です。ここからのコードは全部 CameraApp0
で完結するように書いてあります。
S+ Camera Basicのアルゴリズム無限ループ実装が基本となります。ここでは while True
を使いましたが、threading
を使っても良いでしょう。
Uptimeを3秒毎に標準出力(= ログ)へ出力する例です。
注意点はバッファリングのFlushです。これを行わないと、いつまで経ってもログに出力されて来ません。2 (参考: https://qiita.com/mmsstt/items/469a9346ce545709f53c)
#!/opt/soracom/python/bin/python
import time
started_at = time.time()
while True:
payload = {"uptime_s": int(time.time() -started_at)}
print(payload, flush=True)
time.sleep(3)
ローカルでの動作確認をしておきましょう。
PreSetup
PreSetup
は CameraApp0 実行前の hook スクリプトです。
今回は何もしません。ログ(標準出力)に出力しておしまいです。
#!/bin/bash
echo "Output from PreSetup"
exit 0
ここまででお気づきになったかもしれませんが、CameraApp0 や PreSetup は冒頭のshebangで指定されたもので動くため、ほかのランタイムで動かすことも可能ではあります。
info.json
このアルゴリズムに関するメタ情報を記載します。
SORACOM Mosaic コンソールで表示されるので、わかりやすい名称を書くのが良いでしょう。
デプロイ
3つのファイルが揃ったらデプロイです。
デプロイスクリプトが提供されていますが、何がされているのかを確認するためにもステップを紹介します。
$ cd mosaic_app_logging_test/
$ chmod 740 CameraApp0/{CameraApp0,PreSetup}
$ tar czvf mosaic_app_logging_test.tgz CameraApp0/
このtgzファイル(ファイル名は任意)をSORCOM Harvest Filesにアップロードし、そのアップロードパスをSORACOM Mosaicの "Install new algorithm..." で指定すると、S+ Camera Basic から SORACOM Harvest Files上の tgzファイルを取得し、適用されるといった流れになります。
tgz作成時の注意点は chmod です。CameraApp0 や PreSetup は実行フラグが付いている必要があります。特に WSL の方でPOSIXのフラグが扱えないファイルシステムの場合はご注意ください。
SORACOM Harvest Files の /Max/mosaic_app/mosaic_app_logging_test.tgz
とアップロードしたら、SORACOM Mosaic では http://private.harvest-files.soracom.io/Max/mosaic_apps/mosaic_app_logging_test.tgz
と指定する関係となっています。
SORACOM Harvest Filesの様子
SORACOM Mosaic コンソール側の指定
特に info.json でバージョン(PkgVersion)を更新しておくと、ちゃんと適用されたかの確認がわかりやすいのでお勧めです。
ログの確認
SORACOM Mosaic コンソールの "Download logs" で取得できます。
テキストファイルです。お好きなエディタでどうぞ。
よりもっとダイレクトに確認したい方は トラブルシューティング に記載されている手順で tail -f
を使えるようにしてください。
これにて、アルゴリズム開発の流れとデバッグ環境は揃った事になります。
S+ Camera Basicの開発 / TensorFlow で推論
ここからはTensorFlowによる推論です。いきなりジャンプアップした気もしますが大丈夫です。
すでにLobeでモデルを作成しているため、
- Lobeからのモデルのexport
- コーディングとテストとデプロイ
と、まあ簡単(?!)ですね。
LobeからのモデルのexportとSORACOM Harvest Filesへの保存
Lobe から TensorFlow 形式で export すると Objects TensorFlow
というフォルダが作られます。この中にモデルとサンプルコードまで(!)入っています。
このモデルが入ったフォルダをアルゴリズムの中に同梱したかったのですが、思いのほかサイズが大きく(私は100MBくらいあった)、アルゴリズムインストールでタイムアウトが起こってしまいます。
そのため SORACOM Harvest Files にZIPでアップロードしておき、PreSetupでダウンロード&展開することにしています。
※PreSetup毎にダウンロードを行うので、通信速度や量は特に気をつけてください。
コーディングとテストとデプロイ
Objects TensorFlow/example/tf_example.py
は import 可能なように作られています。さすが。ぜひ、流用させていただきましょう。
ここからは Objects TensorFlow
が CameraApp0
(フォルダ)内に存在する前提で書いています。
tf_example.py
と等価の処理なら、以下のように書くことができます。
#!/opt/soracom/python/bin/python
import os
import sys
from PIL import Image
sys.path.append('Objects TensorFlow/example')
import tf_example
target = sys.argv[1]
if not os.path.isfile(target):
print(f"Couldn't find image file {target}")
exit(255)
image = Image.open(target)
# convert to rgb image if this isn't one
if image.mode != "RGB":
image = image.convert("RGB")
model = tf_example.Model()
model.load()
outputs = model.predict(image)
print(f"Predicted: {outputs}")
"""
e.g.)
$ python this_file.py image.jpg
Predicted: {'Prediction': 'Nothing', 'Confidences': [0.0, 0.0, 1.0]}
これを基に S+ Camera Basic のカメラから読み込んで判定させ続けるコード(CameraApp0)は以下の通りです。
※今回は mosaic_app_inference_by_tensorflow
としています。
Soracom Santa か Max を検出したら、その時の画像を SORACOM Harvest Files にアップロードします。判定は Prediction
の部分です。
#!/opt/soracom/python/bin/python
import os
import sys
from PIL import Image
import time
import io
import requests
print("START: mosaic_app_inference_by_tensorflow", flush=True)
sys.path.append('Objects TensorFlow/example')
import tf_example
requests.get("http://localhost:8080/v1/stopCameraCapture")
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)
while True:
started_at = time.time()
r = requests.get("http://localhost:8080/v1/cameraJpegImage")
if r.status_code != 200:
print(f"Failed /v1/cameraJpegImage (HTTP {r.status_code}), next.", flush=True)
break
image = Image.open(io.BytesIO(r.content))
# convert to rgb image if this isn't one
if image.mode != "RGB":
image = image.convert("RGB")
model = tf_example.Model()
model.load()
outputs = model.predict(image)
print(f"Predicted: {outputs}", flush=True)
process_time = time.time() - started_at
print(f"Elapsed: {process_time} s", flush=True)
if outputs["Prediction"] == "Soracom Santa" or outputs["Prediction"] == "Max":
print("Dareka-Hakken !?", flush=True)
virtIO = io.BytesIO()
image.save(virtIO, format="JPEG")
r = requests.post("http://harvest-files.soracom.io", data=virtIO.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)
PreSetup は、SORACOM Harvest Filesからのダウンロードとunzip処理を加えました。(同じファイルだったらダウンロードしないとか、ダウンロード後のハッシュ検証とかは省きました、、ごめん)
#!/bin/bash
echo $PWD
echo "Cleanup"
rm -rf "Objects TensorFlow"
rm -f Objects%20TensorFlow.zip
echo "Download and Extract"
curl -O http://private.harvest-files.soracom.io/Max/mosaic_apps/Objects%20TensorFlow.zip
unzip -x Objects%20TensorFlow.zip
echo "done."
あとはデプロイするだけです!
$ cd mosaic_app_inference_by_tensorflow/
$ chmod 740 CameraApp0/{CameraApp0,PreSetup}
$ tar czvf mosaic_app_inference_by_tensorflow.tgz CameraApp0/
さあ、いつでも来やがれ、ソラコムサンタ!
Predicted: {'Prediction': 'None', 'Confidences': [0.0, 0.0, 1.0, 0.0]}
Elapsed: 28.12995481491089 s
Predicted: {'Prediction': 'Max', 'Confidences': [0.0, 1.0, 0.0, 0.0]}
Elapsed: 28.623439073562622 s
Dareka-Hakken !?
Save to SORACOM Harvest Files
Predicted: {'Prediction': 'Soracom Santa', 'Confidences': [0.0, 1.1005343888624412e-28, 0.0, 1.0]}
Elapsed: 27.988136529922485 s
Dareka-Hakken !?
Save to SORACOM Harvest Files
おっと、誰か発見していますね!2件あります。
ダウンロードしてみると、たしかに私と、私の扮するソラコムサンタの姿が!
動いてくれて良かったです!
ご覧の通り、推論に30秒近くかかっています。S+ Camera BasicはCoral Edge TPUに対応しているので、こういったもので底上げは出来るかなと思います。(もちろんそれに合わせた実装をする必要もありますが)
まとめ (というか、オチ)
「ソラコムサンタ風の私」は、無事検出できました。
Lobe上でテストした後にエッジへデプロイできるのは、手軽ですし検証性も上がって良かったと個人的に思っています。
まあ本物のソラコムサンタが現れたら検出できるかと言われれば「知らないものは推論できない」というのがオチですね (^^;;
機械学習がアツい昨今ではありますが、銀の弾丸ではない事を改めて認識しました。
もちろん、私はソラコムサンタじゃありませんからね!!!
モデルもクラウドも、利用して「迅速に回す」
さて、特にモデルは1回作って終わりではありません。
機械学習は正解が見えづらいからこそ、作って試すサイクルを高速に回す事が重要です。モデル更新のために現場に行くような運用では、高速に回すことは不可能でしょう。だからこそ通信を活用していく、特に設置の状況に左右されることの無い安定した環境は不可欠です。
クラウド側のシステムも閉じることなく、様々な可能性に対応できるべきです。
今回はSORACOM Harvest Filesに画像をアップするだけとなりましたが、S+ Camera Basic自体はSORACOMのサービスに閉じることなく、他のクラウドサービス連携が可能となっています。
たとえば reuqests を使ってSaaSの HTTP REST APIを呼び出すといった構成もできます。検出したらリモート雲台を操作してフォーカスを当てるといった、そういう事も組み合わせによって実現できるでしょう。
また、その時はデータ転送支援の「SORACOM Beam」やクラウドアダプタ「SORACOM Funnel」、FaaSを直接呼び出せる「SORACOM Funk」を経由すると、より簡単に外部サービス連携ができますが、その時に一番楽をして成果を得るようにすることが重要ではないでしょうか。
充実している「サンプルアルゴリズム」
ここまで書いておいて「ズコーッ」的な紹介ですが。
S+ Camera Basicには目的別のアルゴリズムが用意されており、ボタン一発でインストールが可能です。パフォーマンスもTensorFlowを使って自力でやるよりもよほど良さそうです。
特に12/24にリリースされたばかりの顔認識アルゴリズムは、画像の調整からラベル付けのUIまでそっろた仕組みとして、今回の解決にも使えたかもしれません。こっちを使えばよかったかな?
ご興味があれば、試してみてください。使用感のフィードバックもお待ちしております!
いかがだったでしょうか?
実はIoTLT アドベントカレンダー 2020 でもアンカー担当で、同時進行がかなり厳しかったのですが、「KeiganMotorによるリモート雲台」と同時にやってしまうというアイデアで、なんとかかき揚げ(!)ました。
こんな感じで KeiganMotor に S+ Camera Basic を取り付けて、画角調整もできます。詳しくは SORACOMとモーターモジュール "KeiganMotor" で作る「どこでもリモート雲台」 もご覧ください。
最後はS+ Camera Basicの雲台として利用したKeiganMotorのご挨拶で〆たいと思います。これもさっきのブログの使いまわしですが「流用して、迅速に回す」!
良いお年を! (^^/~
EoT