Générer par programme une vidéo ou un GIF animé en Python?

221

J'ai une série d'images à partir desquelles je veux créer une vidéo. Idéalement, je pourrais spécifier une durée de trame pour chaque trame, mais un taux de trame fixe serait bien aussi. Je fais cela dans wxPython, donc je peux effectuer un rendu sur un wxDC ou je peux enregistrer les images dans des fichiers, comme PNG. Existe-t-il une bibliothèque Python qui me permettra de créer soit une vidéo (AVI, MPG, etc.) ou un GIF animé à partir de ces images?

Edit: j'ai déjà essayé PIL et cela ne semble pas fonctionner. Quelqu'un peut-il me corriger avec cette conclusion ou suggérer une autre boîte à outils? Ce lien semble sauvegarder ma conclusion concernant PIL: http://www.somethinkodd.com/oddthinking/2005/12/06/python-imaging-library-pil-and-animated-gifs/

FogleBird
la source

Réponses:

281

Je recommande de ne pas utiliser images2gif de visvis car il a des problèmes avec PIL / Pillow et n'est pas activement maintenu (je devrais le savoir, car je suis l'auteur).

Au lieu de cela, veuillez utiliser imageio , qui a été développé pour résoudre ce problème et plus encore, et qui est destiné à rester.

Solution rapide et sale:

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

Pour les films plus longs, utilisez l'approche 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
la source
37
également la durée du paramètre = 0,5 définit les durées de 0,5 seconde pour chaque trame.
Alleo
3
ValueError: Impossible de trouver un format pour lire le fichier spécifié en mode «i» - je reçois cette erreur sur Windows 2.7 Winpython. Des indices?
Vanko
1
@Vanko, l'erreur semble être liée à la lecture du fichier, vous pouvez essayer imagio.mimread, ou si c'est un film avec de nombreuses images, utilisez l'objet lecteur comme ici: imageio.readthedocs.io/en/latest/…
Almar
2
@Alleo: "également la durée du paramètre = 0,5 définit les durées de 0,5 seconde pour chaque image". Il existe une fonction de durée pour imageio? Si oui, où est-ce documenté? J'ai lu tous les documents et je n'ai trouvé aucune mention d'un argument de durée.
Chris Nielsen
3
Excellent! imageio in anacondadonne True, yay!
uhoh
47

Eh bien, maintenant j'utilise ImageMagick. J'enregistre mes images sous forme de fichiers PNG, puis j'appelle convert.exe d'ImageMagick à partir de Python pour créer un GIF animé. La bonne chose à propos de cette approche est que je peux spécifier une durée d'image pour chaque image individuellement. Malheureusement, cela dépend de l'installation d'ImageMagick sur la machine. Ils ont un wrapper Python mais il semble assez merdique et non pris en charge. Toujours ouvert à d'autres suggestions.

FogleBird
la source
21
Je suis un gars Python mais j'ai trouvé ImageMagick beaucoup plus facile ici. Je viens de faire ma séquence d'images et j'ai exécuté quelque chose commeconvert -delay 20 -loop 0 *jpg animated.gif
Nick
Je suis d'accord, c'est la meilleure solution que j'ai rencontrée. Voici un exemple minimal (basé sur l'exemple de code de l'utilisateur Steve B publié sur stackoverflow.com/questions/10922285/… ): pastebin.com/JJ6ZuXdz
andreasdr
En utilisant ImageMagick, vous pouvez également facilement redimensionner le gif animé tel queconvert -delay 20 -resize 300x200 -loop 0 *jpg animated.gif
Jun Wang
@ Nick, comment exécutez-vous ce code pour créer des GIF? Dois-je importer quelque chose dans Spyder IDE?
LUNE
@MOON, la commande ImageMagic que j'ai ajoutée ci-dessus est exécutée via la ligne de commande.
Nick
43

Depuis juin 2009, l'article de blog cité à l'origine propose une méthode pour créer des GIF animés dans les commentaires . Téléchargez le script images2gif.py (anciennement images2gif.py , mise à jour gracieuseté de @geographika).

Ensuite, pour inverser les frames dans un gif, par exemple:

