Programaticamente gerar vídeo ou GIF animado em Python?

221

Tenho uma série de imagens das quais quero criar um vídeo. Idealmente, eu poderia especificar uma duração de quadro para cada quadro, mas uma taxa de quadros fixa também seria boa. Estou fazendo isso no wxPython, para poder renderizar em um wxDC ou salvar as imagens em arquivos, como PNG. Existe uma biblioteca Python que me permita criar um vídeo (AVI, MPG, etc) ou um GIF animado a partir desses quadros?

Edit: Eu já tentei PIL e parece não funcionar. Alguém pode me corrigir com esta conclusão ou sugerir outro kit de ferramentas? Este link parece apoiar minha conclusão sobre o PIL: http://www.somethinkodd.com/oddthinking/2005/12/06/python-imaging-library-pil-and-animated-gifs/

FogleBird
fonte

Respostas:

281

Eu recomendaria não usar o images2gif da visvis porque ele tem problemas com o PIL / Pillow e não é mantido ativamente (eu deveria saber, porque sou o autor).

Em vez disso, use o imageio , que foi desenvolvido para resolver esse problema e muito mais, e pretende permanecer.

Solução rápida e suja:

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

Para filmes mais longos, use a abordagem de streaming:

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)
Almar
fonte
37
também o parâmetro duration = 0.5 define as durações de 0.5s para cada quadro.
Alleo 16/09
3
ValueError: Não foi possível encontrar um formato para ler o arquivo especificado no modo 'i' - estou recebendo esse erro no Windows 2.7 winpython. Alguma pista?
Vanko 04/10
1
@ Vanko, o erro parece estar relacionado à leitura do arquivo, você pode tentar imagio.mimread, ou se for um filme com muitos quadros, use o objeto leitor como aqui: imageio.readthedocs.io/en/latest/…
Almar 08/10
2
@Alleo: "também parâmetro duration = 0.5 define as durações de 0.5s para cada quadro". Existe um recurso de duração para o imageio? Em caso afirmativo, onde isso está documentado? Li todos os documentos e não consegui encontrar nenhuma menção a um argumento de duração.
31817 Chris Nielsen
3
Excelente! imageio in anacondaproduz True, yay!
uhoh
47

Bem, agora estou usando o ImageMagick. Salvei meus quadros como arquivos PNG e invoco o convert.exe do ImageMagick do Python para criar um GIF animado. O bom dessa abordagem é que posso especificar uma duração de quadro para cada quadro individualmente. Infelizmente, isso depende do ImageMagick ser instalado na máquina. Eles têm um wrapper Python, mas parece muito ruim e sem suporte. Ainda aberto a outras sugestões.

FogleBird
fonte
21
Eu sou um cara de Python, mas achei o ImageMagick muito mais fácil aqui. Eu só fiz a minha seqüência de imagens e correu algo comoconvert -delay 20 -loop 0 *jpg animated.gif
Nick
Eu concordo, esta é a melhor solução que eu já encontrei. Aqui está um exemplo mínimo (com base no código de exemplo do usuário Steve B postado em stackoverflow.com/questions/10922285/… ): pastebin.com/JJ6ZuXdz
andreasdr
Usando ImageMagick, você também pode facilmente redimensionar o GIF animado comoconvert -delay 20 -resize 300x200 -loop 0 *jpg animated.gif
Jun Wang
@ Nick, como você executa esse código para criar GIF? Devo importar alguma coisa para o Spyder IDE?
MOON
@ MOON o comando ImageMagic que adicionei acima é apenas executado através da linha de comando.
Nick
43

Em junho de 2009, a postagem do blog originalmente citada tinha um método para criar GIFs animados nos comentários . Faça o download do script images2gif.py (anteriormente images2gif.py , atualização cortesia de @geographika).

Em seguida, para reverter os quadros em um gif, por exemplo:

#!/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)
kostmo
fonte
2
Há uma nova versão desse script que produz resultados de qualidade muito melhor em visvis.googlecode.com/hg/vvmovie/images2gif.py, que pode ser usada como um script independente, separado do pacote.
geographika
1
O script mencionado neste comentário constantemente apresenta uma falha de segmentação para mim quando usado no Mac, mesmo quando simplesmente é executado (usando o nome __ == '__ main ' exemplo). Estou tentando o script mencionado na resposta, na esperança de que funcione corretamente. EDIT - Posso confirmar que o script mencionado na resposta acima funciona corretamente no meu Mac.
Scubbo 22/05
6
Em vez de apenas baixar o script, use pip pip install visvis, por exemplo , em seu script from visvis.vvmovie.images2gif import writeGif.
Daniel Farrell
8
Eu tentei isso com o Python 2.7.3 no Windows 8 e recebo UnicodeDecodeError: o codec 'ascii' não pode decodificar o byte 0xc8 na posição 6: ordinal fora do intervalo (128). A execução de python images2gif.py
reckoner
3
Eu sou o autor de visivis (e images2gif) e recomendo não usá-lo para esse fim. Eu tenho trabalhado em uma solução melhor como parte do projeto imageio (veja minha resposta).
Almar
40

