R.Mitaをフォローする

【Python】moviepyを使った動画編集

バックエンド

■はじめに

Pythonのライブラリmoviepyを使った動画編集についてまとめてみました。

■実行環境

  • python 3.12.3
  • ライブラリ
    • moviepy 2.1.2
    • ffmpeg
    • ffplay
    • opencv-python-headless 4.11.0.86

■moviepyでの動画編集

基本機能説明をmoviepyの公式サイトにチュートリアルの流れに沿って説明していきます。

moviepyのチュートリアルでは「The Big Buck Bunny」の予告編を作成するという内容になっています。

・moviepy 公式チュートリアル

 MoviePy in 10 Minutes: Creating a Trailer from “Big Buck Bunny” — MoviePy documentation

・チュートリアル用の素材動画

 Big Buck Bunny 10分素材

・チュートリアルで作成する予告編風動画

 Big Buck Bunny 編集チュートリアル

moviepy のインストール

pip install moviepy

動画の読み込み

video = VideoFileClip("../resources/rendered/bbb.mp4")

# AudioFileClip クラスもあり音声ファイルの読み込みも可能
  • VideoFileClipを使用して指定したパスから動画を読み込む
  • video変数は以下のような情報を持っている
    • 動画の長さ、終了時間
    • クリップのfps(fps=1秒の間に何枚の画像で動画が構成されているか)
    • フレームの大きさ(width、height)

動画の加工

1. カット

  • subclippedを使用して切り出しの開始位置、終了位置を指定しクリップに切り出す
# Pattern1 - 秒数を指定する
intro_clip = video.subclipped(1, 11)

# Pattern2 - HH:MM:SS.uS形式で指定する
rambo_clip = video.subclipped('04:41.5', '04:44.70')

2.テキスト、画像の追加

  • 動画内に任意の文字、画像を挿入する
○テキスト挿入
# フォントをインポートする
font = "../resources/font/font.ttf"

# TextClipクラスを使用して詳細を設定していく
intro_text = TextClip(
		font= font,
		text="The Big Buck Bunny",
		font_size=50,
		color='white',
		text_align="center",
)

# 位置の設定
top = intro_clip.h // 2
black_band = black_band.with_position(("center", "top"))
ace_text = face_text.with_position(("center",top ))
intro_text = intro_text.with_position(("center", 200))

○画像挿入
# ImageClipクラスを使用して動画の読み込み、詳細の設定を行う
logo_clip = ImageClip("../resources/logo_bbb.png").resized(width=400) 

3. 動画効果追加

それぞれ切り取ったクリップにフィルタをかけていきます。

  • フィルタを関数として切り出す
  • 施したい色を配列形式で定義する
  • numpyを使用して各ピクセルに対して処理を行う
  • その他のEffectをかける場合はwith_effectsとvfxという機能を使用する
import numpy as np

def sepia_filter(frame: np.ndarray):
    # セピアフィルターの変換行列を定義
    sepia_matrix = np.array(
        [
            [0.393, 0.769, 0.189], # Red [R, G, B]   配列内で定義された色に基づく「赤」
            [0.349, 0.686, 0.168],  # Green [R, G, B] 配列内で定義された色に基づく「緑」
            [0.272, 0.534, 0.131],  # Blue [R, G, B]  配列内で定義された色に基づく「青」
        ]
    )
    
    # フレームを浮動小数点数型に変換(計算の精度を上げるため)
    frame = frame.astype(np.float32)
    
    # フレームにセピアフィルターを適用
    # 各ピクセルのRGB値にセピア行列を掛ける
    sepia_image = np.dot(frame, sepia_matrix.T)
    
    # ピクセル値を0から255の範囲にクリップ(範囲外の値を修正)
    sepia_image = np.clip(sepia_image, 0, 255)
    
    # フレームを再び8ビット整数型に変換(画像として扱いやすくするため)
    sepia_image = sepia_image.astype(np.uint8)
    
    # セピアフィルターが適用されたフレームを返す
    return sepia_image

