masato-ka's diary

日々思ったこととか、やったことの備忘録。

AI+IoTなデバイスをお手軽に作れるM5StickVとSORACOM LTE-M Button Plus

1. この記事について

夏の終わりに身の回りのものを整理していたら、購入していたまま放置していたM5StickVとSORCOM LTE-M Button Plusを発掘した。こんな二つを悪魔合体することでお手軽にAIとIoTを組み合わせられるのではと思いつき、「ソラコムポーズを取るだけで触れることなくSORACOM LTE-M Button Plusnを押す」を試してみた。

念の為説明しておくと映像の左上にはM5StickVで撮影した映像が写っている。この部分にソラコムポーズをした手が映るとM5StickVがソラコムポーズを認識してLEDの色を変える。その後三秒ほど待つとM5StickVがリレーを動かしてSORACOM LTE-M Button Plusを動作させる。

補足 ソラコムポーズとは

ソラコムポーズは右手と左手を組み合わせて「S」の字を作ることをそう呼んでいる。株式会社ソラコムサービスを利用しているユーザが集まって構成されているSORACOM UGではユーザグループイベント終了時に希望者でこのソラコムポーズを撮り集合写真を取ることが慣例となっている。

soracomug-tokyo.connpass.com

そんなSORACOM LTE-M Button Plusと縁もゆかりも深いソラコムポーズをキーとしてボタンを押すことができるようになる!

2. やったこと

こんな素敵な使い方ができるデバイス実現のポイントについて解説する。

2.1. M5StickVとSORACOM LTE-M Button Plusの接続

実現したいことはM5StickVとSORACOM LTE-M Buttonを繋げることだ。SORACOM LTE-M Button Plus にはボタンの代わりにA接点が搭載さ入れてる。 M5StickVでソラコムポーズを認識した後、Groveリレーモジュール を介してSORACOM LTE-M Button PlusをA接点をONにする。これでAIの推論結果に応じてSORACOM LTE-M Button Plus経由で情報をクラウドへ上げることができる。立派なAIoTなデバイスの完成だ。接続は無極性の接点接続とGrove端子の接続のみなので難しいことはない、フォースの感じるままに接続すれば良い。

f:id:masato-ka:20190903234016j:plain
接続時の状態、説明する隙がないくらい簡単

2.2. M5StickVのモデル学習

ソラコムポーズを認識させるためにはそのための学習データが必要となる。今回は200毎程度のソラコムポーズを取った手の画像とソラコムポーズを取っていない手の画像を同じく200毎程度作成し学習のデータセットとした。画像サイズは244x244だ。

学習モデルはImageNetでpre-train済みのモデルから転移学習を行なっている。学習はGoogle Colaboratoryで行なった。Google Colaboratoryで動かすPython notebookは以下を参考にさせてもらった。

https://colab.research.google.com/drive/1mirG8BSoB3k87mh-qyY3-8-ZXj0XB6h6

上記のノートブックでは転移学習の際に、MobileNetの畳み込み層の重みは変更しない設定となっていた。何度か学習を試みたが精度が芳しくなかった。そこで、過去の経験からMobileNetの畳み込み層の最終レイヤのみ重みを学習するように変更を行なった。はっきりとはわからないが、pre-train済みのモデルは画像から十分に特徴を抽出できる性能を持っているが、最終段のみタスクに合わせて微調整すると良いのではないかと思っている。(学術的な裏ずけは何もないおまじないです)

上記、Python notebookの「Transfer learning using MobileNet V1」節、2つ目のコードブロックを以下のように変更した。

# 変更前
for i, layer in enumerate(base_model.layers):
    layer.trainable = False
# 変更後
for i, layer in enumerate(base_model.layers):
    layer.trainable = False
    if i > 80:
      layer.trainable = True

Python notebookを最後まで実行するとモデルとモデルを動かすためのMicroPythonのスクリプトがzipファイルとしてダウンロードされる。

ちなみにデータさえあれば細かいことは気にせずAI作れる専用のサービスも始まったようです。

M5Stack - A series of modular stackable development devices

2.3. M5StickVへのモデルの書き込み

 作成されたモデルはM5StickVに配置してプログラム上からロードして利用する。モデルはMicroPythonで書かれたソースコードとともにSDカードに書き込んで、M5StickVにSDカードごと差し込んで利用する使い方が主流だ。M5StickVのファームウェアはSDカードにboot.pyファイルが入っているとそちらを優先して起動するようになっている。しかしM5StickVのSDカードは相性問題がシビアなため、今回はモデルとプログラムを内蔵Flash ROMへ書き込んで利用した。ソースコード中でモデルをロードしている箇所ではFlash ROMのモデルが書き込まれているメモリ番地の先頭をload関数に渡すことでFlash ROMに書き込んだモデルを読み込むことができる。書き込みやモデルロードソースコードのイメージは以下のサイトがわかりやすい。

qiita.com

下記サイトからmaixpy_v0.4.0_39_g083e0cc_m5stickv.binをダウンロードし、FlashROMの先頭0x000000へ書き込む。モデルは0x300000あたりを先頭にして同時に書き込みを行う。

Index of /MAIX/MaixPy/release/master/maixpy_v0.4.0_39_g083e0cc/

3. M5StickVとSORACOM LTE-M Button Plusを組み合わせると何がいいのか。(まとめ)

 M5StickVはカメラとディープラーニング向けアクセラレータが搭載されたRISC-V CPUK210が搭載されたデバイスだ。内蔵のリポバッテリーでディープラーニングを利用した物体認識や物体検知を動かせることができる。しかし、現状のバージョンでは外部との通信機能はついていないため、認識した結果をクラウドに送るなどIoTや遠隔センシング用途に使うには工夫が必要になる。

 対して、SORACOM LTE-M Button Plusは低電力のLTE-M通信ができるデバイスだ。その基本機能は3パターンのボタンクリック(シングルクリック・ダブルクリック・長押し)をイベントとしてクラウドへ通知することだ。また簡易位置計測機能がついているため、通信時の基地局情報を元に位置情報と合わせて送信することができる。つまりこの2つを悪魔合体して得られる効果は以下のようになる。

  1. 必要に応じてディープラーニングのモデルを差し替えるだけで、汎用的なセンサとしてM5StickVを利用することができる。
  2. 認識した結果に応じて簡易位置計測による位置情報とともにイベントをクラウドに送ることができる。
  3. M5StickVもSORACOM LTE-M Button Plusもそれぞれバッテリー内蔵なのでポータブル性に優れている。

 これこれ、こういうのでいいんだよ、最高じゃないか!例えばM5StickVで鳥獣か人かを認識してSORACOM LTE-M Button Plusで位置情報をともにクラウドプラットフォームに送ることで監視アプリができる。田畑をカメラで見張れば正確な水位はわからなくても水位が多い少ないくらいは非接触で見分けることができるだろう。部屋の中でのジェスチャー操作で照明を制御するというのも面白そうだ。M5StickVとSORACOM LTE-M Button Plusの愛称は抜群かもしれない。

 AI+IoTでお手軽に何か作ってみたいと思ったら選択肢の一つとして強くオススメしたい。

JetsonにEdge TPUにM5StickV で、エッジAI用やるには何を選べばいいの?

1. 概要

追記 公開当初Jetson Nanoの性能表記に誤記があったため修正しています。

最近組み込みデバイス(以下エッジと表現)で画像認識や音声認識、センサ情報の処理といったディープラーニングを利用した処理を実行することが容易になっている。低消費電力で、高速にディープラーニングを処理するためのエッジAI用アクセラレータが各社から発売されていることがその理由の一つだろう。

こういった、エッジAI用のアクセラレータは各社によってその使用や対応フレームワーク、利用できるディープラーニングのネットワーク構成に違いがある。どれも同じように利用できるわけではない。自分でエッジAI用アクセラレータを利用しようとしたときにいくつか調べてみた内容をメモがわりに残してみる。ちなみに個人で遊べるものを中心にしてるので、産業的にどうなのかは知らない、悪しからず。。。 あとこのブログではAndroid Thingsとかいう「実績」があるので話半分で読んでいただけるとありがたい。

