JetBotの車体でDonkeyCarプログラムを動かしてみた

スポンサーリンク

f:id:sato_susumu:20201020165023j:plain:w400

JetBot をハード無改造で Donkey Car 化するという記事を見かけたので、自分でもやってみた。 ソフトウェアセットアップから学習データ作成、学習、推論と一通り動かしたので、その記録です。

目次 (クリックで展開)

確認環境

コメント
JetBot レゴ製シャーシのJetBot。公式とはモーターが異なる。
Jetson Nano 古いA02モデル。メモリ4G
カメラ SainSmart IMX219カメラモジュール
ソフト JetBot公式イメージ(JetPack4.3)をベースに修正
ゲームパッド ロジクール ワイヤレスゲームパッド F710

ソフトウェアセットアップ

ベースとなるSDカードイメージの選択、基本的な設定

SD Card ImageはJetPack4.3をベースにした jetbot_image_v0p4p0.zip を選択。

Wi-Fi設定、I2C確認などが必要になるけど、JetBot作成時の記事と内容が変わらないため、そちら参照。

www.sato-susumu.com

Donkey Carのソフトウェアセットアップ

次にDonkey Carのソフトウェアセットアップ。
tensorflow以外はDonkey Car公式ドキュメント通りに実行すればOK。
tensorflowはなぜかjetpack 4.3用の記述がないので、適当に追加。

$ sudo apt update
$ sudo apt install -y build-essential python3 python3-dev python3-pip python3-pandas python3-h5py libhdf5-serial-dev hdf5-tools nano ntp
$ sudo apt install -y python3-virtualenv virtualenvwrapper
$ echo 'export WORKON_HOME=$HOME/.virtualenvs' >> ~/.bashrc
$ mkdir ~/.virtualenvs
$ source ~/.bashrc
$ mkvirtualenv donkey
(donkey) $
(donkey) $ mkdir -p ~/projects && cd ~/projects
(donkey) $ git clone https://github.com/autorope/donkeycar
(donkey) $ cd donkeycar
(donkey) $ git checkout master
(donkey) $ sudo pip3 install -U numpy grpcio absl-py py-cpuinfo psutil portpicker six mock requests gast==0.2.2 h5py astor termcolor protobuf keras-applications keras-preprocessing wrapt google-pasta
(donkey) $ sudo pip3 install -e .[nano]
(donkey) $ sudo pip3 install --pre --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v43 tensorflow==1.15.2+nv20.2

最後のtensorflowインストールで依存関係のエラーが表示される。けど、その直後に必要なパッケージが再インストールされているので、たぶんOK。

(donkey) $ donkey createcar --path ~/mycar

Donkey CarプログラムでJetBotを動かすための変更

そのままだとDonkey Carは動くだろうけど、JetBotは動かないので、変更していく。
まずは必要なライブラリを追加。

(donkey) $ pip3 install Adafruit-MotorHAT

次にプログラムとmycofig.pyを修正する。
修正対象のファイルや内容は次のgit diffを参照。

修正後のgit diffの内容 (クリックで展開)

注意:下記は修正の手順ではなく、修正後のgit diff表示です。

