ラズパイ4BとUSBカメラで、Python Bottle、OpenCVを使ったブラウザ上のMJPGストリーミング実験(備忘録)

ラズパイ4BとUSBカメラで、Python Bottle、OpenCVを使ったブラウザ上のMJPGストリーミング実験(備忘録) Raspberry Pi (ラズパイ)

今回はラズパイ(Raspberry Pi 4B)とUSBカメラでOpenCV-Pythonを使って、Web上でMotion JPEG(MJPG)ストリーミングする実験をしてみました。Web表示はBottleフレームワークを使いました。
ラズパイ4Bのハードウェア能力のおかげかも知れませんが、Pythonで 640×480 pixel の画角で30fps出せたのは大満足ですね。

スポンサーリンク

前回記事でようやくラズパイ4Bのプチフリーズが解消できて不安要素が全く無かったので、今回の実験のカメラストリーミングもバッチリでした。しかもメチャメチャ安定していましたね。さすがラズパイ!

とにかく以下の動画をご覧ください。
中央のiPadにはフリー動画を流していて、それをUSBカメラで撮影しています。

いい感じで30fps出せていますね。しかもガッツリ安定してプチフリーズもありません。
素晴らしい!!!

このブログでは以前、ESP32のM5Camera等でMotion JPEG(MJPG) over HTTP を扱ったことがありますが、あの時は320×240 pixel 、25fps程度で、しかもフリーズは付き物でしたから、ラズパイ4Bのこの安定感は驚きでしたね。

今回はカメラ選びでいろいろ迷ったこともありましたが、いざOpenCVで動かしてみるとあっさり動いてしまいました。AI開発を目指すならとっても便利ですね。C言語よりもかなり扱いやすいと思いました。

そんなわけで、今回やったことを備忘録として綴っていこうと思います。
なお、私はラズパイもLinuxもPythonもド素人です。
もし誤り等ありましたらコメント投稿でご連絡いただけると助かります。

    【目次】

  1. 使ったもの
  2. 自分のラズパイ4BおよびPython環境
  3. カメラ選びに迷った
  4. OpenCV(GUI無し)のインストール
  5. v4l-utils をインストールする
  6. ファイアウォール(ufw)設定でポート番号を開放しておく
  7. Motion JPEG(MJPG) over HTTPについてのおさらい
  8. 簡単なPythonコード
  9. フレームレート値をリアルタイム表示させるPythonコード
  10. ストリーミングの停止方法(Web表示画面から強制停止できる?)
  11. まとめ

1.使ったもの

Raspberry Pi 4 model B

以前、以下の記事で紹介した、OKdo製のRaspberry Pi 4 Model B 4GBの全部入りセットを使っています。

ラズパイ(Raspberry Pi 4 Model B)をSSDブートにする(備忘録)

(図01-01)

Raspberry Pi 4 model B 写真

Raspberry Pi 4 model B 写真

USB-SSD

これも以前のこちらの記事で紹介した、スティック型のUSB-SSDを使ってUbuntuブートしています。

BUFFALO (バッファロー) SSD-PUT250U3-B/N

USB3.0ハブ(外部電源供給)

前回記事でも説明したように、外部電源供給可能なUSBハブを使ったのは、ラズパイ4BのUSBポートの電力供給能力に不安があったからという理由です。
今回はUSB-SSDにプラスしてUSBカメラも使っているので、電力不足にならないようにしたいです。

エレコム U3H-T410SBK

USBカメラ

今回は以下の2種類を試してみました。


パソコン環境

Windows 10
Visual Sutudio Code (VSCode) SSHリモート

ルーター環境

一般的な有線LAN環境です。
前回の記事にあるように、プチフリーズの原因だったイーサネットスイッチングハブを買い替えました。

また、スマホとのストリーミングはローカルのWiFiを使っています。

2.自分のラズパイ4BおよびPython環境

まずは現在の自分のラズパイ4BのブートローダーやOSの環境、そしてPythonの環境を示しておきます。

ラズパイ4Bブートローダーバージョン

ラズパイ4Bのブートローダーバージョンは以下です。

$ sudo rpi-eeprom-update
BOOTLOADER: up to date
    CURRENT: 2022年  1月 25日 火曜日 14:30:41 UTC (1643121041)
    LATEST: 2022年  1月 25日 火曜日 14:30:41 UTC (1643121041)
    RELEASE: default (/lib/firmware/raspberrypi/bootloader/default)
            Use raspi-config to change the release.

    VL805_FW: Dedicated VL805 EEPROM
        VL805: up to date
    CURRENT: 000138a1
    LATEST: 000138a1

