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


221

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

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

Ответы:


281

Я бы рекомендовал не использовать 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)

37
также параметр duration = 0.5 устанавливает длительность 0,5 с для каждого кадра.
Alleo

3
ValueError: Не удалось найти формат для чтения указанного файла в режиме 'i' - я получаю эту ошибку на Windows 2.7 winpython. Есть какие-нибудь подсказки?
Ванько

1
@ Vanko ошибка, похоже, связана с чтением файла, вы можете попробовать imagio.mimread или, если это фильм с множеством кадров, использовать объект для чтения, как здесь: imageio.readthedocs.io/en/latest/…
Алмар

2
@Alleo: «также длительность параметра = 0,5 устанавливает длительность 0,5 с для каждого кадра». Есть ли функция продолжительности для imageio? Если да, то где это задокументировано? Я прочитал все документы и не смог найти упоминания о продолжительности аргумента.
Крис Нильсен,

3
Превосходно! imageio in anacondaурожайность True, ууу!
Ух

47

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


21
Я парень из Python, но нашел ImageMagick здесь намного проще. Я просто сделал свою последовательность изображений и запустил что-то вродеconvert -delay 20 -loop 0 *jpg animated.gif
Ник

Я согласен, это лучшее решение, с которым я столкнулся. Вот минимальный пример (на основе примера кода пользователя Steve B, размещенного на stackoverflow.com/questions/10922285/… ): pastebin.com/JJ6ZuXdz
andreasdr

Используя ImageMagick, вы также можете легко изменить размер анимированного GIF, например,convert -delay 20 -resize 300x200 -loop 0 *jpg animated.gif
Jun Wang

@ Ник, Как вы запускаете этот код, чтобы сделать GIF? Должен ли я импортировать что-либо в Spyder IDE?
Луна

@MOON Команда ImageMagic, которую я добавил выше, просто запускается из командной строки.
Ник

43

По состоянию на июнь 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)

2
Существует новая версия этого скрипта, которая обеспечивает гораздо более качественный вывод по адресу visvis.googlecode.com/hg/vvmovie/images2gif.py, который можно использовать как отдельный скрипт отдельно от пакета.
география

1
Сценарий, упомянутый в этом комментарии, постоянно вызывает ошибку сегментации при использовании на Mac, даже при простом запуске (на примере имени __ == '__ main '). Я пытаюсь сценарий, упомянутый в ответе, в надежде, что он будет работать правильно. РЕДАКТИРОВАТЬ - Я могу подтвердить, что сценарий, указанный в ответе выше, правильно работает на моем Mac.
подводное

6
Вместо того, чтобы просто скачать скрипт, используйте pip, например pip install visvis, затем в вашем скрипте from visvis.vvmovie.images2gif import writeGif.
Даниэль Фаррелл

8
Я попробовал это с Python 2.7.3 на Windows 8, и я получаю UnicodeDecodeError: кодек «ascii» не может декодировать байт 0xc8 в позиции 6: порядковый номер не в диапазоне (128). Из запущенного питона images2gif.py
счет

3
Я автор visivis (и images2gif) и не рекомендую использовать его для этой цели. Я работал над лучшим решением в рамках проекта imageio (см. Мой ответ).
Альмар

40

Вот как вы делаете это, используя только 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)

4
это должен быть принятый ответ, спасибо @Kris
ted930511

1
Что содержит переменная звездочка ("* imgs")?
denisb411

3
Это особенность языка Python. Делает повторяемую распаковку . Вы можете грубо думать об этом как о распаковке, x = [a, b, c]о *xкоторой можно думать как a, b, c(без заключающих в скобки). В вызовах функций они являются синонимами: f(*x) == f(a, b, c). При распаковке кортежей это особенно полезно в тех случаях, когда вы хотите разделить итерируемое на голову (первый элемент) и хвост (остальное), что я и делаю в этом примере.
Крис

25

Я использовал 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)

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


Я сделал сообщение в блоге об этом .. robert-king.com/#post2-python-makes-gif
Роберт

2
Я получаю ошибки .. Файл "C: \ Python27 \ lib \ images2gif.py", строка 418, в writeGifToFile globalPalette = palettes [происходит.index (max (происходит))] ValueError: max () arg является пустой последовательностью
Harry

происходит, вероятно, пусто. В моем файле images2gif.py нет переменной globalPalette.
Роберт

как я могу это изменить? Я использую самый последний скрипт images2gif.py там ( bit.ly/XMMn5h )
Гарри

4
@ Роберткинг с кодом Я получил сообщение об ошибкеfp.write(globalPalette) TypeError: must be string or buffer, not list
LWZ

19

Чтобы создать видео, вы можете использовать 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])

9

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

Проблемы с другими решениями до сих пор:
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

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

Я бы предложил сделатьfilenames.append(os.path.join(path, filename))
правдоподобно

Secodning Nohs, images = [imageio.imread(f) for f in filenames]чище, быстрее и питоннее.
Брэндон Дуб

6

Как сказал Уоррен в прошлом году , это старый вопрос. Поскольку люди все еще просматривают страницу, я бы хотел перенаправить их на более современное решение. Как сказал здесь Блакев , на 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, похоже, указывает на ограниченную поддержку (только чтение)



5

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

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 (напрямую или с плагином), чтобы увидеть анимацию.


Кстати, Chrome тоже. Один вопрос - может ли seq быть повторяемым? Поддерживаете ли вы «потоковую передачу» (то есть открытие целевого APNG и добавление кадров один за другим в цикле)?
Томаш Гандор

Он не поддерживает произвольную итерацию или потоковую передачу, но это не значит, что в будущем это невозможно. :) Создайте проблему на странице github с предложенным улучшением. Если у вас есть идеи по поводу API для этой функции, опишите их в этом выпуске.
Уоррен Векессер

У меня была странная ошибка, из-за которой я сделал ошибку в вашем репо.
mLstudent33

5

Как упомянул один из участников, 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 с использованием этого метода



4

С 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)

3

Самая простая вещь, которая заставляет это работать для меня, это вызов команды оболочки в 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.


2

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

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

1
Попробовал это ... не работает для меня под Python 2.6. Возвращено: «Функция scitools.easyviz.movie запускает команду: / convert -delay 100 g4testC _ *. Png g4testC.gif / Неверный параметр - 100»
Дан Х

Проблема не с Python точно. Переустановите imagemagick в вашей системе и повторите попытку.
ArKE

2

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

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

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

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

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

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

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

magick -quality 100 *.png outvideo.mpeg

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

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


0

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

Сначала загрузите библиотеки 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)

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()

0

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

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()

0

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

ffmpeg -i input.mp4 output.gif

-1

Это действительно невероятно ... Все предлагают какой-то специальный пакет для воспроизведения анимированного 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()

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

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


1
Обычно это плохой идеал для вызова sleepв основном потоке графического интерфейса. Вы можете использовать afterметод для периодического вызова функции.
Брайан Оукли

Вы правы, но это не главное, не так ли? Дело в том, весь метод. Так что я бы предпочел реакцию на это!
Апостол

1
Я просто пытался дать совет о том, как улучшить ваш ответ.
Брайан Оукли

Кстати, я обычно использую tk.after()себя. Но здесь мне нужно было сделать код максимально простым. Тот, кто использует этот метод анимации GIF, может применить свою собственную функцию задержки.
Апостол

Наконец! Да, это действительно очень хороший момент. Я был не по теме! Спасибо, @Novel. (Интересно посмотреть, как другие пропустили это, например, Брайан, который говорил о методе задержки!)
Апостолос
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.