ラズパイでGreengrass v1、Lambda、DLRを使ってエッジで画像分類やってみた


こんにちは、皆川です。AWS IoTからMQTTでメッセージを送って、エッジデバイスであるラズパイでカメラの画像から画像分類をして結果を返すような仕組みを作ります。

家にはこんな感じでラズパイが設置してあって、会社からIoT CoreにMQTT送信すると推論結果をMQTTでIoT Coreに返してくれます。

こんな感じ。AI/IoTの基本という感じです。

※ちなみにGreengrassのバージョンは1、LambdaはGreengrassコンテナなしで動かします。

はじめに

本記事は基本的にはAWSの公式ドキュメント(https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/ml-dlc-console.html )をなぞる内容になっていますが、2021年5月31日現在ドキュメントに不備が多くその通りにやると複数の箇所で詰まってしまうので改良してあります。

早速始めていきましょう。

必要なもの

ラズパイモデル3B

ラズパイカメラモジュール

https://www.amazon.com/Raspberry-Pi-Camera-Module-Megapixel/dp/B01ER2SKFS
(USBカメラでできるかは未確認です)

必要だけど本記事で説明していないもの

ラズパイの初期セットアップ

OSはRaspberryPi OS(Raspbianから名前が変わった)を使用しました。
https://www.raspberrypi.org/software/
インターネットへの接続、SSH接続及びVNC接続の許可が必要です。

Greengrassグループ設定

こちらを参照してください。

ラズパイへのMacからのSSH接続とVNC接続

筆者はどちらもremote.itで行った。SSHもVNCも簡単なGUIで接続できるようになるので便利(弊社で扱っている商品なので使い始めたが、使用感は悪くない)。
https://qiita.com/masa-e/items/321d2962886931593ad4

手順

OSのアップデート

20分ほどで完了します。

sudo apt-get update
sudo apt-get dist-upgrade

必要なPythonライブラリーのインストール

sudo apt-get install -y python3-dev \
python3-setuptools python3-pip python3-picamera

pipのインストール

# pipのインストール
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py

# パスを通す。これでpipコマンドが使えるようになる。
export PATH=$PATH:/home/pi/.local/bin

# お掃除
sudo rm get-pip.py

DLRのインストール

今回はラズパイモデル3Bなので以下のコマンドでDLRをインストールします。

pip3 install \
https://neo-ai-dlr-release.s3-us-west-2.amazonaws.com/v1.6.0/rasp3b/dlr-1.6.0-py3-none-any.whl
python3
>>>import dlr

でNo Moduleエラーが出ないことを確認できたらオッケーです(urllib3とchardetのバージョンがサポートされていませんというWarningは無視します)。

※他のハードウェアにDLRをインストールする場合

x86 CPUへは普通にpip install dlr でいけるようです。
Jetsonやラズパイはハードウェア別のWheelファイルのURLをpipで引いてくればいけます。詳しくは
https://github.com/neo-ai/neo-ai-dlr/releases を参照してください。ただ、v1.7以降はdlr.soというダイナミックリンクファイルがついてこないためv1.6をインストールしてください。

PATH問題を解決する(重要)

pipでdlrをインストールすると~/.local・・・にインストールされますが、Lambdaはそこを探してくれない(PATHが通っていない)のでLambdaも探してくれる場所にコピーします

cd ~/.local/lib/python3.7/site-packages
sudo cp -r dlr* /usr/lib/python3.7
# libdlr.soファイルもLambdaが探さないところ(~/.local/dlr/)にあるのでPATHがLambdaが探すところ(/usr/lib/python3.7/dlr/)にコピーする

sudo cp ~/.local/dlr/libdlr.so /usr/lib/python3.7/dlr/

今回使うMLモデルをダウンロード

sudo wget https://d1onfpft10uf5o.cloudfront.net/greengrass-ml-samples/dlr/dlr-py3-armv7l.tar.gz

# 解凍する
sudo tar -xvzf dlr-py3-armv7l.tar.gz

参考:
https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/what-is-gg.html#gg-ml-samples

# 後々Lambdaで参照するML用のディレクトリを作る
sudo mkdir /ml_model

# コンパイルされたMLモデル(model.json, model.params, model.so)とアノテーションファイル(synset.txt)を作成したディレクトリのルート(直接配下)へコピー(`mv`でも大丈夫です)
cd dlr-py3-armv7l/models/resnet50
sudo cp * /ml_model/

