動画をYoutube等にアップロードする場合、ユーザビリティを考えてテロップを入れたい時があります。動画編集のソフトは有料無料様々ありますが、プログラムを自分で組むことができれば、細かい演出も自由自在です。ここではPythonで動画の任意の位置・時間にテロップを入れる方法を紹介します。
こんにちは。wat(@watlablog)です。
Python動画編集シリーズです!今回は動画へテロップを入れる方法を紹介します!
ここではPythonを使った動画編集方法を説明していますが、基本的な文法の説明はしていません。もしまだPythonプログラミングに不慣れであればPython特化型学習サービスのPyQ(パイキュー)がオススメです!僕はPythonを初めて数か月後に登録しましたが、てっとり早く文法を覚えるのに最適!
他にも無料体験がある「TechAcademy」でもPythonを学ぶことが出来ます!当ブログのコード説明は基本的な文法の説明まではしていないので、自分に合った学習方法を見つけてスキルアップしておくと読みやすいと思います。
動画にテロップを入れる3つのメリット
①ユーザビリティが向上する
動画にテロップを入れることでユーザビリティが向上します。
ユーザビリティとは、日本語で使用性と訳されたりしますが、
「特定の利用状況において、特定のユーザによって、ある製品が、指定された目標を達成するために用いられる際の、有効さ、効率、ユーザの満足度の度合い」
…と国際規格で定義されています。
動画の場合、Youtube等の動画サイトで視聴する人が多いと思いますが、Youtube動画を視聴する時のシチュエーションは様々です。
音声だけ聞いていたい人、映像だけ見たい人…利用形態は色々ありますよね。
例えば、電車の中で音声をミュートにして映像だけ楽しみたい人にとって、音声の内容がテロップで書いてあればユーザの満足度が上がります。これがユーザビリティの向上です。
②視聴者に効率的に情報を伝えることができる
テロップは単に出演者がしゃべっている内容を書くだけではありません。
関連の情報を画面内に表示させることで、視聴者により多くの情報を伝えることができます。
あまり多くの情報を詰め込み過ぎるのはよくありませんが、視聴者が求める情報を文字で表示させることは大変有効な手段です。
③オリジナリティを高めることができる
テロップは情報を伝えるだけではありません。
フォントの種類、大きさ、色、装飾を変化させることで視聴者の視線を集めることができます。
人は目立つ所に目線が行きます。科学技術計算の中には人の視線がどこに行くかを推定するためのサリエンシーディテクションというものがあります。おそらく重要な場面でフォントのサイズを大きくしたりするだけで効果的な演出ができることでしょう。
テロップ1つとっても、動画作成者のオリジナリティをふんだんに盛り込むことができます!
当WATLABブログでは「Pythonでサリエンシーマップを作成!人の視線の行き場を数値化」という記事でプログラミング方法を紹介していますので、興味のある方は是非読んでみて下さい。
Pythonで動画にテロップを入れるコード
プログラムの概要と実行結果の例
コードを説明する前に、実行結果を説明します。
今回のプログラムでは予め用意しておいたmp4形式の動画に対し、任意の場所に、任意の時間を指定して任意の文章を埋め込みます。
これから紹介するコードを実行すると以下のYoutube動画のような結果を得ることができます。
それでは、以下にコードを説明していきます!全コードは下の方に載せていますので、説明よりも先に全コードが見たい方はスクロールして下さい。
インポートするパッケージ
今回のコードでインポートするパッケージは、動画を取り扱うためにOpenCVことcv2、日本語フォントの文字入れに対応したPillowことPIL、配列処理のNumPy、ファイルパスの取り扱いにosです。
1 2 3 4 |
import cv2 from PIL import Image, ImageFont, ImageDraw import numpy as np import os |
メイン実行文
本コードは動画が保存されているディレクトリdir、ファイル名path、フレーム間引き用のstepを設定し、さらにテロップとして書き込む文章が入ったmessageを使います。
このmessageには書き込む文章(str型)の他に、テロップを入れる開始時間と終了時間(浮動小数点も可)をまとめて入れています。型の異なる変数をひとまとめにした型をタプル型と呼びます。
これら変数を使ってm_slice関数を実行する、というのが全体の流れです。
1 2 3 4 5 6 7 8 9 10 11 |
dir = 'movie' # 動画が保存されているディレクトリ path = 'telop-sample.mp4' # ファイル名 step = 1 # 動画内で処理するフレーム間隔(1より大きい整数だと動画が間引かれる) # [文章, 開始時間, 終了時間]:ここで指定した動画内時間の範囲にテロップ文章を入れる message = [['このように', 1, 4], ['動画内のアクションの時間さえわかっていれば', 4.5, 11], ['アクションに合わせたテロップを入れることができます', 12, 17]] # 動画処理の関数を実行 m_slice(path, dir, step, message) |
動画からフレームを抽出して画像処理をする関数
動画編集に必要な設定部分
動画編集の関数を説明しますが、少々長いのでパラメータの設定と動画へのフレーム処理部分の2つに分割して説明していきます。
関数の前半は以下のコードに示すように動画編集に使用するパラメータ設定です。
この関数は動画読み込み用のパスpath、動画が保存されているディレクトリdir、動画のフレーム間引き用のステップstep、テロップとして動画に書き込む文章と動画の何秒目に文字を入れるかといった時間情報をタプル型でまとめたmessageを引数として受け取ります。
in_pathやout_pathは動画の読み込みと保存に使用します。フルパスを作成するためにos.path.joinを使っています。
先に紹介した文字入れの関数は日本語を扱うためにPILを使っていましたが、動画の読み込みやフレーム処理はOpenCVを使っています。そのため文字入れ関数は入出力をOpenCV形式で記載しています。
その他、fourccやVideoWriterは「Pythonでブログの広告が目立つかどうか「動的」に評価する方法」で解説した通りですので、是非この記事も参考にしてみて下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 動画を読み込み1フレームずつ画像処理をする関数 def m_slice(path, dir, step, message): in_path = os.path.join(*[dir, path]) # 読み込みパスを作成 out_path = os.path.join(*[dir, 'out_' + path]) # 書き込みパスを作成 movie = cv2.VideoCapture(in_path) # 動画の読み込み Fs = int(movie.get(cv2.CAP_PROP_FRAME_COUNT)) # 動画の全フレーム数を計算 fps = movie.get(cv2.CAP_PROP_FPS) # 動画のFPS(フレームレート:フレーム毎秒)を取得 W = int(movie.get(cv2.CAP_PROP_FRAME_WIDTH)) # 動画の横幅を取得 H = int(movie.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 動画の縦幅を取得 fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 動画保存時のfourcc設定(mp4用) # 動画の仕様(ファイル名、fourcc, FPS, サイズ) video = cv2.VideoWriter(out_path, fourcc, int(fps / step), (W, H)) ext_index = np.arange(0, Fs, step) # 動画から静止画(フレーム)を抽出する間隔 |
フレームに対する条件分岐部分
関数の後半は動画からフレームを取り出し、各種条件分岐でどの文章を、どのタイミングで、どれくらいの時間描画するかを決定しています。
テロップとして書き込む文章は、指定した時間範囲でフレームに対し描画をするわけですが、それ以外にも書き込む時間になるまで待機したり、用意した文章を使い切るといったパターンにも対応させる必要があります。これらは全てif文で条件分岐させています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
j = 0 # message配列から文章と時間を抜き出す指標番号 section = message[j] # フレームに書き込む文章と時間の初期値 for i in range(Fs): # フレームサイズ分のループを回す print(i) flag, frame = movie.read() # 動画から1フレーム読み込む check = i == ext_index # 現在のフレーム番号iが、抽出する指標番号と一致するかチェックする time = i / int(fps/step) # 抽出したフレームの動画内経過時間 if flag == True: # フレームを取得できた時だけこの処理をする # もしi番目のフレームが静止画を抽出するものであれば、ファイル名を付けて保存する if True in check: # ここから動画フレーム処理と動画保存--------------------------------------------------------------------- # 抽出したフレームの再生時間がテロップを入れる時間範囲に入っていれば文字入れする if section[1] <= time <= section[2]: frame = telop(frame, section[0], W, H) # テロップを入れる関数を実行 # 再生時間がテロップ入れ開始時間より小さければ待機する elif section[1] > time: pass else: # 用意した文章がなくなったら何もしない if j >= len(message) - 1: pass # 再生時間範囲になく、まだmessage配列にデータがある場合はjを増分しsectionを更新 else: j = j + 1 section = message[j] video.write(frame) # 動画を1フレームずつ保存する # ここまでが動画フレーム処理と保存--------------------------------------------------------------------- # i番目のフレームが静止画を抽出しないものであれば、何も処理をしない else: pass else: pass retur |
画像に文字を入れる関数
次は画像に文字を入れる関数です。
動画も1枚1枚のフレームを抜き取って文字を入れているので、動画編集といっても画像処理をしていることに変わりはありません。
ここで紹介する関数は「Pythonで画像に日本語文字を入れる方法」で紹介した文字入れコードに、「Pythonで画像に描画するテキストのピクセルサイズを取得」で紹介したテキストのピクセルサイズ取得コードを使っています。
テキストのピクセルサイズは文章の位置制御をするために使用しています。画像と文章の横幅と高さを両方知ることで、文章全体を中央揃えにしたり、字幕のようにフレームの下に配置させたりすることができるようになります。
その他の内容は上記2記事で説明した通りなので、是非参考にしてみて下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 画像に文字を入れる関数 def telop(img, message, W, H): font_path = 'C:\Windows\Fonts\meiryo.ttc' # Windowsのフォントファイルへのパス font_size = 24 # フォントサイズ font = ImageFont.truetype(font_path, font_size) # PILでフォントを定義 img = Image.fromarray(img) # cv2(NumPy)型の画像をPIL型に変換 draw = ImageDraw.Draw(img) # 描画用のDraw関数を用意 w, h = draw.textsize(message, font) # .textsizeで文字列のピクセルサイズを取得 # テロップの位置positionは画像サイズと文字サイズから決定する # 横幅中央、縦は下 position = (int((W - w) / 2), int(H - (font_size * 1.5))) # 中央揃え #position = (int((W - w) / 2), int((H - h) / 2)) # テキストを描画(位置、文章、フォント、文字色(BGR+α)を指定) draw.text(position, message, font=font, fill=(255, 255, 255, 0)) # PIL型の画像をcv2(NumPy)型に変換 img = np.array(img) return img |
全コード(コピペ用)
以下にコピペがしやすいように全コードをまとめて再掲します。
是非動画や文章を変更して使ってみて下さい!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import cv2 from PIL import Image, ImageFont, ImageDraw import numpy as np import os # 画像に文字を入れる関数 def telop(img, message, W, H): font_path = 'C:\Windows\Fonts\meiryo.ttc' # Windowsのフォントファイルへのパス font_size = 24 # フォントサイズ font = ImageFont.truetype(font_path, font_size) # PILでフォントを定義 img = Image.fromarray(img) # cv2(NumPy)型の画像をPIL型に変換 draw = ImageDraw.Draw(img) # 描画用のDraw関数を用意 w, h = draw.textsize(message, font) # .textsizeで文字列のピクセルサイズを取得 # テロップの位置positionは画像サイズと文字サイズから決定する # 横幅中央、縦は下 position = (int((W - w) / 2), int(H - (font_size * 1.5))) # 中央揃え #position = (int((W - w) / 2), int((H - h) / 2)) # テキストを描画(位置、文章、フォント、文字色(BGR+α)を指定) draw.text(position, message, font=font, fill=(255, 255, 255, 0)) # PIL型の画像をcv2(NumPy)型に変換 img = np.array(img) return img # 動画を読み込み1フレームずつ画像処理をする関数 def m_slice(path, dir, step, message): in_path = os.path.join(*[dir, path]) # 読み込みパスを作成 out_path = os.path.join(*[dir, 'out_' + path]) # 書き込みパスを作成 movie = cv2.VideoCapture(in_path) # 動画の読み込み Fs = int(movie.get(cv2.CAP_PROP_FRAME_COUNT)) # 動画の全フレーム数を計算 fps = movie.get(cv2.CAP_PROP_FPS) # 動画のFPS(フレームレート:フレーム毎秒)を取得 W = int(movie.get(cv2.CAP_PROP_FRAME_WIDTH)) # 動画の横幅を取得 H = int(movie.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 動画の縦幅を取得 fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # 動画保存時のfourcc設定(mp4用) # 動画の仕様(ファイル名、fourcc, FPS, サイズ) video = cv2.VideoWriter(out_path, fourcc, int(fps / step), (W, H)) ext_index = np.arange(0, Fs, step) # 動画から静止画(フレーム)を抽出する間隔 j = 0 # message配列から文章と時間を抜き出す指標番号 section = message[j] # フレームに書き込む文章と時間の初期値 for i in range(Fs): # フレームサイズ分のループを回す print(i) flag, frame = movie.read() # 動画から1フレーム読み込む check = i == ext_index # 現在のフレーム番号iが、抽出する指標番号と一致するかチェックする time = i / int(fps/step) # 抽出したフレームの動画内経過時間 if flag == True: # フレームを取得できた時だけこの処理をする # もしi番目のフレームが静止画を抽出するものであれば、ファイル名を付けて保存する if True in check: # ここから動画フレーム処理と動画保存--------------------------------------------------------------------- # 抽出したフレームの再生時間がテロップを入れる時間範囲に入っていれば文字入れする if section[1] <= time <= section[2]: frame = telop(frame, section[0], W, H) # テロップを入れる関数を実行 # 再生時間がテロップ入れ開始時間より小さければ待機する elif section[1] > time: pass else: # 用意した文章がなくなったら何もしない if j >= len(message) - 1: pass # 再生時間範囲になく、まだmessage配列にデータがある場合はjを増分しsectionを更新 else: j = j + 1 section = message[j] video.write(frame) # 動画を1フレームずつ保存する # ここまでが動画フレーム処理と保存--------------------------------------------------------------------- # i番目のフレームが静止画を抽出しないものであれば、何も処理をしない else: pass else: pass return dir = 'movie' # 動画が保存されているディレクトリ path = 'telop-sample.mp4' # ファイル名 step = 1 # 動画内で処理するフレーム間隔(1より大きい整数だと動画が間引かれる) # [文章, 開始時間, 終了時間]:ここで指定した動画内時間の範囲にテロップ文章を入れる message = [['このように', 1, 4], ['動画内のアクションの時間さえわかっていれば', 4.5, 11], ['アクションに合わせたテロップを入れることができます', 12, 17]] # 動画処理の関数を実行 m_slice(path, dir, step, message) |
まとめ
動画に対してテロップを入れることで、自作した動画のユーザビリティ向上、効率的な情報の提供、視線を意図的に集めた効果的でオリジナリティのある演出といった様々なメリットがあります。
本記事ではPythonを使った動画へのテロップ挿入プログラムを紹介しました。
これまで習得したプログラミングテクニックを多く使っていますので、是非記事内の関連リンクも参考にしてみて下さい!
動画へのテロップ挿入はやってみたかった内容でしたが、結構簡単に出来ることがわかりました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!
コメント