2025年3月27日木曜日

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

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

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

皆さん、こんにちは!裏方です!
前回は動画に映っている人物をMask R-CNNという方法で検出し、四角でマーキングする方法を紹介しました。
本来そこでモザイクのかけかたのコードを紹介するところでしたが、説明を失念したため、今回ご紹介します!

1. 今回の記事で扱うモザイクについて

モザイクと言えば、通常は以下のような形をイメージすることが多いかもしれません。

モザイクの画像

しかし、この方法ではどうしても動画に集中しづらくなる場合もあります。
そこで、この記事では以下のような「ぼかし」処理を使用して、人物が誰なのか分からないようにします。

ぼかし処理後の画像

モザイクよりも視覚的に不自然さが少なく、見やすくなったかと思います。

2. 追加するコードについて

それでは、実際にPythonでモザイクをかけるためのコードを追加していきます。
まず、以下のコードを draw_box(frame, boxes, scores) 関数内に追加してください。

# まず、ぼかしをかける範囲を切り出す
region = frame[int(y1):int(y2), int(x1):int(x2)]

# ガウスぼかしを適用 (ksizeはぼかしの強さを決定)
blurred_region = cv2.GaussianBlur(region, (45, 45), 20)

# ぼかしをかけた結果を元のフレームに戻す
frame[int(y1):int(y2), int(x1):int(x2)] = blurred_region

このコードを挿入することで、Mask R-CNNで検出した人物の周辺にぼかし処理を適用できます。
具体的には、検出した座標を基にその領域にガウスぼかしをかけます。

3. 処理内容

ここでは、コードがどのように動作するかを説明します。

  1. 人物検出
    Mask R-CNNを使用して動画内の人物を検出し、その座標x1, y1, x2, y2を取得します。
  2. ぼかし処理
    検出された人物の領域(座標で切り取られた部分)に対してガウスぼかしを適用します。 ぼかしの強さは、cv2.GaussianBlur(region, (45, 45), 20)の 45の部分、20の部分を適宜調節してください。 細かい説明は省きますが、 値が高ければ高い程ぼかしが強くなりわかりにくくなるというイメージです。
  3. 元の動画に反映
    ぼかし処理をかけた領域を、元のフレームに戻すことでモザイク処理を実現します。

4. 実行結果

実際にこの処理を適用した結果がこちらです:

実行前



実行後




上記のように、人物にぼかしが適用され、誰なのか特定できないようになりました。これで、プライバシーを守りつつ、動画の内容をそのまま活かすことができます。

5. 全体のコード

ここで紹介したコードを含む全体のコードは以下のようになります:



import tkinter as tk
from tkinter import filedialog
import cv2
import moviepy.editor as mp
import os
import time
# 第3回で追加#
import torch
from torchvision import models, transforms # ここでmodelsをインポート
#第3回で追加#
import numpy as np

#第3回で追加#
model = models.detection.maskrcnn_resnet50_fpn_v2(pretrained=True)
model.eval()  # 評価モードにする
#第3回で追加#

# ファイルダイアログで動画を選択
def select_video_file():
    root = tk.Tk()
    root.withdraw()  # Tkinterウィンドウを非表示にする
    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.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

    start_time = time.time()


    # 第3回で追加#
    # 500x500の全画面黒い画像を作成
    black_image = np.zeros((500, 500, 3), dtype=np.uint8)

    # Faster R-CNNを使用して、黒い画像上で検出処理を行う
    boxes, scores = process_torch(black_image)  # process_torchの適用
    # 第4回で追加#


    while True:
        # 動画の1フレームを読み込む
        ret, frame = cap.read()
        if not ret:
            break  # 動画の終わりに達したら終了

        #第3回で追加
        if 65 < frame_count < 255:

            if frame_count % 3 == 0:
                boxes , scores = process_torch(frame) #Fasterでframeから人を検知する

        frame = draw_box(frame,boxes , scores)


        #フレーム番号
        #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)

        cv2.imshow("frame", frame)
        cv2.waitKey(1)

    # 動画キャプチャと書き込みを解放
    cap.release()
    out.release()
    cv2.destroyAllWindows()

    end_time = time.time() - start_time
    end_time_m = end_time / 60
    end_time_s = end_time % 60

    print(f"処理時間: {end_time_m} 分 {end_time_s}")

    # 動画と音声をそのまま出力
    add_audio_to_video(input_video_path, output_video_path_temp,output_video_path, total_frames,fps)

    print(f"出力ファイル: {output_video_path}")