Veja como você faz isso usando apenas PIL (instale com :)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)
Kris
fonte
4
esta deve ser a resposta aceita, obrigado @Kris
ted930511
1
O que a variável asterisco contém ("* imgs")?
denisb411 31/03
3
Esse é um recurso da linguagem python. Ele faz a descompactação iterable . Pode cerca de pensar nisso como desembalar x = [a, b, c]para *xo que pode ser pensado como a, b, c(sem os colchetes encerram). Em chamadas de função são sinônimos: f(*x) == f(a, b, c). Na descompactação da tupla, é particularmente útil nos casos em que você deseja dividir um iterável em uma cabeça (primeiro elemento) e uma cauda (o resto), que é o que faço neste exemplo.
Kris
25

Eu usei o images2gif.py, que era fácil de usar. Parecia dobrar o tamanho do arquivo.

26 arquivos PNG de 110kb, eu esperava 26 * 110kb = 2860kb, mas my_gif.GIF tinha 5,7mb

Também porque o GIF tinha 8 bits, os belos png ficaram um pouco confusos no GIF

Aqui está o código que eu usei:

__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

Aqui estão 3 dos 26 quadros:

Aqui estão 3 dos 26 quadros

encolher as imagens reduziu o tamanho:

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

gif menor

Robert King
fonte
Eu fiz um post sobre isso .. robert-king.com/#post2-python-makes-gif
Robert King
2
Eu recebo erros .. O arquivo "C: \ Python27 \ lib \ images2gif.py", linha 418, em writeGifToFile globalPalette = palettes [Ocorre.index (max (ocorrer))] ValueError: max () arg é uma sequência vazia
Harry
ocorrer provavelmente está vazio. Meu arquivo images2gif.py não tem variável "globalPalette".
Robert King
como eu mudo isso? Estou usando o script images2gif.py mais recente ( bit.ly/XMMn5h )
Harry
4
@robertking com o código Eu recebi um erro dizendo:fp.write(globalPalette) TypeError: must be string or buffer, not list
LWZ
19

Para criar um vídeo, você pode usar 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])
attwad
fonte
9

Me deparei com este post e nenhuma das soluções funcionou, então aqui está a minha solução que funciona

Problemas com outras soluções até agora:
1) Nenhuma solução explícita sobre como a duração é modificada
2) Nenhuma solução para a iteração de diretório fora de ordem, essencial para GIFs
3) Nenhuma explicação sobre como instalar o imageio para python 3

instale o imageio como este: python3 -m pip install imageio

Nota: convém garantir que seus quadros tenham algum tipo de índice no nome do arquivo para que possam ser classificados, caso contrário, você não terá como saber onde o GIF começa ou termina

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
Daniel McGrath
fonte
1
sortpoderá gerar resultados inesperados se o seu esquema de numeração não incluir zeros à esquerda. Além disso, por que você usou o mapa em vez de uma simples compreensão de lista?
NOHS
Eu sugiro que façafilenames.append(os.path.join(path, filename))
trueter 28/10/19
A separação de Nohs images = [imageio.imread(f) for f in filenames]é mais limpa, mais rápida e mais pitônica.
precisa
6

Como Warren disse no ano passado , esta é uma pergunta antiga. Como as pessoas ainda parecem estar visualizando a página, eu gostaria de redirecioná-las para uma solução mais moderna. Como blakev disse aqui , há um exemplo de travesseiro no github .

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

Nota: Este exemplo está desatualizado ( gifmakernão é um módulo importável, apenas um script). Pillow possui um GifImagePlugin (cuja fonte está no GitHub ), mas o documento no ImageSequence parece indicar suporte limitado (somente leitura)

Nathan
fonte
5

Pergunta antiga, muitas boas respostas, mas ainda pode haver interesse em outra alternativa ...

O numpngwmódulo que eu instalei recentemente no github ( https://github.com/WarrenWeckesser/numpngw ) pode gravar arquivos PNG animados a partir de matrizes numpy. ( Atualização : numpngwagora está em pypi: https://pypi.python.org/pypi/numpngw .)

Por exemplo, este script:

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)

cria:

png animado

Você precisará de um navegador que suporte PNG animado (diretamente ou com um plug-in) para ver a animação.

Warren Weckesser
fonte
Agora, o Chrome também funciona, BTW. Uma pergunta - seq pode ser iterável? Você suporta "streaming" (ou seja, abrir o APNG de destino e adicionar quadros um a um em um loop)?
Tomasz Gandor
Ele não suporta iterável ou streaming arbitrário, mas isso não significa que não poderia no futuro. :) Crie um problema na página do github com o aprimoramento proposto. Se você tiver alguma idéia sobre a API para esse recurso, descreva-a no problema.
Warren Weckesser
Ocorreu um erro estranho que cometeu um problema no seu repo.
precisa saber é o seguinte
5

