¿Genera video o GIF animado mediante programación en Python?

221

Tengo una serie de imágenes a partir de las cuales quiero crear un video. Idealmente, podría especificar una duración de cuadro para cada cuadro, pero una velocidad de cuadro fija también estaría bien. Estoy haciendo esto en wxPython, por lo que puedo renderizar a un wxDC o puedo guardar las imágenes en archivos, como PNG. ¿Existe una biblioteca de Python que me permita crear un video (AVI, MPG, etc.) o un GIF animado a partir de estos cuadros?

Editar: ya he probado PIL y parece que no funciona. ¿Alguien puede corregirme con esta conclusión o sugerir otro conjunto de herramientas? Este enlace parece respaldar mi conclusión con respecto a PIL: http://www.somethinkodd.com/oddthinking/2005/12/06/python-imaging-library-pil-and-animated-gifs/

FogleBird
fuente

Respuestas:

281

Recomiendo no usar images2gif de visvis porque tiene problemas con PIL / Pillow y no se mantiene activamente (debo saberlo porque soy el autor).

En su lugar, utilice imageio , que fue desarrollado para resolver este problema y más, y está destinado a quedarse.

Solución rápida y sucia:

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

Para películas más largas, use el enfoque de transmisión:

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
fuente
37
también la duración del parámetro = 0.5 establece las duraciones de 0.5 segundos para cada cuadro.
Alleo
3
ValueError: no se pudo encontrar un formato para leer el archivo especificado en el modo 'i'. Recibo este error en Windows 2.7 winpython. ¿Alguna pista?
Vanko el
1
@Vanko, el error parece estar relacionado con la lectura del archivo, puede probar imagio.mimread, o si es una película con muchos cuadros, use el objeto lector como aquí: imageio.readthedocs.io/en/latest/…
Almar
2
@Alleo: "también la duración del parámetro = 0.5 establece las duraciones de 0.5 segundos para cada cuadro". Hay una función de duración para imageio? Si es así, ¿dónde está documentado esto? Leí todos los documentos y no pude encontrar ninguna mención de un argumento de duración.
Chris Nielsen
3
¡Excelente! imageio in anacondacede True, yay!
uhoh
47

Bueno, ahora estoy usando ImageMagick. Guardo mis cuadros como archivos PNG y luego invoco el convert.exe de ImageMagick desde Python para crear un GIF animado. Lo bueno de este enfoque es que puedo especificar una duración de cuadro para cada cuadro individualmente. Lamentablemente, esto depende de que ImageMagick esté instalado en la máquina. Tienen un envoltorio de Python pero se ve bastante malo y sin soporte. Todavía abierto a otras sugerencias.

FogleBird
fuente
21
Soy un tipo de Python pero encontré ImageMagick mucho más fácil aquí. Acabo de hacer mi secuencia de imágenes y ejecuté algo comoconvert -delay 20 -loop 0 *jpg animated.gif
Nick
Estoy de acuerdo, esta es la mejor solución que he encontrado. Aquí hay un ejemplo mínimo (basado en el código de ejemplo del usuario Steve B publicado en stackoverflow.com/questions/10922285/… ): pastebin.com/JJ6ZuXdz
andreasdr el
Usando ImageMagick, también puede cambiar fácilmente el tamaño del gif animado comoconvert -delay 20 -resize 300x200 -loop 0 *jpg animated.gif
Jun Wang
@ Nick, ¿cómo ejecutas ese código para hacer GIF? ¿Debo importar algo en Spyder IDE?
LUNA
@MOON, el comando ImageMagic que agregué anteriormente solo se ejecuta a través de la línea de comando.
Nick
43

A partir de junio de 2009, la publicación de blog citada originalmente tiene un método para crear GIF animados en los comentarios . Descargue el script images2gif.py (anteriormente images2gif.py , actualización cortesía de @geographika).

Luego, para invertir los cuadros en un gif, por ejemplo:

#!/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
fuente
2
Hay una nueva versión de este script que ofrece una calidad de salida mucho mejor en visvis.googlecode.com/hg/vvmovie/images2gif.py , puede usarse como un script independiente separado del paquete.
geographika
1
La secuencia de comandos mencionada en este comentario siempre me da un error de segmentación cuando se usa en Mac, incluso cuando simplemente se ejecuta (usando el nombre __ == '__ main ' ejemplo). Estoy probando el guión mencionado en la respuesta, con la esperanza de que funcione correctamente. EDITAR: puedo confirmar que el script al que se hace referencia en la respuesta anterior funciona correctamente en mi Mac.
scubbo
66
En lugar de simplemente descargar el script, use pip pip install visvis, por ejemplo , luego en su script from visvis.vvmovie.images2gif import writeGif.
Daniel Farrell
8
Intenté esto con Python 2.7.3 en Windows 8 y obtengo UnicodeDecodeError: el códec 'ascii' no puede decodificar el byte 0xc8 en la posición 6: el ordinal no está en el rango (128). Desde correr images2gif.py pitón
reckoner
3
Soy el autor de visivis (e images2gif) y recomiendo no usarlo para este propósito. He estado trabajando en una mejor solución como parte del proyecto imageio (vea mi respuesta).
Almar
40

