Python 動画編集にて、抽出範囲を指定して更にリサイズする

 本記事で動画編集は3度目となる。
今回は、下図赤矢印の箇所、つまり、抽出エリアを指定して、不要な場所をカットすることで画像サイズをアップして見易くした。

f:id:HK29:20191201172021p:plain

▼作成した動画はこちら↓

www.youtube.com

■本プログラム

過去の二つの記事よりも、多少コードを見やすくしたつもり。

#!/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
import time

# 動画時間の指定区間の抽出とfps調整する関数
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 extract_area_movie(input_file, out_file, fps, extract_area):
    x, y, w, h = extract_area
    print("start extract_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, (w, h))

    # フレーム1枚ずつ処理する
    for i in range(num_of_frame):
        ret, frame = video.read()
        dst = frame[y:y+h, x:x+w]
        writer.write(dst)
        if i % 50 == 0:
            print(str(i) + "/" + str(num_of_frame))
            #cv2.imwrite('extract_area' + str(i) + '.jpg', dst)
    # 後処理
    writer.release()
    video.release()
    print("num_of_frame -> " + str(num_of_frame))
    print("fps -> " + str(fps))

# 動画にモザイクを掛ける関数
# 関数内で「画像」にモザイクを掛ける関数を呼び出す。
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()
                         #(frame, x, y, w, h, scale=0.1) # モザイク関数を呼び出し
        #dst0 = mosaic_area(frame, 70, 850, 360, 50, scale=0.1)
        #dst1 = mosaic_area(dst0, 600, 850, 180, 50, scale=0.1)
        dst = mosaic_area(frame, 600, 200, 100, 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 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 image_add_message(img, message):
    font_path = 'C:\Windows\Fonts\meiryo.ttc' #msgothic.ttc'
    font_size = 48
    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, 255) 
    draw.text((200, 180), message, font_color, font)
    
    return np.array(img)

# 音声を抽出する関数
def extract_audio(input_video_w, out_audio):
    print("start extract_audio")
    # 読み出す動画のオブジェクト作成
    clip_in = mp.VideoFileClip(input_video_w).subclip()
    # 音声を抽出してファイルに保存
    clip_in.audio.write_audiofile(out_audio)

# 音声を合成する関数
def combine_with_audio(input_video_wo, out_file_w, out_audio):
    # 出力する動画のオブジェクト作成
    clip_out = mp.VideoFileClip(input_video_wo).subclip()
    # 出力する動画のオブジェクトへ音声を合成する
    clip_out.write_videofile(out_file_w, audio=out_audio)

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

# 動画に加える日本語のリスト
global_list = [
'星のドラゴンクエスト\n 略して、星ドラ',
'このゲームの\n配信開始は2015年',
'今や、モンスター闘技場\nなるものがある',
'12/01現在、\n1周年記念イベントで\nバトリア王と対戦できる',
'モンスター闘技場とは\n4匹のモンスターを1チーム\nとして闘うチーム戦',
'戦闘はAIで自動であーる\n',
'ユーザーが行うのは\n主に次の3つ\n1. キャラのレベルを上げること\n2. 武器を選択すること\n3. 性格を選定すること',
'性格を変えるには、\n本というアイテムで\n変えられる',
'本プレイ動画の\n自キャラの紹介をします',
'一番左手前の\nヴァールは、ゾンビ系である。\n「亡者の一撃」スキルが\n強力',
'このヴァールは他に、\nHP20%未満で\n「ピンチにゾンビのちから」と\n「亡者のちから」で\nHP400程回復する',
'カンダタは、\n怪人系である。\nパンツ一丁にマントの\n変質者であーる',
'このカンダタは、\n戦闘開始時に\n「いきなりすっころびの罠」\nと自分がピンチ時の\n「ピンチにゆるしてくれよな」\nが強力',
'ギュメイは、\nけもの系である。\nネコサムライでR',
'このギュメイは、\n素早さが早く、\n攻撃力も高い',
'一番右のジャミスは、\n鳥である。\n通常攻撃で敵を30%で\n「やすみ」状態にさせる',
'このジャミスは、\n戦闘開始時に、\n「いきなり魔鳥のひとみ」で\n45%の確率で敵全体の\n守備力を20%下げる',
'重要なことは、\n1.状態異常を敵キャラに\n 付与するキャラを選定\n2.敵一匹をねらい打つ性格\n3.一撃の攻撃力が高い\nこと',
'ところで、本動画は\niPhoneXRで保存した。\nそしてPythonで編集した',
'1.動画再生時間は\n指定した区間を抽出。\nfpsを60→30へ変更',
'2.スマホの画面サイズ\nから必要範囲を抽出\n画面サイズを拡大した',
'3.一部箇所を\nモザイク処理した。\nどこでしょう?^^',
'4.YouTube用の\nアスペクト比\n(1920, 1080)へ変更した。\n文章も挿入',
'結局、\n星ドラと\nPythonは面白い=3',
'終わり',
]

# メイン関数
if __name__ == '__main__':
    ##### principal parameter
    input_video = "GQRN5478.mp4"
    cut_time = (6, 282) # (start, end) second
    # ※左上が原点→(x,   y,   w,  h)
    extract_area = (0, 180, 828, 1430)
    out_video1 = 'zzz_01_simple.mp4'
    out_audio = 'zzz_audio.mp3'
    out_size = (1920, 1080) # (width, height) = (1920, 1080) or (1280, 720)
    fps = 30
    
    ##### call function
    ### 開始時刻
    start = time.time()
    
    ### 編集1. 動画時間の指定区間の抽出とfps調整する
    simplification(input_video, out_video1, cut_time, fps)
    # 音声ファイルを抽出する
    extract_audio(out_video1, out_audio)
    
    ### 編集2. 動画エリアの指定範囲を抽出する
    input_video = out_video1
    out_video2_wo = "zzz_02_extract_area_wo_sound.mp4"
    #out_video2_w = "zzz_02_extract_area_w_sound.mp4"
    extract_area_movie(input_video, out_video2_wo, fps, extract_area)
    # 音声ファイルを編集2の動画へ結合する
    #combine_with_audio(out_video2_wo, out_video2_w, out_audio)
    
    ### 編集3. モザイク処理
    #input_video = out_video2_w
    input_video = out_video2_wo
    out_video3_wo = "zzz_03_mosaic_wo_sound.mp4"
    #out_video3_w = "zzz_03_mosaic_w_sound.mp4"
    mosaic_movie(input_video, out_video3_wo, fps, x=800, y=400, w=400, h=300, scale=0.1)
    # 音声ファイルを編集3の動画へ結合する
    #combine_with_audio(out_video3_wo, out_video3_w, out_audio)
    
    ### 編集4. アスペクト比を保ったまま指定のサイズへ変更し、日本語文も入れる。
    ###        但し、ここで音がなくなる。次の編集3で音声を結合する。
    #input_video = out_video3_w
    input_video = out_video3_wo
    out_video4_wo = "zzz_04_final_wo_sound.mp4"
    out_video4_w = "zzz_04_final_w_sound.mp4"
    edit_mp4(input_video, out_video4_wo, out_size, fps)
    # 音声ファイルを編集4の動画へ結合する
    combine_with_audio(out_video4_wo, out_video4_w, out_audio)
    
    ### 完了時刻
    out_time = time.time() - start
    print('time:{0:.3f}sec'.format(out_time))
    
    ### 完了時にビープ音で知らす。
    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")

以上

<広告>