Loading [MathJax]/extensions/tex2jax.js
MENU

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

python

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

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

やりたいこと

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

(ここまで前回完成)

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

方針

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

コード

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

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
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()

解説

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

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

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

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

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

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

 まずフェードイン

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

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

 続いて通常の箇所

1
for file in files[FADE:-FADE]:

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

 そしてフェードアウト

1
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の比率が高いことになります。

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

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

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

1
2
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をコピーしました