Программно генерировать видео или анимированный GIF в Python?

У меня есть серия изображений, из которых я хочу создать видео. В идеале я мог бы указать длительность кадра для каждого кадра, но фиксированная частота кадров тоже подойдет. Я делаю это в wxPython, поэтому я могу рендерить в wxDC или я могу сохранить изображения в файлы, такие как PNG. Есть ли библиотека Python, которая позволит мне создавать видео (AVI, MPG и т. Д.) Или анимированный GIF из этих кадров?

Изменить: я уже пробовал PIL, и это не похоже на работу. Может кто-то исправить меня с этим выводом или предложить другой инструментарий? Эта ссылка, кажется, подтверждает мое заключение относительно PIL: http://www.somethinkodd.com/oddthinking/2005/12/06/python-imaging-library-pil-and-animated-gifs/

25 ответов

Решение

Ну, теперь я использую ImageMagick. Я сохраняю свои кадры в виде файлов PNG, а затем вызываю файл ImageMagick convert.exe из Python для создания анимированного GIF-файла. Хорошая вещь об этом подходе - я могу указать длительность кадра для каждого кадра отдельно. К сожалению, это зависит от установки ImageMagick на компьютере. У них есть оболочка Python, но это выглядит довольно дрянно и не поддерживается. Все еще открыты для других предложений.

Я бы рекомендовал не использовать images2gif от visvis, потому что у него проблемы с PIL/Pillow и он не поддерживается активно (я должен знать, потому что я автор).

Вместо этого, пожалуйста, используйте imageio, которая была разработана, чтобы решить эту проблему и многое другое, и предназначена, чтобы остаться.

Быстрое и грязное решение:

import imageio
images = []
for filename in filenames:
    images.append(imageio.imread(filename))
imageio.mimsave('/path/to/movie.gif', images)

Для более длинных фильмов используйте потоковый подход:

import imageio
with imageio.get_writer('/path/to/movie.gif', mode='I') as writer:
    for filename in filenames:
        image = imageio.imread(filename)
        writer.append_data(image)

Вот как вы это делаете, используя только PIL (установите с помощью:pip install Pillow):

import glob
from PIL import Image

# filepaths
fp_in = "/path/to/image_*.png"
fp_out = "/path/to/image.gif"

# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#gif
img, *imgs = [Image.open(f) for f in sorted(glob.glob(fp_in))]
img.save(fp=fp_out, format='GIF', append_images=imgs,
         save_all=True, duration=200, loop=0)

По состоянию на июнь 2009 года в первоначально цитируемом сообщении блога появился способ создания анимированных GIF-файлов в комментариях. Загрузите скрипт images2gif.py (ранее images2gif.py, обновление любезно предоставлено @geographika).

Затем, чтобы перевернуть кадры в GIF, например:

#!/usr/bin/env python

from PIL import Image, ImageSequence
import sys, os
filename = sys.argv[1]
im = Image.open(filename)
original_duration = im.info['duration']
frames = [frame.copy() for frame in ImageSequence.Iterator(im)]    
frames.reverse()

from images2gif import writeGif
writeGif("reverse_" + os.path.basename(filename), frames, duration=original_duration/1000.0, dither=0)

Я использовал images2gif.py, который был прост в использовании. Это, казалось, удвоило размер файла, хотя..

26 PNG-файлов размером 110 КБ, я ожидал 26 * 110 КБ = 2860 КБ, но my_gif.GIF был 5,7 МБ

Кроме того, потому что GIF был 8-битным, хорошие png стали немного размытыми в GIF

Вот код, который я использовал:

__author__ = 'Robert'
from images2gif import writeGif
from PIL import Image
import os

file_names = sorted((fn for fn in os.listdir('.') if fn.endswith('.png')))
#['animationframa.png', 'animationframb.png', 'animationframc.png', ...] "

images = [Image.open(fn) for fn in file_names]

