LoginSignup
10
8

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-06-10

普段は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

参考

 
おわり

10
8
0

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
  3. You can use dark theme
What you can do with signing up
10
8