f:id:masato-ka:20190829220337j:plain
いろんなアクセラレータがあるが何が違うのか。。。

2. 比較対象のエッジAIアクセラレータについて

比較する対象は以下のボードである。現在個人でも入手が容易で利用時の取り回しが良さそうなものを恣意的にピックアップした。名称の粒度もボードだったりチップだたりまちまちだけど自分の呼びやすさで書いている。(免責事項)

  1. Coral Edge TPU
  2. Neural Compute Stick 2 (NCS2)
  3. K210
  4. Jetson Nano

今回はFPGAについてはちょっと毛色が違うので扱わない事にした。またSpressensなどについては後述するがAIアクセラレータは入っていないと判断した。(もし専用の処理回路が入っていれば指摘してほし。) それぞれについて簡単に紹介しておく。  

2.1 Coral Edge-TPU(USB/Board)

GoogleディープラーニングアクセラレータであるTPUのエッジ版となる。USB3.1接続のドングルCoral Dev Boardと呼ばれるRaspberry Piライクなシングルボードコンピュータが存在する。USB接続の場合は3.1接続以外でも利用できるが処理速度が多少落ちるという話もある。

  • 参考

Edge TPU USB Acceleratorの解析 - Operationとモデル構造 - Qiita

2.2 Neural Compute Stick 2

Intel社が出している、 Myrad X VPUを搭載したUSB接続タイプのアクセラレータだ。後述するがTensorFlowやCaffeなどのフレームワークに対応し、IntelOpenVINOという開発環境で開発することができる。以後NCS2と呼ぶ。

2.3 K210

中国のKendryte社のRISC-Vプロセッサである。KPUというディープラーニング用のアクセラレーション機能が入ってる。最近流行りのM5StickVSipeed Maixシリーズに搭載されている。

2.4 Jetson Nano

Jetson Nano はNVIDIATegra X1(Nintendo Switchの中身らしい)で作られたシングルボードコンピュータだ。GPUなのでAIアクセラレータかというと疑問はあるが、用途としては組み込みでのAI処理がメインだと思うのであえてリストに載せている。

 

3. エッジAIアクセラレータの比較

前出のアクセラレータをそれぞれ比較してみる。軸もかなり恣意的だが以下のようなった。しつこいが、Jetson Nanoは基本的にGPUだ。

  • 2019/8/30 コメント欄記載の通り、Jetson Nanoの処理能力を誤記していたため修正
アクセラレータ  Edge-TPU Neural Compute Stick 2 K210 Jetson Nano
処理能力    4TOPS 4 TOPS(NN用に1TOPS) 0.25TOPS〜0.5TOPS 472 GFLOPs(FP16)
浮動小数点計算 - FP16 - FP16,FP32
消費電力 0.5w(1TOPS) 不明 0.3w(0.25TOPS) 5w~10w
開発ツール Edge TPUコンパイラ,Python/C++ API OpenVINO MaixPy(MicroPython) Kモデル用変換ツール CUDAライブラリ
対応フレームワーク TensorFlow Lite Tensor Flow, Caffe, MxNet, Kaldi TensorFlow Lite, Darknet TensorFlow Lite, Keras| TensorFlow, Keras, TensorRT, Pytorch
クラウドサービス GCP AutoML Vison Edge - M5StickVのβサービスあり -
対応モデル(掲載以外も対応あり) InceptionV3, SSD-MobileNetV2(300x300), PoseNet ResNet, SSD-MobileNetV2(300x300), Tiny-YOLOv3, OpenPOSE,UNET Tiny-YOLOv3, MobileNet 特に制限なし
on device学習の可否 物体認識の限定的な転移学習のみ 不可 不可 可能
備考 上記値はおそらくUSBドングルバージョンの値
  • 参考

第 1 回 Jetson ユーザー勉強会 K210 introduction Google Coral Edge TPU vs NVIDIA Jetson Nano: A quick deep dive into EdgeAI performance https://www.taxan.co.jp/jp/pdf/products/topics/ncs2_brochure.pdf Hardware For Every Situation | NVIDIA Developer

それぞれの項目について以下補足していく。

3.1 処理能力と浮動小数点演算

 処理速度ではTOPS(Tera | Tensor Operations per sec )を基準値とした。ただあくまで指標レベルに考えておいたほうがいいかもしれない。後述するが、同じモデル動かすにもアクセラレータによってモデルのどの処理をサポートするか変わるようだ。また実装によっても速度が変わるだろう。Edge TPU、NCS2、K210と続いており、おおよそ感覚的にもこんな感じなんではないだろうか。(2019/08/30 コメント欄記載の通りJetson Nanoについては472GFLOPs(FP16)となる。)

 通常ディープラーニング浮動小数点の演算を行うが、量子化といって、計算速度を上げるため、推論精度をある程度落とし8bit整数などで演算できるようモデルを変換して利用する。そのため、それぞれの値は別途記載がない限り整数演算の能力となる。ただし、NCS2とJetson Nanoは浮動小数点演算をサポートしている点も付け加えておく。

3.2 消費電力

 Edge TPUは1TOPSあたり0.5Wとのことなので、フルで使うと2W程度になるはずだ。対して、K210は0.3TOPSで0.25Wなので、実際の消費電力としては少ないが、効率としてはEdge TPUが良さそう。Jetson Nanoについてはそれでも5W程度で動くのだから効率は結構いいともう。ちなみにNCSの消費電力については公式情報がなかったため割愛する。デバイス単体の電力で見るとK210が小さい。

3.3 開発ツール

エッジAI用アクセラレータを使うためには以下の3種類のツールが必要になってくる。

ディープラーニングフレームワーク

 ディープラーニング用のフレームワークはエッジAIのモデルそのものを作るためのフレームワークだ。今回のデバイスでは全て共通してTensor Flow, Keras利用することができる。またNCS2ではCaffeフレームワークを使うことができる。Jetson NanoについてはUbuntuが動いているので環境を構築すればなんでも動かせるだろう。ちなみにサンプルではPytorchの例が多いようだ。

 ONNXなどでフレームワーク間でモデルを変換してということもできるかもしれないが、ONNX自体各フレームワークでの対応状況がまちまちなようなので、後述のモデル変換の話を考えるとどのフレームワークも制限なく使えるという訳ではないようだ。利用前に、事前の検証が必要そうだ。

モデル変換ツール

 Jetson Nano以外のエッジAIアクセラレータを利用するためには各アクセラレータに合わせてAIのモデルを変換する必要がある。そのためのツールがモデル変換ツールだ。モデル変換のツールについてはもちろん各アクセラレータごとに用意されている。注意したいのは各アクセラレータごとに、サポートしている処理が違う点だ。ここでいう処理とは畳み込み層やプーリング層、ReLu関数、全結合層、Softmax関数といったデープラーニングのフレームワークでネットワーク記述する際に用いられる各関数のことを指す。乱暴にいうとこの対応処理に違いがあるため、アクセラレータごとに実行できるディープラーニングのモデルが変わってくる。CNN以外は割と難しいのではないだろうか。

 例えばEdge TPUの場合は、Tensor Flowで作成したモデルを量子化した上でTensor Flow Lite(tflite)モデルに変換し、Edge TPUのモデルへモデル変換ツールを利用して変換を行う。ニューラルネットワークの入力層から変換を行っていき、変換中にEdge TPU でサポートしていない処理が現れた場合は移行の処理はCPUへフォールバックするようだ。  

 このような場合に、NCS2で利用されるOpenVINOやK210の変換ツールがどうのような挙動をするかまだ調査がついていないが、おそらくEdge TPUと同様か、そのまま変換できないかのどちらかだろう。      また、生成されるモデルのサイズによってはメモリに乗らないなどの問題モデルだろう。この辺りは各アクセラレータの資料をみるのがいいだろう。下記のInterface 2019年8月号に記載されているEdge TPUの解説記事ではEdge TPUの性能を出すための条件が書かれている。このように各アクセラレータごとの癖があると思う。決して万能ではない。

  • 参考

