はじめに
OpenCVで、フレーム間差分と画像の結合を行ってみました。(+ffmpegも少し紹介してます)
最終的にできる動画は下記のような感じです。
元動画
処理後動画
Pythonコード
素材
フリー素材のサイトから適当に落とさせていただきました。
コード全文
# -*- coding: utf-8 -*-
from pathlib import Path
from typing import Tuple
import cv2
import numpy as np
def frame_difference(
prev_frame: np.ndarray,
curr_frame: np.ndarray,
next_frame: np.ndarray,
threshold: int = 2,
) -> np.ndarray:
"""
Calculate the difference between three consecutive frames to detect moving objects.
Args:
prev_frame (np.ndarray): Previous grayscale frame.
curr_frame (np.ndarray): Current grayscale frame.
next_frame (np.ndarray): Next grayscale frame.
threshold (int, optional): Threshold value for binarization. Defaults to 30.
Returns:
np.ndarray: Binary mask highlighting the moving objects.
"""
# Calculate the absolute difference between frames
diff1 = cv2.absdiff(prev_frame, curr_frame)
diff2 = cv2.absdiff(curr_frame, next_frame)
# Perform bitwise AND operation on the two difference images
combined_diff = cv2.bitwise_and(diff1, diff2)
# Binarize the combined difference
_, mask = cv2.threshold(combined_diff, threshold, 255, cv2.THRESH_BINARY)
# Apply median blur to remove salt-and-pepper noise
mask = cv2.medianBlur(mask, 5)
return mask
def main() -> None:
video_path = "./movie/car_10sec.mp4"
output_path = "./movie/car_10sec_concat.mp4"
output_path_diff = "./movie/car_10sec_diff.mp4"
# Open the video capture
cap = cv2.VideoCapture(video_path)
# Read the first three frames and convert to grayscale
ret, prev_frame = cap.read()
prev_frame_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)
ret, curr_frame = cap.read()
curr_frame_gray = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)
ret, next_frame = cap.read()
next_frame_gray = cv2.cvtColor(next_frame, cv2.COLOR_BGR2GRAY)
# Set up video writer for output
outpath = Path(output_path)
outpath.parent.mkdir(parents=True, exist_ok=True)
height, width = curr_frame.shape[0], curr_frame.shape[1]
fps = cap.get(cv2.CAP_PROP_FPS) # 動画からFPSを取得
fmt = cv2.VideoWriter_fourcc(*"mp4v") # コーデックを指定
writer = cv2.VideoWriter(str(outpath), fmt, fps, (width * 2, height))
writer_diff = cv2.VideoWriter(str(output_path_diff), fmt, fps, (width, height))
while cap.isOpened():
# Calculate the frame difference
mask = frame_difference(prev_frame_gray, curr_frame_gray, next_frame_gray)
# Display the current frame and the mask
concatframe = np.concatenate(
[curr_frame, cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)], axis=1
)
writer.write(concatframe)
writer_diff.write(cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR))
# writer_diff.write(mask) これだと、うまく動画が生成できなかった。ch数が1だから?
# Update the frame references
prev_frame_gray = curr_frame_gray
curr_frame = next_frame
curr_frame_gray = next_frame_gray
ret, next_frame = cap.read()
if ret:
next_frame_gray = cv2.cvtColor(next_frame, cv2.COLOR_BGR2GRAY)
else:
break
# Wait for the 'q' key to exit
if cv2.waitKey(30) & 0xFF == ord("q"):
break
# Release the video capture and close all windows
cap.release()
writer.release()
writer_diff.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
コード説明
フレーム間差分に関しては下記のサイトを参考にさせていただきました。
- フレーム差分法の原理・特徴・計算式 | 西住工房 (joho.info)
- 【Python/OpenCV】背景差分・フレーム差分で移動物体の検出 | 西住工房 (joho.info)
- Microsoft PowerPoint – 20140919-CRCフォーラム「動的背景差分(中島)」Web公開用.ppt (dendai.ac.jp)
紹介されているアルゴリズムと同じで、3つのフレームを用いて、差分取得→論理積→メディアンフィルタという流れで処理を行っています。
フレーム画像の連結に関しては、下記の部分で行っています。(cv2.hconcatとかでもできるみたい)
concatframe = np.concatenate(
[curr_frame, cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)], axis=1
)
writer.write(concatframe)
その後、VideoWriterを用いて、動画として保存しています。
動画保存の流れとしては下記のような感じです。 while cap.isOpened():
の部分はフレームをwriterに追加できればいいので、連番画像をforで回すとかでも良いです。
def main() -> None:
output_path = "./movie/car_4sec_480p_concat.mp4"
...
# Set up video writer for output
outpath = Path(output_path)
outpath.parent.mkdir(parents=True, exist_ok=True)
height, width = curr_frame.shape[0], curr_frame.shape[1]
fps = cap.get(cv2.CAP_PROP_FPS) # 動画からFPSを取得
fourcc = cv2.VideoWriter_fourcc(*"mp4v") # コーデックを指定
writer = cv2.VideoWriter(str(outpath), fourcc, fps, (width * 2, height))
...
while cap.isOpened():
...
writer.write(concatframe)
...
writer.release()
動画を読み込んで、FPSを取得するときに下記が使用できるのは知らなかったので、勉強になりました。
fps = cap.get(cv2.CAP_PROP_FPS) # 動画からFPSを取得
おまけ:ffmpeg
ブログに動画をアップロードしようとした時に、画像サイズがでかすぎて(20MBまでしか無理っぽい)、できなかったので、圧縮のためにffmpegというツールを使いました。
インストールは下記のサイトに従って進めたら簡単にできました。
インストールができたら、今回は下記のコマンドでビットレートを落として圧縮しました。ビットレートを指定する以外にもいろいろオプションがあるっぽいので、圧縮率みたいな感じで指定する方法(例えば、200MBの動画があって、20MBくらいにしたいときに圧縮率0.1とか入れるイメージ)がないか探してみようと思ってます。
ffmpeg -i ./movie/car_10sec_concat.mp4 -b:v 10M ./movie/car_10sec_concat_ffmpeg.mp4
おわりに
今回はPythonの勉強のために簡単な画像処理的なことをやりました。
- フレーム間差分
- 画像の結合
- (ffmpegによる動画の圧縮)
コメント