Generare a livello di programmazione video o GIF animate in Python?


221

Ho una serie di immagini da cui voglio creare un video. Idealmente, potrei specificare una durata del fotogramma per ciascun fotogramma, ma anche una frequenza fotogrammi fissa andrebbe bene. Lo sto facendo in wxPython, quindi posso renderizzare in un wxDC o posso salvare le immagini in file, come PNG. Esiste una libreria Python che mi permetterà di creare un video (AVI, MPG, ecc.) O una GIF animata da questi frame?

Modifica: ho già provato PIL e non sembra funzionare. Qualcuno può correggermi con questa conclusione o suggerire un altro toolkit? Questo link sembra confermare le mie conclusioni su PIL: http://www.somethinkodd.com/oddthinking/2005/12/06/python-imaging-library-pil-and-animated-gifs/

Risposte:


281

Consiglio di non usare images2gif di visvis perché ha problemi con PIL / Pillow e non è attivamente mantenuto (dovrei saperlo, perché sono l'autore).

Invece, si prega di utilizzare imageio , che è stato sviluppato per risolvere questo problema e altro ancora ed è destinato a rimanere.

Soluzione rapida e sporca:

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

Per film più lunghi, utilizzare l'approccio di 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)

37
anche la durata del parametro = 0,5 imposta la durata di 0,5 secondi per ciascun fotogramma.
Alleo,

3
ValueError: Impossibile trovare un formato per leggere il file specificato in modalità 'i' - Ricevo questo errore su Windows 2.7 winpython. Qualche indizio?
Vanko,

1
@Vanko l'errore sembra essere correlato alla lettura del file, potresti provare imagio.mimread, o se è un film con molti frame, usa l'oggetto reader come qui: imageio.readthedocs.io/en/latest/…
Almar,

2
@Alleo: "anche la durata del parametro = 0,5 imposta la durata di 0,5 secondi per ciascun fotogramma". C'è una funzione di durata per imageio? In tal caso, dove è documentato? Ho letto tutti i documenti e non sono riuscito a trovare alcuna menzione di un argomento sulla durata.
Chris Nielsen,

3
Eccellente! imageio in anacondacedi True, yay!
uho

47

Bene, ora sto usando ImageMagick. Salvo i miei frame come file PNG e quindi invoco convert.exe di ImageMagick da Python per creare una GIF animata. La cosa bella di questo approccio è che posso specificare una durata del fotogramma per ogni fotogramma individualmente. Sfortunatamente questo dipende dall'installazione di ImageMagick sul computer. Hanno un wrapper Python ma sembra piuttosto scadente e non supportato. Ancora aperto ad altri suggerimenti.


21
Sono un ragazzo di Python ma ho trovato ImageMagick molto più semplice qui. Ho appena creato la mia sequenza di immagini e ho eseguito qualcosa di simileconvert -delay 20 -loop 0 *jpg animated.gif
Nick,

Sono d'accordo, questa è la migliore soluzione che ho trovato. Ecco un esempio minimo (basato sul codice di esempio dell'utente Steve B pubblicato su stackoverflow.com/questions/10922285/… ): pastebin.com/JJ6ZuXdz
andreasdr

Usando ImageMagick, puoi anche ridimensionare facilmente le gif animate comeconvert -delay 20 -resize 300x200 -loop 0 *jpg animated.gif
Jun Wang,

@ Nick, come si esegue quel codice per creare GIF? Devo importare qualcosa nell'IDE di Spyder?
LUNA

@MOON il comando ImageMagic che ho aggiunto sopra viene semplicemente eseguito dalla riga di comando.
Nick,

43

A partire da giugno 2009, il post sul blog citato in origine ha un metodo per creare GIF animate nei commenti . Scarica lo script images2gif.py (precedentemente images2gif.py , aggiornamento per gentile concessione di @geographika).

Quindi, per invertire i frame in una gif, ad esempio:

#!/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
Esiste una nuova versione di questo script che produce un output di qualità molto migliore su visvis.googlecode.com/hg/vvmovie/images2gif.py che può essere utilizzato come script autonomo separato dal pacchetto.
geographika,

1
Lo script menzionato in questo commento fornisce costantemente un errore di segmentazione per me quando utilizzato su Mac, anche quando semplicemente eseguito (usando il nome __ == '__ main ' esempio). Sto provando la sceneggiatura menzionata nella risposta, nella speranza che funzioni correttamente. MODIFICA - Posso confermare che lo script a cui fa riferimento la risposta sopra funziona correttamente sul mio Mac.
scubbo,

6
Invece di scaricare lo script, usa pip ad es. pip install visvis, Quindi nel tuo script from visvis.vvmovie.images2gif import writeGif.
Daniel Farrell,