TensorFlow models on the Edge TPU | Coral Release Notes for Intel® Distribution of OpenVINO™ toolkit 2019 | Intel® Software Optimization Guide - OpenVINO Toolkit

推論用API

 モデル変換後は実際にデバイスへモデルをデプロイして推論処理を行う。この時に各デバイスへモデルをロードして推論させる必要がある。Edge TPUは専用のPython/C++ APIが提供されている。NCSではOpenVINOのPython APIを利用できる。K210はMaixPyというMicroPython環境でAPIが提供されている。Jetson Nanoは各フレームワークの推論APIを利用することになる。それぞれのAPIは透過的に各デバイスの制御を行っている。

 ちなみにUSB版のEdge TPUとNCS2は複数本接続することにより、並列で推論させることが可能なようだ。やり方についは以下の記事で@PINTOさんの貴重な記録を確認することができる。

[〜24 FPS] RaspberryPi3をNeural Compute Stick 2(NCS2) 4本 + OpenVINO でブースト MobileNet-SSD / YoloV3 [Core i7なら48FPS] - Qiita

[Multi-TPU / Multi-Model] Coral Edge TPU Acceleratorを3本突き刺して3モデルを非同期で並列推論する (Posenet + DeeplabV3 + MobileNet-SSD) - Qiita

アクセラレータの対応モデルについてはモデルの実装方法と変化ツールの対応状況がキーとなるので、利用したい/実装したいモデルと変換ツールの対応状況を合わせて確認する必要があるだろう。

3.4 AIモデル作成のクラウドサービス

 AIモデルを開発するためには基本的に前述の開発ツールが必要になる。が、Edge TPUについてはGCPのCloud AutoML Vision Edgeというサービスを使い、クラウドサービスに学習用データをアップロードするだけで、Edge TPU対応もデールを自動的に学習、生成させることができる。ただし、物体認識のモデルのみが対象となる。また、K210についてもデータをアップロードするだけでKPU用モデルが出力されるV-TrainingというサービスをM5StickV向けにベータ提供していた。今後、正式版が出るかもしれない。

cloud.google.com

 基本的には転移学習により学習済みでると少量データで学習させるのだが、自前でやるとハイパーパラメータの調整や、学習スクリプトを毎度用意するのがめんどくさいので、こういうサービスを利用できるのはアドバンテージになる。

3.5 On device 学習

 これまでの話は基本的にエッジ側で推論を実行するための話だ。しかしエッジ側で学習させたいユースケースもあるだろう。こういった要望に答えられるのはJetson Nanoしかない。ここが低消費電力で動くパワフルなGPUの醍醐味だと感じる。

 エッジAIアクセラレータとしてはEdge TPUがMovileNet v1用の学習が行える。学習といっても最終段の全結合層のみをCPUで学習させて使えるようにするだけなんじゃないかと思う。試した訳ではないが、簡単な画像分類だったらできそう。

Retrain a classification model on-device with backpropagation | Coral

Retrain a classification model on-device with weight imprinting | Coral

4. その他番外編

 エッジAIでは必ずしもアクセラレータを必要とする訳ではない。処理速度を問わなければRaspberryPi 3B+を使うというのも手かもしれない。また、IdeinのActcastを使うとRaspberry Pi Zeroでの30FPS以上で物体認識やPoseNetを動かすことができる。また、SonyのSpressenseもディープラーニングモデルを実行することができる。こちらはSonyクラウドサービス、Neural Network Consoleで生成したモデルをSpressense用に変換することで利用することができる。

masato-ka.hatenablog.com

 今回は取り上げなかったがTensor Flow Lite for microcontrollerといった、ARM M0 CPUで動かせるモデルをC++のコードとして生成するフレームワークもある。

5. まとめ

 で、エッジAIやるには何を選べばいいのか?個人的にはとにかく、組み込みでAIっぽいことしてみたいと思ったらJetson Nanoで遊んでみるのが一番いいんじゃないかと思っている。一番制約なくパワフルに使えるからだ。その分消費電力も大きい。価格は1万2千円程度とコストパフォーマンスは良い。  

 物体検出や認識を動かしたいのであればEdge TPUのUSB版もいいだろう。ただしUSB 3.1で真価をはっきするようなのでRaspberry Pi3B+がホストだと辛い。Jetson Nanoに挿してもいいだろう。Coral Dev Boardを使ってみるのもいいかもしれない。価格はUSBドングルで1万円、Dev Boardで1万8千円するのでJetson Nanoに比べると割高感はあるかもしれない。ただUSBアクセラレータの場合は好きなシングルボードコンピュータをAI対応できる魅力がある。

   NCS2は対応モデルやフレームワークの数で言えばEdge TPUより優れているだろう。またIntel のOpenVINOも強力なツールなので、USBドングル式のアクセラレータの選択肢に入れて良いだろう。

 バッテリーで動くお手頃サイズのデバイスを作りたければ K210を搭載したM5StickVやSipeedのボードを買うのが良いのではないだろうか3000円程度からデバイスが手に入っる。もちろん、Spressensを買うのもありだろう。

 どんなモデルを使いたいのか、つまりアクセラレータと変換ツールの対応状況を検討し、動作させるシュチュエーションや電力などのリソース、価格に注意して選択するのが良いと思う。

ディープラーニングとJetson nanoでEnd-to-Endな自動走行を実現した話〜Jetpilotを作ってみた〜

1. この記事について

 Jetson nanoを搭載した移動ロボットJetbotを作成し、前回はJetbotを使った単眼vSLAMを実行させた。

masato-ka.hatenablog.com

 しかしvSLAMはPC側で処理される。Jetson nanoはPCへ映像を送信するだけだ。これでは搭載されているGPUが活躍していない。せっかくなのでJetson nanoに搭載されているGPUの威力を体感したかった。

 そこで今回はディープラーニング を利用して画像から直接判断して走行する、End-to-Endな自動走行にチャレンジしてみた。コースを追従するだけであれば、単純な画像認識によるライントレースと簡単なルールベースの仕組みで十分だろう。しかし、今回は前提となるルールを作るのではなくデータだけ与えて、ディープラーニングで解くのがポイントとなる。以下の画像のようにJetbotがコース上を追従して走行していくイメージだ。

f:id:masato-ka:20190823163621p:plain
End-to-Endの自動走行イメージ

2. End-to-Endのディープラーニングによるラジコン自動走行の方式

 End-to-Endの自動走行について説明する。ここでいうEnd-to-Endの自動走行とは以下のNVIDIAの論文のように車両で撮影した画像から直接その車両を制御する信号をディープラーニングで推論することを目指している。以下、論文の例では走行中の画像を撮影して、道路の状況に即して最適なハンドル操作角、つまりステアリング制御を行なっている。

 実車を公道など複雑な環境で自動走行する場合、SLAMなど自己位置推定の技術に加えて、ディープラーニングを利用して障害物や標識を探し、走行ルールに則り車を制御するのが一般的かもしれない。そのためEnd-to-Endな使い方は知る限り実車では例が少ない。しかし、近年草の根の盛り上がりがあるDIY Robot CarやAI RC Carと呼ばれる分野でいくつもの先例がみられる。

2.1 Jetbotのroad_followingサンプル

 Jetbotのサンプルリポジトリにはroad_followingというサンプルがある。これは自動走行したいコースの各ポイントで画像を撮影する。撮影した画像のそれぞれで、進行方向となる点をマーキングしその画像上でのX,Y座標をラベルとする。この学習データを元に転移学習されたResNetはコース走行時に、画像場で推定された進行方向の座標(X,Y)を返す。この座標を目標にPID制御でJetbotを動かすと言うものだ。上位ランクのJetRacerも基本的には同じ仕組みになっている。

https://github.com/NVIDIA-AI-IOT/jetbot/tree/master/notebooks/road_following

画像上での進行方向を推論し、その進行方向を目標値としてPID制御によって車両を制御している。正確にはEnd-to-Endにはなっていないが、実現方式としては近い。

