[レジなし無人販売冷蔵庫] 物体検出モデルをSageMaker Neoで最適化してJetson Nanoで利用してみました
1 はじめに
CX事業本部の平内(SIN)です。
Amazon Web Services ブログでは、「レジなし無人販売冷蔵庫を構築についての記事」が公開されています。
レジなし無人販売冷蔵庫を構築できる、This is my Smart Cooler プログラムを公開しました
こちらでは、「お客様自らがレジなし無人販売冷蔵庫を迅速に構築し学習や体験ができる This is my Smart Cooler プログラムを発表します。」ということで、そのレシピがGithubで公開されています。
レジ無し無人販売冷蔵庫 構築レシピ
「これを真似てみたい」ということで、ここまで作業を進めています。
紹介されているレシピでは、物体検出のモデルは、Amazon SageMaker(以下、SageMaker)のエンドポイントにデプロイして利用されていますが、これを、「できるだけエッジ側で処理したい」と言うことで、Jetson Nano上で利用してみました。
当初、RaspberryPiで試してみたのですが、「推論」に少し時間がかかり過ぎるので、Jetson Nanoに変更しちゃいました。
利用したモデルは、下記で作成されたものです。
「レジなし無人冷蔵庫」冷蔵庫内の商品を検出する物体検出モデルを作成してみました
以下は、冷蔵庫の中のカメラの画像を推論しているようすです。SageMakerのエンドポイントと比べると、当然遅いですが、フレームあたりの推論は、0.3sec程度であり、推論は、ドアを締めたタイミングしか必要なので、こんなもんでも良いのではと思ってます。
2 SageMaker Neo
SageMakerの組み込みアルゴリズム(物体検出のモデル)で作成されたモデルをSageMake Neo(以下、Neo)でコンパイルするためには、変換が必要ですが、要領は下記のとおりです。
Neoでのコンパイル(最適化)の設定です。
- データ入力値: {"data":[1,3,512,512]}
- 機械学習フレームワーク: MXNet
- 対象デバイス: jetson_nano
コンパイル後のモデルは、指定したバケットに出力されます。
作成したモデルは./modelに展開しています。
$ mkdir model $ tar xvfz deploy_model-jetson_nano.tar.gz -C model $ $ ls -la model -rw-r--r-- 1 nvidia nvidia 709 12月 29 09:51 compiled.meta -rw-r--r-- 1 nvidia nvidia 6114 12月 29 09:51 compiled_model.json -rw-r--r-- 1 nvidia nvidia 88 12月 29 09:51 compiled.params -rwxr-xr-x 1 nvidia nvidia 113847448 12月 29 09:51 compiled.so -rw-r--r-- 1 nvidia nvidia 14977 12月 29 09:51 dlr.h -rw-r--r-- 1 nvidia nvidia 6539120 12月 29 09:51 libdlr.so -rw-r--r-- 1 nvidia nvidia 647 12月 29 09:51 manifest
3 DLR (Deep Learning Runtime)
Neoで最適化されたモデルをJetson Nano上で使用するためには、DLRが必要です。
現在、手元のJetson Nanoで動作している、JetPackのバージョンは、4.2でした。
2020.12.30現在、DLRの最新バージョンであるVer1.7では、JePack 4.2用のコンパイル済みのモジュールが、公開されていましたので、これを使用させて頂いています。
https://github.com/neo-ai/neo-ai-dlr/releases
$ pip3 install cython $ pip3 install https://neo-ai-dlr-release.s3-us-west-2.amazonaws.com/v1.7.0/jetpack4.2/dlr-1.7.0-py3-none-any.whl
インストール後の状況です。
$ python3 Python 3.6.9 (default, Oct 8 2020, 12:12:24) >>> import dlr ・・・(略) パフォーマンス向上のためのメトリック収集について >>> dlr.__version__ '1.7.0' >>> import numpy >>> numpy.__version__ '1.19.4'
参考: Installing DLR
4 SWAP
今回作成したモデルでは、大丈夫そうだったのですが、念の為、SWAPを8Gまで拡張しています。
$ sudo dd if=/dev/zero of=/swapfile bs=1G count=8 $ sudo swapoff -a $ sudo mkswap /swapfile $ sudo swapon /swapfile $ free -h total used free shared buff/cache available Mem: 3.9G 1.8G 768M 37M 1.4G 1.9G Swap: 7.9G 0B 7.9G
5 動作確認
最初にセットアップしたDLR環境が正常に動作しているかを、簡単なコードで確認しています。環境変数TVM_TENSORRT_CACHE_DIR で指定したディレクトリにキャッシュが作成されますので、初回の推論以外は、0.3secで動作していることが分かります。
import numpy as np import time import os import dlr from dlr.counter.phone_home import PhoneHome PhoneHome.disable_feature() os.environ['TVM_TENSORRT_CACHE_DIR'] = '.' model = dlr.DLRModel('model/', 'gpu', 0) print("DLRModel() end") print("model.get_input_dtypes(): {}".format(model.get_input_dtypes())) print("model.get_input_names(): {}".format(model.get_input_names())) img = np.random.rand(1, 3, 512, 512) print(img.shape) print("start") for _ in range(10): start = time.time() model.run({'data': img}) processing_time = time.time() - start print("{:.2f} sec".format(processing_time))
$ python3 run.py 2020-12-30 10:36:53,450 INFO Found libdlr.so in model artifact. Using dlr from model/libdlr.so DLRModel() end model.get_input_dtypes(): ['float32'] model.get_input_names(): ['data'] (1, 3, 512, 512) start 180.06 sec 0.30 sec 0.30 sec 0.30 sec 0.30 sec 0.30 sec 0.30 sec 0.30 sec 0.30 sec 0.30 sec
6 推論
推論しているコードです。
カメラの画素数は、800✕600ですが、モデル入力は、512✕512なので、推論時に縮小し、結果を引き伸ばしています。
なお、JetPackでは、最初からOpenCVがセットアップされていますが、入力が、GStreamer経由となっているので、DeviceID で直接指定できないことに注意が必要です。
import cv2 import numpy as np import dlr import time import os from dlr.counter.phone_home import PhoneHome PhoneHome.disable_feature() os.environ['TVM_TENSORRT_CACHE_DIR'] = '.' SHAPE = 512 MODEL_PATH = './model' CLASS_NAMES = ["jagabee","chipstar","butamen","kyo_udon","koara","curry"] COLORS = [(0,0,175),(175,0,0),(0,175,0),(175,175,0),(0,175,175),(175,175,175)] DEVICE_ID = 0 WIDTH = 800 HEIGHT = 600 GST_STR = ('v4l2src device=/dev/video{} ! video/x-raw, width=(int){}, height=(int){} ! videoconvert ! appsink').format(DEVICE_ID, WIDTH, HEIGHT) def main(): PhoneHome.disable_feature() cap = cv2.VideoCapture(GST_STR, cv2.CAP_GSTREAMER) model = dlr.DLRModel('model/', 'gpu', 0) print("DLRModel() start") model = dlr.DLRModel(MODEL_PATH, 'gpu', 0) print("DLRModel() end") print("model.get_input_dtypes(): {}".format(model.get_input_dtypes())) print("model.get_input_names(): {}".format(model.get_input_names())) print("model OK") # SHAPE(512)に変換する場合の縮尺 h_magnification = SHAPE/HEIGHT w_magnification = SHAPE/WIDTH print("magnification H:{} W:{}".format(1/h_magnification, 1/w_magnification)) while(True): _, frame = cap.read() if(frame is None): continue # 入力画像生成 img = cv2.resize(frame, dsize=(SHAPE, SHAPE)) # height * width => 512 * 512 img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # BGR => RGB img = img.transpose((2, 0, 1)) # 224,244,3 => 3,224,224 img = img[np.newaxis, :] # 3,224,224 => 1,3,224,224 print("img.shape: {}".format(img.shape)) start = time.time() out = model.run({'data' : img}) elapsed_time = time.time() - start print("{} [Sec]".format(elapsed_time)) for det in out[0][0]: if(det[0]%1==0 and det[0]!=-1 and det[1] > 0): # クラス index = int(det[0]) # 信頼度 confidence = det[1] # 検出座標(縮尺に合わせて座標を変換する) x1 = int(det[2] * SHAPE * 1/w_magnification) y1 = int(det[3] * SHAPE * 1/h_magnification) x2 = int(det[4] * SHAPE * 1/w_magnification) y2 = int(det[5] * SHAPE * 1/h_magnification) print("{} [{}] {:.1f} {}, {}, {}, {}".format(det[0], CLASS_NAMES[index], confidence, x1, y1, x2, y2)) if(confidence > 0.2): # 信頼度 frame = cv2.rectangle(frame,(x1, y1), (x2, y2), COLORS[index],2) frame = cv2.rectangle(frame,(x1, y1), (x1 + 150,y1-20), COLORS[index], -1) label = "{} {:.2f}".format(CLASS_NAMES[index], confidence) frame = cv2.putText(frame,label,(x1+2, y1-2), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 1, cv2.LINE_AA) # 画像表示 cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break main()
7 最後に
今回は、冷蔵庫の商品を確認するための物体検出モデルをエッジデバイス側で動作させて見ました。
だいぶ、部品が揃って来ました。「レジなし無人販売冷蔵庫」の完成は、近いかも知れません。 \(^o^)/