How to animate a time-ordered sequence of matplotl

2019-01-22 08:49发布

问题:

I want to plot a sequence of .png images in matplotlib. The goal is to plot them rapidly to simulate the effect of a movie, but I have additional reasons for wanting to avoid actually creating an .avi file or saving matplotlib figures and then viewing them in sequence outside of Python.

I'm specifically trying to view the image files in sequence inside a for-loop in Python. Assuming I have imported matplotlib correctly, and I have my own functions 'new_image()' and 'new_rect()', here's some example code that fails to work because of the blocking effect of the show() function's call to the GUI mainloop:

 for index in index_list:
     img = new_image(index)
     rect = new_rect(index)

     plt.imshow(img)
     plt.gca().add_patch(rect)
     plt.show()

     #I also tried pausing briefly and then closing, but this doesn't
     #get executed due to the GUI mainloop from show()
     time.sleep(0.25)
     plt.close()

The above code works to show only the first image, but then the program just hangs and waits for me to manually close the resultant figure window. Once I do close it, the program then just hangs and doesn't re-plot with the new image data. What should I be doing? Also note that I have tried replacing the plt.show() command with a plt.draw() command, and then adding the plt.show() outside of the for-loop. This doesn't display anything and just hangs.

回答1:

Based on http://matplotlib.sourceforge.net/examples/animation/simple_anim_tkagg.html:

import time
import numpy as np
import matplotlib
matplotlib.use('TkAgg') # do this before importing pylab

import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)

def animate():
    tstart = time.time()                   # for profiling
    data=np.random.randn(10,10)
    im=plt.imshow(data)

    for i in np.arange(1,200):
        data=np.random.randn(10,10)
        im.set_data(data)
        fig.canvas.draw()                         # redraw the canvas
    print 'FPS:' , 200/(time.time()-tstart)

win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate)
plt.show()

plt.imshow can accept a float array, uint8 array, or a PIL image. So if you have a directory of PNG files, you could open them as PIL images and animate them like this:

import matplotlib
matplotlib.use('TkAgg') # do this before importing pylab
import matplotlib.pyplot as plt
import Image
import glob

fig = plt.figure()
ax = fig.add_subplot(111)

def animate():
    filenames=sorted(glob.glob('*.png'))
    im=plt.imshow(Image.open(filenames[0]))
    for filename in filenames[1:]:
        image=Image.open(filename)
        im.set_data(image)
        fig.canvas.draw() 

win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate)
plt.show()


回答2:

The best way I have found for this was with the command pylab.ion() after you import pylab.

Here is a script that does use show(), but which displays the different plots each time pylab.draw() is called, and which leaves the plot windows showing indefinitely. It uses simple input logic to decide when to close the figures (because using show() means pylab won't process clicks on the windows x button), but that should be simple to add to your gui as another button or as a text field.

import numpy as np
import pylab
pylab.ion()

def get_fig(fig_num, some_data, some_labels):

    fig = pylab.figure(fig_num,figsize=(8,8),frameon=False)
    ax = fig.add_subplot(111)
    ax.set_ylim([0.1,0.8]); ax.set_xlim([0.1, 0.8]);
    ax.set_title("Quarterly Stapler Thefts")
    ax.pie(some_data, labels=some_labels, autopct='%1.1f%%', shadow=True);
    return fig

my_labels = ("You", "Me", "Some guy", "Bob")

# To ensure first plot is always made.
do_plot = 1; num_plots = 0;

while do_plot:
    num_plots = num_plots + 1;
    data = np.random.rand(1,4).tolist()[0]

    fig = get_fig(num_plots,data,my_labels)
    fig.canvas.draw()
    pylab.draw()

    print "Close any of the previous plots? If yes, enter its number, otherwise enter 0..."
    close_plot = raw_input()

    if int(close_plot) > 0:
        pylab.close(int(close_plot))

    print "Create another random plot? 1 for yes; 0 for no."
    do_plot = raw_input();

    # Don't allow plots to go over 10.
    if num_plots > 10:
        do_plot = 0

pylab.show()

By modifying the basic logic here, I can have it close windows and plot images consecutively to simulate playing a movie, or I can maintain keyboard control over how it steps through the movie.

Note: This has worked for me across platforms and seems strictly superior to the window canvas manager approach above, and doesn't require the 'TkAgg' option.



回答3:

I have implemented a handy script that just suits your need. Try it out here

Below is a example that show images together with its bounding box:

import os
import glob
from scipy.misc import imread
from matplotlib.pyplot import Rectangle

video_dir = 'YOUR-VIDEO-DIRECTORY'

img_files = glob.glob(os.path.join(video_dir, '*.jpg'))
box_files = glob.glob(os.path.join(video_dir, '*.txt'))

def redraw_fn(f, axes):
    img = imread(img_files[f])
    box = bbread(box_files[f])  # Define your own bounding box reading utility
    x, y, w, h = box
    if not redraw_fn.initialized:
        im = axes.imshow(img, animated=True)
        bb = Rectangle((x, y), w, h,
                       fill=False,  # remove background
                       edgecolor="red")
        axes.add_patch(bb)
        redraw_fn.im = im
        redraw_fn.bb = bb
        redraw_fn.initialized = True
    else:
        redraw_fn.im.set_array(img)
        redraw_fn.bb.set_xy((x, y))
        redraw_fn.bb.set_width(w)
        redraw_fn.bb.set_height(h)
redraw_fn.initialized = False

videofig(len(img_files), redraw_fn, play_fps=30)