print writeGif.__doc__
# writeGif(filename, images, duration=0.1, loops=0, dither=1)
#    Write an animated gif from the specified images.
#    images should be a list of numpy arrays of PIL images.
#    Numpy images of type float should have pixels between 0 and 1.
#    Numpy images of other types are expected to have values between 0 and 255.


#images.extend(reversed(images)) #infinit loop will go backwards and forwards.

filename = "my_gif.GIF"
writeGif(filename, images, duration=0.2)
#54 frames written
#
#Process finished with exit code 0

Вот 3 из 26 кадров:

Вот 3 из 26 кадров

Уменьшение изображения уменьшило размер:

size = (150,150)
for im in images:
    im.thumbnail(size, Image.ANTIALIAS)

меньший подарок

Чтобы создать видео, вы можете использовать OpenCV,

#load your frames
frames = ...
#create a video writer
writer = cvCreateVideoWriter(filename, -1, fps, frame_size, is_color=1)
#and write your frames in a loop if you want
cvWriteFrame(writer, frames[i])

Я наткнулся на этот пост, и ни одно из решений не сработало, так что вот мое решение, которое работает

Проблемы с другими решениями до сих пор:
1) Нет явного решения относительно того, как изменяется продолжительность
2) Нет решения для итерации каталога не по порядку, что важно для GIF
3) Нет объяснения, как установить imageio для Python 3

установить imageio так: python3 -m pip установить imageio

Примечание: вам нужно убедиться, что ваши кадры имеют какой-то индекс в имени файла, чтобы их можно было отсортировать, иначе у вас не будет возможности узнать, где начинается или заканчивается GIF

import imageio
import os

path = '/Users/myusername/Desktop/Pics/' # on Mac: right click on a folder, hold down option, and click "copy as pathname"

image_folder = os.fsencode(path)

filenames = []

for file in os.listdir(image_folder):
    filename = os.fsdecode(file)
    if filename.endswith( ('.jpeg', '.png', '.gif') ):
        filenames.append(filename)

filenames.sort() # this iteration technique has no built in order, so sort the frames

images = list(map(lambda filename: imageio.imread(filename), filenames))

imageio.mimsave(os.path.join('movie.gif'), images, duration = 0.04) # modify duration as needed
from PIL import Image
import glob  #use it if you want to read all of the certain file type in the directory
imgs=[]
for i in range(596,691): 
    imgs.append("snap"+str(i)+'.png')
    print("scanned the image identified with",i)  

начальное и конечное значение +1 индекса, который идентифицирует разные имена файлов

imgs = glob.glob("*.png") #do this if you want to read all files ending with .png

мои файлы были: snap596.png, snap597.png ...... snap690.png

frames = []
for i in imgs:
    new_frame = Image.open(i)
    frames.append(new_frame)

Сохраните в файл GIF, который зацикливается навсегда

frames[0].save('fire3_PIL.gif', format='GIF',
    append_images=frames[1:],
    save_all=True,
    duration=300, loop=0)

Я обнаружил мигающую проблему с imageio, и этот метод исправил ее.

Как отметил один из участников, imageio - отличный способ сделать это. imageio также позволяет вам устанавливать частоту кадров, и я фактически написал функцию на Python, которая позволяет вам удерживать окончательный кадр. Я использую эту функцию для научной анимации, где циклы полезны, но немедленный перезапуск - нет. Вот ссылка и функция:

Как сделать GIF с использованием Python

import matplotlib.pyplot as plt
import os
import imageio

