Processing/OpenCVとWebカメラで動体検知・追跡してみて以来のOpenCV、今回は、PythonとOpenCVでUSB|Web|IP|ネットワークカメラ映像のキャプチャ、保存、フレームを切り出すことで映像内の一部を画像として保存してみた話。
自作の見守りカメラや定点カメラ、監視・防犯カメラでも、こういったことは必要なこともあるので、その一環として。
自作ほにゃららカメラは、カメラサーバとしてRaspberry Pi/Raspberry Pi OSを使っているのですが、PythonスクリプトとOpenCV、cronを使えば、定期的に保存することも簡単です。
OpenCVは、コンピュータビジョン向けライブラリであり、これは、デジタル画像や動画(映像)をコンピュータがいかに理解できるのかを追求する分野であり、画像・映像認識に長けた技術で顔認識を含む、画像・映像認識も既に高いレベルにあります。
よってカメラの使いみちによっては、物体検知、顔検出や顔認証後に、また、人感(PIR/赤外線焦電)センサーや開閉センサー、照度センサーなどなんらかのセンサー値に応じて、また、これらを任意に組み合わせて写真や映像を保存するということもできます。
そのベースになる部分の話です。
また、市販のその手のIP|ネットワークカメラには、クラウドとmicroSDなどSDカードに保存というものが多いですが、外部ではなく、自前(オンプレミス)のサーバに保存したいと思っているので、必然的に映像・画像の保存は通り道なわけです。
そんなこんなでカメラには、ラズパイに接続したUSB|Webカメラの他、IP|ネットワークカメラとして技適取得済みOV2640カメラ付きESP32-WROVER-DEVを複数使います。
どうもこのESP32-WROVERカメラボードの技適番号の刻印は偽装っぽいので、世界で唯一、カメラ付きESP32で技適を通っているらしきFreenoveが販売している2種の内の1つ、技適番号201-220052の方であるカメラ付きESP32-S3-WROOM-1開発ボードを購入。
よって、ここでは、USB|Webカメラだけでなく、IP|ネットワークカメラの映像捕捉・保存、フレーム画像の保存も行なっています。
と言っても自作のなんちゃらカメラについては、ZoneMinderという監視カメラシステムを使っており、これに複数カメラの表示機能のみならず、モーション検知機能などイベント監視エリア指定や各種イベントに応じた保存機能などもあり、覚えるのも大変なほど機能が充実しているので十分と言えば十分なのですが。
OSは、Raspberry Pi OSであるものとします。
その上でPythonとOpenCV、そしてcronがあればOKです。
Python自体なければ、Python3、そしてpip3にあたるpython3-pipなどのパッケージをインストールします。
Python2系からPython3系へ完全以降してしばらく経ちました。
たいていは、Pyhon3のみが、また、pip3前提のpipも入っていることが多いと思われますが、共存している場合、コマンドも明示的にpython3やpip3とする必要があるでしょう。
そしてpipでopencv-pythonをインストールします。
端末からpythonインタプリタを起動してimport cv2と入力、Enter後、プロンプトが返ってくればPythonとOpenCVの基本機能は準備完了です。
また、crontab -l(一覧モード)として自分で登録済みのcron一覧か、[no crontab for USER]と返ってくればcronも準備完了。
そうでなければ、apt install cronなどとしてインストール後に確認します。
Web|USBカメラの場合、cv2.VideoCapture()に1台なら基本、0、他にあるなら1含む以降の数値を渡すだけです。
Linux系だとカメラは、デバイス/dev/video*に、/dev/video0や/dev/video1として割り当てられますが、おそらく、この数値と一致するものと思われます。
この場合、ラズパイに1台USB|Webカメラを接続した状態で(PCならたぶん内蔵カメラだけでも)、このスクリプトを実行するとframeというタイトルの付いたウィンドウにカメラ映像が表示され、キーボードのq/Qキーで終了させることができます。
当然、こうしたスクリプト含め、複数のアプリから同時に映像を表示することはできません。 => できるかも?
ネットワーク|IPカメラの場合、RTP/RTSP/HTTP/HLC/Web-RTC/WebSocket等々、映像・画像・ストリーミング転送プロトコルがあるようです。
VLC Media PlayerやpythonではRTSPが使えるのですが、なぜか、著しく遅く、映像の確認にすら使えそうもなかったのでHTTPパスでキャプチャしてみることにしました。
ちなみにRTSPでは、BASIC認証(パスワード認証)の場合、ユーザー名とパスワードも必要で[ユーザー名]:[パスワード]@IP_ADDRESS_OR_mDNSのようになりますが、そうでない場合は、IP_ADDRESS_OR_mDNSのみで、protocolにrtsp、PORTとpathはRTSPサーバ側で設定したものとすることでいけました。
また、今回、カメラとして技適取得済みOV2640カメラ付きESP32-WROVER-DEVを使ったのですが、ブラウザからアクセスした際にソースを表示、追ってみるとimageタグのsrc属性には、ポートに81、パスにstreamが必要でhttp://IP_ADDRESS_OR_mDNS:81/streamとすることで映像を捕捉できました。
このボードで使ったスケッチの場合、ブラウザで見る分には、そんなことをしなくてもIP_ADDRESS_OR_mDNSへのアクセスのみで映像が表示されますが。
この場合、ラズパイと同一LAN内にあるライブ中の任意のネットワーク|IPカメラが存在する状態で、このスクリプトを実行するとframeというタイトルの付いたウィンドウにカメラ映像が表示され、キーボードのq/Qキーで終了させることができます。
ただし、HTMLからのキャプチャの場合?既にブラウザなど他で映像が表示されているとアクセスできず、エラーとなります。
逆も同様で、少なくともこのままだと、crontabで登録したスケージュールと完全に一致する時間にブラウザからカメラ映像を見ようとしてもアクセスできないでしょう。
cronを使う時点で映像を延々と録画することはないでしょうし、数分、数十分、1時間やそれ以上間隔をあければ、あけるほど、その時間を把握していれば尚の事、そうしたことに遭遇することは少なくなりますが。
数秒の遅延はありつつもRTSP/RTPなら複数のクライアントに同時にストリーミングできることがわかりました。
[Python]OpenCVで動画のフレームを画像で保存する方法のようにキャプチャしている映像・ストリーミングから画像を保存する場合、映像を構成するフレームの1つを画像として保存すれば良いようです。
そのフレームをwhileで延々と連ねたものが映像となっているのでwhile中に任意のフレームを切り出して保存することになります。
ここでは、リンク先のカウンタをそのままに、ループの1回め(n == 0)のフレームをcv2.imwrite()で保存することにしました。
これはスクリプトと同じディレクトリに保存しますが、他のパスを指定したい場合は、ファイル名の前に絶対パス、相対パスをクォートして+で付加します。
尚、cron実行で定期的に命名することを想定し、ファイル名には、Pythonでファイル名に現在時刻を付けるのように日付時刻を使うことにしました。
Python/OpenCVでWebカメラ!撮影した動画を保存するのようにcv2.videoWriter()に渡す引数をセット、video.write(frame)のようにコンストラクタのwrite関数に当該フレームを渡してあげればよいようです。
ここでは、時間の差分をとって5秒経過以降にスクリプトを終了させるようにしてみました。
cronで一定時間ごとに5秒程度の動画を保存するイメージです。
これはスクリプトと同じディレクトリに保存しますが、他のパスを指定したい場合は、ファイル名の前に絶対パス、相対パスをクォートして+で付加します。
尚、cron実行で定期的に命名することを想定し、フレーム画像保存の例同様、ファイル名には、日付時刻を使うことにしました。
cron設定は、crontab -e(編集モード)とし、最終行にスペース区切りで[分 時 日 月 曜日]の要領で設定しない部分は[*](アスタリスク)とします。
詳細は、man 5 crontabに譲りますが、設定範囲は、順に[0-59]/[0-23]/[1-31]/[1-12]/[0-7]で、このように昇順でハイフンを間に挟んで任意の範囲を、更に0-23/numとするとnum時間おきという間隔の指定も可、カンマ区切りで5,9,12とか、9-12,15-18といった指定の時間軸のリストを、値1つ指定で特定の時を指定することができたりします。
一方、ほにゃららカメラで自動撮影する場合、そのままではファイルが増殖する一方なので自動的に削除したいところ。
そんな時にもcronが威力を発揮してくれます。
例えば、このようにすると毎日12時にpath/to/dirのファイルの内、3日経過したファイルを全て削除してくれます。
もちろん、これを設定した、その時点にサーバ・マシンが稼働中であればの話ですが。
特定の拡張子のものをということであれば、-nameオプションで[-name "*.mp4"]のように指定します。