自由課題

学んだり、考えたり、試したりしたこと。

Jetson NanoでFPVラジコンを作る(10)

前回までの記事

この記事のコードは以下のGitリポジトリに公開している。(リポジトリ名変更済)

github.com

前回までで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.com

情報を辿っていくとやり方がここに書いてあった。
ラズパイカメラはRG10というピクセルフォーマットを使用しているが、momoはこのピクセルフォーマットに対応していない。

そこでv4l2loopbackというkernel moduleを使用して仮想ビデオデバイスを作成し、このデバイスにRG10から変換したI420の映像を流し込むことでmomoが認識するらしい。

まず、ラズパイカメラをラジコンに取り付ける。
以前使用した1.7mmの透明プラバンと、東急ハンズで調達したL字のアクリル棒を加工してカメラ固定用のプレートを作成し、このプレート経由でカメラをベースプレートに固定した。車体にきちんと収めるための位置決めと、カメラ固定用プレートにフレキケーブルを通すための長方形の穴を加工するのに難儀した。

f:id:kimito_k:20210418154959j:plain

ボディを取り付けてもきちんと収まっている。

f:id:kimito_k:20210419081403j:plain

次にソフトウェア側の作業をする。
まず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のみとなるようだ。

これでめでたくシステムの全機能が開通した。 システム構成は以下になった。

f:id:kimito_k:20210420072705p:plain

ゲームパッドでラジコンを操作している様子は以下。
少しアクセルの遊びが大きい気もするが、特に問題はない。

実際に走行させてみた。

しばらく運転して遊んでみたが、やはり視野が広い(視野角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) - 自由課題

*1:オリジナルの記事のコマンドはもっと長いが、冗長に思える部分があったので改変して短くしている。ただ、性能としてはほとんど変わらないようだ

*2:Jetsonだとフレキケーブルのコネクタが上に来るようにカメラを配置すべきだったらしい。ラズパイとは上下逆?