2.2 DonkeyCar

   ラジコンカーにRaspberryPiまたはJetson nanoをマウントして作成するDonkeyCarでは人間の操作を直接学習さる。手順としては以下の通りだ。

  1. ラジコン操作で人間が実際に教示走行させる
  2. 教示走行で走行画像と合わせてそのタイミングで人間が操作した速度とステアリングの値をラベルとして記録する。
  3. 学習データを元に入力画像に対して最適な速度とステアリングを学習する。

 DonkeyCarではいくつかのDNNのネットワークモデルが提供されている。上記のNVIDIAの論文に記載している素朴なCNNを使い、分類問題として解く方法や、回帰問題として学習させる方法がある。また、3D-CNNやRNN, LSTMを利用し、前後の画像列から時系列での動作を意識したモデルを学習をさせることもできる。DonkeyCarにどんなモデルが実装されているかは以下のリポジトリからKerasでの実装を確認できる。

donkeycar/keras.py at dev · autorope/donkeycar · GitHub

 学習は1万枚程度の画像を使い素のネットワークから学習する。1万枚と聞くとかなりの量に感じるが、コースを10周もすれば集まるようだ。 DonkeyCarでは画像から車両の制御情報を直接推論するため、End-to-Endな方式と呼んで問題ないだろう。

2.3 今回実装した方法

 road_followingのデモでは画像一枚ごとに自分で画像上でマーキングを行いラベルをつける必要がある。また、End-to-EndではなくPID制御がシステムに入っている。DonkeyCarは教示走行により学習データを集めるため、比較的に簡単にデータを収集できる。また、End-to-Endの学習を実現している。しかし、DonkeyCarではネットワークをゼロから学習するため、Jetson nano上で学習するのに時間がかかる。  そこで今回はroad_followingで使用されている転移学習のコードをベースにDonkeyCar同様、画像とコントローラからの指令値をペアにした学習データを与えて学習させる方法をとった。

3. 実装

 ソフトウェアの実装ではROSを使ったコードで試作し実現性を確認した。その後、公開、利用がしやすいようにJupyter notebookとして再実装した。このnotebookは3つのノートブックで構成されている。学習データの収集にはUSB接続のゲームコントローラを必要とする。詳細な利用方法や実装については以下リポジトリを掲載する。プロジェクト名はJetpilot名付けた。

github.com

以下、実装時のポイントについて解説していく。

3.1 学習データの取得 (data_collection.ipynb)

 学習データの収集はコースをJetbotをゲームコントローラで走行させる。コースはテープなど2本のラインで構成された1週可能なものを想定しいてる。走行時に画像と一緒にコントローラの入力値を画像のファイル名として[speed]_[steering]_[uuid].jpgという形で保存していく。画像の保存はカメラの画像取得タイミングと同期しているため、カメラのFPSを10Hzに設定するとサンプリングの間隔は10Hzになる。ただ正確に計測していないので10Hzは厳密ではない。以下映像のようなコースの場合、3〜5周ほど周回ささせ700〜900枚程度になる。

※ 音が出るので再生には注意


Jetbotとディープラーニングで自動走行を学習させる

 Jetbotのサンプルはtraitletsというライブラリとipywidgetを使ってJupyter notebook上でインタラクティブなアプリを実装している。今回もこれらを利用してゲームコントローラの入力処理や画像の取得処理、Jetbotの制御を行なっている。    Jetbotの運動制御はmobile.pyのMobileControllerクラスで実装している。このクラスにSpeedとSteeringの値を入力することでJetbotを制御できる。Jetbotは単純な対向二輪型のロボットのため、制御に使う逆運動学は既知の式を利用している。

車輪ロボット(対向2輪型)の運動を計算してみよう | Tajima Robotics

Speedについては入力値をある範囲で区切り、離散化している。Steeringの値は60cmを最大値とする旋回半径として与えられ、与えられた値が大きくなるにつれ、旋回半径が短くなるようにしている。つまり、コントローラを大きく倒すほどJetbotが急激に曲がることになる。

3.2 学習(train_data.ipynb)

 学習スクリプトはroad_followingのサンプルをベースにResNet18をベースに転移学習を行った。出力はtorch.nn.Linear(512,2)としてSpeed とSteeringの値を出すようにする。MSE-Lossを使用して回帰問題として学習させる。 road_followingの学習スクリプトから大きな変更はない。road_followingでは画像上でのXY座標を回帰問題として求めるのに対して今回はその時のコントローラの出力値を出力するだけだ。違うのは与えるデータだけ。そのため、Datasetクラスの実装で画像名からのラベルデータの取り出し部分を変更している。

 ResNet18のLayer1からLayer3までは学習しないようにフリーズすると少ないエポック数でそれっぽく学習できた。ResNetの畳み込み層はLayer4までなので最後の特徴抽出層とその後の全結合層のみ学習させる。次のコードで層をフリーズさせることができる。

# ResNet18のLayer1からLayer3までを学習しないようフリーズさせる場合は、学習前に追加
for param in model.layer1.parameters():
    param.requires_grad = False
for param in model.layer2.parameters():
    param.requires_grad = False
for param in model.layer3.parameters():
    param.requires_grad = False

 それ以外にもvalidation lossが下がらない場合に過学習しないよう、学習を止めるEarlyStoppingを設定した。10〜30エポック程度で停止する。 Jetson nano上でそのまま学習させることもできるが、Google Coraboratoryなどで学習させると短時間に学習できる。

3.3 走行の様子(live_demo.ipynb)

 学習したモデルを使い実際にJetbotを走行させる。各フレームで入力された画像からスピードとステアリングを直接推論する。推論された値はそのままSpeedとSteeringになるので前述のMobileControllerへ直接与えることで走行することができる。  走行中のフレームレートを稼ぎたいため、推論では半精度の浮動小数点で計算をしている。以下が、走行時の動画となる。

※ 音が出るので再生には注意


Jetbotとディープラーニングで自動走行を学習させる

見事コース一周に成功。しかし、数回走らせたうちの1回成功なので精度的にはまだまだ改良の余地がある。

3.4 Grad-CAMでの評価

 実際に走行できたが、このモデルはなにを見てスピードとステアリングを決めているのか?Grad-CAMと呼ばれる手法を用いて、各画像の推論時に画像のどの部分が結果に一番寄与したのかを可視化した。今回はSteeringの推論にて画像中のどの部分が特徴として一番寄与したかを可視化している。動画中の色が赤に近づくほど結果への寄与が高いことになる。


Grad-CAMによるテストデータの可視化

基本的には白線部分をみているようだが、大きくカーブする場面では画像全体を均一に特徴として捉えている。過学習っぽいのかな。

4. やってみて学んだこと

以下、やってみて自分で感じたことなどを記載しておく。ごくごく当たり前の内容も含まれているが、自分でやってみると以外に見落としがちだった。

4.1 データ取得のための調整

 そもそもディープラーニングで学習する前にデータ取得での地道な作業が重要だった。データ取得での調整ポイントは次の通りだ。

  1. コースのレーンの色がはっきりとわかるようにする。(コースとそれ以外のコントラスト)
  2. 速度が0のデータや誤操作時の画像の除去といった学習データのクレンジング
  3. 学習走行時に周回ごとに操作がなるべくブレないようにする。これもデータを綺麗にする意味で重要
  4. カメラの設置角を調整して画像内でのコースの割合を大きくするといったハードウェア周りの調整。

 一般的にデータの前処理は地味だが重要な作業という。今回の場合も例外にもれず、身をもって体感した。また、ロボットなどハードウェアが絡む場合ではハードウェアの調整も重要なポイントとなる。

4.2 エッジ学習のやり方

 Jetson nanoで試行錯誤しながら学習するには処理が遅い。そこで、同じコードでGoogle Coraboratoryなど他の環境で学習し、エポック数やデータセット、バッチサイズなどのハイパーパラメータを調整しておく。「あたり」をつけたパラメータを使いEdgeで学習させると効率的。そのため、エッジ側とクラウド側で同じ環境、同じコードで学習できることが重要。今回はJupyter notebookが役に立った。

 こうなると全部クラウドでと考えてしまうが、データの収集、学習から推論まで エッジでできるのは手軽さがあり、データの転送など煩雑な操作がない分、手軽さがある。クラウド側とエッジ側の分担や使いわけはまだまだ課題だろう。

 また、エッジのように利用するデータを少なくしなければならない場合は転移学習は強力な手法になる。ゼロから学習させると画像の枚数が多くなり、Jetson nanoでの学習は現実的に厳しい。Jetson nano上で学習させるなら転移学習させるのが現実解なのだろう。