# image_taransform各フレームに対して処理を適用する
rambo_clip = rambo_clip.image_transform(sepia_filter)
Before

Big Buck Bunny © Blender Foundation | www.blender.org

After

4.動画のプレビュー

  • 動画の加工にあたり、プレビュー機能が活躍します。
    • 加工した動画を書き出し前に確認できる
    • 引数でfpsを指定することができる(プレビューはPCの動作が重くなるので低めの値設定にしておく )
edited_clip.preview(fps=10)
edited_clip2.preview(fps=10)

5.動画の表示タイミング設定

1~3のステップで作成したクリップの表示タイミングを設定していきます。

# 6秒間表示、動画開始3秒後から再生
intro_text = intro_text.with_duration(6).with_start(3) 

# intro_text再生から2秒後に再生、intro_textの終了と同時に再生終了
logo_clip = logo_clip.with_start(intro_text.start + 2).with_end(intro_text.end)  

# intro_clipが終了したら再生
bird_clip = bird_clip.with_start(intro_clip.end)

#  bunny_clip の開始時間から2秒後に始まり、7秒間表示
bunny_text = bunny_text.with_start(bunny_clip.start + 2).with_duration(7)

動画レンダリング

最終工程となる加工したクリップをつなぎ合わせてレンダリングを行います。

  • CompositeVideoClipに加工クリップの配列を渡し、final_clipに結合したクリップ格納
  • 様々なクラスがmoviepyには用意されており、音声ファイル、Gifなどの形式で出力が可能
final_clip = CompositeVideoClip(
        [
        intro_clip,
        intro_text,
        logo_clip,
        bird_clip,
        bird_text,
        bunny_clip,
        bunny_text,
        rodents_clip,
        rodents_text,
        rambo_clip,
        revenge_text,
        made_with_text,
        moviepy_clip,
])

# クリップの書き出し(bbb_edit.mp4という名前の動画ファイル)
final_clip.write_videofile("../resources/rendered/bbb_edit.mp4")

完成物

上記を踏まえてBig Back Bunnyの予告編に編集を加えてみました。

チュートリアル予告編 再編集動画

■2.OpenCVを使用したロトスコープ風動画の作成

前提:ロトスコープとは?

モデルの動きをカメラで撮影し、それをトレースしてアニメーションにする手法。マックス・フライシャーにより考案され、短編アニメーション映画『インク壺の外へ』(1919年)で初めて商業作品に使用された。