def gif_maker(gif_name,png_dir,gif_indx,num_gifs,dpi=90):
    # make png path if it doesn't exist already
    if not os.path.exists(png_dir):
        os.makedirs(png_dir)

    # save each .png for GIF
    # lower dpi gives a smaller, grainier GIF; higher dpi gives larger, clearer GIF
    plt.savefig(png_dir+'frame_'+str(gif_indx)+'_.png',dpi=dpi)
    plt.close('all') # comment this out if you're just updating the x,y data

    if gif_indx==num_gifs-1:
        # sort the .png files based on index used above
        images,image_file_names = [],[]
        for file_name in os.listdir(png_dir):
            if file_name.endswith('.png'):
                image_file_names.append(file_name)       
        sorted_files = sorted(image_file_names, key=lambda y: int(y.split('_')[1]))

        # define some GIF parameters

        frame_length = 0.5 # seconds between frames
        end_pause = 4 # seconds to stay on last frame
        # loop through files, join them to image array, and write to GIF called 'wind_turbine_dist.gif'
        for ii in range(0,len(sorted_files)):       
            file_path = os.path.join(png_dir, sorted_files[ii])
            if ii==len(sorted_files)-1:
                for jj in range(0,int(end_pause/frame_length)):
                    images.append(imageio.imread(file_path))
            else:
                images.append(imageio.imread(file_path))
        # the duration is the time spent on each image (1/duration is frame rate)
        imageio.mimsave(gif_name, images,'GIF',duration=frame_length)

Пример GIF с использованием этого метода

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

 import ImageSequence
 import Image
 import gifmaker
 sequence = []

 im = Image.open(....)

 # im is your original image
 frames = [frame.copy() for frame in ImageSequence.Iterator(im)]

 # write GIF animation
 fp = open("out.gif", "wb")
 gifmaker.makedelta(fp, frames)
 fp.close()

Примечание: этот пример устарел (gifmaker не импортируемый модуль, только скрипт). У подушки есть GifImagePlugin (источник которого находится на GitHub), но документ по ImageSequence, похоже, указывает на ограниченную поддержку (только чтение)

Старый вопрос, много хороших ответов, но еще может быть интерес к другой альтернативе...