4.4 過学習に気をつけろ

 最初は何も考えず、 70エポックをフルで回していた。Early Stopping入れることで短い時間で割と良い結果が出ることに気がついた。エポックの回数をむやみに増やせば良いというものでもないのだな。

4.5 GPUメモリリーク

 当初Jetson nano上で400枚程度の画像でGPUのメモリが足りなくなり、学習が止まってしまう問題があった。これはGPU上のメモリに保存されているLossの値をトータルLossの計算でそのまま加算していたため、トータルLossの値がGPUのメモリを確保し続け、メモリが足りなくなる事象が発生していた。    GPUGPUに得意なことしかさせない。GPUを使わない処理や必要のないデータはGPUのメモリから外すようにするというのが特にエッジGPUでは重要だろう。

ちなみにこの修正は本家のリポジトリへプルリクエストを送ってみた。マージされるといいな。

Fix GPU memory leaks. by masato-ka · Pull Request #117 · NVIDIA-AI-IOT/jetbot · GitHub

5 まとめ

ディープラーニングを使ったEnd-to-Endな自動走行を実現した。ちゃんと教示走行を学習してそれっぽく動く姿は非常に興味深かった。今後はモデルを変えたり、センサ情報や、画像上に移った物体の情報で動きを変える(標識の画像で一時停止や反転など)ようなものに発展させたい。

お礼

この記事を執筆するにあたり、茅場町 コワーキングスペースCo-Edoの会議室を実験、撮影にて利用させていただきました。

www.coworking.tokyo.jp

JetbotでOpenVSLAMを試してみた。

この記事にてついて

この記事ではJetbotを使ってOpenVSLAMを動かした内容をまとめている。SLAMの実行はJetson nanoではなく、別のホストマシンで実行した。ROSの設定方法や、動かし方を中心に記載している。

ROSで使える単眼SLAM

ORB_SLAM2とOpenVSLAMがROSパッケージのサンプルを同梱している。ORB_SLAM2のROSサンプルはSLAMのローカライゼーション結果(カメラの姿勢)もトピックに投げられるようになっている。OpenVSLAMはローカライゼーション結果はビュアーに表示されるのみだ。またORB_SLAMとOpenVSLAMは実行結果の表示にPangolinと呼ばれるOpenGLを使った3DViewerライブラリを利用する。そのためそれなりのビデオチップを搭載したマシンが必要になる。OpenVSLAMではPangolinを使わずにWEBブラウザのみで描画可能な表示モードが用意されている。今回はこれが決め手となり、OpenVSLAMを利用することにした。ちなみにORB_SLAM2はGPLv3でOpenVSLAMはBSDでそれぞれ公開されている。

環境構築

構成

SLAMの実行はVirtualBox上のUbuntu 18.04で実行した。物理マシンはMacBook Air 2018 メモリ16GBを利用。ROSのマスターはJetbot側にある。Jetbotで取得した画像はimage_procで圧縮し、/jetbot/image_rawへパブリッシュされる。ホスト側のUbuntuではimage_transferで画像をrawに戻してOpenVSLMAの入力に送っている。JetbotのコントロールゲームコントローラをホストUbuntuに接続し、ROSのJoyパッケージで操作を行なっている。

f:id:masato-ka:20190719083653j:plain

Jetson nano上でもOpenVSLAMが動くことは確認したが、GPUを使うわけではないのでJetson nanoで動かす旨味はあまりない。

ROSのインストール

Jetson nano とホスト側のUbuntu環境へROSをインストールする。また、Jetson nano側のROSはPython3でROS Nodeを開発できるように設定を済ませておく必要がある。

masato-ka.hatenablog.com

Jetbot用ROSパッケージの準備

JetbotへJetbot用のROSパッケージを準備する。以下のリポジトリをJetson nano側のROSワークスペースにクローンしcatkin_makeしておく。

github.com

cd ~/your_ros_workspace/src
git clone https://github.com/masato-ka/ros_jetbot.git
cd ../
catkin_make

image_procパッケージをインストールしていない場合はインストールしておく。

sudo apt-get install -y ros-melodic-image-proc

OpenVSLAMのインストール

ホスト側のUbuntu 18.04へOpenVSLAMをインストールする。以下のページの手順に進めればインストールできる。16.04 testedと書いているが、18.04でも問題なくインストールできる。

openvslam.readthedocs.io

ポイントは以下の2つ。

  1. OpenCVは上記手順通りソースからビルドする
  2. SocketViewerでインストールする
  3. SocketViewer用のNode.js環境も構築する。

もちろんPangolinを選択しても問題はないが、それなりのビデオカードが必要になる。

OpenVSLAMのビルドまで完了したらROSパッケージのインストールを行う。こちらも公式の手順通りで問題ない。また、ビルドオプションはSocketViewerを選択する。image_transportパッケージのインストールも忘れずに実施する。

openvslam.readthedocs.io

ORB特徴のボキャブラリーファイルをダウンロードする。ちなみに何に使うのかは理解してないのでこの辺りはどっかで調べる。

# download an ORB vocabulary from Google Drive
cd /path/to/openvslam/build/
FILE_ID="1wUPb328th8bUqhOk-i8xllt5mgRW4n84"
curl -sc /tmp/cookie "https://drive.google.com/uc?export=download&id=${FILE_ID}" > /dev/null
CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)"
curl -sLb /tmp/cookie "https://drive.google.com/uc?export=download&confirm=${CODE}&id=${FILE_ID}" -o orb_vocab.zip
unzip orb_vocab.zip

またカメラパラメータのファイルを作っておく。Jetbotに使う広角レンズ(SainSmart IMX219)の場合

cd /path/to/openvslam/build/
vi jetbot_mono.yaml

でjetbot_mono.yamlを新規作成する。記載内容は以下の内容を記載する。

#==============#
# Camera Model #
#==============#

Camera.name: "Jetbot"
Camera.setup: "monocular"
Camera.model: "perspective"

Camera.fx: 1138.042594 
Camera.fy: 1152.150891 
Camera.cx: 314.573090 
Camera.cy: 273.959896 

Camera.k1: -2.663941 
Camera.k2: 5.967979 
Camera.p1: -0.053529 
Camera.p2: 0.031916 
Camera.k3: 0.0

Camera.fps: 5.0
Camera.cols: 640
Camera.rows: 480 

Camera.color_order: "RGB"

#================#
# ORB Parameters #
#================#

Feature.max_num_keypoints: 2000
Feature.scale_factor: 1.2
Feature.num_levels: 8
Feature.ini_fast_threshold: 20
Feature.min_fast_threshold: 7

カメラが違う場合はcamera_calibration パッケージを利用してキャリブレーションをする。

wiki.ros.org

実行

Jetbot側の準備

JetbotのROSモジュールを実行する。

export ROS_MASTER_URI=http://[Jetbot IP]:11311
export ROS_IP=[Jetbot IP]
roslaunch ros_jetbot jetbot.launch

ホスト側の準備

Jetbotコントロール用のjoy_nodeの立ち上げ

export ROS_MASTER_URI=http://[Jetbot IP]:11311#JetbotのIPを指定する
export ROS_IP=[Ubuntu IP]#Ubuntu VMのIP
rosparam set joy_node/dev [ゲームパットのデバイスパス]
rosrun joy joy_node

image_transportモジュールの起動

export ROS_MASTER_URI=http://[Jetbot IP]:11311#JetbotのIPを指定する
export ROS_IP=[Ubuntu IP]#Ubuntu VMのIP
 rosrun image_transport republish compresin:=/jetbot/image_color raw out:=/camera/image_raw

Viewersサーバの起動

cd /path/to/openvslam/viewer
node app.js

OpenVSLAM ROSモジュールを起動