OSバージョン

次に、OSのUbuntu Serverのバージョンは以下です。

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

カーネルバージョン

次にカーネルのバージョンは以下です。

$ cat /proc/version
Linux version 5.15.0-1029-raspi (buildd@bos02-arm64-006) (gcc (Ubuntu 11.3.0-1ubuntu1~22.04.1) 11.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #31-Ubuntu SMP PREEMPT Sat Apr 22 12:26:40 UTC 2023

Pythonバージョン

pyenvを使っていて、Python 3.10.10 です。
自分のPython環境は以前の以下の記事で紹介しています。

ラズパイ(Ubuntu Server)とVSCodeでPythonプログラミング環境構築する(備忘録)

Python各パッケージバージョン

pipでインストールしたPythonパッケージのバージョンは以下です。

$ pip list --format=freeze
bottle==0.12.25
numpy==1.24.3
opencv-python-headless==4.7.0.72
pip==23.1.2
setuptools==67.8.0
somepackage==1.2.3
wheel==0.40.0

3.カメラ選びに迷った

以前、このブログのこちらの記事群で、ESP32のM5CameraでMotion JPEGストリーミングを扱ったことがあります。
今回もそれでラズパイと連携してみようかなと思ったのですが、今回はOpenCVを使ってみたいということもあり、ラズパイと有線で繋がるカメラを扱うことにしました。

さて、ラズパイにカメラを接続するとしたら、まずは以下の様なカメラモジュールを想像すると思います。

自分も最初はこれを買おうと思いネット予約しましたが、コロナ環境でなかなか製品が到着せず、結局キャンセルしました。

しばらく経ってから、再びカメラを探していたところ、上記のカメラモジュールよりもWebカメラの方が取り回し易いだろうなと思っていました。
そこで、WiFiネットワークカメラがいいのかな?と思い、ラズパイと相性の良い製品を探しました。
しかし、安価なネットワークカメラは第三者のバックドアが仕込まれている危険性を強く感じたので、日本の大手メーカー製を探しました。
例えば、Panasonicの以下の製品などです。


パナソニック 防犯カメラ スマ@ホーム 屋外カメラ KX-HJC100-W
パナソニック
¥18,545(2024/02/27 18:16時点)

でも残念ながらとても高価だったのと、逆にセキュリティがしっかりし過ぎていて、ラズパイのPythonで動かせないかも知れないと思ったため、選択肢から外すことにしました。

結局、USBカメラが比較的に安価で、ケーブルが長く柔軟性があって取り回し易く、ラズパイのOpenCVに対応している物が多かったので、USBカメラを選択することにしました。
その中でも、ネットの多くの情報でOpenCVと相性の良いLogicoolの以下の製品、C270nをまず購入しました。

大まかに以下のスペックです。

●HD 720p/30fps
●自動光補正
●モノラルノイズリダクションマイク

これは安価で小型でイイ感じでしたが、OpenCV-PythonプログラミングでMJPG出力設定にすると、なぜか警告メッセージが連続で出現してしまいました(これについては後で述べます)。

ネットの情報によると上位機種のUSBカメラでは警告メッセージが出ないらしいとのことだったので、思い切って以下の上位機種 C920nも購入してしまいました。

大まかなスペックは以下です。

●フルHD 1080p/30fps
●オートフォーカス
●自動光補生
●ステレオマイク

この2つのカメラの外観を比べてみると、下図の様な感じです。
(左:C920n  右:C270n)

(図03-01)

Logicool USBカメラ C920n と C270n の写真

Logicool USBカメラ C920n と C270n の写真

左のC920nは結構大きいですね。個人的には大きさだけで言えば右側のC270nがコンパクトで好きなんですけどね。
C920nはC270nよりも倍以上の価格で手痛い出費でした。

C920n と C270n の詳細なスペック比較は公式の以下のサイトを参照下さい。

https://www.logicool.co.jp/ja-jp/products/webcams/hd-pro-webcam-c920n.960-001261.html

では、ラズパイ4BのOpenCV-PythonでこのUSBカメラを動かしてみたいと思います。

4.OpenCV(GUI無し)のインストール

ラズパイ4BでUSBカメラをPythonで動かすためにはOpenCVパッケージをインストールします。

ただ、自分の場合のようにpyenv環境下の場合は注意する必要があります。
ネット上の多くの情報によると、Ubuntuの場合、以下のコマンドを打てばインストールできるとあります。

sudo apt install python3-opencv

しかし、この方法だとOS付属のシステムPythonにしかOpenCVがインストールされません。
pyenv環境下では、ユーザーがインストールしたPythonバージョンにOpenCVをインストールせねばなりません。

自分のラズパイの場合は、RasbianなどのディスクトップGUIを使わず、Ubuntu Serverをつかっているので、容量の少ないGUI無しのOpenCVをインストールすることにしました。
pyenvのPython3.10.10環境下では以下のコマンドでインストールしました。

pip install opencv-python-headless

requirements.txt を使う場合は、リストに opencv-python-headless を加えておきます。
注意点として、カメラ映像をウインドウ表示させるための cv2.imshow関数が使えません。

ならば、GUIを使わずにどうやってカメラ映像を表示させるのかと言うと、LANで繋がった別のパソコンやスマホでラズパイにアクセスして、ブラウザ画面にMotion JPEG over HTTPというストリーミング方式で表示させるわけです。
Motion JPEGについては過去の複数のこちらの記事で扱っていますので参照してみて下さい。

5.v4l-utils をインストールする

Pythonプログラミングには直接関係はありませんが、USBカメラを使う場合は、v4l-utils をインストールしておいた方が良いです。
以下のサイトを参考にさせていただきました。

https://www.prototype00.com/2021/11/v4l2-ctl-usbcam.html

それを使うと、USBカメラの解像度や露出などを設定したり、現在の設定を変更したりできるようになります。
そして何よりも重要なのは、プログラミングで使用可能な設定範囲を確認できるようになります。

まず、以下のようにv4l-utilsパッケージをインストールします。

sudo apt install -y v4l-utils

ただ、私の環境の場合は以下のように表示されてしまいました。

E: パッケージ v4l-utils が見つかりません

どうやら、/etc/apt/sources.list に必要なパッケージディレクトリのリポジトリが無いことが原因のようです。

そこで、Ubuntu公式のパッケージ検索できる以下のサイトにアクセスします。

https://packages.ubuntu.com/

次に、下の方にスクロールして、下図の様に「パッケージディレクトリを検索」の欄で、Distribution項目を「すべて」にして、検索欄に「v4l-utils」と入力します。「パッケージ内容」の方ではなくて、「パッケージディレクトリ」の方の検索ということがミソです。

(図05-01)

Ubuntu公式サイトのパッケージ検索ページ https://packages.ubuntu.com/

Ubuntu公式サイトのパッケージ検索ページ https://packages.ubuntu.com/

すると、下図の様に検索がヒットします。赤で囲った部分に「universe」と表示されていて、このリポジトリのリストがあれば良いようです。

(図05-02)

universe がヒットした

universe がヒットした

 

私のラズパイ4BのUbuntu 22.04 LTSでは、jammy でインストールされていると思いますので、そこの部分では以下のように表示されていました。

jammy (22.04LTS) (utils): Collection of command line video4linux utilities [universe]
1.22.1-2build1: amd64 arm64 armhf i386 ppc64el riscv64 s390x

ということで、/etc/apt/sources.list にuniverseリポジトリを追記するには以下のコマンドを使います。

sudo add-apt-repository universe

(※将来的に add-apt-repository というコマンドは非推奨になるらしいです。)

その後、以下のコマンドでv4l-utilsパッケージがインストールできるようになると思います。

sudo apt install -y v4l-utils

インストール終了したら、ラズパイのUSBポートにUSBカメラを接続し、以下のコマンドを試してみます。

v4l2-ctl --list-formats

すると以下のように接続されたUSBカメラが使用可能な動画フォーマットを表示してくれます。

ioctl: VIDIOC_ENUM_FMT
    Type: Video Capture

    [0]: 'YUYV' (YUYV 4:2:2)
    [1]: 'MJPG' (Motion-JPEG, compressed)

また、以下のコマンドにすると、利用できる解像度とフレームレートを一覧表示してくれます。

v4l2-ctl --list-formats-ext

C270nの場合は以下のように表示されます。

$ v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YUYV' (YUYV 4:2:2)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x120
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 176x144
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x176
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 352x288
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 432x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 544x288
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 640x360
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 752x416
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x448
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 864x480
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 960x544
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 960x720
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1024x576
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1184x656
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x960
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
        [1]: 'MJPG' (Motion-JPEG, compressed)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x120
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 176x144
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x176
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 352x288
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 432x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 544x288
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 640x360
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 752x416
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x448
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 864x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 960x544
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 960x720
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1024x576
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1184x656
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x960
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)

