2025年2月23日日曜日

Pythonで簡単にできる自動モザイク処理と動画編集方法(第2回) by裏方徹也

 第2回: Pythonで動画にコメントを追加する方法

こんにちは!裏方です!第2回の記事へようこそ!前回は、動画編集の基礎として、Pythonを使って単純に入力の動画をそのまま出力する基本的な操作を紹介しましたが、今回はさらに進んで、動画の各フレーム単位にデバッグ用のコメントを追加する方法を紹介します。


前回の記事はこちら

リンク: 前回の記事


★目標

今回の目標は、動画の各フレームに「フレーム番号」や「座標の目盛り」を表示することです。


フレーム番号: 動画内のフレームごとに番号を表示することで、特定のフレームに関するデバッグや処理の確認をしやすくします。

座標の目盛り: 動画内の100ピクセルごとに目盛りを表示することで、座標に基づく処理(物体追跡やエフェクト適用)の精度を視覚的に確認できます。


★出力例

実行前


実行後



★コードの準備その1

それでは実際にコードを書いてみましょう!

まずは、前回のコードをベースにして、

process_video(input_video_path) 関数の frame_count += 1 の直後に以下の2行を追加します。




        # 左上にフレーム番号を載せる
        frame = writeinfo(frame, frame_count, original_width, original_height) 
        # 目盛りを描画
        frame = add_scale_to_frame(frame, original_width, original_height)
    


① フレーム番号の追加

frame = writeinfo(frame, frame_count, original_width, original_height) 

ここでは、入力動画の映像(frame)とフレーム番号(frame_count)、動画の横の長さ(original_width)、動画の縦の長さ( original_height)を使ってwriteinfo処理を実行しています。writeinfoの詳細はコードの準備その2にて説明します。


② 座標の目盛りを追加

# 目盛りを描画

frame = add_scale_to_frame(frame, original_width, original_height)

次に②では、①でフレーム番号を付けた後の画面に、入力動画の映像(frame)とフレーム番号(frame_count)、動画の横の長さ(original_width)、動画の縦の長さ( original_height)を使ってadd_scale_to_frame処理を実行しています。add_scale_to_frameの詳細はコードの準備その2にて説明します。




★コードの準備その2

次に、下記のコードを記載します。

