OpenCV+Pillowで動画上に秒単位で変化する日本語テキストを表示する


はじめに

業務で秒単位のセンサ情報テキストデータを動画に直接紐付けるために、動画に秒単位で日本語テキストを表示する作業を行った。
その際に少し手間取ったので、テキスト表示用のPython実行コードサンプルを備忘録も兼ねて紹介します。

やったこと (プログラム入出力内容)

入力動画より、下の出力動画のように秒単位で変化する日本語テキストを表示できるようにした。

・入力動画 (※ https://www.home-movie.biz/free_movie.html より取得)

・出力動画

実行コード

動画のフレーム入出力にOpenCV (v4.5.1)、日本語テキスト書き込みにPillow (v7.0.0)を利用している。
※ OpenCVでもputTextメソッドによりテキスト書き込みは可能だが、日本語・改行に非対応

フォントはプログラム内の定数「FONT_PATH」にてお好みで変更して実行してください。

write_text_on_video.py
"""動画へのテキスト書き込みプログラムサンプル"""
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

# Windows
FONT_PATH = "C:\Windows\Fonts\meiryo.ttc"
# Linux(Ubuntu)
# FONT_PATH = "/usr/share/fonts/truetype/takao-gothic/TakaoGothic.ttf"


def main():
    # 入出力動画ファイルパス
    video_infile = "./mov_hts-samp003.mp4"
    video_outfile = "./outvideo.mp4"

    # 動画読み込み
    cap = cv2.VideoCapture(video_infile)

    # 動画の各プロパティ取得
    print("# Video Property List")
    frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = round(cap.get(cv2.CAP_PROP_FPS), 2)
    bitrate = int(cap.get(cv2.CAP_PROP_BITRATE))
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    print(f"FrameCount:{frame_count}, FPS:{fps}, BitRate:{bitrate}")
    print(f"Width:{width}, Height:{height}")

    # 出力用の動画(MP4)オブジェクト設定
    fourcc = cv2.VideoWriter_fourcc("m", "p", "4", "v")
    outvideo = cv2.VideoWriter(video_outfile, fourcc, fps, (width, height))

    while True:
        # フレーム読み込み
        ret, frame = cap.read()
        if ret is False:
            break

        # フレーム読み込み時点の再生時間[秒]を取得
        frame_pos = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
        video_sec = int(frame_pos / fps)

        # フレームへテキスト書き込み
        text = f"動画ファイル:{video_infile}\n再生時間:{video_sec}秒"
        frame = write_text_on_frame(frame, text, width, height)

        # 編集フレーム出力
        outvideo.write(frame)


def write_text_on_frame(
    frame: np.ndarray, text: str, width: int, height: int
) -> np.ndarray:
    """入力フレームに指定テキスト書き込み

    Args:
        frame (np.ndarray): フレーム
        text (str): 書き込みテキスト
        width (int): フレーム幅
        height (int): フレーム高

    Returns:
        np.ndarray: テキスト書き込みフレーム
    """
    image = Image.fromarray(frame)
    draw = ImageDraw.Draw(image)

    # テキスト書き込み
    font = ImageFont.truetype(font=FONT_PATH, size=72)
    draw.rectangle(xy=(0, 0, width, int(height / 5)), fill=(255, 255, 255))
    draw.text(xy=(10, 10), text=text, fill=(0, 0, 255), font=font)

    return np.array(image)


if __name__ == "__main__":
    main()

参考