export ROS_MASTER_URI=http://[Jetbot IP]:11311
export ROS_IP=[Jetbot IP]
 rosrun openvslam run_slam -v /path/to/openvslam/build/orb_vocab/orb_vocab.dbow2 -c /path/to/openvslam/build/jetbot_mono.yaml

実行結果の確認

http://[Ubuntu IP]:3001にアクセスするとOpenVSLAMの実行結果を確認することができる。

f:id:masato-ka:20190718233102p:plain
OpenVSLAM-Jetbot

  • 動画

動画だと実験環境のネットワークコンディションのせいでカクカクしているがもう少しスムーズに動く。また、途中でロストしているが、再度ローカライゼーションに成功している。


Visual SLAM with Jetbot

まとめ

Jetson nanoのGPUを使っていないので、あまりうまみはないが、ローカライゼーションさせれば何か面白いことができるかもしれない。CNN SLAMなどDNNを使ったSLAMを使うと状況が違うのかもしれない。

Jetbot+ROS+Python3環境構築

この記事について

この記事ではJetbotのOSイメージにROSをインストールしPython3でROSのスクリプトを記述する方法について説明する。Jetbotのイメージで提供されているPythonのライブラリはPython3で記載されている。しかし、公式ではROSはPython2系対応のみだ。そのためJetbotのライブラリを利用してJetbot向けのROSのパッケージを作ることができない。 そこでROSのパッケージ開発(rospyなどを使った開発)のみPython3で行えるようにした。今回はROSのシステムはPython2.7で動作する。実際は以下のように ROS全てをPython3で動くようソースからビルドするのが望ましいだろう。

ros.youtalk.jp

手順

1. ROSのインストール

ROSのインストールは公式ページに従って進める。今回ROSのバージョンはMelodicをインストールした。またros-melodic-desktopを利用した。Jetson nano及びJetbotの場合、OSはUbuntu 18.04 がインストールされている。

roscore, rosrunが実行できる状態まで持っていく。またpython2系でimport rospyができるところまで確認する。

2. Python3向け準備

ROSコマンドやPYTHONPATHはROSインストール時にインストールされるPython2.7をさしている。ROSのPythonライブラリであるrospyやsensor_msgsなどはPython3系でそのまま動くようになっている。そこで、Python3からPython2.7のdist-packageにインストールされているROSライブラリを参照できるようにする。

2. Python3からrospyを使えるようにする。

基本的にはPyhotn3のパスにPyhton2.7のdist-packageのパスが追加できればいい(と信じている)rospkgというパッケージをインストールするとこれが実現できる。何らかの副作用だと思うが深くは追っていない。

sudo pip3 install rospkg catkin_pkg

パッケージのインストール後、Python3でrospy, sensor_megsがインポートできるようになる。

3. vision_opencvのインストール

この状態でOpenCVとROSのブリッジライブラリcv_bridgeライブラリを利用するとエラーが発生する。cv_bridgeで利用しているネイティブライブラリがPython2系向けにコンパイルされているためだ。そこで、cv_bridge自体をPython3系向けにコンパイルし直す必要がある。手順は以下のページを参考にした。一部パスがx86向けなので、ARM向けに修正した。

How to setup ROS with Python 3 - Omri Ben-Bassat - Medium

  • python3系向けにビルドするための準備
mkdir ~/catkin_build_ws && cd ~/catkin_build_ws
catkin config -DPYTHON_EXECUTABLE=/usr/bin/python3 -DPYTHON_INCLUDE_DIR=/usr/include/python3.6m -DPYTHON_LIBRARY=/usr/lib/aarch64-linux-gnu/libpython3.6m.so
catkin config --install
mkdir src
cd src
git clone -b melodic https://github.com/ros-perception/vision_opencv.git
cd ~/catkin_build_ws
catkin build cv_bridge
source install/setup.bash --extend

これで、Python3でROSパッケージを書く準備が整った。

4. スクリプトの書き方と実行方法

rosのワークスペースで新しいパッケージを作成する。ワークスペースはパスさえ通っていれば先ほどのワークスペースと違うものでも大丈夫。 パッケージ内にscriptsフォルダを作成し、次のファイルを作成する。

  • camera.py
#!/usr/bin/env python3

import yaml
import rospy
from sensor_msgs.msg import Image
from cv_bridge import CvBridge, CvBridgeError
from jetbot import Camera
from sensor_msgs.msg import CameraInfo

NODE_NAME = 'jetbot_camera_node'
IMAGE_TOPIC = 'jetbot/image_raw'

class JetbotCamera:

    def __init__(self, width=640, height=480, calib_file=''):
        self.calib_file = calib_file
        self.camera = Camera(width=width,height=height)
        self.publisher = rospy.Publisher(IMAGE_TOPIC, Image, queue_size=1)
        self.bridge = CvBridge()

    def start(self):
        rospy.init_node(NODE_NAME)
        self.camera.observe(self.image_proc, names='value')
        self.camera.start()
        rospy.spin()

    def image_proc(self, change):
        image_value = change['new']
        cv2_image = self.bridge.cv2_to_imgmsg(image_value, 'bgr8')
        self.publisher.publish(cv2_image)

if __name__ == '__main__':
    jetbot_camera = JetbotCamera()
    jetbot_camera.start()
    pass

ファイル先頭に#!/usr/bin/env python3を記載する。またファイルのパーミッションを755に設定し、実行ファイルにする。その後以下のコマンドで実行する。

roscore
#別ターミナルに切り替え
rosrun ros_package-name camera.py

そうするとjetbot/image_rawというトピックが設定される。rosのimage_viewで画像を表示させてみる。

rosrun image_view image_veiw image:=jetbot/image_raw

まとめ

jetbot用に車輪のコントロールと画像を受信するrosパッケージを作ってみた。広角カメラのカメラパラメータも一緒に入ってる。 時間があるときにちょくちょくメンテする予定。

github.com

Jetbotを3G USBモデム(AK-020)でSORACOM対応する。

この記事について

この記事ではJetson nanoに3G USBモデムAK-020を接続する方法を紹介する。RaspbianやUbuntuへの接続方法が多数紹介されている。Jetson nanoのUbuntu 18.04に接続する場合基本的な手順は同じだが、いくつか細かい違いがあったのでまとめておく。Jetbotへ接続し、3G回線を使いたい場合の参考に。

f:id:masato-ka:20190715205916j:plain

接続手順

1. 3Gモデムのマウント

AK-020はJetson nanoへ接続するとCD-ROMとしてマウントされる。そのため、CD-ROMをアンマウントして、3Gモデムとして動作させる必要がある。Raspbianで実行する場合はいくつか方法がある。Jetson nanoの場合はusb-modeswitchを使う方法で唯一成功した。

usb_modeswitchがインストールされていない場合はインストールする。

sudo apt-get install usb-modeswitch

usb_modeswitchの設定ファイルの末尾にAK-020用の設定を記載する。

# ファイル名 /lib/udev/rules.d/40-usb_modeswitch.rules

#AK-020
ATTR{idVendor}=="15eb", ATTR{idProduct}=="a403", RUN+="usb_modeswitch '%b/%k'"

さらに、GSMモデムとして認識させるための設定ファイルを作成する。

# ファイル名 /etc/usb_modeswitch.d/15eb:a403
DefaultVendor = 0x15eb
DefaultProduct = 0xa403
TargetVendor = 0x15eb
TargetProduct = 0x7d0e
StandardEject = 1
WaitBefore=2

ここまで設定したら一度Jetson nanoを再起動させる。Jetson nanoが起動したらAK-020を刺した時のイベントを確認する。dmesgだとコマンド実行時のみ表示されるので、tail -f /var/log/kern.logで確認するとよい。 CD-ROMがアンマウントされGMSモデムとして認識される。またこの時/dev/ttyUSB0〜/dev/ttyUSB3まで4つのシリアルデバイスが作成されている。このうちどれかがUSBモデムとの通信ポートになる。少し長ったらしくなるが、認識した時のログを掲載する。