今回は便宜上、def process_video(input_video_path):の真下に追加しました。


        def writeinfo(frame, frame_count, width, height):
            # フレームにframe_countを表示
            cv2.rectangle(frame, (0, 0), (100, 40), (255, 255, 255), -1)
            cv2.putText(frame, f"{frame_count}", (0, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
            return frame

        def add_scale_to_frame(frame, width, height, scale_interval=100, large_scale_interval=500):
            # 目盛りを描画
            font = cv2.FONT_HERSHEY_SIMPLEX
            font_scale = 0.5
            font_thickness = 1
            text_color = (0, 0, 0)
            red_color = (0, 0, 255)  # 赤色
            green_color = (0, 255, 0)  # 緑色

            # 縦横の目盛りを描画
            for y in range(scale_interval, height, scale_interval):
                cv2.line(frame, (0, y), (10, y), (0, 0, 0), 2)  # 目盛り線
                cv2.putText(frame, str(y), (15, y + 5), font, font_scale, text_color, font_thickness, cv2.LINE_AA)

            for x in range(scale_interval, width, scale_interval):
                cv2.line(frame, (x, 0), (x, 10), (0, 0, 0), 2)  # 目盛り線
                cv2.putText(frame, str(x), (x + 5, 20), font, font_scale, text_color, font_thickness, cv2.LINE_AA)

            # 100ピクセルごとの交点に赤い点を描画
            for x in range(scale_interval, width, scale_interval):
                for y in range(scale_interval, height, scale_interval):
                    cv2.circle(frame, (x, y), 3, red_color, -1)  # 赤い点
                    cv2.putText(frame, f"({x},{y})", (x + 5, y + 10), font, font_scale, red_color, font_thickness, cv2.LINE_AA)

            # 500ピクセルごとの交点に緑の点を描画
            for x in range(large_scale_interval, width, large_scale_interval):
                for y in range(large_scale_interval, height, large_scale_interval):
                    cv2.circle(frame, (x, y), 3, green_color, -1)  # 緑の点
                    cv2.putText(frame, f"({x},{y})", (x + 5, y + 10), font, font_scale, green_color, font_thickness, cv2.LINE_AA)

            return frame
    



③def writeinfo(frame, frame_count, width, height):

入力から1フレーム単位で、映像(frame)を受け取り、画面左上にフレームの番号(frame_count)に応じて番号を記載する処理をしています。


④add_scale_to_frame(frame, width, height)

入力から1フレーム単位で、映像(frame)を受け取り、100ピクセル毎に座標目盛りと赤色の点を、500ピクセル毎に緑色の点を付けています。動画の横の長さ(width)、縦の長さ(height)の上限まで目盛りと点を付しています。


★改良後の全体のコード


        import tkinter as tk
        from tkinter import filedialog
        import cv2
        import moviepy.editor as mp
        import os

        # ファイルダイアログで動画を選択
        def select_video_file():
            root = tk.Tk()
            root.withdraw()
            video_path = filedialog.askopenfilename(title="動画ファイルを選択", filetypes=[("動画ファイル", "*.mp4 *.avi *.mov *.mkv")])
            return video_path

        # 動画の音声と映像を出力
        def process_video(input_video_path):
            output_video_path_temp = input_video_path.rsplit('.', 1)[0] + "_outputtemp.mp4"
            output_video_path = input_video_path.rsplit('.', 1)[0] + "_output.mp4"

            cap = cv2.VideoCapture(input_video_path)
            original_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            original_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            fps = cap.get(cv2.CAP_PROP_FPS)
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

            fourcc = cv2.VideoWriter_fourcc(*'mp4v')
            out = cv2.VideoWriter(output_video_path_temp, fourcc, fps, (original_width, original_height))

            frame_count = 0

            while True:
                ret, frame = cap.read()
                if not ret:
                    break

                frame = writeinfo(frame, frame_count, original_width, original_height)
                frame = add_scale_to_frame(frame, original_width, original_height)

                frame_count += 1
                out.write(frame)

            cap.release()
            out.release()

            add_audio_to_video(input_video_path, output_video_path_temp, output_video_path, total_frames, fps)
            print(f"出力ファイル: {output_video_path}")

        # 音声を動画に統合
        def add_audio_to_video(input_video_path, output_video_path_temp, output_video_path, total_frames, fps):
            video_clip = mp.VideoFileClip(input_video_path)
            audio_clip = video_clip.audio
            duration = total_frames / fps
            audio_clip = audio_clip.subclip(0, duration)

            final_video = mp.VideoFileClip(output_video_path_temp)
            final_video = final_video.set_audio(audio_clip)
            final_video.write_videofile(output_video_path, codec="libx264", audio_codec="aac")

            if os.path.exists(output_video_path_temp):
                os.remove(output_video_path_temp)
                print(f"中間ファイル {output_video_path_temp} を削除しました。")


                def writeinfo(frame, frame_count, width, height):
            # フレームにframe_countを表示
            cv2.rectangle(frame, (0, 0), (100, 40), (255, 255, 255), -1)
            cv2.putText(frame, f"{frame_count}", (0, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
            return frame

        def add_scale_to_frame(frame, width, height, scale_interval=100, large_scale_interval=500):
            # 目盛りを描画
            font = cv2.FONT_HERSHEY_SIMPLEX
            font_scale = 0.5
            font_thickness = 1
            text_color = (0, 0, 0)
            red_color = (0, 0, 255)  # 赤色
            green_color = (0, 255, 0)  # 緑色

            # 縦横の目盛りを描画
            for y in range(scale_interval, height, scale_interval):
                cv2.line(frame, (0, y), (10, y), (0, 0, 0), 2)  # 目盛り線
                cv2.putText(frame, str(y), (15, y + 5), font, font_scale, text_color, font_thickness, cv2.LINE_AA)

            for x in range(scale_interval, width, scale_interval):
                cv2.line(frame, (x, 0), (x, 10), (0, 0, 0), 2)  # 目盛り線
                cv2.putText(frame, str(x), (x + 5, 20), font, font_scale, text_color, font_thickness, cv2.LINE_AA)

            # 100ピクセルごとの交点に赤い点を描画
            for x in range(scale_interval, width, scale_interval):
                for y in range(scale_interval, height, scale_interval):
                    cv2.circle(frame, (x, y), 3, red_color, -1)  # 赤い点
                    cv2.putText(frame, f"({x},{y})", (x + 5, y + 10), font, font_scale, red_color, font_thickness, cv2.LINE_AA)

            # 500ピクセルごとの交点に緑の点を描画
            for x in range(large_scale_interval, width, large_scale_interval):
                for y in range(large_scale_interval, height, large_scale_interval):
                    cv2.circle(frame, (x, y), 3, green_color, -1)  # 緑の点
                    cv2.putText(frame, f"({x},{y})", (x + 5, y + 10), font, font_scale, green_color, font_thickness, cv2.LINE_AA)

            return frame


        # メイン処理
        def main():
            video_file = select_video_file()
            if video_file:
                process_video(video_file)
            else:
                print("動画ファイルが選択されませんでした。")

        if __name__ == "__main__":
            main()
    

★実行例

上記のコードを実行してみた結果、以下の通りとなりました。


〇実行前


〇実行後





実行後の動画には画面左上にフレーム番号が記載されており、100ピクセル毎に印がプロットされていますね。


★まとめ

今回は、動画にデバッグ用のコメントを追加する方法を紹介しました。動画に動的にフレーム番号や座標を表示することで、デバッグやトラブルシューティングを効率的に行うことができます。


次回は、いよいよ顔検出技術を使って動画にモザイクをかける方法を紹介しますので、ぜひお楽しみに!


0 件のコメント:

コメントを投稿