Así es como lo hace usando solo PIL (instalar con :)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
fuente
44
esta debería ser la respuesta aceptada, gracias @Kris
ted930511
1
¿Qué contiene la variable de asterisco ("* imgs")?
denisb411
3
Esa es una característica del lenguaje Python. Lo hace desembalaje iterable . Puede más o menos pensar en él como el desembalaje x = [a, b, c]a *xque puede ser pensado como a, b, c(sin los corchetes que encierran). En las llamadas a funciones son sinónimos: f(*x) == f(a, b, c). En el desempaquetado de tuplas es particularmente útil en los casos en que desea dividir un iterable en una cabeza (primer elemento) y una cola (el resto), que es lo que hago en este ejemplo.
Kris
25

Solía images2gif.py que era fácil de usar. Aunque parecía duplicar el tamaño del archivo ...

26 archivos PNG de 110kb, esperaba 26 * 110kb = 2860kb, pero my_gif.GIF fue 5.7mb

También porque el GIF era de 8 bits, los buenos png se volvieron un poco borrosos en el GIF

Aquí está el código que usé:

__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

Aquí hay 3 de los 26 cuadros:

Aquí hay 3 de los 26 cuadros

reducir las imágenes redujo el tamaño:

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

gif más pequeño

robert king
fuente
Hice una publicación de blog sobre esto ... robert-king.com/#post2-python-makes-gif
robert king
2
Recibo errores. El archivo "C: \ Python27 \ lib \ images2gif.py", línea 418, en writeGifToFile globalPalette = paletas [ocurre.index (max (ocurra))] ValueError: max () arg es una secuencia vacía
Harry
ocurrir está probablemente vacío. Mi archivo images2gif.py no tiene una variable "globalPalette".
Robert King
¿Cómo cambio eso? Estoy usando el script images2gif.py más reciente que existe ( bit.ly/XMMn5h )
Harry
44
@robertking con el código Recibí un error que decíafp.write(globalPalette) TypeError: must be string or buffer, not list
LWZ
19

Para crear un video, puede 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
fuente
9

Me encontré con esta publicación y ninguna de las soluciones funcionó, así que aquí está mi solución que funciona

Problemas con otras soluciones hasta ahora:
1) No hay una solución explícita sobre cómo se modifica la duración
2) No hay solución para la iteración del directorio fuera de orden, que es esencial para los GIF
3) No hay explicación de cómo instalar imageio para python 3

instale imageio así: python3 -m pip install imageio

Nota: querrás asegurarte de que tus marcos tengan algún tipo de índice en el nombre del archivo para que se puedan ordenar, de lo contrario no tendrás forma de saber dónde comienza o termina el 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
Daniel McGrath
fuente
1
sortpodría producir resultados inesperados si su esquema de numeración no incluye ceros a la izquierda. Además, ¿por qué usaste el mapa en lugar de una simple lista de comprensión?
NO
Sugeriría hacerfilenames.append(os.path.join(path, filename))
trueter
Secodning Nohs, images = [imageio.imread(f) for f in filenames]es más limpio, más rápido y más pitónico.
Brandon Dube el
6

Como Warren dijo el año pasado , esta es una vieja pregunta. Como las personas todavía parecen estar viendo la página, me gustaría redirigirlas a una solución más moderna. Como dijo blakev aquí , hay un ejemplo de Pillow en 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 ejemplo está desactualizado ( gifmakerno es un módulo importable, solo un script). Pillow tiene un GifImagePlugin (cuya fuente está en GitHub ), pero el documento en ImageSequence parece indicar un soporte limitado (solo lectura)

Nathan
fuente
5

Antigua pregunta, muchas buenas respuestas, pero aún podría haber interés en otra alternativa ...

El numpngwmódulo que coloqué recientemente en github ( https://github.com/WarrenWeckesser/numpngw ) puede escribir archivos PNG animados desde matrices numpy. ( Actualización : numpngwahora está en pypi: https://pypi.python.org/pypi/numpngw ).

Por ejemplo, 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)

crea:

png animado

Necesitará un navegador que admita PNG animado (ya sea directamente o con un complemento) para ver la animación.

