Jetson Nanoで自律走行ラジコンを作る(1)
「JetsonでFPVラジコンを作る」というシリーズ記事で、ラジコンにカメラを取り付けてbluetoothゲームパッドで操作できるようにした。
記事一覧はここ。
今回からは上記の環境をベースにラジコンの自律走行にチャレンジしようと思う。現状システム(LunchJetと命名した)は以下のような感じである。
JetsonではDeep Leaningのフレームワークが使用できるので、DNNベースのモデルを実行する。
具体的には、車載カメラの映像と人間が操縦している際の制御値(ステアリングの方向やアクセル強度)をそれぞれ入出力とした教師あり学習のモデルを作成するつもりでシステムを組組んでみるつもりである。
汎化性能次第ではあるが、まずはライントレーサーのラインなし版ができれば上出来といったレベルではないだろうかと思っている。何をするにしてもまずは訓練データが必要なので、カメラ映像と対応する制御値をJetson内に収集する機能から作り込んでいくことにする。
現時点のソフトウェア構成は以下。矢印はデータの流れを表している。
<<gst-element>>
はGStreamerのエレメントである。
現時点では、momoによりカメラ映像を配信する処理パスと、ゲームパッドの入力を元にモーターを制御するパスの2系統が存在する。
これを以下のようにする。
図が少しややこしくなったように見えるが、要は右下のRC car server
にカメラ映像を入力して、訓練データとしてラジコンの制御値と画像を所定のディレクトリに保存するようにしただけである。
めでたくモデルが出来上がって推論できるようになったら、ゲームパッドの代わりにモデルから出力された制御値をつかってラジコンを制御する。
これを実現するために、カメラ画像のストリーム上流(左側)にtee
を挿入し、カメラ映像のストリームを複製して解像度やフレームレートを落としつつRC car server
に映像を入力する。その上で、RC car server上に実装を加えれば訓練データをJetsonのSDカードに保存できる(はずである)。
なお、tee
の直後のqueue
は分枝した2つのストリームを別スレッドで並列に動作させるために必要となる(Gsteamerのチュートリアルを参照)。
上記のデータフローを実現するGStreamerのコマンドは以下となる。かなり長くなった。
$ /usr/bin/gst-launch-1.0 -v nvarguscamerasrc ! 'video/x-raw(memory:NVMM), format=NV12, width=1280, height=720, framerate=30/1' ! tee name=t ! queue ! nvvidconv flip-method=2 ! 'video/x-raw, format=I420, width=1280, height=720' ! identity drop-allocation=1 ! v4l2sink device=/dev/video1 t. ! queue ! nvvidconv flip-method=2 ! videorate ! 'video/x-raw, format=I420, width=360, height=240, framerate=15/1' ! identity drop-allocation=1 ! v4l2sink device=/dev/video2
tee
をtという名前付きエレメントとして定義し、このエレメントにqueueを2つ連結している。
RC car serverで/dev/video2
側のカメラ映像を取得・保存するのにOpenCVを使用した。
映像の取得にはcv::VideoCapture
クラス(ドキュメントはここ)を、各フレームの画像の保存にはcv::imwrite()
(ドキュメントはここ)を使用した。
LunchJetでは、映像の取得はこのあたり
に、保存はこのあたり
に実装がある。いずれも非常に簡単である。
訓練データとして、
- カメラ画像(JPEG)
- カメラ画像のファイル名と制御値のセット(テキストファイル)
をJetsonのSDカードに保存することにする。
テキストファイルは以下のような簡単なフォーマットにした。
/var/log/lunchjet$ cat control/annotations/2021_05_12_08_22_17_434.log 2021_05_12_08_22_17_434.jpg 1.5259e-05 0.135875
一番目がカメラ画像のファイル名で、二番目・三番目がそれぞれステアリングと駆動モーターの制御値である。 ちなみにカメラ画像はミリ秒単位で時刻をファイル名としている。
ということで、ラジコンを適当に走らせていれば秒間15枚ずつ訓練データが蓄積されていくようになった。訓練データを5000枚収集するとすると、5〜6分遊んでいればよい。
次回以降で、Google Colabを使ってこの訓練データをネタにぼちぼちモデルを作ってみる。
「JetsonでFPVラジコンを作る」記事一覧
Jetson NanoでFPVラジコンを作る(1) - 自由課題
Jetson NanoでFPVラジコンを作る(2) - 自由課題
Jetson NanoでFPVラジコンを作る(3) - 自由課題
Jetson NanoでFPVラジコンを作る(4) - 自由課題
Jetson NanoでFPVラジコンを作る(5) - 自由課題
Jetson NanoでFPVラジコンを作る(6) - 自由課題
Jetson NanoでFPVラジコンを作る(7) - 自由課題
Jetson NanoでFPVラジコンを作る(8) - 自由課題
Jetson NanoでFPVラジコンを作る(9) - 自由課題
Jetson NanoでFPVラジコンを作る(10) - 自由課題
Jetson NanoでFPVラジコンを作る(10)
この記事のコードは以下のGitリポジトリに公開している。(リポジトリ名変更済)
前回まででJetsonへの入出力に関するコードは書いたので、あとはこのように結合するだけである。
class RCCarServer : public RCCarControllerListener { public: RCCarServer() : controller("/dev/input/event2", *this, stop_controller_thread){} ~RCCarServer() = default; int start() { return controller.listen(); } void on_connect() override { std::cout << "controller connected" << std::endl; }; void on_change_steering(float value) override { driver.steer(value); } void on_change_accel(float value) override { if(is_going_back) { driver.back(value * 0.3f); } else { driver.go(value * 0.2f); } } void on_change_back(int value) override { if(value) { is_going_back = true; } else { is_going_back = false; } } void on_close() override { std::cout << "controller disconnected" << std::endl; } private: RCCarDriver driver; RCCarController controller; bool is_going_back = false; };
のようなクラスを作って以下のように使用するだけ。
int main(int argc, const char *argv[]) { //snip RCCarServer server; std::cout << "starting server..." << std::endl; server.start(); std::cout << "server started" << std::endl; pause(); std::cout << "terminating server..." << std::endl; return 0; }
ちなみにコントローラの入力値をそのまま駆動モーターに伝えてしまうと速度が出過ぎるので、ゲインは適当に下げてある。
ということでめでたくタミヤのラジコンがFPVで操作できるようになった。
のだが、実際に操作してみるとやはり画角が狭い。(下の動画は以前撮影したもの)
実はmomoで以下の広角ラズパイカメラを認識しなかったため、今までは仕方なくWebカメラを使用していた。
改めて調べてみたところ以下のツイートが見つかった。
mobile.twitter.comjetsonでCSIカメラをmomoで使うには、
— alarky (@alarky1) May 6, 2020
gstreamerでRG10→NV12→I420に変換して仮想L4V2デバイスに流しながら、
momoでは仮想側のデバイスを読めば使えた
情報を辿っていくとやり方がここに書いてあった。
ラズパイカメラはRG10というピクセルフォーマットを使用しているが、momoはこのピクセルフォーマットに対応していない。
そこでv4l2loopbackというkernel moduleを使用して仮想ビデオデバイスを作成し、このデバイスにRG10から変換したI420の映像を流し込むことでmomoが認識するらしい。
まず、ラズパイカメラをラジコンに取り付ける。
以前使用した1.7mmの透明プラバンと、東急ハンズで調達したL字のアクリル棒を加工してカメラ固定用のプレートを作成し、このプレート経由でカメラをベースプレートに固定した。車体にきちんと収めるための位置決めと、カメラ固定用プレートにフレキケーブルを通すための長方形の穴を加工するのに難儀した。
ボディを取り付けてもきちんと収まっている。
次にソフトウェア側の作業をする。
まずv4l2loopbackのビルド・インストール・有効化を行う。
$ git clone https://github.com/umlaeute/v4l2loopback.git $ cd v4l2loopback $ make && sudo make install $ sudo depmod -a $ sudo modprobe v4l2loopback exclusive_caps=1
これでまず仮想ビデオデバイスが見えるようになる。
$ ls /dev/video* /dev/video0 /dev/video1
video0
というのがラズパイカメラで、v4l2loopbackによりvideo1
という仮想ビデオデバイスが追加されている。
ここで上記の情報の通りgstreamerのコマンドを実行し、ラズパイカメラの映像をRG10->I420に変換し、/dev/video1
に流し込む*1。ついでに解像度を落として画面を180°回転*2させている。
$ sudo /usr/bin/gst-launch-1.0 -v nvarguscamerasrc ! 'video/x-raw(memory:NVMM), format=NV12, width=1280, height=720, framerate=30/1' ! nvvidconv flip-method=2 ! identity drop-allocation=1 ! 'video/x-raw, width=1280, height=720, format=I420, framerate=30/1' ! v4l2sink device=/dev/video1
こうすると、/dev/video1
がI420(YU12)の入力デバイスとして見えるようになる。
$ v4l2-ctl -d 1 --all Driver Info (not using libv4l2): Driver name : v4l2 loopback Card type : Dummy video device (0x0000) Bus info : platform:v4l2loopback-000 Driver version: 4.9.201 Capabilities : 0x85208003 Video Capture Video Output Video Memory-to-Memory Read/Write Streaming Extended Pix Format Device Capabilities Device Caps : 0x05208003 Video Capture Video Output Video Memory-to-Memory Read/Write Streaming Extended Pix Format Priority: 2 Video input : 0 (loopback: ok) Video output: 0 (loopback in) Format Video Capture: Width/Height : 1280/720 Pixel Format : 'YU12' Field : None Bytes per Line : 1280 Size Image : 1382400 Colorspace : sRGB Transfer Function : sRGB YCbCr/HSV Encoding: ITU-R 601 Quantization : Limited Range Flags : Format Video Output: Width/Height : 1280/720 Pixel Format : 'YU12' Field : None Bytes per Line : 1280 Size Image : 1382400 Colorspace : sRGB Transfer Function : sRGB YCbCr/HSV Encoding: ITU-R 601 Quantization : Limited Range Flags : Streaming Parameters Video Capture: Frames per second: 30.000 (30/1) Read buffers : 2 Streaming Parameters Video Output: Frames per second: 30.000 (30/1) Write buffers : 2 User Controls keep_format 0x0098f900 (bool) : default=0 value=0 sustain_framerate 0x0098f901 (bool) : default=0 value=0 timeout 0x0098f902 (int) : min=0 max=100000 step=1 default=0 value=0 timeout_image_io 0x0098f903 (bool) : default=0 value=0
ここでmomoを以下のパラメーターで起動すると、めでたくmomo経由で映像が見えるようになる。
$ sudo /opt/lunchjet/momo/momo --hw-mjpeg-decoder=false --video-device /dev/video1 --no-audio-device test
ハマったのは--hw-mjpeg-decoder=false
という部分で、これがないとエラーでmomoが立ち上がらない。
原因がわからずに結局momoのソースまで確認したのだが、最近のmomoのJetson用ビルドではデフォルトで--hw-mjpeg-decoder=true
となっており、この場合には対応ピクセルフォーマットがJPEGかMJPEGのみとなるようだ。
これでめでたくシステムの全機能が開通した。 システム構成は以下になった。
ゲームパッドでラジコンを操作している様子は以下。
少しアクセルの遊びが大きい気もするが、特に問題はない。
実際に走行させてみた。
しばらく運転して遊んでみたが、やはり視野が広い(視野角160°)ので運転しやすい。
レーサータイプのラジコンに比べおそらく視点がだいぶ高いせいか、思ったよりスピード感はない気もした。
というわけで、「Jetson NanoでFPVラジコンを作る」シリーズはこれでおしまい。
次回以降は改題して自動運転にぼちぼち取り組むつもりである。
前回までの記事
Jetson NanoでFPVラジコンを作る(1) - 自由課題
Jetson NanoでFPVラジコンを作る(2) - 自由課題
Jetson NanoでFPVラジコンを作る(3) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(4) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(5) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(6) - 自由課題
Jetson Nanoを使ってFPVラジコンを作る(7) - 自由課題
Jetson NanoでFPVラジコンを作る(8) - 自由課題
Jetson NanoでFPVラジコンを作る(9) - 自由課題
Jetson NanoでFPVラジコンを作る(9)
前回記事を書いたあとステアリングモーターと駆動モーターを制御するライブラリを書いた。
RCCar::steer(float)
とRCCar::go(float)
を使ってこんな感じで制御できる。
両関数とも引数の値域は0.0〜1.0である。
int main(int argc, const char *argv[]) { RCCar car; //swing the steering for(float s = 0.f; s <= 3.f; s+=0.05f) { car.steer(std::sin(2*M_PI*s)); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } //stop and go for(float s = 0.f; s <= 2.f; s+=0.01f) { auto speed = 0.2/*max speed cap*/ * ((-std::cos(2*M_PI*s)+1)/2); car.go(speed); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } return 0; }
今回はゲームパッドの入力を受け取るためのライブラリを書く。
現在使用しているゲームパッドは以下。
Jetson NanoでFPVラジコンを作る(8)
相変わらずタミヤのランチボックスをJetson Nanoで制御しようとしている。
1/12 XBシリーズ No.49 XB ランチボックス 2.4GHz プロポ付き 塗装済み完成品 57749
- 発売日: 2012/04/07
- メディア: おもちゃ&ホビー
今のところシステム構成は以下。
前回まででハードの基本的な動作確認が終わったので、今回から本格的にコーディングを行う。
(あまりよく見ていないが)JetRacerやDonkyCarはPythonっぽいが、今回はC++を使うことにする(f:id:kimito_k:20210403113108p:plain)。
まだまともに何か動くレベルではないが*1、ぼちぼちgithubにソースコードをpushしている。
今日はステアリングモーターと駆動モーターを制御するためのI2C通信の基本部分をコーディングする。
続きを読むJetson NanoでFPVラジコンを作る(6)
前回はJetson Nano単体でラジコンを制御するのに失敗したので、今回は PCA9685というモータードライバーのチップを使用して制御を試みる。
実績はたくさんあるので、できないということはないはずだ。
Jetson NanoでFPVラジコンを作る(5)
今回はいよいよラジコンのステアリングサーボとモーターを制御してみる。 JetsonにはハードウェアPWMが2系統あるのでそれを使って制御できないか試してみる。
結論からいうと上手くいかなかったが、どうも同じことを試した人はほとんどいなかったようなので一応以下記録として残しておく。
(詳しい人であればそもそもこんな問題は踏まないのかもしれないが)