OpenCV フレーム間差分&画像の結合 – Python

Programming
スポンサーリンク

はじめに

OpenCVで、フレーム間差分と画像の結合を行ってみました。(+ffmpegも少し紹介してます)

最終的にできる動画は下記のような感じです。

元動画

./movie/car_10sec.mp4

処理後動画

./movie/car_10sec_concat.mp4

Pythonコード

素材

フリー素材のサイトから適当に落とさせていただきました。

./movie/car_10sec.mp4

コード全文

# -*- 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()

コード説明

フレーム間差分に関しては下記のサイトを参考にさせていただきました。

紹介されているアルゴリズムと同じで、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による動画の圧縮)

コメント

タイトルとURLをコピーしました