Warren Weckesser
fuente
Chrome ahora también lo hace, por cierto. Una pregunta: ¿seq puede ser iterable? ¿Soporta "streaming" (es decir, abrir el APNG objetivo y agregar cuadros uno por uno en un bucle)?
Tomasz Gandor
No admite una iteración o transmisión arbitraria, pero eso no significa que no podría en el futuro. :) Cree un problema en la página de Github con la mejora propuesta. Si tiene alguna idea sobre la API para esta función, descríbala en el problema.
Warren Weckesser
Tuve un error extraño que hice un problema en su repositorio.
mLstudent33
5

Como un miembro mencionó anteriormente, imageio es una excelente manera de hacer esto. imageio también le permite establecer la velocidad de fotogramas, y en realidad escribí una función en Python que le permite establecer una retención en el fotograma final. Utilizo esta función para animaciones científicas donde el bucle es útil pero el reinicio inmediato no lo es. Aquí está el enlace y la función:

Cómo hacer un 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)

Ejemplo de GIF usando este método

Portal del creador
fuente
4

Con windows7, python2.7, opencv 3.0, lo siguiente funciona para mí:

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
fuente
3

Lo más fácil que hace que funcione para mí es llamar a un comando de shell en Python.

Si sus imágenes se almacenan como dummy_image_1.png, dummy_image_2.png ... dummy_image_N.png, puede usar la función:

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

Solo ejecuta:

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

Esto construirá su archivo gif my_output.gif.

Pkjain
fuente
2

La tarea se puede completar ejecutando el script de Python de dos líneas desde la misma carpeta que la secuencia de archivos de imagen. Para archivos con formato png, el script es:

from scitools.std import movie
movie('*.png',fps=1,output_file='thisismygif.gif')
ARKE
fuente
1
Lo intenté ... no funcionó para mí en Python 2.6. Devuelto: "la función scitools.easyviz.movie ejecuta el comando: / convert -delay 100 g4testC _ *. Png g4testC.gif / Parámetro no válido - 100"
Dan H
El problema no es con Python con seguridad. Vuelva a instalar imagemagick en su sistema y vuelva a intentarlo.
ArKE
2

Estaba buscando un código de línea única y encontré que lo siguiente funcionaba para mi aplicación. Aquí esta lo que hice:

Primer paso: instale ImageMagick desde el siguiente enlace

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

ingrese la descripción de la imagen aquí

Segundo paso: Apunte la línea cmd a la carpeta donde se colocan las imágenes (en mi caso, formato .png)

ingrese la descripción de la imagen aquí

Tercer paso: escriba el siguiente comando

magick -quality 100 *.png outvideo.mpeg

ingrese la descripción de la imagen aquí

Gracias FogleBird por la idea!

Jesu Kiran Spurgen
fuente
0

Acabo de intentar lo siguiente y fue muy útil:

Primero descargue las bibliotecas Figtodaty images2gifen su directorio local.

En segundo lugar, reúna las figuras en una matriz y conviértalas en un 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
fuente
0

Encontré el módulo ImageSequence de PIL , que ofrece una mejor (y más estándar) aninmación GIF. También utilizo el método after () de Tk esta vez, que es mejor que 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
fuente
0

Una función simple que hace 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()
Willem
fuente
0

Entiendo que preguntaste sobre convertir imágenes a un gif; sin embargo, si el formato original es MP4, puede usar FFmpeg :

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

Es realmente increíble ... ¡Todos están proponiendo un paquete especial para reproducir un GIF animado, en el momento en que se puede hacer con Tkinter y el clásico módulo PIL!

Aquí está mi propio método de animación GIF (creé hace un tiempo). Muy simple:

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

Puede establecer sus propios medios para detener la animación. Avíseme si desea obtener la versión completa con los botones reproducir / pausar / salir.

Nota: No estoy seguro si los marcos consecutivos se leen de la memoria o del archivo (disco). En el segundo caso, sería más eficiente si todos leen a la vez y se guardan en una matriz (lista). (¡No estoy tan interesado en descubrirlo! :)

Apostolos
fuente
1
Por lo general, no es un buen ideal para llamar sleepal hilo principal de una GUI. Puede usar el aftermétodo para llamar a una función periódicamente.
Bryan Oakley
Tienes razón, pero este no es el punto en absoluto, ¿no? El punto es todo el método. Entonces, ¡preferiría esperar una reacción a eso!
Apostolos
1
Solo intentaba darte consejos sobre cómo mejorar tu respuesta.
Bryan Oakley
Por cierto, normalmente tk.after()me uso a mí mismo. Pero aquí necesitaba hacer el código lo más simple posible. Quien use este método de animación GIF puede aplicar su propia función de retraso.
Apostolos
¡Al final! Sí, este es realmente un muy buen punto. Estaba fuera de tema! Gracias, @Novel. (¡Es interesante ver cómo otros se perdieron esto, por ejemplo, Bryan, que estaba hablando sobre el método de retraso de tiempo!)
Apostolos