Pythonでタイムラプス その2(フェードイン)

python

 Python初心者のための目的志向のプログラミング。前回記述した、連番JPEGからのシンプルなタイムラプス動画作成コードを一部編集し、開始と終了フレームに対してフェードイン、フェードアウトを加えて、もう少しもっともらしい動画に仕立てます。

 これも結論数行の追加でできました。前回のコードに20行程度加えるだけで完成してしまいました。

やりたいこと

 適当に設置したカメラでインターバル撮影した複数のJPEGから、タイムラプス動画を作る。適当に設置したので、画角はずれており、残したい画像が必ずしも中心にないため、画像のトリミングを行う。また、三脚が傾いていたため、その補正(回転)も行う。(キャンプ場で設置時フレーミングしないです。ズームも未調整で設置をした前提の画像)

(ここまで前回完成)

 開始に真っ黒からのフェードイン。終了に真っ黒へのフェードアウト。を加えます。

方針

 真っ黒フレーム(画像)を用意して、それに対してフレーム番号をもとに、アルファブレンド(フェード)していく。

コード

 前回のコードとほとんど変わっていません。間違い探しレベル。

import os
import cv2
import numpy as np

ROOT_PATH = 'E:\\tmp\\photo4'       #ソース画像が置いてあるパス
SAVE_FILE = 'E:\\tmp\\photo4.mp4'   #出力動画
WIDTH = 1920    #動画出力幅
HEIGHT = 1080   #動画入力幅
FPS = 10.0      #フレームレート
FADE = 5        #フェードイン・フェードアウトフレーム数

###############################
# imgをrotateだけ回転しshiftだけずらす
def convert(img, shift,angle,scale):
    # img    ソース画像
    # shift  シフト量(shift[0] x方向,shift[1] y方向)
    # rotate 回転角度(度)
    
    height, width = img.shape[:2]
    center = (int(width / 2), int(height / 2))
    trans = cv2.getRotationMatrix2D(center, angle, scale)
    trans[0, 2] -= int(shift[0])
    trans[1, 2] -= int(shift[1])

    return cv2.warpAffine(img, trans, (WIDTH, HEIGHT), cv2.INTER_CUBIC)

###############################
def main():

    #ファイル取得
    files = os.listdir(ROOT_PATH)

    shift = [-39,-178]
    angle = -2
    scale = 1.0

    # 画像入力
    fourcc = cv2.VideoWriter_fourcc('m','p','4','v')
    video = cv2.VideoWriter(SAVE_FILE, fourcc, FPS, (WIDTH, HEIGHT))
    mask = np.zeros((HEIGHT,WIDTH,3), dtype="uint8")    #フェードするフレーム(真っ黒を想定)

    for i,file in enumerate(files[:FADE]):
        img = cv2.imread(ROOT_PATH + "\\" + file)
        print("FADE IN:" + ROOT_PATH + "\\" + file)
        frame = convert(img,shift,angle,scale)
        alpha = i/FADE
        blended = cv2.addWeighted(frame, alpha, mask, 1 - alpha, 0)
        video.write(blended)

    for file in files[FADE:-FADE]:
        img = cv2.imread(ROOT_PATH + "\\" + file)
        print(ROOT_PATH + "\\" + file)
        frame = convert(img,shift,angle,scale)
        video.write(frame)

    for i,file in enumerate(files[-FADE:]):
        img = cv2.imread(ROOT_PATH + "\\" + file)
        print("FADE: OUT" + ROOT_PATH + "\\" + file)
        frame = convert(img,shift,angle,scale)
        alpha = 1-(i+1)/FADE
        blended = cv2.addWeighted(frame, alpha, mask, 1 - alpha, 0)
        video.write(blended)

    video.release()

if __name__ == '__main__':
    main()

解説

 追加コードに対して説明します。

