Как читать и записывать анимированные GIF-файлы с прозрачностью

#python #numpy #python-imaging-library

#python #numpy #python-imaging-library

Вопрос:

Вот (теоретически) простая задача, которая у меня есть под рукой:

  1. Загрузите прозрачный анимированный GIF-файл с диска (или буфера)
  2. Преобразуйте все отдельные кадры в массивы NumPy. Каждый кадр С АЛЬФА-КАНАЛОМ
  3. Сохраните массивы NumPy обратно в прозрачный анимированный GIF

Размер выходного файла не имеет значения, все, что мне действительно нужно, это иметь два одинаковых GIF-файла — исходное входное изображение и то, которое было сохранено на шаге 3.

Что для меня имеет значение, хотя это скорость де / кодирования, поэтому чистые решения на Python (без привязок C к базовой библиотеке изображений) не рассматриваются.

В приложении (в самом низу) вы найдете пример GIF, который я использую для тестирования.

Я перепробовал практически все подходы, которые приходят на ум. Либо результирующий GIF-файл (шаг 3) будет ужасно искажен, отображен только в оттенках серого, либо (в лучшем случае) потеряет прозрачность и будет сохранен либо на белом, либо на черном фоне.

Вот что я попробовал:

Чтение с помощью Pillow:

 from PIL import Image, ImageSequence

im = Image.open("animation.gif")

npArray = []

for frame in ImageSequence.Iterator(im):
    npArray.append(np.array(frame))

return npArray
 

Чтение с помощью imageio:

 import imageio

npArr = []

im = imageio.get_reader("animation.gif")

for frame in im:
    npArr.append(np.array(frame))

return npArr
 

Чтение с помощью MoviePy:

 from moviepy.editor import *

npArr = []

clip = VideoFileClip("animation.gif")

for frame in clip.iter_frames():
    npArr.append(np.array(frame))

return npArr
 

Чтение с помощью PyVips:

 vi = pyvips.Image.new_from_file("animation.gif", n=-1)

pageHeight = vi.get("page-height")
frameCount = int(vi.height / pageHeight)

npArr = []

for i in range(0, frameCount):
    vi = vi.crop(0, i * pageHeight   0, vi.width, pageHeight).write_to_memory()

    frame = np.ndarray(
            buffer = vi,
            dtype = np.uint8,
            shape = [pageHeight, vi.width, 3]
    )

    npArr.append(frame)

return npArr
 

Сохранение с помощью Pillow:

 images = []

for frame in frames:
    im = Image.fromarray(frame)
    images.append(im)

images[0].save(
    "output.gif",
    format = "GIF",
    save_all = True,
    loop = 0,
    append_images = images,
    duration = 40,
    disposal = 3
)
 

введите описание изображения здесь

Ответ №1:

Я полагаю, что вы столкнулись с проблемой, потому что вы не сохраняете палитру, связанную с каждым кадром. Когда вы преобразуете каждый кадр в массив, результирующий массив не содержит никаких данных палитры, которые указывают, какие цвета включены в кадр. Итак, когда вы создаете новое изображение из каждого кадра, палитры нет, и Pillow не знает, какую цветовую палитру он должен использовать для кадра.

Кроме того, при сохранении GIF вам необходимо указать цвет, который будет использоваться для прозрачности, который мы можем просто извлечь из исходного изображения.

Вот некоторый код, который (надеюсь) дает желаемый результат:

 from PIL import Image, ImageSequence
import numpy as np

im = Image.open("ex.gif")

frames = []
# Each frame can have its own palette in a GIF, so we need to store
# them individually
fpalettes = []
transparency = im.info['transparency']

for frame in ImageSequence.Iterator(im):
    frames.append(np.array(frame))
    fpalettes.append(frame.getpalette())

# ... Do something with the frames

images = []

for i, frame in enumerate(frames):
    im = Image.fromarray(frame)
    im.putpalette(fpalettes[i])
    images.append(im)

images[0].save(
        "output.gif",
        format="GIF",
        save_all=True,
        loop=0,
        append_images=images,
        duration=40,
        disposal=2,
        transparency=transparency
)