過去に上記の記事でpythonとOpenCVの環境を構築しました。
pythonとOpenCVを使ってgifファイルは作成できないのか、と思い調べてみるとPillowというものを組み合わせればgifファイルを作成できることがわかったのでその備忘録です。
では始めます(先に上げた記事の内容が終わっている前提で進めるので注意してください)。
1:pipを使ってPillowをインストールする
pythonを入れるとpipも入っているはずなので、コマンドプロンプトかPowerShellを立ち上げて以下のコマンドを実行します。
$ pip install Pillow
pipのバージョンが古いと以下のようなメッセージが出てインストールできない場合があります。
WARNING: You are using pip version 19.1.1, however version 20.0.2 is available. You should consider upgrading via the 'python -m pip install --upgrade pip' command.
その場合はメッセージに従って以下のコマンドを実行してpipをアップグレードします。
$ python -m pip install --upgrade pip
これでpipがアップグレードされるのでもう一度「pip install Pillow」コマンドを実行します。
実際にPillowがインストールされているかは以下のコマンドで確認できます。
$ pip list
実際にコマンドを実行すると以下のようにpipでインストールされた一覧が出てくるので、その中にPillowがあればインストールできています。
これでPillowのインストールは完了です。
2:gifを作成してみる
今回はサンプルとして既にあるgifアニメーションに背景を合成して新たなgifアニメーションを作成するというのをやってみます。
素材は以下の2つです。使用したい場合は右クリックから保存してください。
・anime.gif
・bg.png
これらの背景が何もないアニメーションのファイルanime.gifに背景bg.pngを合成していきます。
ソースコードは以下の通りです。
・gif_blend.py
import cv2 import numpy as np import copy from PIL import Image from datetime import datetime BG_PATH = "./bg.png" # 背景画像 ALPHA_GIF_PATH = "./anime.gif" # 合成アルファgif画像 ALPHA_SCALE = 1.0 # メイン関数 def main(): print("---start---") cap = cv2.VideoCapture(ALPHA_GIF_PATH) # gifファイルを読み込み bg_img = cv2.imread(BG_PATH) # bg読み込み fps = cap.get(cv2.CAP_PROP_FPS) # fps取得 im_list = [] # Pillowのimageリスト while True: ret, frame = cap.read() if not ret: break # bgに画像を合成 alpha_scale = calc_resize_value(frame, bg_img) add_img = load_alphaImage(frame, alpha_scale) result_img = merge_images(bg_img, add_img, 0, 0) # BGRをRGBにする img_array = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB) # numpyのarrayからPillowのimage objectを作る im = Image.fromarray(img_array) im_list.append(im) make_gif(im_list, fps) print("---end---") # 合成するサイズを計算する関数 def calc_resize_value(f_img, bg_img): _, f_width = f_img.shape[:2] _, bg_width = bg_img.shape[:2] resize_value = bg_width / f_width return resize_value # アルファ画像を読み込む関数 def load_alphaImage(add_img, scale): add_img = img_resize(add_img, scale) # リサイズ return add_img # 画像をリサイズする関数 def img_resize(img, scale): h, w = img.shape[:2] img = cv2.resize(img, (int(w*scale), int(h*scale)) ) return img # 画像を合成する関数(s_xは画像を貼り付けるx座標、s_yは画像を貼り付けるy座標) def merge_images(bg, fg_alpha, s_x, s_y): b, g, r = get_bg_bgr(fg_alpha) # 背景色を取得 # 背景色をマスク化 bgr = np.array([b, g, r]) alpha = bgrExtraction(fg_alpha, bgr, bgr) alpha = cv2.cvtColor(alpha, cv2.COLOR_BGR2GRAY) alpha = cv2.bitwise_not(alpha) alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGR) # grayをBGRに alpha = alpha / 255.0 # 0.0〜1.0の値に変換 fg = fg_alpha[:,:,:3] f_h, f_w, _ = fg.shape # アルファ画像の高さと幅を取得 b_h, b_w, _ = bg.shape # 背景画像の高さを幅を取得 # 画像の大きさと開始座標を表示 print("f_w:{} f_h:{} b_w:{} b_h:{} s({}, {})".format(f_w, f_h, b_w, b_h, s_x, s_y)) result_img = copy.deepcopy(bg) # 結果 result_img[s_y:f_h+s_y, s_x:f_w+s_x] = (result_img[s_y:f_h+s_y, s_x:f_w+s_x] * (1.0 - alpha)).astype('uint8') # アルファ以外の部分を黒で合成 result_img[s_y:f_h+s_y, s_x:f_w+s_x] = (result_img[s_y:f_h+s_y, s_x:f_w+s_x] + (fg * alpha)).astype('uint8') # 合成 return result_img # 左上の1pxのBGRの色を取得する関数 def get_bg_bgr(img): color = img[0, 0] # 座標(y, x) = (0, 0)の色を取得 b = color[0] g = color[1] r = color[2] return b, g, r # BGRで特定の色のみを抽出する関数 def bgrExtraction(image, bgrLower, bgrUpper): img_mask = cv2.inRange(image, bgrLower, bgrUpper) # BGRからマスクを作成 result = cv2.bitwise_and(image, image, mask=img_mask) # 元画像とマスクを合成 return result # gifを作成する関数 def make_gif(im_list, fps): date = datetime.now().strftime("%Y%m%d_%H%M%S") path = date + ".gif" duration_time = int(1000.0 / fps) print("duration:{}".format(duration_time)) im_list[0].save(path, save_all=True, append_images=im_list[1:], duration=duration_time, loop=0) if __name__ == '__main__': main()
anime.gif、bg.png、gif_blend.pyの3つのファイルを同じ階層に配置してgif_blend.pyを実行するとタイムスタンプで結果のgifアニメーションが以下のように出力されます。
何をやっているかを簡単に説明すると、gifファイルを1枚ずつ読み込んで背景色を取得し背景色部分を透明にして背景と合成、それをgifファイルの枚数分全部行って最後にPillowの機能でgifとして保存しているという形です。
以上がPillowを使ってgifアニメーションを作成してみた備忘録です。
もっと単純に連番画像でgifアニメーションというのも考えたのですが、今回gifアニメーションも読み込めるかみたいなのも一緒にやりたかったのでやった次第です。
・参考資料