mask = np.zeros((HEIGHT,WIDTH,3), dtype="uint8")    #フェードするフレーム(真っ黒を想定)

 で出力サイズと同じだけのRGB 3chを想定した真っ黒(画素値全部0)の画像(mask)を生成しています。zerosでゼロ埋めされます。グレースケールの画像が入ってきたら残念ながら駄目でしょうね。白に向かってフェードさせたければ、

     mask = np.full((HEIGHT,WIDTH,3),255, dtype="uint8")    #フェードするフレーム(真っ白を想定)

としてあげればよさそうです。この255の部分を任意の値にすればグレーへのフェードもできます。

続いてfor文。3つのフェーズに分けています。フェードイン、通常、フェードアウト定数FADEでループを区切っています。

 まずフェードイン

for i,file in enumerate(files[:FADE]):

 リストの区間の指定が、いかにもpythonらしい記述。まだ慣れません。

 続いて通常の箇所

for file in files[FADE:-FADE]:

 普通です。FADEから最後-FADEまでのループ

 そしてフェードアウト

for i,file in enumerate(files[-FADE:]):

 このマイナスの区間指定も慣れない。

 エラー処理はしていません。全フレーム数はFADEの2倍より多くある前提です。またFADEは1より大きい正の整数を前提としています。これも性善説に立っています。

 ここで見慣れないenumerateという関数ですが、これこういう時にはすごく便利で、indexも同時に返してくれます。なので、

 for 変数1,変数2, in enumerate(list)

 とすると変数1にindexが、変数2にlistの中身が返ってきます。今回fade in/outの時にindex番号が必要なので、別途カウンタを回すよりクールに書けてます。(知らなきゃこんな書き方できないな…)

 まずフェードイン。これはindex iを使って、現在の画像frameと、黒を示すmaskとのアルファブレンド比率alphaを求めています。その後addWeighted関数でアルファブレンドしているだけです。これはOpenCVの関数。iが小さいほどmaskの比率が高いことになります。

     alpha = i/FADE
     blended = cv2.addWeighted(frame, alpha, mask, 1 - alpha, 0)

 ここでframeにあたる画像の作り方ですが、これは前回の解説にあるように、オリジナルの画像から切り出して変換するだけで、コードの変更は加えていません。

 同様にフェードアウト。これはフェードインの逆をやっているだけです。アルファブレンド比率alphaの計算ですが、1から先ほどの計算結果を引く形で逆転させています。フレームが進むほどmaskの比率が高くなります。(i+1してるのは最後真っ黒で終わるため)

     alpha = 1-(i+1)/FADE
     blended = cv2.addWeighted(frame, alpha, mask, 1 - alpha, 0)

 enumerate関数の仕様でしょう。リストの途中(-FADE)からループを回していますが、iは0から始まっています。

 これだけです。FADEで指定したフレーム数だけフェードイン、フェードアウトします。それ以外のフレームに対しての処理は以前のコードのままです。

まとめ

 とりあえず簡単なコードの追加でフェードイン、フェードアウトする動画が作れました。記述量を少なくして、わかりやすくすることを優先させたので、エラー処理や、イン・アウトのフレーム数を同じにするなど、一部手抜コードになっていますが、さほど困りません。(残念なのはFADEを0にしてもフェードなくした動画にはならない…)

 同じような記述のfor文が、3つに分かれてしまったのが少しダサいですが、これよりスマートな書き方が思いつきませんでした。処理時間考慮しなければfor文にifをネストさせるんだろうなぁ。このあたりが処理速度気にしたCの癖なのかな。がトータル70行です。まぁいいか。

 黒からのフェードインを入れると動画のサムネイル表示が黒くなってしまって、どんな動画だかわからない問題があったりするので、その場合はフェードインだけは捨ててもいいかもしれません。するとさらに行数は減ります。

 次は画角のパン、ズームのエフェクトを入れようと思います。

python画像処理
スポンサーリンク
キャンプ工学

コメント

タイトルとURLをコピーしました