これは ドリコム Advent Calendar 2018 の8日目です。
7日目は y-hirowatari-dcm さんによる、「ゲームジャムをやってみました」です。

年末ですね、みなさんいかがお過ごしですか。自分は最近 Googleマップでランダムに降り立った地がどこかを当てるゲーム をして過ごしています。

この記事は数年前から大きく注目され始めたDeep Learningをもっと手軽に感じられたらいいなということを目的に書きました。誰もが簡単に手に入れられる部品でロボットを組み、そこにニューラルネットワークを搭載し自動運転的なものをやっていきます。

今回の手法はDeep Learningのデモ的なもので実際車で行われている自動運転の手法を説明するものではありません。自動運転のプロの方は温かく見守っていて下さい。m(_ _)m

今回の手法


今回はニューラルネットワーク(NN)を用いてカメラを積んだ車両ロボットに道の画像とハンドル操作を学習させて自動で走れるようにします。

この手法は1990年のPomerleauによるものであり、Coursera の有名な機械学習講義 Machine Learning でも取り上げられています。

https://www.youtube.com/embed/ilP4aPDTBPE
Pomerleauによる自動運転実験動画

買ってきたパーツ

次のパーツを用意しました。

・Raspberry Pi ModelB (Amazon)
・カメラ (Amazon)
・ステッピングモータ (Amazon)
・シャフト用ハブ (秋月電子web注文)
・ミニ四駆タイヤ (Amazon)
・ブレッドボード、ジャンパワイヤ (Amazon)
・ユニバーサルプレート (Amazon)
・モバイルバッテリー (Amazon)
・電池 (Amazon Basic)

一歩も家を出ずに全てのパーツを揃えることができました。


Raspberry Piとは簡単に言えば小型PCです。今回はRaspbianというDebianベースのLinuxを入れて使用します。本体横にはピンがたくさんついていてセンサを繋げられるようになっています。今回はUSBカメラと無線ネットワークが使いたかったのでRaspberry Piを採用しました。


カメラは魚眼で視野が広いものを買いました。固定するマウンタは適当にタミヤのユニバーサルプレートで作ります。


ステッピングモータはミニ四駆で使われているようなDCモータと比べて角度を自由に決めて動かしやすい、一定速度で動かしやすいという特徴があり、プリンタにも使用されています。欠点は速度とトルクが出づらいことです。(中身が気になる方はこちらが参考になります→ステッピングモーター(28BYJ-48)を分解して仕組みを調べてみた)


モバイルバッテリーはRaspberry Piの電源として使用します。容量を喰うので2A出力できるものが必要なようです。電池はモータ電源になります。モータには瞬間的に大電流が流れるためのでセンサやRaspberry Piと同じ電源にするとノイズとなってしまうため電源を分けます。

組立


センサはジャンパワイヤをブレッドボードに雑に刺して接続しました。機体はユニバーサルプレートで組み立てました。一度も半田ごてを使用せずに組み上げることができました。

まずはラジコンで走らせる


コースはパワポで適当に作りました。コースが劣化してもすぐに印刷して新しいものに取り換えられるため安定したデータを取ることができます。


Raspberry PiとPCをTCP通信させラジコンのように操作できるようにし動作チェックを行います。

実際に動かしてみると思っていた10倍くらいモータが遅かったです。配線テストの時に気づいてはいたのですが、タイヤが1回転するのに約4秒かかりとてもゆっくり走る車になってしまいました。調べてみるとこのモータの遅さに驚いている人が何人か見つかりました。(Pythonの実行速度のせいではなかった)

さらにトルクも小さく、電池4本(4.8V)では指で少しつまめば止まり機体を走らせられませんでした。電池8本で2相励磁にすれば、速さは出ないですがそこそこいい感じに動いたのでこのままこれを用いることにしました。

データの収集

データの収集を行います。人がラジコンで運転しながらロボットのカメラ画像を取得します。カメラの画像を保存する際にコントローラーのどのボタンが押されていたのか(正解ラベル)も一緒に記録します。

今回は「まっすぐ、右、左、もっと右、もっと左」の5種類の方向に進むようにしました。

正しく走行しているときと道を逸れたとき

ここでデータを集める時のテクニックを1つ使用します。カメラ1つで画像を集めた場合、正しい道のりの対処方法しかわかりません。つまり、もし少しでも道が逸れた場合、正しい道の走り方しか知らないロボットはどうすることもできません。これを防ぐにはデータを集めるときにわざと道を外し、そこから戻るときのデータを集めれば解決はできるのですがスマートではありません。

カメラを2つ取り付けたとき
左、中央、右のカメラの見え方と直進時に振られるラベル

そこでカメラを図のように2台、左右に少し傾けて取り付けます。すると正しく道を進んでいる場合、右のカメラには道を右に逸れたときの画像、左のカメラには左に逸れたときの画像が映るとみなすことができます。よって仮に3フレーム分のラジコンからの制御が「前,右,左」だった場合、右カメラの画像のラベルを「左,前,もっと左」、左カメラの画像のラベルを「右,もっと右,前」とすることで道を逸れた時のデータを集めることができます。

Giustiらはドローンの自動運転でこの手法を使用しています。カメラは中央、右、左の3つを使用して、正しいデータと同時に道から逸れたデータを集めています。

https://www.youtube.com/embed/umRdt3zGgpU
Giustiらによる複数カメラでのデータ収集