8
Ho provato questo con Python 2.7.3 su Windows 8 e ottengo UnicodeDecodeError: il codec 'ascii' non può decodificare il byte 0xc8 in posizione 6: ordinale non nell'intervallo (128). L'esecuzione pitone images2gif.py
reckoner

3
Sono l'autore di visivis (e images2gif) e sconsiglio di utilizzarlo per questo scopo. Ho lavorato su una soluzione migliore come parte del progetto imageio (vedi la mia risposta).
Almar,

40

Ecco come lo fai usando solo PIL (installa 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)

4
questa dovrebbe essere la risposta accettata, grazie @Kris
ted930511

1
Cosa contiene la variabile asterisco ("* imgs")?
denisb411

3
Questa è una funzione del linguaggio Python. Lo fa iterable disimballaggio . Puoi approssimativamente pensarlo come un disimballaggio x = [a, b, c]a *xcui si può pensare come a, b, c(senza le parentesi quadre). In questi chiamate di funzione sono sinonimi: f(*x) == f(a, b, c). Nel disimballare la tupla è particolarmente utile nei casi in cui si desidera dividere un iterabile in una testa (primo elemento) e una coda (il resto), che è ciò che faccio in questo esempio.
Kris

25

Ho usato images2gif.py che era facile da usare. Sembrava raddoppiare la dimensione del file però ..

26 file PNG da 110kb, mi aspettavo 26 * 110kb = 2860kb, ma my_gif.GIF era 5.7mb

Anche perché la GIF era a 8 bit, i simpatici png sono diventati un po 'sfocati nella GIF

Ecco il codice che ho usato:

__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

Ecco 3 dei 26 frame:

Ecco 3 dei 26 frame

la riduzione delle immagini ha ridotto le dimensioni:

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

gif più piccolo


Ho pubblicato un post sul blog su questo .. robert-king.com/#post2-python-makes-gif
robert king

2
Ottengo errori .. File "C: \ Python27 \ lib \ images2gif.py", riga 418, in writeGifToFile globalPalette = palette [value.index (max (si verificano))] ValueError: max () arg è una sequenza vuota
Harry

si verifica è probabilmente vuoto. Il mio file images2gif.py non ha una variabile "globalPalette".
Robert King,

come lo cambio? Sto usando lo script images2gif.py più recente disponibile ( bit.ly/XMMn5h )
Harry,

4
@robertking con il codice Ho ricevuto un errore dicendofp.write(globalPalette) TypeError: must be string or buffer, not list
LWZ

19

Per creare un video, puoi utilizzare 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

Mi sono imbattuto in questo post e nessuna delle soluzioni ha funzionato, quindi ecco la mia soluzione che funziona

Problemi con altre soluzioni finora:
1) Nessuna soluzione esplicita su come viene modificata la durata
2) Nessuna soluzione per l'iterazione di directory fuori servizio, essenziale per GIF
3) Nessuna spiegazione su come installare imageio per python 3

installa imageio in questo modo: python3 -m pip installa imageio

Nota: ti consigliamo di assicurarti che i tuoi frame abbiano una sorta di indice nel nome del file in modo che possano essere ordinati, altrimenti non avrai modo di sapere dove inizia o finisce la 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
sortpotrebbe produrre risultati imprevisti se lo schema di numerazione non include zeri iniziali. Perché hai usato map invece di una semplice comprensione dell'elenco?
NO

Suggerirei di farefilenames.append(os.path.join(path, filename))
True

Secodning Nohs, images = [imageio.imread(f) for f in filenames]è più pulito, più veloce e più pitonico.
Brandon Dube,

6

Come ha detto Warren l'anno scorso , questa è una vecchia domanda. Poiché le persone sembrano ancora visualizzare la pagina, mi piacerebbe reindirizzarle a una soluzione più moderna. Come ha detto Blakev qui , c'è un esempio di Pillow su 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: questo esempio non è aggiornato ( gifmakernon è un modulo impostabile, ma solo uno script). Pillow ha un GifImagePlugin (la cui fonte è su GitHub ), ma il documento su ImageSequence sembra indicare un supporto limitato (solo lettura)



5

Vecchia domanda, molte buone risposte, ma potrebbe esserci ancora interesse per un'altra alternativa ...

