277.ラズパイ無双[18_WebSocketストリーミング2]
初回:2022/9/28
Raspberry Pi (ラズベリーパイ、通称"ラズパイ")で何か作ってみようという新シリーズです。『WebSocket』ベースの画像配信サーバーを構築していきたいとおもいます。
P子「今回は完成版の説明ね」※1
以前に作成した『ogServer.py』と比較しながら『wsServer.py』を作りたいと思います。
例によって、サンプルプログラムは、下記に用意しました。ただし、www_og には、bottle.py とwaitress を入れていません。同様に、www_ws には、bottle.pyを入れていません。これらは、各自でご用意ください。
≪サンプル≫
https://osdn.net/downloads/users/39/39313/No277.zip
【目次】
1.ogServer.py 修正
今回の解説を行う前に、既存のogServer 関係のプログラムを少し修正しておきます。wsServer.py との互換性を意識した作りにしておきたいと思います。
266.ラズパイ無双[12_opencvストリーミング3] が、旧ソースの解説記事ですので、必要であれば再確認してみてください。
1) camera.py
2) ogServer.py
3) views/header.html
4) static/jquery-3.6.1.min.js
1) camera.py
・cap = cv2.VideoCapture の箇所で、V4L2から直接キャプチャできるようにしました。
・cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) でバッファサイズを 1 に設定しました。
これは、カメラ映像を出来るだけ現在の物を使うようにするためです。
・cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc( *'MJPG'))
・width、height、fps、fourcc の外部からの指定
・def frames で返すのは、tobytes() していないjpg画像
・def info で、カメラの使用可能なエンコードと画素サイズをdebug時に表示
若干、細かいところもありますが、一番大きいのは、『def frames で返すのは、tobytes() していないjpg画像』という所です。wsServer.py では、Ascii化(BASE64)した画像をJSON形式で返す事にしましたので、カメラ画像をバイト変換しないで受け取りたいからです。
2) ogServer.py
・def gen メソッド内で、frame.tobytes() している。
・closeable の set で、close()処理を一元化
先のcamera.pyで、tobytes() していないため、ogServer.py の genメソッドでtobytes()しています。
3) views/header.html
・jquery-3.6.1.min.js をローカルに置いて、scriptタグで取り込んでいます。これは、こちらの個人的な理由で、社外のネットワークに接続できないような環境でも使えるようにしておきたいからです。
4) static/jquery-3.6.1.min.js
・jquery-3.6.1.min.js を配置
2.camera2.py 解説
カメラ映像を取得するプログラムですが、camera.py から、スレッドの制御を削除しています。wsServer.py でもcamera.pyをそのまま使用することはできますが、やはり、スレッド制御は難しいです。そのあたりは、gevent でうまく処理してくれているようなので、camera2.py では本当にカメラ映像を取得するだけのプログラムになっているので、判りやすいと思います。
P子「それだけで、解説は終了?」
wsServer に関係する説明はほとんどありませんが、openCV関係の解説だけ行っておきます。
1) V4L2から直接キャプチャ
OpenCV 4以降ではキャプチャを直接V4L2で行われず GStreamer を経由されることがあるので、V4L2から直接キャプチャする。
try:
capture = cv2.VideoCapture(devno, cv2.CAP_V4L2)
except TypeError:
capture = cv2.VideoCapture(devno)
2) 遅延(コマ遅れ)現象
キャプチャ時のコマ落ち対応でバッファを持っているが、逆に遅延(コマ遅れ)が発生する。バッファサイズを小さくすれば、遅延が減る
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
3) 取り込みデータ形式
カメラの持っている解像度やフレームレートは、取り込みデータ形式に依存します。これを指定できれば、フレームレートを大きく取ったり、解像度を大きく取ったりできます。
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*fourcc) )
fourcc に指定できる値や、解像度、フレームレートは、v4l2-ctl --list-formats-ext で 調べる事が出来ます。
4) def info の debug時の機能
infoメソッドには、取得されたカメラの解像度(幅、高さ)とフレームレートを表示しますが、debugモードを指定した時、先のv4l2-ctl --list-formats-ext の実行結果を表示します。そのままでは、だらだらと表示されるので、fourcc とサイズを整理しています。ただし、手持ちのカメラで実行しているので、余計なお世話かもしれません。
3.wsServer.py 解説
これは、ogServer.py と比較するのが判りやすいと思います。
1) class WsSender()
クライアントに対してメッセージを送るためのクラスです。ogServer.py の時には、filter、module、subapp のプログラムを動的に組み込んでいますが、wsServer.pyでは、callback オブジェクトを追加できます。このcallback オブジェクトのsetCallbackメソッドに、WsSenderクラスの sendMessage メソッドを渡すことで、各組み込みオブジェクトから、クライアントにいつでもメッセージを返すことができるようになります。
この、sendMessage メソッド では、jsonオブジェクトを受け取って、文字列化して渡します。その際、アクセスしてきたクライアントすべてに対して、メッセージを送り返します。そのあたりが、wsbsocketの常時接続状態であるメリットでしょう。
※ つまり、クライアントごとに値を返すなら、各クライアントから呼び出せばよいので、通常の HTTPで良いという意味です。
2) main メソッドのhandle_websocket()
ここでは、while True で無限ループになっています。これで、クライアントからの接続をずっと持ち続けることになります。
WebSocket 自体は、双方向なのですが、今回の実装では、サーバー側の画像をクライアントに送信することをメインに、ついでにサーバー側の組み込みモジュールからのリアルタイムな応答を受け取るだけの機能しか入れていません。サーバーからクライアントには、文字列としてJSONオブジェクトを渡すことで、各種データを返しています。
クライアントからサーバーへのデータ転送は、本来ならWebSocketで通信すればよいのですが、どちらかというと、クライアントからの送信をサーバーが待ち受けるというのが主な使い方なのですが、今回は、画像を送信するためにループを回しているため、待ち受けるとすれば、常に監視するという感じになってしまいます。
そこで、クライアントからサーバーへは、前回のogServer と同じで、ajax で送信することにしました。
4.まとめ
とりあえず、駆け足でしたが、『WebSocket』ベースの画像配信サーバーの完成です。
次回からは、このサーバーで利用できる組み込みモジュールを説明していきたいと思います。
P子「でも、気まぐれで別の事を書いたりしてね」
ほな、さいなら
======= <<注釈>>=======
※1 P子「今回は完成版の説明ね」
P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。