OpenCVを用いてRasberry Pi 4にUSB接続したwebカメラから動画を撮影しようとしています。解像度を変更せず、デフォルトの640, 360で撮影したところ、問題なくスムーズな動画を得ることができました(実際の時間と同じ時間で録画できている)。一方で、capture.setやcv2.VideoWriterで以下のように1280, 720の解像度に変更すると10秒程度録画したとしても動画ファイルとしては数秒に圧縮され、早送りしたような動画になってしまいました。調べたところ、カメラの設定であるcapture.setと出力する動画の設定であるcv2.VideoWriterでのFPSに乖離があることでこのような早送り動画になっているとのことですが、同じFPS 30で設定しているにも関わらず、なぜこのようなことが起きているのかわかりません。webカメラを用いて高解像度の動画を撮影したいので、もし解決方法をご存知であれば、教えていただけると大変助かります。
よろしくお願いいたします。
import cv2 capture = cv2.VideoCapture(0, cv2.CAP_V4L) WIDTH = 1280 HEIGHT = 720 FPS = 30 capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')) capture.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH) capture.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT) capture.set(cv2.CAP_PROP_FPS, FPS) fourcc = cv2.VideoWriter_fourcc('M','J','P','G') videoWriter = cv2.VideoWriter('test.avi', fourcc, 30, (1280, 720)) while (True): ret, frame = capture.read() videoWriter.write(frame) if cv2.waitKey(1) == 27: break capture.release() videoWriter.release() cv2.destroyAllWindows()
カメラがその解像度とFPSの組み合わせに対応していない可能性があります。
ret = capture.set(cv2.CAP_PROP_FPS, FPS)のようにしてTrueが返ってくるかを確認ください。
「カメラから画像を取得して,VideoWriterに書き込む」というループの動作速度が完璧に 30FPS に一致しない限りは,出力された動画を再生した際には
> 早送り
や逆に「スロー」に見えることになるでしょう.
ありがとうございます。
具体的には1分撮影しても7秒の動画になってしまっています。また、capture.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)などで解像度を変更しない場合は、スムーズな動画が撮れているので、capture.setによる解像度の変更が乖離を生んでいるようですが、原因がわからないといったところです。今カメラが手元にないので、明日Trueか返ってくるかも確認してみようと思います。
while (True):
のすぐ上に
i = 0
を、
videoWriter.write(frame)
のすぐ下に(インデントを合わせて)
i = i + 1
を、コードの一番最後に
print(i)
を、それぞれ追加して実行したら、test.aviに何フレーム保存したのかが分かりますけど、それは 撮影時間(秒)x30 とだいたい合いますでしょうか?
ありがとうございます。試しに60秒撮影したところ、最後に出力されたiは212でした。なので、60秒あたり212フレーム(3.5FPS)保存しており、動画の出力としてFPS30が設定されているとすると、212フレームが7秒に圧縮されているのは妥当そうです。なので、VideoWriterは問題なく、capture.setのFPSの設定がうまくいっていないようでした。ちなみに、解像度を下げていくと、iの値は上がっている様子でした(1280x720は444、640x360は1332)。
capture.setはTrueが返ってきており、print(capture.getcv2.CAP_PROP_FPS)としてもちゃんと30となっていました。ラズパイの処理速度の問題でしょうか?
> capture.set(cv2.CAP_PROP_FPS, FPS)
これにどれだけの意味があるのか? っていう話ですね.
仮に,この指定によって「カメラ装置側が 1/30 秒ごとに撮影する」動作になったとしても,「あなたのプログラムがカメラ装置側から撮影結果画像をもらう処理」の速度が1/30毎になるわけではないのだから.
概ね,以下のようなイメージだと考えればわかりやすいかな.
・capture.read() は,カメラ装置側がその時点で持っている「最新の撮影画像」を取得する処理.
・カメラ装置側は「最新の撮影画像」を1/30秒毎に更新している.
なので,capture.read() を実施しているプログラム側が「カメラが 1/30 毎に撮影している画像の全て」を得られるわけではない.
プログラムのループ処理が「遅い」場合,「飛び飛びの」画像をサンプリングするだけになる.
(言うまでもない気がするけど)画像解像度により生じる違いは,画像サイズがでかいほど種々の処理に要する時間も増えるから前記「サンプリング」の頻度が遅くなってしまう,ということですね.
サンプリング間隔に関しては「カメラからのキャプチャ」と「動画への書き出し」を別スレッドにすれば多少は緩和できる可能性はあるかもだけど,本質的な話は変わらない.
詳細に説明していただき、大変ありがとうございました。OpenCVでは難しいこと、よく理解できました。v4l2-ctlであれば30fpsが実現できそうなので、試してみたいと思います。
私が紹介したWebサイトの二つ目の方の「エンコード性能の検証」で、ffmpeg使って条件を変えながら「fps」を測ってますので、それやってみてください
それで「fps≒30」出ない条件なら、python+opencvでもダメです
「無圧縮」以外に1280x720で「fps≒30」出る条件はありますでしょうか?
アドバイスいただき、ありがとうございます。
紹介いただいたサイトを参考にffmpeg使って試してみたところ、1280x720 mjpeg(4CPU)でfps≒30がでました!1920x1080でもfps≒30がでました!