1934年にフライシャースタジオが所有していたロトスコープに関する特許の独占権の期限が切れ、他の会社でもロトスコープの使用が見られるようになった。ディズニーアニメーション映画白雪姫』(1937年

以下の動画を元に進めていきます。

目標物とする形

実際のロトスコープ

OpenCV(cv2)とは

  • OpenCV(Open Source Computer Vision Library)は、コンピュータビジョンや画像処理のためのオープンソースライブラリです(Copylot)
  • 主な機能
    • 画像処理、動画解析、機械学習、3D再構築、物体検出、顔認識など

ロトスコープフィルタの作成

  • transform関数(image_transeformと機能は類似)を使用して全フレームに加工を行っていく。
  • 以下のサイト、ソースをベースに値を調整していく(Copylotにも頼ります。)

参考サイト:

  cv2.Canny(): Canny法によるエッジ検出の調整をいい感じにする #Python – Qiita

  OpenCVで使われるcannyとは?利用法からCanny法の理論などを徹底解説 | 「モノづくりから始まるエンジニア」 

import cv2

def pen_sketch_filter(frame: np.ndarray) -> np.ndarray:
    """
    動画フレームにペンスケッチ風のエフェクトを適用する
    Parameters:
        frame (np.ndarray): 入力動画フレーム
    Returns:
        np.ndarray: スケッチ風のエフェクトが適用されたフレーム。
    """
    # フレームをグレースケールに変換
    # 入力: RGB形式のフレーム
    # 出力: グレースケール画像
    gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
    
    # コントラストと明るさを調整
    # alpha: コントラストの強さ(1.0で元の画像と同じ)
    # beta: 明るさの調整値(正の値で明るく、負の値で暗くする)
    alpha = 1.6  # コントラストの強さ
    beta = 5     # 明るさの調整値
    gray = cv2.convertScaleAbs(gray, alpha=alpha, beta=beta)

    # ノイズを減らし、エッジ検出の精度を向上させるためにガウシアンぼかしを適用
    # 第一引数: 入力画像
    # 第二引数: カーネルサイズ(奇数で指定、(1, 1)はぼかし効果がほとんどない)
    # 第三引数: X方向の標準偏差(0の場合、自動計算)
    blurred = cv2.GaussianBlur(gray, (1, 1), 0)

    # Canny法を使用してエッジを検出
    # 第一引数: 入力画像
    # 第二引数: 弱いエッジを検出するための閾値(副次的にハッチングの制度にも影響する)
    # 第三引数: 強いエッジを検出するための閾値
    edges = cv2.Canny(blurred, threshold1=50, threshold2=70)
    
    # エッジを強調する処理(エッジを太くする)
    # 第一引数: カーネルサイズ(エッジを膨張させる範囲を指定)
    # 第二引数: 繰り返し回数(膨張処理を何回繰り返すか)
    kernel = np.ones((5, 5), np.uint8)  # カーネルサイズを指定
    edges = cv2.dilate(edges, kernel, iterations=1)

    # エッジを反転して黒い線を白い背景にする
    # 第一引数: 入力画像
    # 出力: 白い背景に黒い線の画像
    inverted_edges = cv2.bitwise_not(edges)

    # ハッチング効果を追加(反転したエッジとグレースケール画像をブレンド)
    # 第一引数: 最初の画像(エッジ画像)
    # 第二引数: 最初の画像の重み(エッジの強さ)
    # 第三引数: 二番目の画像(グレースケール画像)
    # 第四引数: 二番目の画像の重み(背景の強さ)
    # 第五引数: 加算されるスカラー値(全体の明るさ調整)
    pen_sketch = cv2.addWeighted(inverted_edges, 0.8, gray, 0.0, 0)

    # RGB形式に変換して互換性を保つ
    # 入力: グレースケール画像
    # 出力: RGB形式の画像
    return cv2.cvtColor(pen_sketch, cv2.COLOR_GRAY2RGB)

def main():
    # 動画を読み込む
    video = VideoFileClip("../resources/rendered/walking.mp4")

    # 人物検出とスケッチフィルタを適用(各フレームをフィルタに通すイメージ)
    filtered_clip = video.transform(lambda get_frame, t: pen_sketch_filter(get_frame(t)))

パターン1:コントラスト低め、エッジの検出高め

walking_sketch_filtered1.gif

パターン2:明るさ高、エッジの検出高め

walking_sketch_filtered2.gif

パターン3:明るさ低め、コントラスト高め、ぼかし弱め、弱いエッジの検出のしやすさ高め、エッジの強調高め

walking_sketch_filtered3.gif

”パターン3” が個人的には理想的な加工になりました。

おまけ: 人物をトラッキングする(動いているものを対象とする)

  • 生成AIに依頼したので処理の詳細は割愛
  • フレームの前後を比較し、動きを検知するため、背景なども一部取り込まれてしまう。
  • 長方形のマスクで対象を切り取るため完全な切り抜きにはならない

■終わりに

  • moviepyは簡易的な操作であれば動画の編集が行える
  • 長めのコンテンツ作成には不向き。その反面、定型的な作業であればかなり効率的な編集作業ができる
  • OpenCVなどのライブラリと組み合わせることで、対象クリップのみ加工することができる。(機械学習を取り入れることでより効率的に)

■参考サイト

MoviePy documentation — MoviePy documentation

Pythonで動画編集を簡単に!MoviePyライブラリの完全ガイド #Video – Qiita