Python初心者のための目的志向のプログラミング。4回に分けて、基本機能から、もっともらしいタイムラプス動画作成に至るまでのコードを記述してきました。いきなり全部入りのコードを見てもわかりづらいと思ったので。
もともと自分の勉強がてら、調べながらじょじょにコードを膨らませてきました。
自分自身が、本を読みながら文法から始めるのが苦手なので、まずは作るものから決めて慣れていこうと思って始めています。
これを最後整理したいと思います。
これまでの履歴
大前提として、Python触ったことなかったです。Cないしそれに似た言語しか触ったことがない、そんな状態からスタートしています。勉強がてら書いてみたものです。逆にそんな人を対象にしています。
まず、ソースの画像データに関しては、適当に設置したカメラでインターバル撮影した複数のJPEGから、タイムラプス動画を作る。適当に設置したので、画角はずれており、残したい画像が必ずしも中心にない。というデータを前提にしていました。
なのでまずその1で、「画像のトリミングを行う。また、三脚が傾いていたため、その補正(回転)」を行いました。これは画像処理としてOpenCVの力を借りつつ、画像の切り出しを特定のフォルダにある全画像に対して行い、動画として保存するといった流れで行いました。
続けて、その2で、もっともらしい動画にするために、「フェードイン、フェードアウト」を行いました。これは真っ黒の画像を用意して、そこに対してフェードイン、フェードアウトを行うものを作りました。
その3では、動画にフレームの動きを入れるために、「パン、ズーム」を入れてみました。これは切り出し位置とサイズをじょじょに変化させるもので、最初の切り出し位置から最後の切り出し位置までを線形補間でつないで、ループさせました。例えば、画像の左から右など、タイムラプス動画に動きを持たせることができるようになりました。
最後にその4では、動画ファイル管理をしやすくするために「タイムスタンプの付与」を行いました。
今回整理したコードは上記4つの手順を踏んだものになっているので、勉強する目的であれば順に追っていただいた方がよいです。わかる人であれば今回の整理済み、全機能搭載のコードだけ見ていただいて、アドバイスいただければ…。
コード
いったんその4までで完成を見たのですが、最後にコードを整理して、ちょっとダサい部分や、今後GUIを導入したときに不便になりそうな箇所を修正しようと思います。
以下全コードを記載します。90行です。mainに記述のfile pathさえ変更すれば、コピペで動くと思います。
import os import cv2 import numpy as np import glob from PIL import Image from PIL.ExifTags import TAGS import datetime WIDTH = 1920 #動画出力幅 HEIGHT = 1080 #動画入力幅 FPS = 30.0 #フレームレート FADE = 10 #フェードイン・フェードアウトフレーム数 TEXT = '2019 camp in hokkaido' ############################### # jpegファイルからexifデータがあればそこから、なければファイル更新日時を取得する関数 def getDate(fname): try: img = Image.open(fname) exif = img._getexif() for id, val in exif.items(): tg = TAGS.get(id, id) if tg == "DateTimeOriginal": return datetime.datetime.strptime(val, '%Y:%m:%d %H:%M:%S') except AttributeError: return datetime.datetime.fromtimestamp(os.path.getmtime(fname)) return datetime.datetime.fromtimestamp(os.path.getmtime(fname)) ############################### # 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] -= shift[0] trans[1, 2] -= shift[1] return cv2.warpAffine(img, trans, (WIDTH, HEIGHT), cv2.INTER_CUBIC) ############################### # 連番画像からタイムラプス動画を作る関数 def makeLaps(files,outvideo,start,end): dt = getDate(files[len(files)-1]) #最終フレームの更新日時を取得 # 画像入力 fourcc = cv2.VideoWriter_fourcc('m','p','4','v') video = cv2.VideoWriter(outvideo, fourcc, FPS, (WIDTH, HEIGHT)) mask = np.zeros((HEIGHT,WIDTH,3), dtype="uint8") #フェードするフレーム(真っ黒を想定) # mask = np.full((HEIGHT,WIDTH,3),255, dtype="uint8") #フェードするフレーム(真っ白を想定) for i,file in enumerate(files): img = cv2.imread(file) print(i,"/",len(files),"[",file,"]") current = (end*i + start*(len(files)-i)) / len(files) #線形補間 frame = convert(img,current[:2],current[2],current[3]) alpha = 1 if i < FADE: alpha = i/FADE elif i > len(files)-FADE: alpha = (len(files)-i-1)/FADE blended = cv2.addWeighted(frame, alpha, mask, 1 - alpha, 0) # cv2.putText(blended, TEXT, (0, HEIGHT-10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA) video.write(blended) video.release() print("date:",dt) os.utime(outvideo, (dt.timestamp(), dt.timestamp())) ############################### def main(): root_path = 'img\\photos' #ソース画像が置いてあるパス save_file = 'img\\photos\\out.mp4' #出力動画 files = glob.glob(root_path + "\\*.jpg") #ファイル取得 if len(files) == 0: print ("no files in ",root_path) return start = np.array([1000,800,1,0.5]) #座標x,座標y,回転角,倍率の順 end = np.array([1000,800,1,0.5]) makeLaps(files,save_file,start,end) if __name__ == '__main__': main()
解説
その1から順に追っていただければ基本的には解説は不要だと思います。ほぼそのままです。2点ばかり修正をしているので、その箇所だけ簡単に。
listdirよりglobの方が使いやすい
これまで、特定のフォルダにあるすべてのファイル名を取得する際に
files = os.listdir(ROOT_PATH)
を使っていました。ただこれで得られるリストはファイル名だけで、フルパスになっていません。なので取り出しには
img = cv2.imread(ROOT_PATH + "\" + file)
とする必要がありました。またJPEG以外のファイルが混在していても、それをリストにしてしまいます。エラー処理をしていないので、おそらく落ちます。そこでglob。これだと、正規表現が使えるようなので、
files = glob.glob(ROOT_PATH + "\*.jpg")
で拡張子.jpgのファイルだけリストにしてくれます。加えて、フルパスが入ってくるので、取り出しには
img = cv2.imread(file)
でOKです。今回のような目的であれば、globの方がわかりやすいです。
main部を関数化
今後タイムラプス動画を作る箇所を別のコードから呼び出したくなることを想定して、関数化させました。大したことではないですが以前からの差分です。GUIを作ってそこから呼び出せるとかっこいいかなと思って分けました。
併せて、ファイルが存在するパス、保存するファイル名を定数からmain関数の変数にしました。動画を作成する関数としてはその方が自然かと思いまして。
あとリストサイズが0の場合エラーを出すようにしました。処理中の画像ファイル名をprintするようにしました。ちょっとした心遣いです。
cv2.putText
あとおまけ的には、動画に文字列を入れるコードも1行足しました。
cv2.putText(blended, TEXT, (0, HEIGHT-10), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
で、画像の左下座標(0, Height-10)から、白(255,255,255)で文字列(TEXT)を挿入しています。ちょっとしたメモを入れておくのに便利かなと思って入れています。がコメントアウトしてます。日本語入りません。フォント情報がないようです。OS依存もしちゃいそうなので、そこまで頑張りませんでした。この関数の詳細は別途調べていただいた方がよろしいかと思います。
まとめ
いったんPythonとしてのタイムラプス動画作成スクリプトは完成とします。
流行りに乗った形で、Python触れてみました。とりあえず作るものから決めて、いろいろ試行錯誤してみましたが、何とか出来上がりました。何せ初心者のコードなので、文法的にはいけてないところもあるかもしれませんが、逆にそのあたりが初心者向きのコードなのではないかとも思います。Cに近くなってしまっているかもしれません。
次は心機一転pythonで、GUIを使って、このシフト、回転、ズームのパラメータを決めるビューアーを作ってみようと思います。
コメント