# いらないファイルを消す
cd
sudo rm -rf dlr*

Lambdaの作成

Macでもターミナルを開いて必要なLambdaを持ってきます。

sudo wget https://d1onfpft10uf5o.cloudfront.net/greengrass-ml-samples/dlr/dlr-py3-armv7l.tar.gz

tar -xvzf dlr-py3-armv7l.tar.gz

dlr-py3-armv7l/にあるexampleフォルダをフォルダごとzipファイルにします。その後AWSコンソールのLambdaに行って、登録していきます。

.zipファイルを選んで今圧縮したzipファイルを登録します。



その後ハンドラーの設定やエイリアスの設定をします。詳しくはこちら

Lambdaのコード上の注意点

Lambdaのinference.pyの中でmodel_resource_pathでコンパイルしたモデルとそのラベルファイル(model.json, model.params, model.so, synset.txt)が存在するフォルダへのファイルパスを指定してやる必要があります。ハードコードしても良いですが、GGGのリソース追加でS3からモデルを引っ張ってくるときは以下のコードを参考にしてください(ちなみにモデルはIoT CoreコンソールからGreengrassGroupのMLリソース追加から/trained_modelsにモデルを追加してLambdaと紐づけてあると仮定します)。

※GreengrassGroupの設定時にS3からモデルを追加するとGGデバイス上の/greengrass/ggc/core/deployment/…/mlmodel/もしくは/greengrass/ggc/core/deployment/…/mlmodel_public/配下にモデルがインストールされます。

inference.pyから一部抜粋
# LambdaをGGコンテナ内で動かす設定のとき
model_resource_path = os.environ.get('MODEL_PATH', '/trained_models')

# LambdaをGGコンテナなしで動かす設定のとき
model_resource_path = os.getenv("AWS_GG_RESOURCE_PREFIX") + "/trained_models"

※LambdaをGGコンテナ内で動かすときは同じくリソースの追加から/dev/vcsm/dev/vchiqを追加してLambdaを紐づけてやる必要があります。また、LambdaをGGコンテナなしで動かすときは上記のデバイスリソースの追加をするとデプロイに失敗するので注意が必要です。

Greengrass Groupのデプロイ

以下のように、Groupから自分のGroupに行ってLambdaのタブから先ほどつくったLambdaを登録します。

Lambdaの設定は以下です。UID,GIDを1000でデフォルトのpiユーザーで実行します(ggc_userでもいいと思います)。コンテナはなしを選択。タイムアウトは念の為10秒に引き上げました。Lambdaのライフサイクルはオンデマンド(MQTTを受け取った時だけ起動する)を選びます。