[Jul 14 05:39:04 jetbot kernel: [19427.007551] usb 1-2.4: new high-speed USB device number 4 using tegra-xusb
Jul 14 05:39:04 jetbot kernel: [19427.031933] usb 1-2.4: New USB device found, idVendor=15eb, idProduct=a403
Jul 14 05:39:04 jetbot kernel: [19427.031999] usb 1-2.4: New USB device strings: Mfr=2, Product=3, SerialNumber=4
Jul 14 05:39:04 jetbot kernel: [19427.032041] usb 1-2.4: Product: AK-020
Jul 14 05:39:04 jetbot kernel: [19427.032080] usb 1-2.4: Manufacturer: AK-020
Jul 14 05:39:04 jetbot kernel: [19427.032116] usb 1-2.4: SerialNumber: 
Jul 14 05:39:04 jetbot kernel: [19427.044589] usb-storage 1-2.4:1.0: USB Mass Storage device detected
Jul 14 05:39:04 jetbot kernel: [19427.045364] scsi host0: usb-storage 1-2.4:1.0
Jul 14 05:39:05 jetbot kernel: [19428.069400] scsi 0:0:0:0: CD-ROM            HSPA_USB SCSI CD-ROM       622 PQ: 0 ANSI: 0 CCS
Jul 14 05:39:07 jetbot kernel: [19430.368167] usb 1-2.4: USB disconnect, device number 4
Jul 14 05:39:08 jetbot kernel: [19431.363561] usb 1-2.4: new high-speed USB device number 5 using tegra-xusb
Jul 14 05:39:08 jetbot kernel: [19431.394481] usb 1-2.4: New USB device found, idVendor=15eb, idProduct=7d0e
Jul 14 05:39:08 jetbot kernel: [19431.394525] usb 1-2.4: New USB device strings: Mfr=9, Product=10, SerialNumber=0
Jul 14 05:39:08 jetbot kernel: [19431.394552] usb 1-2.4: Product: AK-020
Jul 14 05:39:08 jetbot kernel: [19431.394580] usb 1-2.4: Manufacturer: AK-020
Jul 14 05:39:08 jetbot kernel: [19431.405649] usb-storage 1-2.4:1.6: USB Mass Storage device detected
Jul 14 05:39:08 jetbot kernel: [19431.410039] scsi host0: usb-storage 1-2.4:1.6
Jul 14 05:39:09 jetbot kernel: [19432.420346] scsi 0:0:0:0: Direct-Access     HSPA_USB SCSI CD-ROM       622 PQ: 0 ANSI: 0 CCS
Jul 14 05:39:09 jetbot kernel: [19432.451840] sd 0:0:0:0: [sda] Test WP failed, assume Write Enabled
Jul 14 05:39:09 jetbot kernel: [19432.458823] sd 0:0:0:0: [sda] Asking for cache data failed
Jul 14 05:39:09 jetbot kernel: [19432.464333] sd 0:0:0:0: [sda] Assuming drive cache: write through
Jul 14 05:39:09 jetbot kernel: [19432.474501] sd 0:0:0:0: [sda] Attached SCSI removable disk
Jul 14 05:39:10 jetbot kernel: [19433.406926] usbcore: registered new interface driver option
Jul 14 05:39:10 jetbot kernel: [19433.406994] usbserial: USB Serial support registered for GSM modem (1-port)
Jul 14 05:39:17 jetbot kernel: [19440.421602] option 1-2.4:1.2: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.430267] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB0
Jul 14 05:39:17 jetbot kernel: [19440.430449] option 1-2.4:1.3: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.431161] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB1
Jul 14 05:39:17 jetbot kernel: [19440.431358] option 1-2.4:1.4: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.431718] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB2
Jul 14 05:39:17 jetbot kernel: [19440.431906] option 1-2.4:1.5: GSM modem (1-port) converter detected
Jul 14 05:39:17 jetbot kernel: [19440.435208] usb 1-2.4: GSM modem (1-port) converter now attached to ttyUSB3

[参考]

2. ModemManagerの停止

接続は後述するpppconfigを使用する。しかしUbuntuの場合、別途ModemManagerがモデムを管理し始めるので競合しないようにあらかじめ停止させておく。

sudo systemctl stop ModemManager
sudo systemctl disable ModemManager

3. 通信接続設定

pppconfigを使ってキャリアへの接続を行う。pppconfigのインストールは以下の通り。

sudo apt-get install pppconfig

インストール後、ターミナルからpppconfigを立ち上けてcreate connectionを選択する。以下の表に合わせて設定進める。ユーザ名やパスワードについてはSORACOM Air SIMのデフォルトの場合。それ以外のキャリアの場合は都度修正を行う。

設定
Provider soracom_ak-020
Configure Nameservers (DNS) Dynamic
Authentication Method PAP
User Name sora
Password sora
Speed 460800
Puls or Tone Tone
Phone Number *99#
Choose Modem Config Method No
Manually Select Modem Port /dev/ttyUSB0

ppp接続時に3G回線をデフォルトルートとして設定できるように、pppconfigの設定ファイルを直接編集する。ファイル名はProviderで設定した値になっている。末尾から3行を追加する。

# This optionfile was generated by pppconfig 2.3.18.
#
#
hide-password
noauth
connect "/usr/sbin/chat -v -f /etc/chatscripts/soracom_ak-020"
debug
/dev/ttyUSB0
460800
defaultroute
noipdefault
user "sora"
remotename soracom_ak-020
ipparam soracom_ak-020

usepeerdns #追加
persist #追加
replacedefaultroute #追加

[参考]

続いて、ATコマンドの修正を行う。このATコマンドファイルに従い、pppconfigがシリアルポートを介してモデムを制御する。

 This chatfile was generated by pppconfig 2.3.18.
# Please do not delete any of the comments.  Pppconfig needs them.
#
# ispauth PAP
# abortstring
ABORT BUSY ABORT 'NO CARRIER' ABORT VOICE ABORT 'NO DIALTONE' ABORT 'NO DIAL TONE' ABORT 'NO ANSWER' ABORT DELAYED
# modeminit
'' ATH
# ispnumber
OK AT+CFUN=1
OK ATZ
OK AT+CGDCONT=1,"IP","soracom.io"
OK-AT-OK "ATD*99#"
# ispconnect
CONNECT \d\c
# prelogin

# ispname
# isppassword
# postlogin

# end of pppconfig stuff

3.起動

以下のコマンドで接続を行う。AK-020の場合はLEDが緑色に点滅したら接続成功となる。

pon soracom_ak-020

ifconfigを打つと以下のようにppp0というインタフェースができている。IPが当たっていたら接続成功。

