search
LoginSignup
8

More than 3 years have passed since last update.

posted at

updated at

【Python】動画からGIFをつくる

普段はWebサービスを使っていますが、時間がかかりすぎたり失敗したりするんで自分で作ってみる。

普段使ってるやつ

GIFにテキスト埋め込んだりできるので好き。
GIF Maker - Video to GIF Creator Tools | GIPHY

使うライブラリ

  • Pillow
  • OpenCV

コード

make_gif.py
import math

import cv2
from PIL import Image


def get_fps_n_count(video_path):
    """動画のfpsとフレーム数を返す"""
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        return (None, None)

    count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = round(cap.get(cv2.CAP_PROP_FPS))

    cap.release()
    cv2.destroyAllWindows()
    return (fps, count)


def aspect_ratio(width, height):
    """アスペクト比を返す"""
    gcd = math.gcd(width, height)
    ratio_w = width // gcd
    ratio_h = height // gcd
    return (ratio_w, ratio_h)


def resize_based_on_aspect_ratio(aspect_ratio, base_width, max_width=400):
    """アスペクト比を元にリサイズ後のwidth, heightを求める"""
    if base_width < max_width:
        return None

    base = max_width / aspect_ratio[0]
    new_w = int(base * aspect_ratio[0])
    new_h = int(base * aspect_ratio[1])
    return (new_w, new_h)


def get_frame_range(video_path, start_frame, stop_frame, step_frame):
    """指定された範囲の画像をPillowのimage objectのリストにする"""
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        return None

    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    asp = aspect_ratio(width, height)
    # でかすぎてもあれなので最大幅を400にしとく
    width_height = resize_based_on_aspect_ratio(asp, width, max_width=400)

    im_list = []
    for n in range(start_frame, stop_frame, step_frame):
        cap.set(cv2.CAP_PROP_POS_FRAMES, n)
        ret, frame = cap.read()
        if ret:
            if width_height is not None:
                frame = cv2.resize(frame, dsize=width_height)
            # BGRをRGBにする
            img_array = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            # numpyのarrayからPillowのimage objectを作る
            im = Image.fromarray(img_array)
            im_list.append(im)

    cap.release()
    cv2.destroyAllWindows()
    return im_list


def make_gif(filename, im_list):
    """gifを作る"""
    im_list[0].save(filename, save_all=True, append_images=im_list[1:], loop=0)


def main():
    """メイン処理"""
    video_file = "Wildlife.wmv"

    fps, count = get_fps_n_count(video_file)
    if fps is None:
        print("動画ファイルを開けませんでした")
        return

    # gifにしたい範囲を指定
    start_sec = 0
    stop_sec = 8

    start_frame = int(start_sec * fps)
    stop_frame = int(stop_sec * fps)
    # 適当(fpsに応じてうまいことやれるようにしたい)
    step_frame = 3

    print("開始(けっこう時間がかかる)")
    im_list = get_frame_range(video_file, start_frame, stop_frame, step_frame)
    if im_list is None:
        print("動画ファイルを開けませんでした")
        return

    make_gif('どうぶつ.gif', im_list)
    print("終了")


if __name__ == "__main__":
    main()

BGRとRGB問題

上のコードから抜粋
# BGRをRGBにする
img_array = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

動画から画像を読むのにOpenCV、GIFをつくるのにPillowを使っているわけですが、この処理がないと色が変な感じになります。

調べたところcv2.read()ではBGRで読み込まれるようで、これが色がおかしくなる原因だった。

出来たGIF

u2egKgw.gif

参考

 
おわり

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8