Giustiらの論文ではドローンではなく人が歩いてデータを集めています。参考文献を見ると1番目にGoogleマップが入っています。

NNの選定

データが集まったのでNNをセットアップしていきます。入力は画像、出力は「直進、右、左、もっと右、もっと左」の5つです。

5つの出力がそれぞれ0~1になるようにし、これをその操作を選ぶ確率とみなします。つまり入力された画像からそれぞれの方向に進む確率が予測され、最も大きいものを選択することで自動運転が行われます。

今回の程度の分類であれば実際のところ単純な多層パーセプトロンでもある程度外れずに運転することができます。しかしコース以外のオブジェクトもカメラに映ることやコースに反射する光の影響によりコースを設置する環境が異なると失敗しやすくなります。そのため画像の特徴をより捉えられる畳み込みニューラルネットワーク(CNN)を使います。(そのほうがかっこいいので)

Raspberry Piで1秒間に10回は推論を行いたいため全体として軽いNNでなくてはなりません。自分で構築してチューニングするのもいいですが、たいてい自分で作ったオレオレNNはうまくいかないので素直に他の人の案を採用させていただきます。

(Loquercio+, 2017)

今回はDroNet(Loquercio+, 2017)を使用します。こちらもドローンの自動運転に関する論文です。CNNにResNetが使用されていて、遠隔のPCでNN計算するのではなくドローンに載ったCPUで直接計算できるように設計されています。これをRaspberry Piでも動くように調整します。

https://www.youtube.com/embed/ow7aw9H4BcA
DroNetで自動運転している動画
入力画像の加工
今回使用するNN

カメラからは 640x480x3 のRGB画像が得られるのでこれをクロップリサイズして 200×200 のモノクロ画像に変換しました。さらに一部のCNNのパラメータを1/2にしてこれで1秒に10回程度推論が行えるようになりました。ちなみにRaspberry PiではVGG19という有名なCNNのエンコードに約2秒かかります。(Raspberry PiのGPUを利用できるともっと速い)

実装と学習

真ん中のカメラの画像を5,000枚、左右のカメラの画像を2,500枚ずつ収集しました。
Raspberry Pi上のプログラムはPython2.7、NNは TensorFlow 0.12.0 で実装しました。
学習は CPU:Core i7-5960X、GPU:NVIDIA TITAN X で1分程度でした。
Python2.7の完璧な保守はできないので現在は大人しくPython3を使用しています。

走った


最初うまく走らなかったのですが画像のピクセル値0~255を0~1.0に変換していませんでした。それを直すとすぐにコースを自動で走行しました(感動)。学習時とは異なる形にコースを組み替えても走ることができました。

道を逸れた画像を混ぜて学習したほうが道を外れづらくGiustiらの手法は有効でした。画像の一部の領域を消したり(Random Erasing, Cutout)、画像をゆがませたりすることでデータを拡張してノイズに強くする方法などを行うともっとしっかり走ると思います。

以上でRaspberry Piを使用して自動運転をすることができました。今回のコースは中央の黒線やコースの境目など特徴的な箇所が多かったため簡単すぎたかもしれません。今度は部屋の中や屋外にも挑戦してみたいと思います。最後まで読んでいただきありがとうございました。

あとがき

Andrew先生の動画が出たときにこの自動運転を自分でもやってみたいと思い最初はUnityでシミュレーションを行いました。Unity上のフィールドで車を操作してデータを集めMatLabで学習させ、できたモデルをUnityで自動運転、そこそこうまくいったので今度はARM上のJavaでNNを実装しArduinoのロボットでデータを集めて自動運転、といろいろ大変だったのですが、今回はRaspberry PiやPython、Tensorflowのおかげですごく簡単にできてしまい自分でもかなり驚きました。作成したプログラムが画面の中だけでなくロボットなどを通して実際に動いているのを見るのはやはり楽しいですね。今回使用した技術に関わった全ての人、本稿をレビューしていただいた方々に感謝します。


・参考

  • Pomerleau D.A. (1990) Neural Network Based Autonomous Navigation. In: Thorpe C.E. (eds) Vision and Navigation. The Kluwer International Series in Engineering and Computer Science (Robotics:Vision, Manipulation and Sensors), vol 93. Springer, Boston, MA
  • A. Giusti et al., “A Machine Learning Approach to Visual Perception of Forest Trails for Mobile Robots,” in IEEE Robotics and Automation Letters, vol. 1, no. 2, pp. 661-667, July 2016.
  • Karen Simonyan and Andrew Zisserman. Very deep convolutional networks for large-scale image recognition. arXiv preprint arXiv:1409.1556, 2014.
  • K. He, X. Zhang, S. Ren and J. Sun, “Deep Residual Learning for Image Recognition,” 2016 IEEE Conference on Computer Vision and Pattern Recognition (CVPR), Las Vegas, NV, 2016, pp. 770-778.
  • Loquercio et al. DroNet: Learning to Fly by Driving. 2017
  • Zhun Zhong el al. Random Erasing Data Augmentation. 2017
  • Terrance DeVries, Graham W. Taylor. Improved Regularization of Convolutional Neural Networks with Cutout. 2017
  • Coursera:https://www.coursera.org
  • Tensorflow:https://www.tensorflow.org
  • Raspberry Pi:https://www.raspberrypi.org