Pythonを使ってディレクトリ内のすべての画像をアニメーションGIFに変換するプログラムを書いてみました。
PIL(pillow)をimportしていれば簡単にできたのでせっかくなので覚書として残しておきます。
連番JPEGからアニメーションGIF
先の記事で完成させた連番JPEGからタイムラプス動画を作るスクリプトですが、
この結果をmp4動画ではなく、Webで表示できるアニメーションGIFにします。使った関数は、PILのImage.save()関数。
サンプルコード
シンプルな記述をするとこんな感じ。パン、ズーム、フェードイン/アウト等前記事でもりもり入れた機能は今回省いています。
import cv2 import glob from PIL import Image FPS = 10 # フレームレート WIDTH = 400 HEIGHT = 300 ############################### # 連番画像からタイムラプス動画を作る関数 def makeLaps(files, outvideo): # 画像入力 images = [] # fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # video = cv2.VideoWriter(outvideo, fourcc, FPS, (WIDTH, HEIGHT)) for i, file in enumerate(files): img = cv2.imread(file) img = cv2.resize(img, (WIDTH,HEIGHT)) images.append(Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))) # video.write(img) images[0].save(outvideo, save_all=True, append_images=images[1:], optimize=False, duration=1000//FPS, loop=0) # video.release() ############################### def main(): root_path = 'E:\\tmp\\tlps' # ソース画像が置いてあるパス save_file = 'E:\\tmp\\tlps\\test.gif' # 出力動画 files = glob.glob(root_path + "\\*.jpg") # ファイル取得 makeLaps(files, save_file) if __name__ == '__main__': main()
解説
リストの作成
読み込んだ画像をresizeして、ImageのListにappendしていって、最後にImage.saveでgifに保存しているだけです。ここであえてコメントで残しているのが、mp4保存のための記述です。
images = []
で空のリストを作っています。
for i, file in enumerate(files): img = cv2.imread(file) img = cv2.resize(img, (WIDTH,HEIGHT)) images.append(Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)))
for文の中で画像を読み込んで、所定のサイズ(WIDTH,HEIGHT)にresizeして、リストに追加(append)しています。このappendでOpenCVの画像フォーマットをPILの画像フォーマットに変換しています。
この変換ですが、1行で書いてしまっているので、ばらして説明します。まず、OpenCVがなぜかBGR順で画素を扱うので、それをRGB順にします。
cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
これでRGB順になります。その後、フォーマット変換。
Image.fromarray()
これででPILの画像にした後に、リストに追加
images.append()
これでPILで扱う画像のリストが完成します。それを保存します。
リストの保存(images.save())
PILが持つimage.save()を使います。
images[0].save(outvideo, save_all=True, append_images=images[1:], optimize=False, duration=1000//FPS, loop=0)
で保存しています。ここで用いたsaveのオプションですが、このドキュメントによると
save_all
Trueでリスト全フレーム、Falseで最初のフレームだけ保存されるようです。
append_images
追加のフレームとして用いる画像のリストだそうです。あくまで追加されるなので、最初のフレームは呼び元(images[0])の画像になります。なのでこのサンプルのようにリストの0番目でsaveをコールする場合には、リストの1番目の画像から指定してあげないと0番目が2回登場してしまいます。PDFとTIFFのsaveでも使えるらしいです。
optimaze
カラーパレットのうち使わない色を削除してくれるようです。フレーム間で似たようなパレットになる場合に圧縮がかかってくれるのかな。今回のような写真画像をソースとしている場合にはあまり効果が望めない。(一応Trueで小さくはなったが全体の0.3%程度)
duration
1フレームの表示時間。ミリ秒(msec)単位で指定。なので今回は1000msecをFrame Per Secで割り算しています。これもリストで渡すことができるようで、フレーム毎に異なる表示時間の指定もできる模様。(未確認)
loop
表示のループ回数の指定。0を指定すると無限。Webに張り付ける目的であれば無限でいいのかなと思います。
mp4での保存(おまけ)
今回コメントアウトしている箇所を戻して、上記で説明した記述をコメントして、ファイル名の拡張子を*.mp4にしてあげると、mp4動画で保存できます。
images = []
↓
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
video = cv2.VideoWriter(outvideo, fourcc, FPS, (WIDTH, HEIGHT))
images.append(Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)))
↓
video.write(img)
images[0].save(outvideo, save_all=True, append_images=images[1:], optimize=False, duration=1000//FPS, loop=0)
↓
video.release()
逆に前記事で機能もりもりで入れたものに対しては、前記事のコードに対して、上記の入れ替えをしてあげると、GIFでの保存もできました。
結果
これでこんな感じのアニメーションGIFが出来上がりました。
あとがき
本当は前の記事で、タイムラプス動画(mp4)を記事に張り付けたかったのですが、なぜかmp4動画がうまく貼れなかったので、しょうがないのでアニメーションGIFでの投稿を試みました。
結果として、写真のJPEGをソースにしているので、全然圧縮が効きません。あと減色処理にも疑似中間調が使われているようなので、粒粒が見えます。全然mp4の方がいいです。
ということでGIFでの投稿はあきらめたのですが、せっかく試したので記事として残しときます。
コメント