C920n の場合は以下のように表示されます。

$ v4l2-ctl --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'YUYV' (YUYV 4:2:2)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x90
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x120
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 176x144
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x180
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 352x288
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 432x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 640x360
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x448
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 864x480
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 960x720
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1024x576
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1600x896
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 2560x1472
                        Interval: Discrete 0.500s (2.000 fps)
        [1]: 'MJPG' (Motion-JPEG, compressed)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x90
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 160x120
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 176x144
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x180
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 320x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 352x288
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 432x240
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 640x360
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x448
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 864x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 960x720
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1024x576
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1600x896
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.042s (24.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.067s (15.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.133s (7.500 fps)
                        Interval: Discrete 0.200s (5.000 fps)    

これで使える動画フォーマットと解像度およびフレームレートが確認できました。

6.ファイアウォール(ufw)設定でポート番号を開放しておく

忘れがちなのですが、Pythonコードを走らせる前に、ストリーミングさせるポートを開放しておく必要があります。
以前のこちらの記事で紹介したように、Ubuntuの場合はufwでポートを開放しておきます。
たとえば、ストリーミング用に8080を開放したいとすると、ターミナルで以下のコマンドを打ちます。

sudo ufw allow 8080/tcp

その後 ufw を有効にしてラズパイを再起動してけば良いと思います。

7.Motion JPEG(MJPG) over HTTPについてのおさらい

Motion JPEGとは、カメラで取得したJPEG画像をパラパラ漫画のように連続して出力する方式です。
Pythonコードを組む前に予め把握しておく必要があります。

以前、当ブログでも過去にこちらの記事群で扱ったことがあります。

また、Motion JPEGによるWebでのHTTP通信(Motion JPEG over HTTP)の具体的な流れはこちらの記事を参照してみて下さい。

8.簡単なPythonコード

では、OpenCV-PythonでUSBカメラ映像を取得して、BottleフレームワークでブラウザへMotion JPEGストリーミングするコードを組んでみます。

Bottleフレームワークについては、以前のこちらの記事を参照してみてください。

Motion JPEG(MJPG)については先に述べた通りです。

例として、ラズパイのIPアドレスは192.168.0.10とし、先ほどufwで解放したカメラストリーミング用のポート番号を8080とします。
カメラ画像の画角は640×480 pixel で、フレームレートを30fpsとした場合のコードです。

#!/usr/bin/env python3.10
# OpevCVとbottleによる簡単なMotion JPEGストリーミングWeb表示コード

import cv2
from bottle import route, run, response

@route('/', method="GET")
def top():
    return '<img src="http://192.168.0.10:8080/streaming"/>'

@route('/streaming')
def streaming():
    try:
        cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
    except TypeError:
        cap = cv2.VideoCapture(0)

    if cap.isOpened() is False:
        raise IOError
    
    print("VideoCapture OK!")

    fps = cap.get(cv2.CAP_PROP_FPS)
    print("default fps=", fps)

    cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
    #cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('Y', 'U', 'Y', 'V'))

    # コマンド v4l2-ctl --list-formats-ext で使用可能な解像度を確認しておく
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
    cap.set(cv2.CAP_PROP_FPS, 30)

    fps = cap.get(cv2.CAP_PROP_FPS)
    print("Camera now fps=", fps)

    response.set_header('Content-type', 'multipart/x-mixed-replace;boundary=--frame')

    while True:
        ret, img = cap.read()
        if not ret:
            continue

        ret, jpeg = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 100])

        if not ret:
            continue
        # Motion JPEG(MJPG) をブラウザへ送信
        yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n\r\n')

    cap.release()

