【RaspberryPi】Node-Redでカメラを使用する(mjpg_streaming)

前回のエントリではNode-REDを使用したカメラの使用を行っていましたが、その中で気になっていたものが残っていたのですが、 ちょっと試してみるとハマったのでそれに関してメモっておこうと思います。

気になった拡張ノードは以下のものです。

flows.nodered.org

これを使用するとカメラ画像をストリーミング的に使用できそう?

A node that consumes a mjpeg stream and outputs the latest saved frame as buffer

ドキュメントを見ると、mpeg_streamを使用してカメラ撮影した最新のバッファを取得するという機能を持つもののようです。 ページをパッと見た感じではインストールせよとははっきりとはわからないのですが、mjpg_streamというアプリケーションが必要のようです。 もう少しはっきりと書いてほしいかも。

事前準備

ドキュメントにもgithubのURLがあるのでこちらを見ながらインストールをします。aptコマンドではインストールはできないのでちょっと手間がかかります。

github.com

githubのドキュメントに従って行けば概ねインストールには引っかからない感じです。

$ cd ~
$ sudo apt update 
$ sudo apt install -y cmake libv4l-dev libjpeg-dev imagemagick
$ git clone https://github.com/jacksonliam/mjpg-streamer.git
$ cd mjpg-streamer/mjpg-streamer-experimental
$ make
$ sudo make install

ネットで検索すると上記の手順の最後にあるsudo make installを行わず、ローカルディレクトリで実行しているところが多かったのですが、私はインストールまで行っています。

インストールが終わったら試しに実行してみます。 mjpg_streamerは入力方法と出力方法のそれぞれをオプションの指定することによって実行する仕組なのでそれぞれ指定します。

$ mjpg_streamer -o "output_http.so -w /usr/local/share/mjpg-stream/www" -i "input_raspicam.so"

以下の実行例では

  • 入力:RaspberryPiのカメラモジュール(パラメータの類はデフォルト設定)
  • 出力:httpプロトコル

(注記)/usr/local/share/mjpg-stream/wwwはテスト用の静的コンテンツの入ったパスになります。インストールするとローカルからこのパスにコピーされます。

となっています。実行するとエラーが発生しなければ以下のような設定一覧が表示された画面が出力され、起動します。

f:id:ueponx:20191009085231p:plain

ここまできたら、キャプチャがうまく行っているか確認してみます。ブラウザでhttp://raspberrypi.local:8080/にアクセスするとWebでの実行画面が表示されます。 (注)mDNSでのアクセスが出来ていない場合にはhttp://【RaspberryPiのIPアドレス】:8080/でアクセスをしてください。

f:id:ueponx:20191009085948p:plain

これでmjpg_streamのインストールは完了です。 インストールの確認が終わったら、コンソール上で起動したmjpg_streamはCtrl+Cで終了させておきます。

拡張ノード(node-red-contrib-mjpgcamera)をインストール

先程作業で、事前準備が終わったので拡張ノードをインストールします。 これまでの拡張ノード同様に【三】をクリックし、メニューから【パレットの管理】を選択します。

f:id:ueponx:20191009091819p:plain

【ユーザー設定】で【ノード追加】タブをクリックし、検索ボックスにmjpgを入力すると該当の拡張ノードが表示されます。 名前を確認して【ノードを追加】ボタンをクリックします。確認ダイアログでも【追加】ボタンをクリックしてください。●

f:id:ueponx:20191009092659p:plain

インストールが完了するとelectronicplaygroundのカテゴリーに拡張ノードが格納されます。

f:id:ueponx:20191009092954p:plain

名称はmjpg consumerとなっています。

実際に使用してみる

今回は、ノードをワークスペースにドロップせず、以下のテキスト(JSON形式ですが)をクリップボードからペーストしてみます。テキストで保存できるので gitなんかとも親和性ありそうですね。今後調べてみたいです。

