24

I have a couple of images that show how something changes in time. I visualize them as many images on the same plot with the following code:

import matplotlib.pyplot as plt
import matplotlib.cm as cm

img = [] # some array of images
fig = plt.figure()
for i in xrange(6):
    fig.add_subplot(2, 3, i + 1)
    plt.imshow(img[i], cmap=cm.Greys_r)

plt.show()

and get something like:enter image description here

Which is ok, but I would rather animate them to get something like this video. How can I achieve this with python and preferably (not necessarily) with matplotlib

Shir
  • 1,571
  • 2
  • 9
  • 27
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753

7 Answers7

46

For a future myself, here is what I ended up with:

def generate_video(img):
    for i in xrange(len(img)):
        plt.imshow(img[i], cmap=cm.Greys_r)
        plt.savefig(folder + "/file%02d.png" % i)

    os.chdir("your_folder")
    subprocess.call([
        'ffmpeg', '-framerate', '8', '-i', 'file%02d.png', '-r', '30', '-pix_fmt', 'yuv420p',
        'video_name.mp4'
    ])
    for file_name in glob.glob("*.png"):
        os.remove(file_name)
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
18

Another solution is to use AnimationArtist from matplotlib.animation as described in the animated image demo. Adapting for your example would be

import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.animation as animation

img = [] # some array of images
frames = [] # for storing the generated images
fig = plt.figure()
for i in xrange(6):
    frames.append([plt.imshow(img[i], cmap=cm.Greys_r,animated=True)])

ani = animation.ArtistAnimation(fig, frames, interval=50, blit=True,
                                repeat_delay=1000)
# ani.save('movie.mp4')
plt.show()
kcdragon
  • 1,724
  • 1
  • 14
  • 23
Keith Prussing
  • 803
  • 8
  • 19
6

You could export images from matplotlib using Agg interface.

See those examples:

Here is your full code:

# imports
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import cv2

# Use Agg backend for canvas
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

# create OpenCV video writer
video = cv2.VideoWriter('video.mp4', cv2.VideoWriter_fourcc('A','V','C','1'), 1, (mat.shape[0],mat.shape[1]))

# loop over your images
for i in xrange(len(img)):

    fig = plt.figure()
    plt.imshow(img[i], cmap=cm.Greys_r)

    # put pixel buffer in numpy array
    canvas = FigureCanvas(fig)
    canvas.draw()
    mat = np.array(canvas.renderer._renderer)
    mat = cv2.cvtColor(mat, cv2.COLOR_RGB2BGR)

    # write frame to video
    video.write(mat)

# close video writer
cv2.destroyAllWindows()
video.release()
darkdragon
  • 392
  • 5
  • 13
3

You can try drawing the images (frames) sequentially with a delay. If you have many frames, it might make sense to reduce the wait time between frames in the plt.pause() function.

# need this line if you're using jupyter notebooks
%matplotlib notebook

x = [] # Some array of images
fig = plt.figure()
viewer = fig.add_subplot(111)
plt.ion() # Turns interactive mode on (probably unnecessary)
fig.show() # Initially shows the figure

for i in range(len(x)):
    viewer.clear() # Clears the previous image
    viewer.imshow(x[i]) # Loads the new image
    plt.pause(.1) # Delay in seconds
    fig.canvas.draw() # Draws the image to the screen
saetch_g
  • 1,427
  • 10
  • 10
  • 1
    It'd be great to explain the solution and add some details in addition to providing the code. – Peyman Apr 08 '19 at 19:10
2

You could for example export the images to png using plt.savefig("file%d.png" % i), then use ffmpeg to generate the video.

Here you find help to generate video from images

David Frank
  • 5,918
  • 8
  • 28
  • 43
1

Here's a copy-pastable function, handy for if you're dealing with long videos and are using a streaming iterator (from here)

from typing import Iterator, Optional, Tuple
from pathlib import Path

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np


def write_animation(
    itr: Iterator[np.array],
    out_file: Path,
    dpi: int = 50,
    fps: int = 30,
    title: str = "Animation",
    comment: Optional[str] = None,
    writer: str = "ffmpeg",
) -> None:
    """Function that writes an animation from a stream of input tensors.

    Args:
        itr: The image iterator, yielding images with shape (H, W, C).
        out_file: The path to the output file.
        dpi: Dots per inch for output image.
        fps: Frames per second for the video.
        title: Title for the video metadata.
        comment: Comment for the video metadata.
        writer: The Matplotlib animation writer to use (if you use the
            default one, make sure you have `ffmpeg` installed on your
            system).
    """

    first_img = next(itr)
    height, width, _ = first_img.shape
    fig, ax = plt.subplots(figsize=(width / dpi, height / dpi))

    # Ensures that there's no extra space around the image.
    fig.subplots_adjust(
        left=0,
        bottom=0,
        right=1,
        top=1,
        wspace=None,
        hspace=None,
    )

    # Creates the writer with the given metadata.
    Writer = mpl.animation.writers[writer]
    metadata = {
        "title": title,
        "artist": __name__,
        "comment": comment,
    }
    mpl_writer = Writer(
        fps=fps,
        metadata={k: v for k, v in metadata.items() if v is not None},
    )

    with mpl_writer.saving(fig, out_file, dpi=dpi):
        im = ax.imshow(first_img, interpolation="nearest")
        mpl_writer.grab_frame()

        for img in itr:
            im.set_data(img)
            mpl_writer.grab_frame()
benja
  • 31
  • 6
0

I implemented a handy script that just suits you and new comers. Try it out here.

For your example:

imagelist = YOUR-IMAGE-LIST
def redraw_fn(f, axes):
    img = imagelist[f]
    if not redraw_fn.initialized:
        redraw_fn.im = axes.imshow(img, animated=True)
        redraw_fn.initialized = True
    else:
        redraw_fn.im.set_array(img)
redraw_fn.initialized = False

videofig(len(imagelist), redraw_fn, play_fps=30)
Bily
  • 751
  • 6
  • 15