Программно генерировать видео или анимированный 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.