【Python】imageioを使ってWebカメラ映像をmp4リアルタイムエンコードしたらハマった
【状況】Webカメラの映像をリアルタイムエンコードして保存するだけだから簡単かと思ったら,機材によって挙動が変わるようなので試行錯誤.また,1920x1080にしたいのに,1920x1088に自動的にされてしまう.
【対処】カメラの仕様によりOpenCVで指定したフレームレートに実際になっていなかったので,実際のfpsを取得して動画を設定.また,1920x1080にするため,ffmpegのマクロブロックサイズを1に変更した(非推奨ではありますが).
Webカメラのfps設定
fpsを15にしてエンコードしたはずが,動画を再生するとやたら動きが速い.動画のプロパティを確認すると15fpsとなっているので入力がおかしいことに気づきました.カメラのfps設定後にfpsを確認すると実動作5fps.5fpsしか無いのに15fpsでエンコードしたのが原因なので,実fpsを取得して使うことにしました(15fpsの設定がカメラに無かったことがあとで発覚).
# カメラのフレームレートを設定
camera.set(cv2.CAP_PROP_FPS, 15)
actual_fps = camera.get(cv2.CAP_PROP_FPS)
print(f"実際のカメラのFPS: {actual_fps}")
ffpmegのオプションを設定
実fps,ビットレートなどを指定してエンコード.1920x1080の出力を期待したところ,縦が16の倍数でないので1088で出力されました.警告にその旨が書かれ,非推奨ながらマクロブロックサイズを変えればよい,あります.ffmpegのオプションに追加して指定したところ,1920x1080の出力を確認できました.ブロックサイズによる不具合が生じる場合はmacro_clock_sizeの行は削ります.
# ffmpegのオプションを指定して動画書き込みを設定
ffmpeg_output_params = {
'codec': 'libx264',
'fps': actual_fps,
'bitrate': '4000k',
'macro_block_size': 1, # マクロブロックサイズを1に設定(1920x1088ではなく1920x1080にするため)
}
BGRからRGBへ変換
色情報について,pythonの標準はBGR,動画にするときはRGBであることが必要なため変換(基本的なことなんですけど).最初,忘れていたので不気味な映像ができましたw
# 動画保存するため,BGRからRGBに変換
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
完成版
import imageio
import cv2
# ウェブカメラを初期化
camera = cv2.VideoCapture(0)
# カメラの解像度を設定
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
# カメラのフレームレートを設定
camera.set(cv2.CAP_PROP_FPS, 15)
actual_fps = camera.get(cv2.CAP_PROP_FPS)
print(f"実際のカメラのFPS: {actual_fps}")
# ffmpegのオプションを指定して動画書き込みを設定
ffmpeg_output_params = {
'codec': 'libx264',
'fps': actual_fps,
'bitrate': '4000k',
'macro_block_size': 1, # マクロブロックサイズを1に設定(1920x1088ではなく1920x1080にするため)
}
writer = imageio.get_writer('output.mp4', format='FFMPEG', mode='I', **ffmpeg_output_params)
# カメラからフレームを取得して書き込み
while True:
ret, frame = camera.read()
if not ret:
break
# リアルタイムでのプレビュー
cv2.imshow('Live view', frame)
# 動画保存するため,BGRからRGBに変換
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# フレームを動画ファイルに書き込む
writer.append_data(frame_rgb)
# 「q」が押されたら終了
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# リソースの解放
camera.release()
writer.close()
cv2.destroyAllWindows()
とりあえず動画がちゃんとできたからよし♪