if __name__ == '__main__':

    run(
        host='192.168.0.10',
        port=8080,
        reloader=True,
        debug=True)

これを実行して、ブラウザのURL欄に http://192.168.0.10:8080 と入力すると、Bottleフレームワークによって、以下のHTMLをブラウザに出力します。

<img src="http://192.168.0.10:8080/streaming"/>

すると、ブラウザが自動で http://192.168.0.10:8080/streaming へアクセスします。

そして、OpenCVがビデオキャプチャを起動して、カメラ設定をします。

13~19行の

try:
    cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
except TypeError:
    cap = cv2.VideoCapture(0)

if cap.isOpened() is False:
    raise IOError

の部分のコードは、以下のサイトを参考にさせていただきました。
https://qiita.com/kakinaguru_zo/items/eda129635816ad871e9d

V4L2に対応していればそれを使い、そうでなければデフォルトのAPIを使うということです。
V4L2については良く知らないのでネットで検索してみてください。

26行目のように

cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))

と設定すると、USBカメラからMJPG(Motion JPEG)方式でJPEG画像を1枚ずつ出力してきます。
ただ、後で述べるように、USBカメラ C270n の場合はターミナルにズラーッと警告が出ます。
こんな感じです。

Corrupt JPEG data: 4 extraneous bytes before marker 0xd4
Corrupt JPEG data: 3 extraneous bytes before marker 0xd2
Corrupt JPEG data: 1 extraneous bytes before marker 0xd2
Corrupt JPEG data: 3 extraneous bytes before marker 0xd7
Corrupt JPEG data: 8 extraneous bytes before marker 0xd0
・・・・・・・etc