#!/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
la source
2
Il existe une nouvelle version de ce script qui rend la sortie de bien meilleure qualité sur visvis.googlecode.com/hg/vvmovie/images2gif.py, il peut être utilisé comme script autonome distinct du package.
geographika
1
Le script mentionné dans ce commentaire donne systématiquement une erreur de segmentation lorsqu'il est utilisé sur Mac, même lorsqu'il est simplement exécuté (en utilisant l'exemple du nom __ == '__ main '). J'essaie le script mentionné dans la réponse, dans l'espoir qu'il fonctionnera correctement. EDIT - Je peux confirmer que le script référencé dans la réponse ci-dessus fonctionne correctement sur mon Mac.
scubbo
6
Plutôt que de simplement télécharger le script, utilisez pip par exemple pip install visvis, puis dans votre script from visvis.vvmovie.images2gif import writeGif.
Daniel Farrell
8
J'ai essayé cela avec Python 2.7.3 sur Windows 8 et j'obtiens UnicodeDecodeError: le codec 'ascii' ne peut pas décoder l'octet 0xc8 en position 6: l'ordinal n'est pas dans la plage (128). De l'exécution de python images2gif.py
reckoner
3
Je suis l'auteur de visivis (et images2gif) et déconseille de l'utiliser à cette fin. J'ai travaillé sur une meilleure solution dans le cadre du projet imageio (voir ma réponse).
Almar
40

Voici comment vous le faites en utilisant uniquement PIL (installez avec :)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
la source
4
ce devrait être la réponse acceptée, merci @Kris
ted930511
1
Que contient la variable astérisque ("* imgs")?
denisb411
3
C'est une fonctionnalité du langage python. Il fait un déballage itérable . Vous pouvez penser à peu près comme le déballage x = [a, b, c]à ce *xqui peut être considéré comme a, b, c(sans les crochets enserrant). Dans ces appels de fonction sont synonymes: f(*x) == f(a, b, c). Dans le déballage de tuple, il est particulièrement utile dans les cas où vous souhaitez diviser un itérable en une tête (premier élément) et une queue (le reste), ce que je fais dans cet exemple.
Kris
25

J'ai utilisé images2gif.py qui était facile à utiliser. Il semble que cela double la taille du fichier.

26 fichiers PNG de 110 Ko, je m'attendais à 26 * 110 Ko = 2860 Ko, mais mon_gif.GIF était de 5,7 Mo

Aussi parce que le GIF était 8 bits, le joli png est devenu un peu flou dans le GIF

Voici le code que j'ai utilisé:

__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

Voici 3 des 26 cadres:

Voici 3 des 26 cadres

la réduction des images a réduit la taille:

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

gif plus petit

Robert King
la source
J'ai fait un blog à ce sujet .. robert-king.com/#post2-python-makes-gif
robert king
2
J'obtiens des erreurs .. Fichier "C: \ Python27 \ lib \ images2gif.py", ligne 418, dans writeGifToFile globalPalette = palettes [occur.index (max (occur))] ValueError: max () arg est une séquence vide
Harry
se produit est probablement vide. Mon fichier images2gif.py n'a pas de variable "globalPalette".
robert king
comment puis-je changer cela? J'utilise le script images2gif.py le plus récent ( bit.ly/XMMn5h )
Harry
4
@robertking avec le code J'ai eu une erreur en disantfp.write(globalPalette) TypeError: must be string or buffer, not list
LWZ
19

Pour créer une vidéo, vous pouvez utiliser 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
la source
9

Je suis tombé sur ce post et aucune des solutions n'a fonctionné, alors voici ma solution qui fonctionne

Problèmes avec d'autres solutions jusqu'à présent:
1) Aucune solution explicite quant à la façon dont la durée est modifiée
2) Aucune solution pour l'itération du répertoire en panne, ce qui est essentiel pour les GIF
3) Aucune explication sur la façon d'installer imageio pour python 3

installez imageio comme ceci: python3 -m pip installez imageio

Remarque: vous voudrez vous assurer que vos cadres ont une sorte d'index dans le nom de fichier afin qu'ils puissent être triés, sinon vous n'aurez aucun moyen de savoir où le GIF commence ou se termine

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
la source
1
sortpeut donner des résultats inattendus si votre schéma de numérotation n'inclut pas de zéros non significatifs. Aussi pourquoi avez-vous utilisé la carte au lieu d'une simple compréhension de liste?
NOhs
Je suggérerais de fairefilenames.append(os.path.join(path, filename))
Trueter
Secodning Nohs, images = [imageio.imread(f) for f in filenames]est plus propre, plus rapide et plus pythonique.
Brandon Dube
6

Comme Warren l'a dit l'année dernière , c'est une vieille question. Étant donné que les gens semblent toujours consulter la page, j'aimerais les rediriger vers une solution plus moderne. Comme l'a dit blakev ici , il y a un exemple Pillow sur 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()

Remarque: Cet exemple est obsolète ( gifmakern'est pas un module importable, seulement un script). Pillow a un GifImagePlugin (dont la source est sur GitHub ), mais le document sur ImageSequence semble indiquer un support limité (lecture uniquement)

Nathan
la source
5

Vieille question, beaucoup de bonnes réponses, mais il pourrait encore y avoir un intérêt pour une autre alternative ...

Le numpngwmodule que j'ai récemment installé sur github ( https://github.com/WarrenWeckesser/numpngw ) peut écrire des fichiers PNG animés à partir de tableaux numpy. ( Mise à jour : numpngwest maintenant sur pypi: https://pypi.python.org/pypi/numpngw .)

Par exemple, ce 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)

crée:

png animé

Vous aurez besoin d'un navigateur qui prend en charge le PNG animé (directement ou avec un plugin) pour voir l'animation.