##第3回で追加##
def process_torch(frame):

    input_image = preprocess_image(frame)

    with torch.no_grad():
        prediction = model(input_image)

    boxes = prediction[0]['boxes'].cpu().numpy()
    scores = prediction[0]['scores'].cpu().numpy()

    return boxes,scores

def preprocess_image(image):
    transform = transforms.Compose([
        transforms.ToTensor(),  # 画像をテンソルに変換
    ])
    return transform(image).unsqueeze(0)  # バッチ次元を追加


def draw_box(frame,boxes , scores):
    # 信頼度が一定値以上の検出結果に四角形を描画
    threshold = 0.9  # 信頼度の閾値(必要に応じて調整)
    for i in range(len(boxes)):
        if scores[i] > threshold:

            x1, y1, x2, y2 = boxes[i]

            #3.5回追加
            # まず、ぼかしをかける範囲を切り出す
            region = frame[int(y1):int(y2), int(x1):int(x2)]


            # ガウスぼかしを適用 (ksizeはぼかしの強さを決定)
            blurred_region = cv2.GaussianBlur(region, (45, 45), 20)

            # ぼかしをかけた結果を元のフレームに戻す
            frame[int(y1):int(y2), int(x1):int(x2)] = blurred_region
            #3.5回で追加


            # 四角形を描画 (色は青、太さは2)
            #cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (255, 0, 0), 2)

            # scoreを丸める (小数第2位以下を切り捨て)
            #scores_M = int(scores[i] * 1000) / 1000

            # 黒い文字でframe_countを表示
            #cv2.putText(frame, f"{scores_M}", (int(x1), int(y1)), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)

    return frame
##第3回で追加##

##第2回で追加##
def writeinfo(frame,frame_count,width,height):
    # フレームにframe_countを表示
    # 白い背景の矩形を描画
    cv2.rectangle(frame, (0, 0), (100, 40), (255, 255, 255), -1)

    # 黒い文字でframe_countを表示
    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
##第2回で追加##


# 音声を動画に統合する
def add_audio_to_video(input_video_path, output_video_path_temp,output_video_path, total_frames,fps):
    #moviepyで動画と音声を読み込む
    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")

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

    print(f"音声付きの動画が保存されました。最終ファイル名: {output_video_path_temp}")


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

if __name__ == "__main__":
    main()

このコードを使用することで、Mask R-CNNを利用して簡単にモザイクをかけることができます。

6. 問題点と改善方法

実際に使ってみると、いくつかの問題点が浮かび上がります。

  1. 人物全体にモザイクがかかってしまう
    現在、モザイクは人物全体にかかってしまいます。顔や特定の部位だけにモザイクをかけたい場合、顔認識などを追加することで改善できます。
  2. 処理時間がかかる
    モザイク処理には時間がかかる場合があります。前回の記事でもお伝えした通り、全てのフレームに対して行っており莫大な時間がかかってしまいます。対策として一定間隔のフレームで実行することで処理時間を短くできます。

7. まとめ

今回は、PythonとMask R-CNNを使用して動画内の人物にモザイクをかける方法を紹介しました。
この方法を使えば、簡単にプライバシーを保護しながら、自然な形でモザイク処理ができます。
さらに、顔検出や並列処理を組み合わせることで、より精度を上げることも可能です。

もし、この記事が役に立ったなら、コメント欄で教えてください!
また、他の記事でもPythonを使った実践的なチュートリアルを紹介していますので、ぜひチェックしてみてください。

0 件のコメント:

コメントを投稿