I have a series of images that I want to create a video from. Ideally I could specify a frame duration for each frame but a fixed frame rate would be fine too. I’m doing this in wxPython, so I can render to a wxDC or I can save the images to files, like PNG. Is there a Python library that will allow me to create either a video (AVI, MPG, etc) or an animated GIF from these frames?
I’d recommend not using images2gif from visvis because it has problems with PIL/Pillow and is not actively maintained (I should know, because I am the author).
Instead, please use imageio, which was developed to solve this problem and more, and is intended to stay.
Quick and dirty solution:
import imageio
images = []
for filename in filenames:
images.append(imageio.imread(filename))
imageio.mimsave('/path/to/movie.gif', images)
For longer movies, use the streaming approach:
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)
Well, now I’m using ImageMagick. I save my frames as PNG files and then invoke ImageMagick’s convert.exe from Python to create an animated GIF. The nice thing about this approach is I can specify a frame duration for each frame individually. Unfortunately this depends on ImageMagick being installed on the machine. They have a Python wrapper but it looks pretty crappy and unsupported. Still open to other suggestions.
As of June 2009 the originally cited blog post has a method to create animated GIFs in the comments. Download the script images2gif.py (formerly images2gif.py, update courtesy of @geographika).
Then, to reverse the frames in a gif, for instance:
#!/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)
回答 3
仅使用PIL(使用:安装pip install Pillow)的方法如下:
import glob
from PIL importImage# 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)
__author__ ='Robert'from images2gif import writeGif
from PIL importImageimport 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
以下是26帧中的3帧:
缩小图像会减小尺寸:
size =(150,150)for im in images:
im.thumbnail(size,Image.ANTIALIAS)
I used images2gif.py which was easy to use. It did seem to double the file size though..
26 110kb PNG files, I expected 26*110kb = 2860kb, but my_gif.GIF was 5.7mb
Also because the GIF was 8bit, the nice png’s became a little fuzzy in the GIF
Here is the code I used:
__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
Here are 3 of the 26 frames:
shrinking the images reduced the size:
size = (150,150)
for im in images:
im.thumbnail(size, Image.ANTIALIAS)
#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])
#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])
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
I came across this post and none of the solutions worked, so here is my solution that does work
Problems with other solutions thus far: 1) No explicit solution as to how the duration is modified 2) No solution for the out of order directory iteration, which is essential for GIFs 3) No explanation of how to install imageio for python 3
install imageio like this: python3 -m pip install imageio
Note: you’ll want to make sure your frames have some sort of index in the filename so they can be sorted, otherwise you’ll have no way of knowing where the GIF starts or ends
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
importImageSequenceimportImageimport gifmaker
sequence =[]
im =Image.open(....)# im is your original image
frames =[frame.copy()for frame inImageSequence.Iterator(im)]# write GIF animation
fp = open("out.gif","wb")
gifmaker.makedelta(fp, frames)
fp.close()
Like Warren said last year, this is an old question. Since people still seem to be viewing the page, I’d like to redirect them to a more modern solution. Like blakev said here, there is a Pillow example on 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()
Note: This example is outdated (gifmaker is not an importable module, only a script). Pillow has a GifImagePlugin (whose source is on GitHub), but the doc on ImageSequence seems to indicate limited support (reading only)
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 alreadyifnot 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 dataif 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)
As one member mentioned above, imageio is a great way to do this. imageio also allows you to set the frame rate, and I actually wrote a function in Python that allows you to set a hold on the final frame. I use this function for scientific animations where looping is useful but immediate restart isn’t. Here is the link and the function:
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)
The task can be completed by running the two line python script from the same folder as the sequence of picture files. For png formatted files the script is –
from scitools.std import movie
movie('*.png',fps=1,output_file='thisismygif.gif')
fromTkinterimport*from PIL importImage,ImageTk,ImageSequencedef stop(event):global play
play =False
exit()
root =Tk()
root.bind("<Key>", stop)# Press any key to stopGIFfile={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 inImageSequence.Iterator(im):ifnot play:break
root.after(delay);
img =ImageTk.PhotoImage(frame)
lbl.config(image=img); root.update()# Show the new frame/image
root.mainloop()
I came upon PIL’s ImageSequence module, which offers for a better (and more standard) GIF aninmation. I also use Tk’s after() method this time, which is better than 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()
回答 18
制作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()
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()
fromTkinterimport*from PIL importImage,ImageTkfrom time import sleep
def stop(event):global play
play =False
exit()
root =Tk()
root.bind("<Key>", stop)# Press any key to stopGIFfile={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 =0while play:
sleep(delay);
frame +=1try:
im.seek(frame); img =ImageTk.PhotoImage(im)
lbl.config(image=img); root.update()# Show the new frame/imageexceptEOFError:
frame =0# Restart
root.mainloop()
It’s really incredible … All are proposing some special package for playing an animated GIF, at the moment that it can be done with Tkinter and the classic PIL module!
Here is my own GIF animation method (I created a while ago). Very 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()
You can set your own means to stop the animation. Let me know if you like to get the full version with play/pause/quit buttons.
Note: I am not sure if the consecutive frames are read from memory or from the file (disk). In the second case it would be more efficient if they all read at once and saved into an array (list). (I’m not so interested to find out! :)