画像データに0x…マーカーの前に無関係の数バイトがあって、JPEGデータが破損しているというメッセージ。

どうやらlibjpegによって出力されている警告らしく、カメラ映像は問題無く出力されてブラウザには正常に表示されるので、エラーでは無い模様です。

ただ、このログが延々と出力されてしまうと、VSCodeターミナルが汚染されてしまうし、ログを保存している場合は膨大なメモリを消費してしまいます。それは避けたいですね。

その場合は多少フレームレートが落ちますが、MJPGをやめてYUYV設定にすることです。

#cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('Y', 'U', 'Y', 'V'))

他の解決策をネットでいろいろ調べていると、以下のサイト
https://github.com/opencv/opencv/issues/9477
で挙げられているようにlibjpegを再コンパイルすれば直るらしいのですが、それはさすがに面倒です。

さらにいろいろ調べていると、以下のサイト
https://wasabidiary.hatenablog.com/entry/2020/11/22/125237
にあるように上位機種の別のUSBカメラにすると症状が出なくなるという情報がありました。

ハード固有の問題???と半信半疑だったのですが、先にも述べたように思い切って上位機種C920nを試してみたら、警告メッセージは全く出ませんでした。今回はそれでヨシとします。

そして、30~32行で画像の画角サイズとフレームレートを決めます。
これは先ほど説明したように、v4l-utils でUSBカメラの可能な解像度とフレームレートを予め確認してから設定します。

次に、以前のこちらの記事で紹介したように、Motion JPEG over HTTP の流れでブラウザ側とラズパイ側でHTTPリクエスト、レスポンスでコネクション確立してストリーミングが開始します。

HTTPのレスポンスヘッダには必ず以下が必要です。

Content-type: multipart/x-mixed-replace;boundary=--frame\r\n\r\n

--frame」という文字列は半角英数値であれば何でも良いと思いますが、49行目の「--frame」とある文字列と同じにしなければなりません。
これについては、ネットの情報を漁ると「--frame」と「frame」という文字列の対になっている例が多く、それで試してみたら確かにそれでも問題無くストリーミングできました。
さて、どちらが正しいんでしょうかね?

また、49行目の所で、今回は Content-Length を今回省略していますが、それが無くても問題無く通信できました。なぜか、Content-Length を送信すると逆にエラーになって通信できなかったんです。これの原因はまだ不明です。

その他、最初に紹介した動画のように動作すれば概ねOKだと思います。

また、30,31行目の画角をハイビジョンの1920×1080 (C920nのみ)にしてみると、フレームレートは自動的に7fps程度に下がってしまうことが分かると思います。
私の環境の場合、30fpsで動作するのは640×480までで、それ以上の画角になるとフレームレートは下がりました。

9.フレームレート値をリアルタイム表示させるPythonコード

ストリーミングの画角を数種類試していると、サイズによってフレームレートが自動的に変わってしまうことが分かると思います。
そうすると、今の画角のフレームレート値をVSCodeのターミナルではなくてWeb上に表示させたいですよね。

ならば、今度はフレームレート値のテキストを画面上に貼り付けてリアルタイム表示させてみたいと思います。
幸いにOpenCVに putText という便利な関数がありました。
ただ単にそれを使ってもテキスト表示できるのですが、最初に紹介した動画のようにテキストに黒色の縁取りもしたいですよね。
そんな場合は、テキストの太さを少し変えて重ねれば良いわけです。
とりあえず、以下のコードにしてみました。

#!/usr/bin/env python3.10
# フレームレートをMJPG画面上にリアルタイム表示させるコード

import cv2
from bottle import route, run, response
import time

img_width = 640
img_height = 480
# img_width = 800
# img_height = 600
# img_width = 1280
# img_height = 720
# img_width = 1920
# img_height = 1080

