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行です。まぁいいか。
黒からのフェードインを入れると動画のサムネイル表示が黒くなってしまって、どんな動画だかわからない問題があったりするので、その場合はフェードインだけは捨ててもいいかもしれません。するとさらに行数は減ります。
次は画角のパン、ズームのエフェクトを入れようと思います。
コメント