numpngw Модуль, который я недавно установил на github ( https://github.com/WarrenWeckesser/numpngw), может записывать анимированные файлы PNG из массивов numpy. (Обновление: numpngw сейчас на pypi: https://pypi.python.org/pypi/numpngw.)

Например, этот скрипт:

import numpy as np
import numpngw


img0 = np.zeros((64, 64, 3), dtype=np.uint8)
img0[:32, :32, :] = 255
img1 = np.zeros((64, 64, 3), dtype=np.uint8)
img1[32:, :32, 0] = 255
img2 = np.zeros((64, 64, 3), dtype=np.uint8)
img2[32:, 32:, 1] = 255
img3 = np.zeros((64, 64, 3), dtype=np.uint8)
img3[:32, 32:, 2] = 255
seq = [img0, img1, img2, img3]
for img in seq:
    img[16:-16, 16:-16] = 127
    img[0, :] = 127
    img[-1, :] = 127
    img[:, 0] = 127
    img[:, -1] = 127

numpngw.write_apng('foo.png', seq, delay=250, use_palette=True)

создает:

анимированный png

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

Установка

pip install imageio-ffmpeg
pip install imageio

Код

import imageio
images = []
for filename in filenames:
    images.append(imageio.imread(filename))
imageio.mimsave('movie.mp4', images)

Качество повышено, а размер уменьшен с 8 МБ до 80 КБ при сохранении в формате mp4, чем в формате GIF.

Это не библиотека Python, но Mencoder может сделать это: Кодирование из нескольких входных файлов изображений. Вы можете выполнить mencoder из python следующим образом:

import os

os.system("mencoder ...")

Вы пробовали PyMedia? Я не уверен на 100%, но, похоже, этот учебный пример нацелен на вашу проблему.

С windows7, python2.7, opencv 3.0 у меня работает следующее:

import cv2
import os

vvw           =   cv2.VideoWriter('mymovie.avi',cv2.VideoWriter_fourcc('X','V','I','D'),24,(640,480))
frameslist    =   os.listdir('.\\frames')
howmanyframes =   len(frameslist)
print('Frames count: '+str(howmanyframes)) #just for debugging

for i in range(0,howmanyframes):
    print(i)
    theframe = cv2.imread('.\\frames\\'+frameslist[i])
    vvw.write(theframe)

Самая простая вещь, которая заставляет это работать для меня, это вызов команды оболочки в Python.

Если ваши изображения хранятся, например, dummy_image_1.png, dummy_image_2.png... dummy_image_N.png, то вы можете использовать функцию:

import subprocess
def grid2gif(image_str, output_gif):
    str1 = 'convert -delay 100 -loop 1 ' + image_str  + ' ' + output_gif
    subprocess.call(str1, shell=True)

Просто выполните:

grid2gif("dummy_image*.png", "my_output.gif")

Это создаст ваш GIF-файл my_output.gif.

Дополнение к ответам Smart Manoj: сделайте фильм в формате .mp4 из всех изображений в папке.

Установка:

      pip install imageio-ffmpeg
pip install imageio

Код:

      import os
import imageio

root = r'path_to_folder_with_images'

images = []    
for subdir, dirs, files in os.walk(root):
    for file in files:
        images.append(imageio.imread(os.path.join(root,file)))

savepath = r'path_to_save_folder'
imageio.mimsave(os.path.join(savepath,'movie.mp4'), images)

PS: Убедитесь, что ваш список «файлов» отсортирован так, как вы хотите, вы сэкономите время, если уже сохраните свои изображения соответствующим образом.

Задача может быть выполнена путем запуска двухстрочного скрипта Python из той же папки, что и последовательность файлов изображений. Для файлов в формате png скрипт -

from scitools.std import movie
movie('*.png',fps=1,output_file='thisismygif.gif')

Я публикую еще один ответ, так как он проще, чем все, что было опубликовано до сих пор

from PIL import Image

width = 300
height = 300
im1 = Image.new("RGBA", (width, height), (255, 0, 0))
im2 = Image.new("RGBA", (width, height), (255, 255, 0))
im3 = Image.new("RGBA", (width, height), (255, 255, 255))
im1.save("out.gif", save_all=True, append_images=[im2, im3], duration=100, loop=0)

используя существующие изображения:

from PIL import Image

im1 = Image.open('a.png')
im2 = Image.open('b.png')
im3 = Image.open('c.png')
im1.save("out.gif", save_all=True, append_images=[im2, im3], duration=100, loop=0)

И, поскольку слишком низкие версии подушек молча терпят неудачу, вот как бонусная версия с проверкой версии библиотеки:

from packaging import version
from PIL import Image

im1 = Image.open('a.png')
im2 = Image.open('b.png')
im3 = Image.open('c.png')
if version.parse(Image.PILLOW_VERSION) < version.parse("3.4"):
    print("Pillow in version not supporting making animated gifs")
    print("you need to upgrade library version")
    print("see release notes in")
    print("https://pillow.readthedocs.io/en/latest/releasenotes/3.4.0.html#append-images-to-gif")
else:
    im1.save("out.gif", save_all=True, append_images=[
             im2, im3], duration=100, loop=0)

Я искал однострочный код и нашел следующее, чтобы работать для моего приложения. Вот что я сделал:

Первый шаг: установите ImageMagick по ссылке ниже

https://www.imagemagick.org/script/download.php

Второй шаг: наведите строку cmd на папку, в которой находятся изображения (в моем случае формат.png)

Третий шаг: введите следующую команду

magick -quality 100 *.png outvideo.mpeg

Спасибо FogleBird за идею!

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

Сначала скачайте библиотеки Figtodat а также images2gif в ваш локальный каталог.

Во-вторых, соберите фигуры в массив и конвертируйте их в анимированный GIF:

import sys
sys.path.insert(0,"/path/to/your/local/directory")
import Figtodat
from images2gif import writeGif
import matplotlib.pyplot as plt
import numpy

figure = plt.figure()
plot   = figure.add_subplot (111)

plot.hold(False)
    # draw a cardinal sine plot
images=[]
y = numpy.random.randn(100,5)
for i in range(y.shape[1]):
    plot.plot (numpy.sin(y[:,i]))  
    plot.set_ylim(-3.0,3)
    plot.text(90,-2.5,str(i))
    im = Figtodat.fig2img(figure)
    images.append(im)

writeGif("images.gif",images,duration=0.3,dither=0)

Я наткнулся на модуль ImageSequence PIL, который предлагает лучшее (и более стандартное) GIF-анимирование. На этот раз я также использую метод TK after(), который лучше, чем time.sleep ().

from Tkinter import * 
from PIL import Image, ImageTk, ImageSequence

def stop(event):
  global play
  play = False
  exit() 

root = Tk()
root.bind("<Key>", stop) # Press any key to stop
GIFfile = {path_to_your_GIF_file}
im = Image.open(GIFfile); img = ImageTk.PhotoImage(im)
delay = im.info['duration'] # Delay used in the GIF file 
lbl = Label(image=img); lbl.pack() # Create a label where to display images
play = True;
while play:
  for frame in ImageSequence.Iterator(im):
    if not play: break 
    root.after(delay);
    img = ImageTk.PhotoImage(frame)
    lbl.config(image=img); root.update() # Show the new frame/image

root.mainloop()

Простая функция, которая делает гифки:

import imageio
import pathlib
from datetime import datetime


def make_gif(image_directory: pathlib.Path, frames_per_second: float, **kwargs):
    """
    Makes a .gif which shows many images at a given frame rate.
    All images should be in order (don't know how this works) in the image directory

    Only tested with .png images but may work with others.

    :param image_directory:
    :type image_directory: pathlib.Path
    :param frames_per_second:
    :type frames_per_second: float
    :param kwargs: image_type='png' or other
    :return: nothing
    """
    assert isinstance(image_directory, pathlib.Path), "input must be a pathlib object"
    image_type = kwargs.get('type', 'png')

    timestampStr = datetime.now().strftime("%y%m%d_%H%M%S")
    gif_dir = image_directory.joinpath(timestampStr + "_GIF.gif")

    print('Started making GIF')
    print('Please wait... ')

    images = []
    for file_name in image_directory.glob('*.' + image_type):
        images.append(imageio.imread(image_directory.joinpath(file_name)))
    imageio.mimsave(gif_dir.as_posix(), images, fps=frames_per_second)

    print('Finished making GIF!')
    print('GIF can be found at: ' + gif_dir.as_posix())


def main():
    fps = 2
    png_dir = pathlib.Path('C:/temp/my_images')
    make_gif(png_dir, fps)

if __name__ == "__main__":
    main()

Это действительно невероятно... Все предлагают какой-то специальный пакет для воспроизведения анимированного GIF-файла, на данный момент это можно сделать с помощью Tkinter и классического модуля PIL!

Вот мой собственный метод анимации GIF (я создал его недавно). Очень просто:

from Tkinter import * 
from PIL import Image, ImageTk
from time import sleep

def stop(event):
  global play
  play = False
  exit() 

root = Tk()
root.bind("<Key>", stop) # Press any key to stop
GIFfile = {path_to_your_GIF_file}    
im = Image.open(GIFfile); img = ImageTk.PhotoImage(im)
delay = float(im.info['duration'])/1000; # Delay used in the GIF file 
lbl = Label(image=img); lbl.pack() # Create a label where to display images
play = True; frame = 0
while play:
  sleep(delay);
  frame += 1
  try:
    im.seek(frame); img = ImageTk.PhotoImage(im)
    lbl.config(image=img); root.update() # Show the new frame/image
  except EOFError:
    frame = 0 # Restart

root.mainloop()

Вы можете установить свои собственные средства, чтобы остановить анимацию. Дайте мне знать, если вы хотите получить полную версию с кнопками воспроизведения / паузы / выхода.

Примечание. Я не уверен, считываются ли последовательные кадры из памяти или из файла (диска). Во втором случае было бы более эффективно, если бы они все читали одновременно и сохраняли в массив (список). (Мне не так интересно это выяснять!:)

Насколько я понимаю, вы спрашивали о преобразовании изображений в GIF; однако, если исходный формат - MP4, вы можете использовать FFmpeg:

ffmpeg -i input.mp4 output.gif
Другие вопросы по тегам