Difficulty animating a matplotlib graph with movie

2019-03-20 17:11发布

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

2条回答
仙女界的扛把子
2楼-- · 2019-03-20 17:54

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)
查看更多
我命由我不由天
3楼-- · 2019-03-20 17:59

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) 
查看更多
登录 后发表回答