本連載では「Jetson Nano」を使ったAI自律走行車「JetBot」(公式Wiki)について紹介しています。第4回となる今回は前回に引き続き、実際にJetBotを走らせるまでの工程を紹介します。前回は衝突回避走行でしたが、今回はライントレーサとして走行させます。
ライントレーサとして走るまで
ライントレーサとは床に引かれた線に沿って走行するロボットのことです。JetBotがライントレーサとして走るためには、カメラの画像からラインを検出し進行方向を算出する必要があります。ライントレースモデルを作成し、走行するまでの工程は以下の通りとなります。
- ソフトウェアのセットアップ
- 教師データの収集
- ライントレースモデルの学習
- 学習モデルによるライントレース
- 衝突回避走行との組み合わせ
なお本記事の参照元は、公式Wikiのnotebooks/road_followingです。公式ではライントレーサではなく、Road Following(道なり走行)と呼称しています。
1. ソフトウェアのセットアップ(再掲)
まずJetBot上のJetsonNanoを起動させるために、公式WikiのSoftware Setupに従ってセットアップしてください。
セットアップの注意点
JetBotのOSイメージは通常版が64GBを超えているため128GB以上のmicroSDカードが必要になります。(64GBのmicroSDカードでも収まる63GBの縮小版イメージもありますが、後述の教師データの収集等を考えると、大きなサイズのmicroSDカードを準備することをお勧めします)
2. 教師データの収集
ライントレースモデルは、カメラの画像からJetBotの進路をXY軸の2次元座標で算出します。第3回で紹介した衝突回避走行モデルは画像を2値分類する分類モデルでしたが、今回は連続値を取る回帰モデルとなります。教師データも画像1枚につき、進路座標を示すXY座標の連続値を付与して集めます。このXY座標からJetBotがどれくらい曲がるか、どれくらい進むかを制御します。
2-1. 作業の事前確認
教師データはJetBot上で、road_following/data_collection.ipynbを実行して収集します。サンプルの冒頭では、実際に教師データを収集している様子を収めた動画へのリンクが記載されていますので、確認しておくと作業がイメージしやすいと思います。
次にサンプルを上から順に実行し、準備を進めます。
2-2. カメラの初期化と表示
JetBotのカメラから画像を取得し、Jupyter上で表示します。カメラ映像の下部に配置されたスライダーはそれぞれ進路のX座標(steering)とY座標(throttle)を変更するためのものです。スライダーを調整することで、右のカメラ映像に表示されたポインタが動き、動作を確認できます。
2-3. ゲームパッドコントローラの取得
教師データの収集には、ゲームパッドコントローラを使用します(XBOX ONE コントローラを使用)。サンプルの4セル目でコントローラのインスタンスを作成し、コントローラのステータスを表示します。なおコードの実行後、必ずコントローラを操作してください。操作しない限りコントローラの値が格納されないため、ステータスが表示されず、以降の処理もエラーになります。
2-4. 教師データ収集開始
サンプルの6セル目を実行すると、教師データを収集できるようになります。接続したゲームパッドコントローラの右スティックでカメラ上に表示されたポインタを進路上に動かし、十字キーの下を押して画像を保存します。(ラベルのXY座標は画像ファイル名として保存します)
- ファイルの命名関数の誤り
2019年6月10日時点で、イシューにも上がっていますがファイル名をつける箇所に一部間違いがあります。
def xy_uuid(x, y): return 'xy_%03d_%03d_%s' % (x * 50 + 50, y * 50 + 50, uuid1()) def save_steering(change): if change['new']: uuid = steering_throttle_uuid(steering_slider.value,throttle_slider.value) image_path = os.path.join(DATASET_DIR, uuid + '.jpg') with open(image_path, 'wb') as f: f.write(image_widget.value) count_widget.value = len(glob.glob(os.path.join(DATASET_DIR, '*.jpg')))
uuid を取得する関数がsteering_throttle_uuid
になっていますが、該当の関数は存在しないため、xy_uuid()
に変更します。
uuid = xy_uuid(steering_slider.value,throttle_slider.value)
2-5. JetBotが走行するコース
「2-1. 作業の事前確認」で確認した公式の動画では、レゴブロックの道路プレートで教師データを集めています。しかし今回は床に貼った黒テープをコースにして教師データを集め、黒のラインを走行させます。
2-6. 教師データの収集方針
今回は下記のような方針で教師データを集めます。結果を先に述べるとうまくライン上を走行しましたが、今回の方針が良いかどうかは、今後検証する必要があります。
画像は123枚以上収集する(公式が123枚の画像で学習したと述べているため)
直進時、曲折時の画像数は同等にする
進路座標のうちY座標は直進時、曲折時どちらもなるべく画像の中央付近を選ぶ
曲折時の画像は、ライン上にいる時、ラインから左右に逸れた時の3種類の画像を収集する
- ライン上にいる時
- ラインから左右にずれている時
- ライン上にいる時
3. ライントレースモデルの学習
学習は第3回の衝突回避走行編とほぼ同じ手順となります。
3-1. 学習環境のセットアップ
モデルの学習はJetBot上でも行えますが、学習時間を短縮するために別のGPU搭載PC(学習用PC)で実行します。そこで下記に示す学習環境に必要なものをインストールします。また、JetBotのリポジトリからスクリプト一式をクローン、またはダウンロードしておきます。
- CUDA
- cuDNN
- Python
- Jupyter Lab
- Pytorch
3-2. 教師データの移動
教師データをJetBotから学習用PCに移します。教師データはサンプルと同じくスクリプトと同じ階層(jetbot/notebooks/roadfollowing/)の「datasetxy」フォルダに格納していますので、フォルダごと学習用PC側の同じ階層に移動します。
3-3. モデルの学習
学習用PCでJupyter Labのサーバを立ち上げ、road_following/train_model.ipynbを実行します。
学習はResNet18の転移学習となります。エポック数はデフォルトで70回です。今回はデフォルト設定で学習し、およそ5分~10分ほど掛かりました。
4. 学習モデルによるライントレース
4-1. ライントレーススクリプトの実行
road_following/live_demo.ipynbを実行します。まず7番目のコードセルで車体のスピード等のパラメータを調整します。下図のように、スライダが4つ表示されます。初期値が設定されていますが、モデルによってはうまくライン上を走行しないため、スライダで調節します。
8番目のコードセルを実行すると、走行中のJetBotのステータスを表示します。カメラの画像から算出されたXY座標と、それぞれの軸における速度が確認できます。
10番目のコードセルにてcamera.observe()
でカメラを有効にし、ライントレースを開始します。11番目のコードセルは停止命令になります。camera.unobserve()
はカメラを無効にし、robot.stop()
でJetBotを停止します。
4-2. ライントレースモデルの性能
前節で載せた動画では黒いラインの上をうまく走りましたが、下記のように性能はまだ改良の余地があると考えます。
- 直進時、ふらふらと左右に振れながら走る場合がある
- 交差路に差し掛かると直進するか曲折するか明確に決めていなかったためふらつく
- 視野が狭く、大きく道を逸れる、ラインから離れた場所からスタートする等の場合にコースへ復帰しにくい
下の動画はコースアウトからの復帰を撮影した動画です。現在のモデルではうまく復帰できた方ですが、視野が狭いからか、ぐるぐる旋回し続けないとラインを認識できません。
さいごに
今回はJetBotをライントレーサとして走らせるまでを紹介しました。前回の衝突回避走行と比較すると、教師データの収集において考えるべきことが多く、まだ性能を出すための方針が見えてこないと感じました。今後はモデルの性能向上と、GTCで行われたデモのように衝突回避とライントレーサを組み合わせた走行を試したいと考えています。