简体   繁体   中英

Difficulty animating a matplotlib graph with moviepy

I have to make an animation of a large number (~90,000) figures. For context, it's a plot of a map for every day from 1700 - 1950, with events of interest marked on relevent days. I can do this using matplotlib.animation.FuncAnimation , and I have code that does this successfully for a small test period. However, with the complete set of figures this is taking an impractical amount of time to render and will result in a very large movie file. I have read that apparently moviepy offers both speed and file size advantages. However, I am having trouble getting this to work – I believe my problem is that I have not understood how to correctly set the duration and fps arguments.

A simplified version of my code is :

import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy

fig = plt.figure()
ax = plt.axes()
x = np.random.randn(10,1)
y = np.random.randn(10,1)
p = plt.plot(x,y,'ko')

time = np.arange(2341973,2342373)

def animate(i):
   xn = x+np.sin(2*np.pi*time[i]/10.0)
   yn = y+np.cos(2*np.pi*time[i]/8.0)
   p[0].set_data(xn,yn)
   return mplfig_to_npimage(fig)

fps = 1 
duration = len(time)
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)

However, this does not produce the intended result of producing a movie with one frame for each element of time and saving this to an .mp4. I can't see where I have gone wrong, any help or pointers would be appreciated.

Best wishes, Luke

Same solution as JuniorCompressor, with just one frame kept in memory to avoid RAM issues. This example runs in 30 seconds on my machine and produces a good quality 400-second clip of 6000 frames, weighing 600k.

import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy

fig = plt.figure(facecolor="white") # <- ADDED FACECOLOR FOR WHITE BACKGROUND
ax = plt.axes()
x = np.random.randn(10, 1)
y = np.random.randn(10, 1)
p = plt.plot(x, y, 'ko')
time = np.arange(2341973, 2342373)

last_i = None
last_frame = None

def animate(t):
    global last_i, last_frame

    i = int(t)
    if i == last_i:
        return last_frame

    xn = x + np.sin(2 * np.pi * time[i] / 10.0)
    yn = y + np.cos(2 * np.pi * time[i] / 8.0)
    p[0].set_data(xn, yn)

    last_i = i
    last_frame = mplfig_to_npimage(fig)
    return last_frame

duration = len(time)
fps = 15
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)

On a sidenote, there is dedicated class of videoclips called DataVideoClip for precisely this purpose, which looks much more like matplotlib's animate . For the moment it's not really speed-efficient (I didn't include that little memoizing trick above). Here is how it works:

from moviepy.video.VideoClip import DataVideoClip

def data_to_frame(time):
    xn = x + np.sin(2 * np.pi * time / 10.0)
    yn = y + np.cos(2 * np.pi * time / 8.0)
    p[0].set_data(xn, yn)
    return mplfig_to_npimage(fig)

times = np.arange(2341973, 2342373)
clip = DataVideoClip(times, data_to_frame, fps=1) # one plot per second

#final animation is 15 fps, but still displays 1 plot per second
animation.write_videofile("test2.mp4", fps=15) 

Same observations:

  • In animate a float number will be passed
  • One frame per second may cause playback problems in many players. It's better to use a bigger frame rate like 15 fps.
  • Using 15 fps will need many frames. It's better to use caching.

So you can do the following:

import numpy as np
import matplotlib.pyplot as plt
from moviepy.video.io.bindings import mplfig_to_npimage
import moviepy.editor as mpy

fig = plt.figure()
ax = plt.axes()
x = np.random.randn(10, 1)
y = np.random.randn(10, 1)
p = plt.plot(x, y, 'ko')
time = np.arange(2341973, 2342373)
cache = {}

def animate(t):
    i = int(t)
    if i in cache:
        return cache[i]

    xn = x + np.sin(2 * np.pi * time[i] / 10.0)
    yn = y + np.cos(2 * np.pi * time[i] / 8.0)
    p[0].set_data(xn, yn)
    cache.clear()
    cache[i] = mplfig_to_npimage(fig)
    return cache[i]

duration = len(time)
fps = 15
animation = mpy.VideoClip(animate, duration=duration)
animation.write_videofile("test.mp4", fps=fps)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM