【OpenCV】可変FPSのWebカメラで、一定のFPSで録画したいとき。VBR→CBR【力技】
大きく一歩進んで小股で二歩下がるような微妙な前進、あるいは後退を続けている今日このごろですが、うちのWEBカメラのFPSは可変式です。
(脈絡もなく突然ですがw)
つまり暗い場所では勝手にFPSの値が低下し、明るい場所では勝手にFPSの値が上昇します。そして、残念ながらopenCVのVideoCaptureで各種キャプチャデータを取得してやり、capture.set(CV_CAP_PROP_FPS,30.0);としたところで、Webカメラに反映されてFPSが30で固定されることはありません。
これの何が厄介かと言うと、たとえばVideoWriterで30FPSで映像を記録しようとしたとき(←動画ファイルの作成)に、大抵の場合でWebカメラのFPSの値がきっちりと30fps出ません。室内を明るくしておけばあれですが……。
その結果、本来であれば記録されるべきデータスペースが空洞になり、その空洞に本来は次のフレームに収まるべき映像データが詰め込まれてしまうということの繰り返し……つまり早送り状態が発生することになります。
『データと間隔』
- ○○○ ○○○ ○○○ ○○○ ○○○
- ○●○ ●○● ●○● ○●○ ●●○
- ○○○ ○○○ ○●● ●●● ●●●
1が空洞なく一定の間隔で一定時間、Webカメラからデータを取得し、記録できた場合。2がうちのような可変タイプのカメラで、取得データに抜けが生じてしまっている状態。3が2をVideoWriterでそのまま記録した場合の動画ファイルの状態。
対策は? それでも上手く行かないときは?
※ 以下、WebカメラのFPSを30fpsで取得することを前提とした内容。また、VideoWriterで作成する動画も30fpsを前提とします。
まずCapture.set()で設定する
真っ当な方法というか正攻法でWebカメラのFPSを設定するならば、Capture.set(CV_CAP_PROP_FPS,30.0);やCapture.set(5,30.0);といった感じで、Webカメラの取得データのFPSを設定してやります。
これで上手くいくならばそれでOKですが、前述したとおり、うちのWebカメラのように暗いところでは自動的にFPSが落ちて、明るいところではFPSが上がるカメラでは上手くいきませんでした。
たぶんレンズの露出を変えて、シャッタースピードを固定できたり、手動であれこれと細かく設定できるような上等なカメラ用なのでしょう。(ノД`)シクシク
+++追記(更新分)+++
Capture.set(CV_CAP_PROP_EXPOSURE,-4)のように露出を設定してやると、その値で露出で固定される様子。顔検出などの処理によっては遅延が生じるものの、これでFPSも固定できる感じ。
ただし、うちのWEBカメラではどうも一度これを設定すると、カメラ内で設定が引き継がれる様子で、Capture.setで新たにCV_CAP_PROP_EXPOSURE を設定するか、USBケーブルを抜き差しするか、Win10付属のカメラアプリを起動して露出が初期化されるかしない限り、設定した露出が維持されるみたい。Opencvで作った他のプログラム(露出未設定)でも設定が引き継がれていたので……。
また、当然ながら露出が固定されるために暗い場所で明るくなり、明るい場所で暗くなるような調整は行われなくなるので、撮影環境を選ぶかも……。
++++++
仕方ないので、Capture.set()以外でどうにかやる
色々な解説サイトで提示されているサンプルプログラムでは、ループ処理の中で単純な録画処理(writer << frameを一回行うような処理)で済まされています。
これは基本の記録の仕方を、シンプルにわかりやすく解説しているのだから当然といえば当然なのですが、この方法だと上に示したとおり早送り状態が発生してしまい、可変式のWEBカメラではそのまま使えません。
.
ではどうするのかと考えた場合、動画ファイル的な視点では、Webカメラの取得データのFPSが可変であるならば、それに合わせて記録データのFPS(VideoWriterのFPS)を変えること、つまりVBR(可変式)として記録することです。
ただVBRの記録方法なんてサッパリ。取得できた瞬間をキーフレームにして、次に……対応する動画のコンテナは……とか面倒臭いし、そもそもそんな知識がないので無理です。VideoWriter的にも、呼び出されるたびに初期化されているっぽいので無理そう?
\(^o^)/
そうなると、CBRとして記録してやる以外にないかと……。CBRは固定フレームレートなので、VideoWriter的にも一度の呼び出しで終わるし、そっちのほうが無難な感じですね。
ループ1回ずつの処理時間を取得する必要あり
では、どうするかというと、対処療法ですが、ループ内の処理時間を常時計測して、そのときどきで早送りの原因となる空洞ができないように(空洞が埋まるように)、解説などのサンプルではループ内で一回の処理で終わっているwriter << frameを必要回数実行するようなプログラムを書きます。
私の行った方法では、ループの最初と最後で時間取得して、その差分である処理時間を計測し、その時間と前の処理で消化しきれなかった時間を足して、それに対して1/30秒毎に書き込んでいくようなfor文を作りました。
nは処理回数を数えるための変数で、ttはループの処理時間tと未消化の時間btを足したもの。frameTimeは1/30が入っています。ちなみに一部の変数を覗いて、ループの外で初期化しています。
ちなみにループの時間計測は誤差が少ないらしいので、windows.hをインクルードしてQueryPerformanceFrequencyを使いました。また、waitkeyの引数は30ではなく1に……。
各処理にラグがなければWaitkeyの引数は30で良いかもしれませんが、大抵は処理にラグがあるので、waitkeyの引数を30にしてしまうと、カメラのFPSが30出ていても一回の処理時間が0.0333……秒では収まらなくなったので……。
書き込み処理を必要回数させた結果
とりあえず上に書いた処理をすることで、FPSの可変してしまうWebカメラでも早送りにならずに取得データを記録することに成功。下のデータは作成されたMP4ファイルを真空波動研Liteに放り込んだもの。
40秒ほど録画を行い、しっかりと40秒ほどの動画ファイルとして記録されている。(上の処理を行わない場合は40秒ほど録画しても、十数秒ほどの動画ファイルになってしまっていた)
[MOV20181210221538805.MP4]
1920x1080 8Bit AVC/H.264 Baseline@5 1:1 Progressive 30.00fps 1217f 8580.39kb/s
[Extra][2]
MP4 v2
MetaData
[MPEG4] 00:00:40.566 (40.566sec) / 43,515,484Bytes
近況
とりあえず録画における早送り問題は力技だけど解決。相変わらずCAP_DSHOWじゃないと取得映像が16:9にならない問題は継続中。
どうすればいいの(ノД`)シクシク
とはいえ動画の録画、写真の撮影、HSVの変更、1フレームの処理時間、動画の撮影時間、ループ回数、その他などの表示、取得時間のファイルへの反映はできるように。
(*^ω^*)ニコッ
しかし、街がクリスマスソングに溢れていますねぇ。サンタさん、うちにも来ないかなぁ。とりあえずクリスマスケーキはチョコレートケーキでお願いしますw
- 関連記事
-
-
【OpenCV】可変FPSのWebカメラで、一定のFPSで録画したいとき。VBR→CBR【力技】 2018/12/11
-
【OpenCV】waitKeyの実働が遅い件……【Webカメラ】 2018/05/27
-
【OpenCV】ビルドエラーで少しハマったので解決するまでの流れを記録【覚書】 2019/10/11
-
【openCV】遅いのはCAP_DSHOWだった?【Webカメラ】 2018/11/27
-
【OpenCV】インターバル撮影メモ2 2019/03/29
-