$ cd ~/mycar
$ git diff
diff --git a/manage.py b/manage.py
index ce912e0..b1eb47d 100644
--- a/manage.py
+++ b/manage.py
@@ -526,6 +526,20 @@ def drive(cfg, model_path=None, use_joystick=False, model_type=None, camera_type
         V.add(left_motor, inputs=['left_motor_speed'])
         V.add(right_motor, inputs=['right_motor_speed'])
 
+    elif cfg.DRIVE_TRAIN_TYPE == "DC_TWO_WHEEL_AF":
+        from donkeycar.parts.actuator import TwoWheelSteeringThrottle, Adafruit_DCMotor_Hat
+
+        left_motor = Adafruit_DCMotor_Hat(cfg.ADAFRUIT_MOTOR_NUM_LEFT)
+        right_motor = Adafruit_DCMotor_Hat(cfg.ADAFRUIT_MOTOR_NUM_RIGHT)
+        two_wheel_control = TwoWheelSteeringThrottle()
+
+        V.add(two_wheel_control,
+                inputs=['throttle', 'angle'],
+                outputs=['left_motor_speed', 'right_motor_speed'])
+
+        V.add(left_motor, inputs=['left_motor_speed'])
+        V.add(right_motor, inputs=['right_motor_speed'])
+
     elif cfg.DRIVE_TRAIN_TYPE == "SERVO_HBRIDGE_PWM":
         from donkeycar.parts.actuator import ServoBlaster, PWMSteering
         steering_controller = ServoBlaster(cfg.STEERING_CHANNEL) #really pin
diff --git a/myconfig.py b/myconfig.py
index ab263f7..1d8f449 100644
--- a/myconfig.py
+++ b/myconfig.py
@@ -285,4 +285,19 @@
 # # Stop Sign Detector
 # STOP_SIGN_DETECTOR = False
 # STOP_SIGN_MIN_SCORE = 0.2
-# STOP_SIGN_SHOW_BOUNDING_BOX = True
\ No newline at end of file
+# STOP_SIGN_SHOW_BOUNDING_BOX = True
+
+CAMERA_TYPE = "CSIC"
+DRIVE_TRAIN_TYPE = "DC_TWO_WHEEL_AF"
+ADAFRUIT_MOTOR_NUM_LEFT = 1
+ADAFRUIT_MOTOR_NUM_RIGHT = 2
+IMAGE_W = 224
+IMAGE_H = 224
+CONTROLLER_TYPE='F710'
+
+BATCH_SIZE = 64
+CACHE_IMAGES = False
+
+JOYSTICK_MAX_THROTTLE = 1.0
+# JOYSTICK_STEERING_SCALE = 1.0
+
$ cd ~/projects/donkeycar
$ git diff
diff --git a/donkeycar/parts/actuator.py b/donkeycar/parts/actuator.py
index e2cbfac..896d2dd 100644
--- a/donkeycar/parts/actuator.py
+++ b/donkeycar/parts/actuator.py
@@ -269,9 +269,10 @@ class Adafruit_DCMotor_Hat:
         from Adafruit_MotorHAT import Adafruit_MotorHAT, Adafruit_DCMotor
         import atexit
         
-        self.FORWARD = Adafruit_MotorHAT.FORWARD
-        self.BACKWARD = Adafruit_MotorHAT.BACKWARD
-        self.mh = Adafruit_MotorHAT(addr=0x60) 
+        self.FORWARD = Adafruit_MotorHAT.BACKWARD
+        self.BACKWARD = Adafruit_MotorHAT.FORWARD
+        self.RELEASE = Adafruit_MotorHAT.RELEASE
+        self.mh = Adafruit_MotorHAT(i2c_bus=1) 
         
         self.motor = self.mh.getMotor(motor_num)
         self.motor_num = motor_num
@@ -280,7 +281,9 @@ class Adafruit_DCMotor_Hat:
         self.speed = 0
         self.throttle = 0
     
-        
+    def turn_off_motors(self):
+        self.mh.getMotor(self.motor_num).run(self.RELEASE)
+
     def run(self, speed):
         '''
         Update the speed of the motor where 1 is full forward and
@@ -301,8 +304,8 @@ class Adafruit_DCMotor_Hat:
         
 
     def shutdown(self):
-        self.mh.getMotor(self.motor_num).run(Adafruit_MotorHAT.RELEASE)
-
+        #self.mh.getMotor(self.motor_num).run(Adafruit_MotorHAT.RELEASE)
+        self.mh.getMotor(self.motor_num).run(self.RELEASE)
 
 class Maestro:
     '''
@@ -541,11 +544,19 @@ class L298N_HBridge_DC_Motor(object):
 class TwoWheelSteeringThrottle(object):
 
     def run(self, throttle, steering):
-        if throttle > 1 or throttle < -1:
-            raise ValueError( "throttle must be between 1(forward) and -1(reverse)")
- 
-        if steering > 1 or steering < -1:
-            raise ValueError( "steering must be between 1(right) and -1(left)")
+        # if throttle > 1 or throttle < -1:
+        #    raise ValueError( "throttle must be between 1(forward) and -1(reverse)")
+        if throttle > 1:
+            throttle = 1
+        if throttle < -1:
+            throttle = -1
+
+        # if steering > 1 or steering < -1:
+        #    raise ValueError( "steering must be between 1(right) and -1(left)")
+        if steering > 1:
+            steering = 1
+        if steering < -1:
+            steering = -1
 
         left_motor_speed = throttle
         right_motor_speed = throttle

だいたいの修正内容は@inachiさんがJetBot をハード無改造で Donkey Car 化するで公開してくれている内容です。
スロットルやステアリングが範囲外の値になって動かないことがあったので修正してます。

カメラにIMX219を使っている場合、myconfig.pyに「IMAGE_W = 224」「IMAGE_H = 224」の指定がないと起動に失敗する。
Jetson Nanoで学習する場合は、myconfig.pyに「BATCH_SIZE = 64」「CACHE_IMAGES = False」の指定がないと失敗するらしい。
デフォルトのスロットルでは動きが遅いので、myconfig.pyに「JOYSTICK_MAX_THROTTLE = 1.0」を設定。

動作確認

プログラムを起動して、ブラウザで http://<IPアドレス>:8887 にアクセス。
カメラや車体の動作を確認。まだゲームパッドでは動かない状態。

(donkey) $ cd ~/mycar
(donkey) $ python3 manage.py drive

確認できたら、Ctrl+Cで停止。

学習データ作成

オプションを指定して、再度プログラムの起動。次はゲームパッドが有効だけど、Webサーバは動かない状態。

(donkey) $ cd ~/mycar
(donkey) $ python3 manage.py drive --js

学習データを作成するために、ゲームパッドを使って車体を操作。
ゲームパッドF710での操作方法は次のとおり。

名称 内容
左アナログスティック 左右でステアリング
右アナログスティック 上下でスロットル。スロットル操作で学習データ作成開始
十字キー上下 throttle_scale変更
A 学習データ作成を停止。スロットル操作で作成を再開できる
Y 直近100レコード削除
START モード切り替え。user -> local_angle -> local -> user

作成するべきレコード数は5000から2万ぐらいだったり、10周分が目安らしい。
学習データは、~/mycar/data以下に保存される。
学習データの収集が終わったら、Ctrl+Cで停止。

Jetson Nano単独で学習

時間がかかるものの、Jetson Nanoならローカルでも学習できる。
5Wモードだと更に時間がかかるので、必要に応じてACアダプタ給電に切り替え、パワーモデルを変更したり、再起動を行う。

パワーモデルをMAXNに変更

(donkey) $ sudo nvpmodel -m 0

dataフォルダの全データを学習元データとして使うなら、次のコマンドを実行。
tubフォルダを指定する場合は、「--tub (tubフォルダ名。カンマ区切りで複数指定可能)」オプションで指定する。

(donkey) $ cd ~/mycar
(donkey) $ time python3 manage.py train --model ~/mycar/models/mypilot.h5

パワーモデルMAXN、レコード数6200で、学習するのに45分かかった。

学習した内容で走行する

学習したモデルを指定してプログラムを起動。

(donkey) $ cd ~/mycar
(donkey) $ python3 manage.py drive --model ./models/mypilot.h5

ブラウザで http://<IPアドレス>:8887 にアクセス

「Mode & Pilot」で「Local PilotLocal」を選択し、「Start Vhecle」を押すと自動走行がはじまる。
・・・はずなんだけど素直に動かない。
毎回、選択操作を2、3回繰り返すと動き出す。何かを待たないといけない?

リンク

Donkey Car ドキュメント

FaBo DonkeyCar Docs

JetBot をハード無改造で Donkey Car 化する

Waveshare Wiki: DonkeyCar for Jetson Nano-Setup Jetson Nano

公式: A Guide to using TensorRT on the Nvidia Jetson Nano
モデルを変換することで画像認識を高速化するらしい。自分の環境では変換はできたけど、うまく読み込めなかった。

JetPackバージョンとtensorflowバージョン別のtensorflowインストール方法

メモ

pythonの仮想環境を切り替える方法

仮想環境に入るコマンド

$ workon donkey

仮想環境から抜け出すコマンド

(donkey) $ deactivate
$