Como um membro mencionado acima, o imageio é uma ótima maneira de fazer isso. O imageio também permite que você defina a taxa de quadros, e na verdade escrevi uma função em Python que permite que você defina o quadro final. Eu uso essa função para animações científicas em que o loop é útil, mas o reinício imediato não é. Aqui está o link e a função:

Como criar um GIF usando 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)

Exemplo de GIF usando este método

Portal do Criador
fonte
4

Você já experimentou o PyMedia ? Não tenho 100% de certeza, mas parece que este exemplo de tutorial está direcionado ao seu problema.

Jakub Šturc
fonte
4

No windows7, python2.7, opencv 3.0, o seguinte funciona para mim:

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)
Jacek Słoma
fonte
3

A coisa mais fácil que faz funcionar para mim é chamar um comando de shell no Python.

Se suas imagens estiverem armazenadas como dummy_image_1.png, dummy_image_2.png ... dummy_image_N.png, você poderá usar a função:

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

Basta executar:

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

Isso criará seu arquivo gif my_output.gif.

Pkjain
fonte
2

A tarefa pode ser concluída executando o script python de duas linhas da mesma pasta que a sequência dos arquivos de imagem. Para arquivos formatados em png, o script é -

from scitools.std import movie
movie('*.png',fps=1,output_file='thisismygif.gif')
ArKE
fonte
1
Tentei ... não funcionou para mim no Python 2.6. Retornado: "A função scitools.easyviz.movie executa o comando: / convert -delay 100 g4testC _ *. Png g4testC.gif / Parâmetro inválido - 100"
Dan H
O problema não está no Python, com certeza. Reinstale o imagemagick no seu sistema e tente novamente.
ArKE 14/10/2015
2

Eu estava procurando por um código de linha única e achei o seguinte para o meu aplicativo. Aqui está o que eu fiz:

Primeiro passo: Instale o ImageMagick no link abaixo

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

insira a descrição da imagem aqui

Segundo passo: aponte a linha de cmd para a pasta onde as imagens (no meu caso, no formato .png) são colocadas

insira a descrição da imagem aqui

Terceiro passo: Digite o seguinte comando

magick -quality 100 *.png outvideo.mpeg

insira a descrição da imagem aqui

Obrigado FogleBird pela ideia!

Jesu Kiran Spurgen
fonte
0

Eu apenas tentei o seguinte e foi muito útil:

Primeiro Baixe as bibliotecas Figtodate images2gifpara o diretório local.

Em segundo lugar, colete as figuras em uma matriz e converta-as em um gif animado:

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)
Cro-Magnon
fonte
0

Encontrei o módulo ImageSequence da PIL , que oferece uma melhor informação GIF (e mais padrão). Eu também uso o método after () da Tk dessa vez, o que é melhor que o 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()
Apostolos
fonte
0

Uma função simples que cria GIFs:

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()
Willem
fonte
0

Entendo que você perguntou sobre a conversão de imagens em um gif; no entanto, se o formato original for MP4, você poderá usar o FFmpeg :

ffmpeg -i input.mp4 output.gif
Sam Perry
fonte
-1

É realmente incrível ... Todos estão propondo um pacote especial para reproduzir um GIF animado, no momento em que isso pode ser feito com o Tkinter e o módulo PIL clássico!

Aqui está o meu próprio método de animação GIF (eu criei há um tempo). Muito simples:

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

Você pode definir seus próprios meios para interromper a animação. Deixe-me saber se você deseja obter a versão completa com os botões reproduzir / pausar / sair.

Nota: Não tenho certeza se os quadros consecutivos são lidos da memória ou do arquivo (disco). No segundo caso, seria mais eficiente se todos lessem de uma vez e salvos em uma matriz (lista). (Não estou tão interessado em descobrir! :)

Apostolos
fonte
1
Geralmente, não é um bom ideal chamar sleepo thread principal de uma GUI. Você pode usar o aftermétodo para chamar uma função periodicamente.
Bryan Oakley
Você está certo, mas esse não é o ponto, não é? A questão é todo o método. Então, eu prefiro esperar uma reação a isso!
Apostolos
1
Eu só estava tentando dar conselhos sobre como melhorar sua resposta.
Bryan Oakley
BTW, eu normalmente uso a tk.after()mim mesmo. Mas aqui eu precisava tornar o código o mais simples possível. Quem usa esse método de animação GIF pode aplicar sua própria função de atraso.
Apostolos
Finalmente! Sim, este é realmente um ponto muito bom. Eu estava fora de tópico! Obrigado, @Novel. (Interessante ver que como os outros perdi isso, por exemplo, Bryan, que estava falando sobre o método de atraso de tempo!)
Apostolos