こんにちは、技術開発の三浦です。
「第二の人生」について、最近ふと考えました。まだまだ先の話ですが、仕事をリタイアしたら自分は何をしたいのかなぁと。ゲームをしたり、自然の中で過ごすのも良さそうです。そして何よりもテクノロジーはきっとこれからも進化していくので、いくつになっても最新のテクノロジーを追いかけていけたらいいな、と思います。
さて以前購入した"Jetson Nano Developer Kit"を久しぶりに触ってみたくなり、初期設定から"Google Colab"で学習したPyTorchの人物検出モデルを動かすところまでやってみました。けっこう手順が多かったので、何をしたのか忘れないようにまとめたいと思います。
Jetson
JetsonはNvidia社が発売している組み込みシングルボードコンピュータシリーズです。最近新しい"Jetson Orin Nano"が発表され、話題になりました。今回取り上げる"Jetson Nano"は2019年に発売された製品で、発売されたときは「こんなに小さいのに深層学習の処理が動かせるんだ!」とびっくりしたのを覚えています。
Jetson NanoはNvidiaのGPUを搭載しており、機械学習の推論処理に最適化されているため、例えば物体検出モデルも動かすことが出来ます。USBカメラやセンサーを接続することも出来ますので、AIを搭載したちょっとしたロボットを作ることも出来ます。
機器そのものはJetsonモジュールと呼ばれ、Jetsonモジュール上でアプリを動かすために必要となるSDKは"JetPack SDK"としてNvidia社から提供されています。このSDKの中にはJetsonモジュールに搭載されているTegra SOC向けのLinux(L4T)やCUDA, TensorRT, NVIDIA Container Runtimeなどのライブラリが含まれています。JetPackはJetson Nanoの初期セットアップ時にダウンロードし、SDカードに書き込んでインストールします。
Jetson Nano初期セットアップ
Jetson Nanoをセットアップする際、Developer Kitには含まれていない、必要なものがいくつかあるので事前に忘れずに用意します。私の場合はmicroSDカード(32GB)とmicroUSB-USBケーブル(シリアル通信用)、DC電源ケーブルを使用しました。セットアップの方法はJetsonにディスプレイやキーボードなどを接続して行う"setup with display attached"の方法と、別のPCからシリアル通信でJetsonに接続して行う"headless mode"の方法を選ぶことが出来て、それによっても必要になるものが変わります。(私は"headless mode"でセットアップしました。)
セットアップ手順はNvidia社のこちらのページで確認することが出来ます。
後で足りないものが出てこないよう、セットアップを始める前に一通り読んでおいた方がよいと思います。私は"headless mode"でセットアップしようとして、途中でDC電源ケーブルが手元に無いことに気づき、作業が一時中断してしまいました。
Jetson NanoでWiFiドングルを使用する
Jetson NanoにはWiFi通信機器が搭載されていないので、WiFiに接続するためには別途WiFi機器を用意する必要があります。たまたま手元にUSBタイプのWiFiドングルがあったので、そちらを使用して上手く接続することが出来ました。
NvidiaのDeveloper Forumで推奨されるドングルが紹介されています。
こちらのページにもリンクが貼っているのですが、以下のページのWirelessの項目にもいくつか機器がリストアップされています。
ドングルを接続したら、nmcli
コマンドを使ってWiFi接続のセットアップを行うことが出来ました。
- ドングルが認識されているか確認
nmcli d
- WiFiをONにする
nmcli r wifi on
- SSIDリストを表示する
nmcli d wifi list
- 対照のSSIDに接続する(sudoが必要)
sudo nmcli d wifi connect "SSID" password "password"
ここまではシリアル通信で行いましたが、以降は同じネットワーク上の端末からSSH接続してJetsonにアクセス出来るようになります。
機械学習の環境を立ち上げる
初期セットアップが完了し、インターネットへの通信経路を確保出来たので、さっそくJetson Nano上で機械学習の環境を立ち上げてみました。
JetPack SDKにはNvidia Runtime Containerが含まれていて、Nvidia Runtime Containerを使用するとDocker ContainerからJetson NanoのGPUなどに簡単にアクセスすることが出来ます。またJetson Nanoに直接影響を及ぼさずに色々と試すことが出来ますので、今回はDocker Containerで機械学習の環境を立ち上げてみました。
l4t-ml
というDocker ImageにはTensorFlow, PyTorch, Scikit-Learn, JupyterLabといった機械学習に必要なライブラリが一通り含まれているのでこのDocker ImageでContainerを立ち上げます。Jetson Nanoにインストールされたl4tのバージョンによってl4t-ml
の対応バージョンが異なるので、まずJetson Nanoのl4tのバージョンを確認します。以下のコマンドを実行します。
cat /etc/nv_tegra_release
出力結果
R32 (release), REVISION: 7.1, GCID:...
l4tのバージョンは、32.7.1であることが分かりました。これに対応するl4t-ml
のDokcer Imageは以下のページよりl4t-ml:r32.7.1-py3
であることが分かります。
まずDocker Imageをpullします。
sudo docker pull nvcr.io/nvidia/l4t-ml:r32.7.1-py3
次にDocker Containerを立ち上げます。
sudo docker run -it --rm --runtime nvidia --network host nvcr.io/nvidia/l4t-ml:r32.7.1-py3
このContainerを起動すると、同時にJupyterLabも起動します。http://"Jetson Nanoのipアドレス":8888で同じネットワーク上の端末からJupyterLabにアクセス出来ます。JupyterLabの初期パスワードはnvidiaでした。
USBカメラを接続する
手元にあったUSBカメラをJetson Nanoに接続し、USBカメラで撮影した画像に対して物体検出を行ってみました。Docker ContainerからUSBカメラにアクセスするにはContainer起動時に--device
オプションを指定します。USBカメラが/dev/video0
に接続されている場合は
sudo docker run -it --rm --runtime nvidia --network --device /dev/video0 host nvcr.io/nvidia/l4t-ml:r32.7.1-py3
のようにしてContainerを起動します。
Google Colabで作った人物検出モデルを使用する
以前こちらの記事でPyTorchでFaseterRCNNの人物検出モデルを学習しました。
この内容に従って構築したモデルをJetson Nanoで動かしてみます。 (backboneのモデルはResNetからMobileNetに変更しました。)
まずモデルは以下のような構造になっています。
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor from torchvision.models.detection.faster_rcnn import fasterrcnn_mobilenet_v3_large_320_fpn num_classes = 2 #0: background, 1: person model = fasterrcnn_mobilenet_v3_large_320_fpn() in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)
また、Google Colabのtorch
とtorchvision
のバージョンを確認しておきました。
print(torch.__version__) print(torchvision.__version__)
出力結果
1.12.1+cu113 0.13.1+cu113
Jetson Nanoでこのモデルを動かすために、学習済みのパラメータを出力します。 以下のコードを実行しました。
torch.save(model.state_dict(),PATH)
出力されたパラメータファイルをJetson Nanoに転送し、JupyterLabでロードしてみます。
model.load_state_dict(torch.load(PATH))
しかし、以下のようなエラーが発生し、ロードに失敗してしまいました・・・。
RuntimeError: Error(s) in loading state_dict for FasterRCNN: Missing key(s) in state_dict: "backbone.fpn.inner_blocks.0.weight", "backbone.fpn.inner_blocks.0.bias", "backbone.fpn.inner_blocks.1.weight", "backbone.fpn.inner_blocks.1.bias", "backbone.fpn.layer_blocks.0.weight", "backbone.fpn.layer_blocks.0.bias", "backbone.fpn.layer_blocks.1.weight", "backbone.fpn.layer_blocks.1.bias", "rpn.head.conv.weight", "rpn.head.conv.bias". Unexpected key(s) in state_dict: "backbone.fpn.inner_blocks.0.0.weight", "backbone.fpn.inner_blocks.0.0.bias", "backbone.fpn.inner_blocks.1.0.weight", "backbone.fpn.inner_blocks.1.0.bias", "backbone.fpn.layer_blocks.0.0.weight", "backbone.fpn.layer_blocks.0.0.bias", "backbone.fpn.layer_blocks.1.0.weight", "backbone.fpn.layer_blocks.1.0.bias", "rpn.head.conv.0.0.weight", "rpn.head.conv.0.0.bias".
エラーの内容から、以下のパラメータの読み込みで失敗しているようです。
backbone.fpn.inner_blocks.0.0.weight backbone.fpn.inner_blocks.0.0.bias backbone.fpn.inner_blocks.1.0.weight backbone.fpn.inner_blocks.1.0.bias backbone.fpn.layer_blocks.0.0.weight backbone.fpn.layer_blocks.0.0.bias backbone.fpn.layer_blocks.1.0.weight backbone.fpn.layer_blocks.1.0.bias rpn.head.conv.0.0.weight rpn.head.conv.0.0.bias
Jetson Nanoで起動しているDocker Containerl4t-ml:r32.7.1-py3
のtorch
とtorchvision
のバージョンを確認します。
1.10.0 0.11.0a0+fa347eb
Google Colabとバージョンが異なっており、それに起因するエラーのようです。実際l4t-ml:r32.7.1-py3
で同様にモデルを定義し、該当する箇所のパラメータの名称をリストアップしてみると以下のように名称が異なっています。
backbone.fpn.inner_blocks.0.weight backbone.fpn.inner_blocks.0.bias backbone.fpn.inner_blocks.1.weight backbone.fpn.inner_blocks.1.bias backbone.fpn.layer_blocks.0.weight backbone.fpn.layer_blocks.0.bias backbone.fpn.layer_blocks.1.weight backbone.fpn.layer_blocks.1.bias rpn.head.conv.weight rpn.head.conv.bias
ですのでGoogle Colabでパラメータを出力する時に、以下のように名称を変更して出力するようにしました。
colabo_param = [ 'backbone.fpn.inner_blocks.0.0.weight', 'backbone.fpn.inner_blocks.0.0.bias', 'backbone.fpn.inner_blocks.1.0.weight', 'backbone.fpn.inner_blocks.1.0.bias', 'backbone.fpn.layer_blocks.0.0.weight', 'backbone.fpn.layer_blocks.0.0.bias', 'backbone.fpn.layer_blocks.1.0.weight', 'backbone.fpn.layer_blocks.1.0.bias', 'rpn.head.conv.0.0.weight', 'rpn.head.conv.0.0.bias', ] jetson_param= [ 'backbone.fpn.inner_blocks.0.weight', 'backbone.fpn.inner_blocks.0.bias', 'backbone.fpn.inner_blocks.1.weight', 'backbone.fpn.inner_blocks.1.bias', 'backbone.fpn.layer_blocks.0.weight', 'backbone.fpn.layer_blocks.0.bias', 'backbone.fpn.layer_blocks.1.weight', 'backbone.fpn.layer_blocks.1.bias', 'rpn.head.conv.weight', 'rpn.head.conv.bias', ] #Jetson paramに対応したパラメータの保存 weight_dict = {} for k, v in model.state_dict().items(): if k in colabo_param: #parameter keyをJetson Nano側に対応させる k = jetson_param[colabo_param.index(k)] weight_dict[k] = v torch.save(weight_dict, PATH)
これでJetson Nanoで正常に読み込める形でモデルのパラメータファイルを出力することが出来ました。
Google Colabで学習したモデルをJetson Nanoで動かし、USBカメラの画像に対して人物検出を行う
最後にGoogle Colabで学習したモデルをJetson Nanoで動かしてUSBカメラの画像に対して人物検出を行うコードを以下に掲載させて頂きます。
from IPython.dislpay import dislpay from PIL import Image import cv2 import torch import torchvision import torchvision.transforms as T from torchvision.utils import draw_bounding_boxes from torchvision.models.detection.faster_rcnn import fasterrcnn_mobilenet_v3_large_320_fpn from torchvision.models.detection.faster_rcnn import FastRCNNPredictor #モデル定義 num_classes = 2 model = fasterrcnn_mobilenet_v3_large_320_fpn() in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) #Google Colabで学習し、出力したパラメータファイルの読み込み model.load_state_dict(torch.load(PATH)) model = model.cuda().eval() #USBカメラで画像を撮影し、人物検知を行う try: #USBカメラで撮影 cap = cv2.VideoCapture(0) _, cv_img = cap.read() #OpenCV形式からPillow形式に変換 pil_img = cv_img.copy() pil_img = cv2.cvtColor(pil_img, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(pil_img) #モデルによる推論 input_img = T.PILToTensor()(pil_img).cuda() input_img = T.ConvertImageDtype(torch.float)(input_img) input_img = torch.unsqueeze(input_img, axis=0) output = model(input_img) boxes = torch.squeeze(torch.stack([o['boxes'] for o in output]),axis=0) #推論結果の描画 draw_img = draw_bounding_boxes(T.PILToTensor()(pil_img), boxes, colors='red', width=5) display(T.ToPILImage()(draw_img)) except Exception as e: print(e) finally: cap.release()
頭の先っちょだけですが、人物として検出されました!
まとめ
ということで、今回はJetson Nanoの初期セットアップから最近作った人物検出モデルを動かしてみた話までを紹介させて頂きました。クラウド環境で学習したモデルがエッジデバイスで動く様子を見ると、なんだか感慨深いものがあります。JetPackにはJetsonモジュールの性能を効率的に利用して深層学習の推論処理を行うことが出来るTensorRTが含まれていて、今回それも試してみたかったのですが時間が間に合わず、至りませんでした。これはまた今度試してみたいと思います。
また、来年発売予定の"Jetson Orin Nano"が今からとても楽しみです!