Самый быстрый способ загрузить анимированный GIF в Python в массив numpy

Удивительно, но я не видел никакого освещения этого.

Я нашел 3 признанных способа сделать это - Pillow, OpenCV и Imageio. Результаты меня удивили, поэтому я опубликовал их в виде ответов на вопросы (ниже).

добро пожаловать. просить «лучшее» по-прежнему просить рекомендации (библиотек), что не по теме. пожалуйста, просмотрите справочный центр

Christoph Rackwitz 08.12.2022 19:40
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
1
90
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Кажется, это стандартный способ загрузки GIF в каждой библиотеке:

import os
import cv2
import time
import imageio
import numpy as np
from tqdm import tqdm
from glob import glob
from PIL import Image, ImageSequence

gifs = glob(os.path.join("/folder/of/gifs", "*"))
print(f"Found {len(gifs)} GIFs")

def load_gif_as_video_pil(gif_path):
    im = Image.open(gif_path)
    frames = []
    for frame in ImageSequence.Iterator(im):
        frame = np.array(frame.copy().convert('RGB').getdata(), dtype=np.uint8).reshape(frame.size[1],
                                                                                        frame.size[0],
                                                                                        3)
        frames.append(frame)

    return np.array(frames)

def load_gif_as_video_imageio(gif_path):
    return imageio.mimread(gif_path)

def load_gif_as_video_opencv(filename):
    gif = cv2.VideoCapture(filename)
    frames = []
    while True:
        ret, frame = gif.read()
        if not ret:
            break
        frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    return np.array(frames)


start = time.time()
[load_gif_as_video_imageio(path) for path in tqdm(gifs)]
end = time.time()
print(f"ImageIO: {end - start}")

start = time.time()
[load_gif_as_video_opencv(path) for path in tqdm(gifs)]
end = time.time()
print(f"OpenCV: {end - start}")

start = time.time()
[load_gif_as_video_pil(path) for path in tqdm(gifs)]
end = time.time()
print(f"PIL: {end - start}")

Более 250 GIF-файлов, вот результаты:

100%|██████████| 250/250 [00:13<00:00, 18.32it/s]
ImageIO: 13.829721689224243
100%|██████████| 250/250 [00:06<00:00, 39.04it/s]
OpenCV: 6.478164434432983
100%|██████████| 250/250 [03:00<00:00,  1.38it/s]
PIL: 181.03292179107666

OpenCV в два раза быстрее, чем imageio, что в 15 раз быстрее, чем PIL (во всяком случае, с использованием моего метода).

Ответ принят как подходящий

Ваш код с использованием Pillow очень неэффективен! Images совместимы с интерфейсом массива Numpy, поэтому ваш код преобразования усложняет ситуацию.

Я бы использовал следующий помощник, чтобы получить кадры в массив Numpy:

from PIL import Image, ImageSequence
import numpy as np

def load_frames(image: Image, mode='RGBA'):
    return np.array([
        np.array(frame.convert(mode))
        for frame in ImageSequence.Iterator(image)
    ])

with Image.open('animated.gif') as im:
    frames = load_frames(im)

Это работает в основном в то же время, что и другие. Например, с размером 400x400 пикселей, 21 кадром, GIF, который у меня есть, mimread занимает ~140 мс, а Pillow — ~130 мс.

Обновление: я только что поиграл с CV2 и заметил, что его «настенные часы» лучше (то есть то, что вы измеряли), потому что он работает в других потоках. Например, если я запускаю Jupyter %time magic, я получаю следующий результат:

ImageIO

CPU times: user 135 ms, sys: 9.81 ms, total: 145 ms
Wall time: 145 ms

ПИЛ

CPU times: user 127 ms, sys: 3.03 ms, total: 130 ms
Wall time: 130 ms

CV2

CPU times: user 309 ms, sys: 95 ms, total: 404 ms
Wall time: 89.7 ms

т.е. хотя он завершает цикл за 90 мс, он использует примерно в 4,5 раза больше процессорного времени.

Поэтому, если вас интересует время, необходимое для создания одного большого изображения, вы можете использовать CV2. Но если вы выполняете пакетную обработку большого количества изображений, я бы предложил использовать Pillow в многопроцессорном пуле.

Другие вопросы по теме