[レジなし無人販売冷蔵庫] 物体検出モデルをSageMaker Neoで最適化してJetson Nanoで利用してみました

2020.12.30

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^)/