[{"id":"5b126518.b3359c","type":"tab","label":"mjpg_streming","disabled":false,"info":""},{"id":"2e754e61.560a52","type":"debug","z":"5b126518.b3359c","name":"","active":true,"console":"false","complete":"false","x":550,"y":60,"wires":[]},{"id":"343fc213.65c74e","type":"file","z":"5b126518.b3359c","name":"Camera Capture","filename":"capture.jpg","appendNewline":false,"createDir":false,"overwriteFile":"true","x":560,"y":140,"wires":[[]]},{"id":"81850657.0c4598","type":"mjpg-consumer","z":"5b126518.b3359c","name":"MjpgConsumer","stream":"http://raspberrypi.local:8080/?action=stream","interval":"5","x":360,"y":100,"wires":[["2e754e61.560a52","343fc213.65c74e"]]},{"id":"1307914e.5c5d0f","type":"inject","z":"5b126518.b3359c","name":"Stop Consumer","topic":"","payload":"stop","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":160,"y":140,"wires":[["81850657.0c4598"]]},{"id":"d30efe8.65f4f","type":"inject","z":"5b126518.b3359c","name":"Start Consumer","topic":"","payload":"start","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":"","x":160,"y":60,"wires":[["81850657.0c4598"]]}]

ペーストの作業は、画面右上の【三】をクリックし、メニューから【読み込み】→【クリップボード】を選択してクリックを行います。●

f:id:ueponx:20191009094603p:plain

【フローをクリップボードから読み込み】というダイアログが開くので、先ほどのJSON形式のテキストをコピー&ペースト、【読み込み先】を新規のタブに変更、【読み込み】ボタンをクリックします。

f:id:ueponx:20191009094914p:plain

すると新しいフローが生成されて以下のようなフローが表示されます。

f:id:ueponx:20191009095009p:plain

拡大するとこんな感じです。(あんまり拡大になってませんが、文字は読みやすくなったと思います)

f:id:ueponx:20191009095034p:plain

各ノードはこんな感じの設定になっています。

Start Consumerのinjectノードは【ペイロード】にstartを設定しています。

f:id:ueponx:20191009095526p:plain

Stop Consumerのinjectノードは【ペイロード】にstopを設定しています。

f:id:ueponx:20191009095547p:plain

MjpgConsumerのmjpg-consumerノードは【Stream】にhttp://raspberrypi.local:8080/?action=stream、【Streaming Interval】は5(秒)を設定してます。mjpg_streamは起動して、http://raspberrypi.local:8080/?action=streamにアクセスするとカメラで撮影した画像が表示され、startを送ると撮影開始し、stopを送ると撮影を停止する機能を持っています。

f:id:ueponx:20191009095451p:plain

debugノードは特に変更はありません。

f:id:ueponx:20191009095643p:plain

Camera Capturefileのfileードは【ファイル名】をcaputure.jpgに設定しています。

f:id:ueponx:20191009095612p:plain

とりあえず、そのまま【デプロイ】ボタンを押して実行してみます。(スタートを指定しなくても5秒間隔で動作します) そして、デバッグのタブを覗いてみると…

f:id:ueponx:20191009095234p:plain

undefined?うまく動作してないようです。なぜだ

うまくいかない?

ググってもなかなか情報が得られないのでいろいろ試したら、割とあっさり理由がわかりました。 Node-REDからこの拡張ノードを使用した場合でもmjpg_streamを起動してはくれないようです…原因は簡単でしたが、そこまで至るのに時間かかり過ぎた。

ということで、この拡張ノードを使用する際は起動時にサービスとしてmjpg_streamを起動しておくか、事前に起動する必要があります。 自分は面倒なので、起動スクリプトと終了スクリプトを書いて、事前に起動することにしました。(RaspberryPi2では結構負荷が高いので) スクリプトには実行権限をつけておきましょう。

起動時の実行に関してはrc.localに入れる方法やSupervisorを使用もあると思いますが、からあげさんのエントリーが参考になると思います。

qiita.com

【起動スクリプト

#!/bin/sh

# mjpg-streamer start script
export LD_LIBRARY_PATH=/usr/local/lib/mjpg_streamer
STREAMER="/usr/local/bin/mjpg_streamer"
 
# Pi camera configurations
XRES="640"
YRES="480"
FPS="10"

# Web configurations
WWWDOC="/usr/local/share/mjpg-streamer/www"
PORT="8080"