Il numpngwmodulo che ho recentemente installato su github ( https://github.com/WarrenWeckesser/numpngw ) può scrivere file PNG animati da array numpy. ( Aggiornamento : numpngwè ora su pypi: https://pypi.python.org/pypi/numpngw .)

Ad esempio, questo 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 animato

Avrai bisogno di un browser che supporti PNG animato (direttamente o con un plugin) per vedere l'animazione.


Anche Chrome ora lo fa, a proposito. Una domanda: seq può essere un iterabile? Supportate lo "streaming" (ovvero aprendo l'APNG di destinazione e aggiungendo i fotogrammi uno alla volta in un ciclo)?
Tomasz Gandor,

Non supporta un iterabile o uno streaming arbitrario, ma ciò non significa che non potrebbe in futuro. :) Crea un problema sulla pagina github con il miglioramento proposto. Se hai idee sull'API per questa funzione, descrivile nel problema.
Warren Weckesser l'

Ho avuto un errore strano che ho fatto un problema sul tuo repository.
mLstudent33,

5

Come menzionato sopra da un membro, imageio è un ottimo modo per farlo. imageio ti consente anche di impostare la frequenza dei fotogrammi e in realtà ho scritto una funzione in Python che ti consente di mettere in attesa il fotogramma finale. Uso questa funzione per animazioni scientifiche in cui il looping è utile ma non il riavvio immediato. Ecco il link e la funzione:

Come realizzare una 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)

Esempio GIF usando questo metodo



4

Con windows7, python2.7, opencv 3.0, il seguente funziona per me:

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

La cosa più semplice per farlo funzionare per me è chiamare un comando shell in Python.

Se le tue immagini sono memorizzate come dummy_image_1.png, dummy_image_2.png ... dummy_image_N.png, puoi usare la funzione:

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

Esegui solo:

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

Questo costruirà il tuo file gif my_output.gif.


2

L'attività può essere completata eseguendo lo script Python a due righe dalla stessa cartella della sequenza dei file di immagine. Per i file formattati in png lo script è -

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

1
Provato ... non ha funzionato per me in Python 2.6. Restituito: "La funzione scitools.easyviz.movie esegue il comando: / convert -delay 100 g4testC _ *. Png g4testC.gif / Parametro non valido - 100"
Dan H

Il problema non è certo con Python. Reinstalla imagemagick sul tuo sistema e riprova.
ArKE,

2

Stavo cercando un codice a riga singola e ho trovato quanto segue per funzionare per la mia applicazione. Ecco cosa ho fatto:

Primo passo: installa ImageMagick dal link in basso

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

inserisci qui la descrizione dell'immagine

Secondo passo: punta la linea cmd verso la cartella in cui sono posizionate le immagini (nel mio caso formato .png)

inserisci qui la descrizione dell'immagine

Terzo passaggio: digitare il comando seguente

magick -quality 100 *.png outvideo.mpeg

inserisci qui la descrizione dell'immagine

Grazie FogleBird per l'idea!


0

Ho appena provato quanto segue ed è stato molto utile:

Per prima cosa scarica le librerie Figtodateimages2gif nella tua directory locale.

In secondo luogo Raccogliere le figure in un array e convertirle in una gif animata:

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

Mi sono imbattuto nel modulo ImageSequence di PIL , che offre una migliore (e più standard) animazione GIF. Questa volta uso anche il metodo after () di Tk , che è meglio di 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

Una semplice funzione che rende 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

Capisco che hai chiesto di convertire le immagini in GIF; tuttavia, se il formato originale è MP4, è possibile utilizzare FFmpeg :

ffmpeg -i input.mp4 output.gif

-1

È davvero incredibile ... Tutti stanno proponendo un pacchetto speciale per la riproduzione di una GIF animata, al momento che può essere fatto con Tkinter e il classico modulo PIL!

Ecco il mio metodo di animazione GIF (l'ho creato poco fa). Molto semplice:

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

È possibile impostare i propri mezzi per interrompere l'animazione. Fammi sapere se ti piace ottenere la versione completa con i pulsanti play / pause / quit.

Nota: non sono sicuro che i frame consecutivi vengano letti dalla memoria o dal file (disco). Nel secondo caso sarebbe più efficiente se leggessero tutti in una volta e salvassero in un array (elenco). (Non sono così interessato a scoprirlo! :)


1
In genere non è un buon ideale per chiamare sleepil thread principale di una GUI. È possibile utilizzare il aftermetodo per chiamare periodicamente una funzione.
Bryan Oakley,

Hai ragione, ma non è questo il punto, no? Il punto è l'intero metodo. Quindi, preferirei aspettarmi una reazione a questo!
Apostolos,

1
Stavo solo cercando di dare consigli su come migliorare la tua risposta.
Bryan Oakley,

A proposito, normalmente uso tk.after()me stesso. Ma qui dovevo rendere il codice il più semplice possibile. Chiunque utilizzi questo metodo di animazione GIF può applicare la propria funzione di ritardo.
Apostolos,

Alla fine! Sì, questo è davvero un ottimo punto. Ero fuori tema! Grazie, @Novel. (Interessante vedere come altri abbiano perso questo, ad esempio Bryan, che stava parlando del metodo del ritardo!)
Apostolos,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.