Python 動画編集(モザイク処理/無地背景色の指定/フォント色の指定/ビープ音)

本記事では、下記リンク先のような動画を作成します。

www.youtube.com

 iPhoneで録画した動画をPythonで編集して作成する。
具体的には、次の1~8のような処理をする。
特に、黒■が本記事の新規項目である。一方、白□は以前の記事に記載したものではあるものの、本記事で出来ること一覧として載せる。
実際に真似する場合には、本記事の最後に記載したプログラム中のそれ前後を参考下さい。

□1. 時刻指定による動画の区間抽出
 →メソッドがある。
 video = mp.VideoFileClip(input_file).subclip(start, end)
■2. 動画の一部分モザイク処理
 →動画からフレーム(画像)毎に処理する。

# モザイクを掛ける範囲を切り取り
cut_area = img[y:y+h, x:x+w]
# 縮小
small = cv2.resize(cut_area, dsize=None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
# 拡大
zoom = cv2.resize(small, dsize=(w, h), interpolation=cv2.INTER_NEAREST)
# 合成する
img[y:y+h, x:x+w] = zoom

□3. 動画のアスペクト比を保ったまま、解像度を変更
 →縦を軸に縮尺して、その変化率を横に適応することで縦横比を保つ

 frame = cv2.resize(frame, dsize=None, fx=scale, fy=scale)

■4. 無地の画像の作成
 →numpyで作成する。背景色の指定はRGBをBGRに変換して与える

new_frame = np.zeros((out_height, out_width, 3), np.uint8) # 黒塗り画像
if True: # True:薄黄色画像に変換。False:黒塗り
rgb_color = (255, 250, 205) #lemonchiffon
bgr_color = tuple(reversed(rgb_color))
new_frame[:] = bgr_color

 □5. 無地の画像を下地に画像の合成
 →合成する画像の配置位置の指定は、左上の座標と幅と高さで指定

#new_frame[上のy座標:下のy座標, 左のx座標:右のx座標]
 new_frame[0:out_height, offset:(frame_width+offset)] = frame

 ■6. 日本語の挿入
 →フォント色の指定方法も記載。指定はRGBの順ではなく、BGRの順で指定

# RGB色の代表例
# 白:(255, 255, 255), 青:(0, 0, 255), 赤:(255, 0, 0), 黒:(0,0,0)
# マゼンダ:(255, 0, 255), ライム:(0, 255, 0), シアン:(0, 255, 255) 
# 但し、PILではRGBでなくてBGRなので、青にしたい場合(255, 0, 0)
font_color = (255, 0, 0) 
draw.text((250, 200), message, font_color, font)

 □7. 動画から音声のみを抽出mp3
 →メソッドがある
clip_in = mp.VideoFileClip(input_video1).subclip()
clip_in.audio.write_audiofile(out_audio)

 ■8. ビープ音を鳴らす。これにより、プログラム終了を音で知らせる
 →from winsound import Beep する。

mysound = [523, 587, 659, 698, 784, 880, 932] # ドレミファソラシ
for e in mysound:
Beep(e, 1000) # 第二引数の1000は1秒

 

▼本プログラム

特記事項:モザイクや画像合成の処理において、思った通りにならない、もしくはエラーになる場合は、凡そ、場所指定に問題がある場合が多いと思われます。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
from PIL import Image, ImageFont, ImageDraw
import numpy as np
import moviepy.editor as mp
from winsound import Beep

# 動画を抽出する関数
def simplification(input_file, out_file, cut_time, fps):
    start, end = cut_time
    video = mp.VideoFileClip(input_file).subclip(start, end)
    video.write_videofile(out_file, fps=fps) # fpsの指定

# 日本語を挿入する関数
def image_add_message(img, message):
    font_path = 'C:\Windows\Fonts\meiryo.ttc' #msgothic.ttc'
    font_size = 58
    font = ImageFont.truetype(font_path, font_size)
    img = Image.fromarray(img)
    draw = ImageDraw.Draw(img)
    
    # RGB色の代表例
    # 白:(255, 255, 255), 青:(0, 0, 255), 赤:(255, 0, 0), 黒:(0,0,0)
    # マゼンダ:(255, 0, 255), ライム:(0, 255, 0), シアン:(0, 255, 255)    
    # 但し、PILではRGBでなくてBGRなので、青にしたい場合(255, 0, 0)
    font_color = (255, 0, 0) 
    draw.text((250, 200), message, font_color, font)
    
    return np.array(img)

# 動画のアスペクト比を調整する関数(但し、ここでは音声は含まれない)
def edit_mp4(input_file, out_file, out_size, fps):
    print("start edit_mp4")
    # アンパック代入
    out_width, out_height = out_size
    # 動画とそのフレームワーク情報を取得
    video = cv2.VideoCapture(input_file)
    width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
    size = (width, height)
    num_of_frame = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = int(video.get(cv2.CAP_PROP_FPS))

    # 出力フォーマットの指定
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    writer = cv2.VideoWriter(out_file, fourcc, fps, out_size)
    # アスペクト比を保ったままた、縦のサイズを指定のサイズに合わせるための比率を算出
    scale = out_height / height
    #cv2.imwrite("blank.bmp", new_img);
    # フレーム1枚ずつ処理する
    flag = num_of_frame // len(global_list)
    j=0
    k=0
    for i in range(num_of_frame):
        if i % 50 == 0:
            print(str(i) + "/" + str(num_of_frame))
        ret, frame = video.read()
        # 無地の画像ファイルをnumpyで作成する
        new_frame = np.zeros((out_height, out_width, 3), np.uint8) # 黒塗り画像
        if True: # True:薄黄色画像に変換。False:黒塗り
            rgb_color = (255, 250, 205) #lemonchiffon
            bgr_color = tuple(reversed(rgb_color))
            new_frame[:] = bgr_color
        if out_width == width:
            #print("set A")
            new_frame[0:out_height, 0:out_width] = frame
        else:
            #print("set B")
            frame = cv2.resize(frame, dsize=None, fx=scale, fy=scale)
            frame_height, frame_width = frame.shape[:2]
            offset = int((out_width - frame_width)*2/3)
            #print(offset)
            #new_frame[上のy座標:下のy座標, 左のx座標:右のx座標]
            new_frame[0:out_height, offset:(frame_width+offset)] = frame
        ### 文字列の挿入
        '''
        print("i -> " + str(i) + "/" + str(num_of_frame))
        print("k/len(global_list) -> " + str(k) + "/" + str(len(global_list)))
        print(global_list[k])
        '''
        new_frame = image_add_message(new_frame, global_list[k])
        writer.write(new_frame)
        j += 1
        if flag < j:
            j = 0
            k += 1
    # 後処理
    writer.release()
    video.release()
    print(str(size) + " -> " + str(out_width) + "," + str(out_height))
    print("num_of_frame -> " + str(num_of_frame))
    print("fps -> " + str(fps))

# 音声を動画に加える関数(編集した動画は一旦、音声がなくなるため)
def extract_audio(input_video1, input_video2, out_file, out_audio):
    print("start extract_audio")
    clip_in = mp.VideoFileClip(input_video1).subclip()
    clip_in.audio.write_audiofile(out_audio)
    clip_out = mp.VideoFileClip(input_video2).subclip()
    clip_out.write_videofile(out_file, audio=out_audio)

# 動画にモザイクを掛ける関数(中で画像にモザイクを掛ける関数を呼び出している)
def mosaic_movie(input_file, out_file, fps, x, y, w, h, scale=0.1):
    print("start mosaic_movie")
    # 動画とそのフレームワーク情報を取得
    video = cv2.VideoCapture(input_file)
    width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
    size = (width, height)
    num_of_frame = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = int(video.get(cv2.CAP_PROP_FPS))

    # 出力フォーマットの指定
    fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
    writer = cv2.VideoWriter(out_file, fourcc, fps, (width, height))

    # フレーム1枚ずつ処理する
    for i in range(num_of_frame):
        ret, frame = video.read()
        #dst = mosaic_area(frame, x, y, w, h, scale=0.1) # モザイク関数を呼び出し
        buf = mosaic_area(frame, 70, 850, 360, 50, scale=0.1)
        dst = mosaic_area(buf, 600, 850, 180, 50, scale=0.1)
        writer.write(dst)
        if i % 50 == 0:
            print(str(i) + "/" + str(num_of_frame))
            #cv2.imwrite('mosaic' + str(i) + '.jpg', dst)
    # 後処理
    writer.release()
    video.release()
    print("num_of_frame -> " + str(num_of_frame))
    print("fps -> " + str(fps))

# 画像にモザイクを掛ける関数
def mosaic_area(img, x, y, w, h, scale=0.1):
    # オリジナルの縦と横長さ
    height, width = img.shape[:2]
    # モザイクを掛ける範囲を切り取り
    cut_area = img[y:y+h, x:x+w]
    # 縮小
    small = cv2.resize(cut_area, dsize=None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST)
    # 拡大
    zoom = cv2.resize(small, dsize=(w, h), interpolation=cv2.INTER_NEAREST)
    # 合成する
    img[y:y+h, x:x+w] = zoom
    
    return img

# ビープ音を鳴らす
def alerm():
    mysound = [523, 587, 659, 698, 784, 880, 932] # ドレミファソラシ
    for e in mysound:
        Beep(e, 1000) # 第二引数の1000は1秒

# 動画に加える日本語のリスト
global_list = ['星のドラゴンクエスト\n 略して、星ドラ',
               '配信開始は2015年。\nその頃から\nプレイしている',
               'スマホはiPhoneXRで\n操作はヌルサク動く',
               'ネット通信で\n4人で協力プレイ可能',
               '知らない3名の名前は\nモザイク掛けてますf^^;',
               'キャラは職業によって、\n攻撃力や体力、守備力\nなどが異なる',
               '武器、防具は、\nガチャで入手する',
               '無論、\nワタクシことぽっくんは\n無課金である',
               'いつの間にか4年。。\n無料ガチャ、\n相当引いた。。',
               'そのため、\n無課金でもそこそこ強い。',
               '最近は、武器の\nハイパーインフレ状態。\n次から次へと\n強いのが出てくる(笑)',
               'このゲームの面白さは、\n武器、防具集めである',
               'それは、昔、\n皆ビックリマンシールを\n集めてたように。。',
               'そして、今や\n私もあなたも\n星ドラおっさん♪おばはん',
               '昔のキャラも出てくる\nドラクエ2の勇者やら=3',
               'ところで、\nこの動画編集には、\nPythonを使用した',
               'スマホの画面サイズから、\nYouTube用の\nアスペクト比へ変更',
               'モザイクを入れたり、\n背景色変えたり、\n文章をこうして入れた',
               '結局、\n星ドラとPythonは\n面白い!ってこと!?',
               'See you',
               ]

# メイン関数
if __name__ == '__main__':
    ### 設定項目
    input_video = "CHEN7662.mp4"
    cut_time = (3, 263) # (start, end) second
    out_video1 = 'zzz_out1_simple.mp4'
    out_audio = 'zzz_audio.mp3'
    out_video2_wo = 'zzz_out2_mosaic_wo_audio.mp4'
    out_video2_w = 'zzz_out2_mosaic_w_audio.mp4'
    out_video3_wo = 'zzz_out3_wo_audio.mp4'
    out_video3_w = 'zzz_out3_w_audio.mp4'
    out_size = (1920, 1080) # (width, height) = (1920, 1080) or (1280, 720)
    fps = 30
    
    ##### 関数呼び出し
    ### 編集1. 指定区間を抽出する
    simplification(input_video, out_video1, cut_time, fps)
    
    ### 編集2. モザイク処理
    input_video = out_video1
    mosaic_movie(input_video, out_video2_wo, fps, x=800, y=400, w=400, h=300, scale=0.1)
    ### 音楽を編集1の動画から抽出して、編集2の動画へ結合する 
    input_video2 = out_video2_wo
    extract_audio(input_video, input_video2, out_video2_w, out_audio)
    
    ### 編集3. アスペクト比を保ったまま指定のサイズへ変更し、日本語文も入れる。
    ###        但し、ここで音がなくなる。次の編集3で音声を結合する。
    input_video = out_video2_w
    edit_mp4(input_video, out_video3_wo, out_size, fps)
    ### 音楽を編集1の動画から抽出して、編集2の動画へ結合する    
    input_video2 = out_video3_wo
    extract_audio(input_video, input_video2, out_video3_w, out_audio)
    
    ### 本プログラムが終わったことを音で知らす。
    alerm()
    
    #############################
    # 上記関数を利用して、動画でなくて画像にモザイクを掛ける場合の例
    '''
    img = cv2.imread('mosaic0.jpg')
    img2 = mosaic_area(img, 70, 850, 400, 50, scale=0.1)
    img3 = mosaic_area(img2, 600, 850, 200, 50, scale=0.1)
    cv2.imwrite('mosaic_000.jpg', img3)
    '''
    print("finished")

以上

<広告>