# Start streaming
$STREAMER \
-i "input_raspicam.so -x $XRES -y $YRES -fps $FPS" \
-o "output_http.so -w $WWWDOC -p $PORT"

【停止スクリプト

#!/bin/bash

if pgrep mjpg_streamer
then
    kill $(pgrep mjpg_streamer) > /dev/null 2>&1
    echo "mjpg_streamer stopped"
else
    echo "mjpg_streamer not running"
fi

起動するタイミングは事前でも、Node-RED作業後でもよいので、起動スクリプトを実行しておけば動作します。 Node-REDからフローを実行するとカレントパスにcapture.jpgが保存されます。dubugノードからの表示はデータ配列の形なので以下の画面のような表示になります。

f:id:ueponx:20191009230747p:plain

今回はpiユーザのhomeディレクトリで実行していたので以下のようになります。

f:id:ueponx:20191009230837p:plain

もし、ターミナルからの都度のmjpg_streamが面倒であれば、Node-REDのexecノードを使用しても良いかもしれません。

f:id:ueponx:20191009231308p:plain

injectノードとexecノードを使用してこんな風に起動させることで起動忘れを防ぐ事もできると思います。

f:id:ueponx:20191009234342p:plain

f:id:ueponx:20191009235047p:plain

おわりに

これでmjpg_streamを使用して、動画っぽいデータを取得できました。 だた、以前使用したcamerapiなどの拡張ノードでも同じことできるような気がしますが、違いはWebサービスとして公開が同時にできつつ、画像を処理することができる点かなと思います。使いどころは難しいかもしれませんが。

とりあえずうまく動いたということで。ググってもNode-REDとmjpg_streamノードの組み合わせの日本語の情報が少なかったので書いてみたというエントリーでした。

【今回のフロー】

[
    {
        "id": "1a4b6256.e141ce",
        "type": "tab",
        "label": "mjpg_streming",
        "disabled": false,
        "info": ""
    },
    {
        "id": "ff084c2e.c225a",
        "type": "debug",
        "z": "1a4b6256.e141ce",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "x": 550,
        "y": 60,
        "wires": []
    },
    {
        "id": "765a06f7.6ca7e8",
        "type": "file",
        "z": "1a4b6256.e141ce",
        "name": "Camera Capture",
        "filename": "capture.jpg",
        "appendNewline": false,
        "createDir": false,
        "overwriteFile": "true",
        "x": 560,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "46e883a0.2e335c",
        "type": "mjpg-consumer",
        "z": "1a4b6256.e141ce",
        "name": "MjpgConsumer",
        "stream": "http://raspberrypi.local:8080/?action=stream",
        "interval": "5",
        "x": 360,
        "y": 100,
        "wires": [
            [
                "ff084c2e.c225a",
                "765a06f7.6ca7e8"
            ]
        ]
    },
    {
        "id": "309b4801.230ef8",
        "type": "inject",
        "z": "1a4b6256.e141ce",
        "name": "Stop Consumer",
        "topic": "",
        "payload": "stop",
        "payloadType": "str",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": "",
        "x": 160,
        "y": 140,
        "wires": [
            [
                "46e883a0.2e335c"
            ]
        ]
    },
    {
        "id": "9577ebc6.72bee8",
        "type": "inject",
        "z": "1a4b6256.e141ce",
        "name": "Start Consumer",
        "topic": "",
        "payload": "start",
        "payloadType": "str",
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": "",
        "x": 160,
        "y": 60,
        "wires": [
            [
                "46e883a0.2e335c"
            ]
        ]
    },
    {
        "id": "aa22e1d5.e6c6",
        "type": "exec",
        "z": "1a4b6256.e141ce",
        "command": "./mjpg-streamer/start_mjpg_streamer.sh",
        "addpay": true,
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "oldrc": false,
        "name": "",
        "x": 440,
        "y": 200,
        "wires": [
            [],
            [],
            []
        ]
    },
    {
        "id": "d141f688.7d0d28",
        "type": "inject",
        "z": "1a4b6256.e141ce",
        "name": "Start",
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "x": 130,
        "y": 200,
        "wires": [
            [
                "aa22e1d5.e6c6"
            ]
        ]
    }
]

【参考】

uepon.hatenadiary.com

/* -----codeの行番号----- */