ppp0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500
        inet 10.xxx.xxx.xxx  netmask 255.255.255.255  destination 10.64.64.64
        ppp  txqueuelen 3  (Point-to-Point Protocol)
        RX packets 62  bytes 649 (649.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 63  bytes 1474 (1.4 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

明示的に切断したい場合は以下のコマンドで接続する。

poff soracom_ak-020

あとはこのコマンドを起動スクリプトに仕掛けておけば良い。起動スクリプトに仕掛ける場合は、usb_modeswitchの実行後になるように順番に留意する。

まとめ

Jetson nanoを3G回線にダイレクトに接続できる。WiFi モバイルルータに経由で接続する方法もあるが、それだと3G回線のみになる。とりあえずソラコムまで繋がったら、Jetbotの走行データをHarvestにあげるもよし、Napterで遠隔操作するも良し。この続きはまたいずれ。

3DプリンタなしでオリジナルJetbotを作った記録

この記事について

JetbotはNVIDIAのJetson nanoを搭載したDIY Robotcarだ。その作成方法をNVIDIAオープンソースで公開し誰もが作成できる。しかし、車体といった筐体パーツは3Dプリントが必要になる。STLファイルが公開されているとはいえ、3Dプリンタサービスで筐体を出力するのにはそれなりの金額がかかる。そこで、入手性の良い部品を使ってオリジナルのJetbotを作成したので紹介する。

近々キットも発売されるようなのでそれでも面倒な方はそちらから。

robotstart.info

完成したオリジナルJetbot

  完成したJetbotはアルミ製のロボット台車をベースにしている。この台車は秋月電子で購入できる市販品だ。この上にアルミ板を一枚載せてバッテリーとJetson nanoをマウントしている。モータドライバーもWaveshareのMotor Driver HATを利用している。こちらは千石通商で購入できる。

f:id:masato-ka:20190623125330j:plain
完成したJetbot

これらの部品選定については以下野良ハックさんのサイトを参考にさせていただいた。

note.mu

部品リスト

使用した部品は次の表のとおり。Jetson nanoを除けば16000円程度で部品か揃う。Jetson nanoも12000円程度なのでトータルで3万円に達しないくらいで構築できる。

  部品名 数量 参考価格 (円) 購入店舗 備考
FT-DC-002(台車) 1 1,480 秋月電子 -
Motor Driver HAT 1 1,560 千石通商 -
1ミリ厚アルミ板100mmx200mm 1 400 ホームセンタなど -
オスオスジャンパー線 1 200 秋月電子など モータとモータドライバの結線用
2mmネジ 14 200 西川電子部品 -
2mmナット 14 200 西川電子部品 -
2mmx15mmスペーサ 4 100 西川電子部品 -
2mmx5mmスペーサ 12 250 西川電子部品 -
INIU モバイルバッテリー 1 1999 Amazon -
広角カメラ 1 2499 Amazon -
Raspberry Pi Camera Model V2 1 3,900 Amazon -
micro SD Card U3 A2 1 3480 Amazon -

 

作成のノウハウ

基本的な作り方や構成はNVIDIA公開のWikiと同じになる。特別なノウハウは特にないのだがNVIDIA公開手順と違うポイントを中心に、紹介する。  

筐体の作成

筐体の構成は3層構造になている。まずはモータ含む台車部分だ。これは台車をそのまま組み立てる。その上にモバイルバッテリーを配置する。さらにスペーサを支柱がわりにアルミ板を載せる。アルミ板の上にJetson nanoをマウントする。

f:id:masato-ka:20190626095056j:plain

台車への穴あけ

台車に空いているネジ穴はモバイルバッテリーで塞がれてしまう。そこで、モバイルバッテリーに干渉しないよう、新しくネジ穴を開ける必要がある。台車の天板四隅に2mmネジの穴を開ける。穴にはスペーサを取り付ける。今回は15mmのスペーサと5mmのスペーサを組み合わせて20mmの長さにした。

f:id:masato-ka:20190623123223j:plain

アルミ板の加工方法は後述する。

アルミ天板の作成

次にアルミの天板を作成する。まずはアルミ板の切り出しサイズと台車と連結するネジ穴を開ける。寸法は実物を使って適当に印をつける。この際に、台車のネジ穴位置を間違えないようにする。

f:id:masato-ka:20190621222638j:plain
アルミ板の切り出しサイズの決定と穴あけ位置の決定

切り出しと穴あけが終わったアルミの天板にJetson nanoをそっとのせる。Jetson nanoがショートしないように電源を全て取り外し、十分に放電しておくと良いだろう。静電気にも注意したい。Jetson nanoのネジ穴の位置を天板城へ直接採寸する。Jetson nanoのネジ穴から細めの油性マジックで天板へ印をつけていく。Jetson nanoの設置むきに注意する。I/O用ピンヘッダがでている側が車体の後方である。また、前方にはカメラを固定する必要があるため、若干天板の後ろ気味に設置し、前方にスペースを出す方が良いだろう。

f:id:masato-ka:20190621224040j:plain
Jetson nanoの採寸、アルミ板に載せるのでショートに注意

 アルミの天板は本体と固定するスペーサ用の穴とJetson nanoを取り付ける穴を開ける。こちらも2mmのネジ穴となる。カメラを固定するためのネジ穴も開けておく。

カメラ天板の作成

 カメラマウントもアルミで作る。Raspberry Pi Camera V2とスペーサを介してアルミ板に固定する。カメラが少し地面を向くように「く」の字に曲げておき、カメラが付いていない辺を本体との固定に使う。

f:id:masato-ka:20190628135625j:plain
カメラマウント

作成したカメラマウントはアルミ天板に設置する。カメラ中心がなるべく車体中央になるように穴あけ位置を決める。先にカメラマウント側に穴を開け、位置合わせをするといいだろう。

f:id:masato-ka:20190623123921j:plain
カメラマウントを天板に設置した様子

加工について

 アルミのカットや穴あけは一見難しそうだが、カッターナイフやピンバイスといった工具で作業ができる。今回は写真のような工具を利用している。

f:id:masato-ka:20190623135318j:plain
利用した工具

左に写っているドリルはタミヤから発売されている組み立て式のドリルでヨドバシカメラでも購入できる。付属に2mmのピンバイスが付属しているのでネジ穴はこれで開けられる。

www.amazon.co.jp

穴を開ける場合はけがき代わりにピンバイスで少し浅めの穴を削ってからドリルで穴あけする。こうすることでずれずに穴を開けることができるドリルを使う際はゴーグルなどをして、アルミの削りカスや折れたドリル歯の破片から目を保護することを強くお勧めする。

アルミのカットについては以下の動画が参考になる。こちらの動画では万力などを利用している。今回のサイズ感のものであれば手で抑えれば十分だ。

www.youtube.com

いずれにしても加工の際の怪我には十分注意する。自身がない場合は、ホームセンターや有料工作スペースで相談してみるのも良いだろう。 カインズホームでは有料の工作スペースがある店舗がある。3Dプリンタも置いていたりする。

www.cainz.co.jp

モータドライバ

モータドライバ接続の注意点

モータドライバは前述の通り、WaveshareのMotor Driver HATを利用している。このボードに外部電源を接続してはいけない。Jetson nanoの接続については何も考えずにピンヘッダに差し込むだけでOKだ。注意点は、モータ用の外部電源を接続してはいけないという点だ。このボードはモータ用に外部から6V以上の電源を供給できるようになっている。しかし、このボードに6Vを供給するとモータドライバ側からJetson nano側の5V端子に電力を供給してしまう。電気的に危険に思うので外部電源は接続しないのが無難だろう。Motor Driver HATのスイッチもオフにしておこう。

ソフトウェアの修正

オリジナルのJetbotで使われているモータドライバとWaveshareのモータドライバは基本構成は同じだ。それぞれPCA9685というI2CインタフェースのPWMコントローラからTB6612というDCモータドライバを制御している。しかし、PCA9685とTB6612の接続ピンアサインに違いがあるため、Jetbotのサンプルを使うためにはAdafruitのドライバを修正する必要がある。以下のコマンドで修正する。

sodo vi /usr/local/lib/python3.6/dist-packages/Adafruit_MotorHAT-1.4.0-py3.6.egg/Adafruit_MotorHAT/Adafruit_MotorHAT_Motors.py

class Adafruit_DCMotor:
    def __init__(self, controller, num):
        self.MC = controller
        self.motornum = num
        pwm = in1 = in2 = 0

        if (num == 0):
                 pwm = 5 # 8 -> 5
                 in2 = 4 # 9 -> 4
                 in1 = 3 # 10 -> 3
        elif (num == 1):
                 pwm = 0 # 13 -> 0
                 in2 = 1 # 12 -> 1
                 in1 = 2 # 11 -> 2
        elif (num == 2):
                 pwm = 2
                 in2 = 3
                 in1 = 4
        elif (num == 3):
                 pwm = 7
                 in2 = 6
                 in1 = 5

また、JetbotのリポジトリをコピーしてJetbotnライブラリをインストールする。自分の環境ではモータドライバのI2Cアドレスを0x60として呼び出しを行っていた。デフォルトは0x40なので修正した。

github.com

jetbot/robot.pyの22行目を以下のように修正する。

       self.motor_driver = Adafruit_MotorHAT(address=0x40, i2c_bus=self.i2c_bus)

まとめ

Jetbotは手軽に移動ロボットが作れるプラットフォームとして興味があった。しかし3Dプリンタのボディー作成にお金がかかりそうで躊躇していた。今回のように既製品とちょっとの工作の組み合わせでオリジナルJetbotを構築できれば同じようなポイントで躊躇してる人もはじめやすくなるのではないだろうか。Jetbotを使った面白い事例がもっと増えてほしい。