サブスクリプションの設定は以下のようにします。Lambdaからはどんなトピックでも来るようにワイルドカード(#)を指定してIoTCoreからはtestというトピックでパブリッシュされたときのみメッセージが届く(すなわちLambdaを起動する)ようにします。

Greengrass Groupのデプロイ

ラズパイで

sudo /greengrass/ggc/core/greengrassd start

でGreengrassをスタートして、
画面右上のアクションからデプロイを選択します。30秒ほどで完了するはずです。

いよいよテスト!

ラズパイでエッジ推論の旅もこれが終わればひと段落です。やっていきましょう。
MQTTテストクライアント(IoTCore内にあります)から#にサブスクライブして、testになんでも良いのでメッセージをパブリッシュしてみましょう。

このようにうまくいったでしょうか。自分の例だとマウスを映してみました。Lambdaのcamera.pyにあるようにラズパイ側でカメラ起動後に2秒スリープが入っているのでそれがボトルネックになっています。

今回はラズパイでGreengrass,Lambda,DLRを使ってエッジ推論を行いました。このパターンをマスターすればクラウドとの連携がスムーズなり、IoTプロジェクトにも幅が広がることでしょう。以上です。

トラブルシューティング

Greengrass Groupのデプロイで失敗する方はGreengrass Group9つのハマりどころを参照ください。Greengrassのデプロイlogは解読が難解なのでここでつまづいて時間を取られたくないものです。

Lambdaのlogファイルは/greengrass/ggc/var/log/user/<デフォルトリージョン名>/<ユーザーアカウントに対応した13桁の数字>/<Lambdaの関数名>となっているので、まずsudo suでroot権限にしてcatなどで確認できます。

権限まわり

筆者自身まだ原因が分かりきっていないですが、権限まわりのエラーがときどき出ます。よくあるエラーはユーザー(主にデフォルトのユーザー)が特定のフォルダに対してwrite権限がないか、vchiqインスタンスに対する権限がないことです。それぞれchmod +w <対象となるフォルダ>chmod 777 vchiqで対応しました。

資料集

Lambdaをroot権限で実行するためのラズパイ側の設定

おそらく使うタイミングはないと思いますが、メモとして残しておきます。
/greengrass/config/config.txtを以下のように変更するとLambdaがrootで実行できます。

    "runtime": {
        "cgroup": {
            "useSystemd": "yes"
        }
    },

となっているのを以下のように"allowFunctionsToRunAsRoot" : "yes",を付け足す。

    "runtime": {"allowFunctionsToRunAsRoot" : "yes",
        "cgroup": {
            "useSystemd": "yes"
        }
    },

ラズパイカメラ少し発展編

英語ですが、ラズパイカメラの動かし方は以下にまとめてあります。
https://projects.raspberrypi.org/en/projects/getting-started-with-picamera/3

nanoかviエディタで以下のようなファイルを作ってください。

camera.sh
#!/bin/bash

DATE=$(date +"%Y-%m-%d_%H%M")

raspistill  -o /home/pi/camera/$DATE.jpg

試しにカメラで一枚撮ってみます。

/home/camera/camera.sh

lsで、

2021-05-17_1003.jpg

といったファイルが生成されているはずです。VNCで接続して確認できます。

PythonのPATH問題が出てきたときは

Lambdaは独自のPython PATHを持っているようなのでそこはlogを注視するしかなさそうですが、それ以外なら

pip show dlr

でdlrの場所を確かめて、そこにPythonのパスが通っているか確かめます。

python
>>>import sys
>>>from pprint import pprint
>>>pprint(sys.path)

['',
 '/usr/lib/python37.zip',
 '/usr/lib/python3.7',
 '/usr/lib/python3.7/lib-dynload',
 '/home/pi/.local/lib/python3.7/site-packages',
 '/usr/local/lib/python3.7/dist-packages',
 '/usr/lib/python3/dist-packages']

dlrのあるディレクトリがなかったら、mvcpでdlrディレクトリをPATHが通っているディレクトリに持っていきます。

(ちなみにPythonのインタラクティブモードからはCtl+Dで抜けれます)

DLRとは

Deep Learning Runtimeの略でAWSのSageMaker NeoでコンパイルしたMLモデルをMacやラズパイ、Jetson上で動かすためのランタイムです。

あとがき

このAWSドキュメントの不備は細かく上げていくとキリがないですが、大まかにいうと、

・書かれているやり方ではdlrがインストールできない
・インストールできても主にPATHの問題でLambdaでは動かせない
・Lambdaをroot権限で動かす必要がないのにroot権限を推奨している。また詳しくはこちらと飛ばされたページの最初にroot権限は原則使わないと書いてある。元の記事にはなぜroot権限なのか書いてない。
・コンテナモードのLambdaとコンテナなしモードのLambdaの記述が混在している。

などです。時間が経ってoutdateしてしまったなら情状酌量の余地はありますが、根本的に記述がおかしい箇所が多い。AWSでAI/IoTを始めようとしている人が多く読むドキュメントで、こういう絶対転ぶ階段をそのままにしているAWSの体たらくに怒りを感じます。これモノ売るっていうレベルじゃねえぞ!!

Greengrass v2を使ったほうがいいのかもしれない、と思う今日このごろ。ただAWS Forumを見ているとv2使用者の悲鳴が毎日のように聞こえてきてこのGreengrassとやらは本当にAWSの問題児だな、としみじみと感じております。

v2で物体検出:https://docs.aws.amazon.com/ja_jp/greengrass/v2/developerguide/dlr-object-detection-component.html
AWS Greengrass Forum(筆者がこの記事を完成できたのもこのフォーラムのおかげです。特にMichael Dombrowski氏、センキュー):https://forums.aws.amazon.com/forum.jspa?forumID=254&start=0