Warren Weckesser
la source
Chrome fait maintenant aussi, BTW. Une question - peut seq être un itérable? Prenez-vous en charge le "streaming" (c'est-à-dire l'ouverture de l'APNG cible et l'ajout d'images une par une en boucle)?
Tomasz Gandor
Il ne prend pas en charge un itérable ou un streaming arbitraire, mais cela ne signifie pas qu'il ne pourrait pas à l'avenir. :) Créez un problème sur la page github avec l'amélioration proposée. Si vous avez des idées sur l'API pour cette fonctionnalité, veuillez les décrire dans le numéro.
Warren Weckesser
J'ai eu une erreur bizarre que j'ai faite un problème sur votre repo.
mLstudent33
5

Comme un membre l'a mentionné ci-dessus, imageio est un excellent moyen de le faire. imageio vous permet également de définir la fréquence d'images, et j'ai en fait écrit une fonction en Python qui vous permet de définir une prise sur l'image finale. J'utilise cette fonction pour des animations scientifiques où le bouclage est utile mais pas un redémarrage immédiat. Voici le lien et la fonction:

Comment créer un GIF en utilisant 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)

Exemple de GIF utilisant cette méthode

Maker Portal
la source
4

Avec windows7, python2.7, opencv 3.0, ce qui suit fonctionne pour moi:

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
la source
3

La chose la plus simple qui le fait fonctionner pour moi est d'appeler une commande shell en Python.

Si vos images sont stockées telles que dummy_image_1.png, dummy_image_2.png ... dummy_image_N.png, alors vous pouvez utiliser la fonction:

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

Exécutez simplement:

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

Cela va construire votre fichier gif my_output.gif.

Pkjain
la source
2

La tâche peut être terminée en exécutant le script python à deux lignes à partir du même dossier que la séquence de fichiers image. Pour les fichiers au format png, le script est -

from scitools.std import movie
movie('*.png',fps=1,output_file='thisismygif.gif')
ArKE
la source
1
Je l'ai essayé ... n'a pas fonctionné pour moi sous Python 2.6. Renvoyé: "La fonction scitools.easyviz.movie exécute la commande: / convert -delay 100 g4testC _ *. Png g4testC.gif / Paramètre non valide - 100"
Dan H
Le problème n'est pas avec Python à coup sûr. Réinstallez imagemagick sur votre système et réessayez.
ArKE
2

Je cherchais un code sur une seule ligne et j'ai trouvé ce qui suit pour mon application. Voici ce que j'ai fait:

Première étape: installez ImageMagick à partir du lien ci-dessous

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

entrez la description de l'image ici

Deuxième étape: pointez la ligne cmd vers le dossier où les images (dans mon cas, au format .png) sont placées

entrez la description de l'image ici

Troisième étape: tapez la commande suivante

magick -quality 100 *.png outvideo.mpeg

entrez la description de l'image ici

Merci FogleBird pour l'idée!

Jesu Kiran Spurgen
la source
0

J'ai juste essayé ce qui suit et j'ai été très utile:

Téléchargez d'abord les bibliothèques Figtodatetimages2gif dans votre répertoire local.

Ensuite, collectez les figurines dans un tableau et convertissez-les en un gif animé:

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
la source
0

Je suis tombé sur le module ImageSequence de PIL , qui offre une meilleure (et plus standard) aninmation GIF. J'utilise également la méthode after () de Tk cette fois, ce qui est mieux 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
la source
0

Une fonction simple qui fait des 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
la source
0

Je comprends que vous avez posé des questions sur la conversion d'images en gif; cependant, si le format d'origine est MP4, vous pouvez utiliser FFmpeg :

ffmpeg -i input.mp4 output.gif
Sam Perry
la source
-1

C'est vraiment incroyable ... Tous proposent un package spécial pour jouer un GIF animé, en ce moment cela peut être fait avec Tkinter et le module PIL classique!

Voici ma propre méthode d'animation GIF (j'ai créé il y a quelque temps). Très 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()

Vous pouvez définir vos propres moyens pour arrêter l'animation. Faites-moi savoir si vous souhaitez obtenir la version complète avec les boutons play / pause / quit.

Remarque: je ne sais pas si les images consécutives sont lues dans la mémoire ou dans le fichier (disque). Dans le second cas, il serait plus efficace s'ils lisent tous en même temps et sont enregistrés dans un tableau (liste). (Je ne suis pas si intéressé de le savoir! :)

Apostolos
la source
1
Ce n'est généralement pas un bon idéal pour appeler sleepdans le thread principal d'une interface graphique. Vous pouvez utiliser la afterméthode pour appeler périodiquement une fonction.
Bryan Oakley
Vous avez raison, mais ce n'est pas du tout le cas, non? Le point est toute la méthode. Je préfère donc une réaction à cela!
Apostolos
1
J'essayais juste de donner des conseils sur la façon d'améliorer votre réponse.
Bryan Oakley
BTW, je m'utilise normalement tk.after(). Mais ici, je devais rendre le code aussi simple que possible. Celui qui utilise cette méthode d'animation GIF peut appliquer sa propre fonction de retard.
Apostolos
Enfin! Oui, c'est en effet un très bon point. J'étais hors sujet! Merci, @Novel. (Intéressant de voir comment d'autres ont manqué cela, par exemple Bryan, qui parlait de la méthode du retard!)
Apostolos