@route('/', method="GET")
def top():
    s = str(img_width) + ' x ' + str(img_height)
    return '<font size="8">{}</font><br><img src="http://192.168.0.10:8080/streaming"/>'.format(s)

@route('/streaming')
def streaming():
    try:
        # V4L2が使えればそれを使う
        cap = cv2.VideoCapture(0, cv2.CAP_V4L2)
    except TypeError:
        cap = cv2.VideoCapture(0)

    if cap.isOpened() is False:
        raise IOError
    
    print("Capture Set OK!")

    fps = cap.get(cv2.CAP_PROP_FPS)
    print("default fps=", fps)

    cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
    #cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('Y', 'U', 'Y', 'V'))

    # コマンド v4l2-ctl --list-formats-ext で使用可能な解像度を確認しておく
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, img_width)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, img_height)
    cap.set(cv2.CAP_PROP_FPS, 30)

    fps = cap.get(cv2.CAP_PROP_FPS)
    print("now fps=", fps)

    response.set_header('Content-type', 'multipart/x-mixed-replace;boundary=--frame')

    fps_count = 0
    fps_time = time.time()

    while True:
        ret, img = cap.read()
        if not ret:
            continue

        put_txt(img, fps, 0, 0, 0, 5)
        put_txt(img, fps, 255, 255, 255, 2)
            
        #ret, jpeg = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 30])
        ret, jpeg = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 100])

        if not ret:
            continue

        # フレームレート計算
        if (time.time() - fps_time) >= 1:
            fps = fps_count
            fps_count = 0
            fps_time = time.time()
        else:
            fps_count += 1
        
        yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n\r\n')

    cap.release()

def put_txt(img, fps, col_r, col_g, col_b, tn):
    # カメラ画像にフレームレートテキストを書き込む
    # 縁取りはthicknessが大きい文字を最初に描画すれば良い
    cv2.putText(img,
        text=str(fps).rjust(3) + ' fps',
        org=(0, 50),
        fontFace=cv2.FONT_HERSHEY_SIMPLEX,
        fontScale=2.0,
        color=(col_r, col_g, col_b),
        thickness=tn,
        lineType=cv2.LINE_4)

if __name__ == '__main__':

    run(
        host='192.168.0.10',
        port=8080,
        reloader=True,
        debug=True)

83~90行にあるように cv2.putText という関数はホントに便利ですね。
これでJPEG画像にテキスト上書きできるのは最高ですね。
まず59行で太さ5の黒色のテキストを描画して、60行で太さ2の白色のテキストを重ねて描画しています。そうすると、下図ように縁取りテキストが表示されると思います。これは便利!

(図09-01)

MJPG画僧にフレームレート値テキストを貼り付け、黒色縁取りした様子

MJPG画僧にフレームレート値テキストを貼り付け、黒色縁取りした様子

10.ストリーミングの停止方法(Web表示画面から強制停止できる?)

さて、先に紹介したコードでMJPGストリーミングを停止するには、VSCodeのターミナル画面で、「Ctrl」+「C」キーを打って、Pythonを強制停止させるしかありません。

このコードは、ストリーミング開始してしまうと、ラズパイからブラウザへという一方通行通信のみで、ブラウザのWeb表示画面から停止したり、画角を変更したりはできません。先のコードのstreaming関数内のWhileループを抜け出すことができないからです。Whileループに入っている間はブラウザから別途リクエストを送ってもPython側で受け付けてくれません。
そりゃ、そうですよね。

ならばどうするかというと、マルチプロセスを使えば可能になるんです。
実は、それは既に成功しているので、次回の記事で紹介したいと思います。

11.まとめ

PythonはC言語より処理が遅いと思っていましたが、今回のカメラストリーミングで30fpsを叩き出せれば充分だと思いました。と言っても、ラズパイ4Bの強力なCPUとメモリのおかげかもしれません。

以前、ESP32やM5StackでのC/C++プログラミングによるMJPGストリーミングはとても苦労したのですが、Pythonでこれだけ手軽にできるのは素晴らしいですね。
ですが、あまり自分自身で創った感が無くて、少し物足りない気がしました。

また、今回はラズパイ側からブラウザへの一方向通信のみで、ストリーミング中にブラウザからコントロールできませんでしたが、次回はマルチプロセスを使ってストリーミング中でもブラウザからコントロールできるようにしてみたいと思います。

ここまでです。
ではまた・・・

コメント

タイトルとURLをコピーしました