Python 在matplotlib动画的特定帧之间添加延迟

Python 在matplotlib动画的特定帧之间添加延迟,python,matplotlib,Python,Matplotlib,我想使用在matplotlib中创建动画。动画包含不同的阶段,我想通过在两个对应帧之间的间隔中添加额外的延迟来分开强调。考虑下面的示例代码,绘制五个圆,并且每个连续两个圆的绘制应该分开1秒: import time from matplotlib.animation import FuncAnimation import matplotlib.pyplot as plt import numpy as np f, ax = plt.subplots() ax.set_xlim([-5, 5])

我想使用在matplotlib中创建动画。动画包含不同的阶段,我想通过在两个对应帧之间的间隔中添加额外的延迟来分开强调。考虑下面的示例代码,绘制五个圆,并且每个连续两个圆的绘制应该分开1秒:

import time
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np

f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])

radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])

def update(frame):
    global radius

    if frame % 50 == 0:
        radius += 1
        circles.append([(radius, 0)])
        plots.extend(ax.plot([radius], [0]))
        # I want to add a delay here, i.e. before the drawing of a new circle starts.
        # This works for `plt.show()` however it doesn't when saving the animation.
        time.sleep(1)
    angle = (frame % 50) * dp
    circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
    plots[-1].set_data(*zip(*circles[-1]))
    return plots[-1]

animation = FuncAnimation(f, update, frames=range(1, 251), interval=50, repeat=False)

### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
#     fh.write(animation.to_html5_video())
# plt.show()
这在通过plt.show播放动画时有效,但在另存为.mp4或HTML5视频时无效。这是有意义的,因为根据文档,FPS确定mp4视频的帧延迟,而interval参数用于HTML5视频。然后一帧接一帧地播放,忽略任何计算时间


那么,如何添加保存动画时将保留的延迟呢?

您应该能够为frames参数使用生成函数。例如:

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np

INTERVAL = 50  # ms
HOLD_MS  = 1000
HOLD_COUNT = HOLD_MS // INTERVAL

def frame_generator():
    for frame in range(1, 251):
        # Yield the frame first
        yield frame
        # If we should "sleep" here, yield None HOLD_COUNT times
        if frame % 50 == 0:
            for _ in range(HOLD_COUNT):
                yield None


f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])

radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])

def update(frame):
    global radius

    if frame is None: return   #--------------------------------- Added

    if frame % 50 == 0:
        radius += 1
        circles.append([(radius, 0)])
        plots.extend(ax.plot([radius], [0]))
        #-------------------------------------------------------- sleep removed

    angle = (frame % 50) * dp
    circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
    plots[-1].set_data(*zip(*circles[-1]))
    return plots[-1]

# Slightly changed
animation = FuncAnimation(f, update, frames=frame_generator(), interval=INTERVAL, repeat=False)
plt.show()
应该有用

print(list(frame_generator()))

可能有助于澄清发生了什么。

您应该能够为框架参数使用生成函数。例如:

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np

INTERVAL = 50  # ms
HOLD_MS  = 1000
HOLD_COUNT = HOLD_MS // INTERVAL

def frame_generator():
    for frame in range(1, 251):
        # Yield the frame first
        yield frame
        # If we should "sleep" here, yield None HOLD_COUNT times
        if frame % 50 == 0:
            for _ in range(HOLD_COUNT):
                yield None


f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])

radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])

def update(frame):
    global radius

    if frame is None: return   #--------------------------------- Added

    if frame % 50 == 0:
        radius += 1
        circles.append([(radius, 0)])
        plots.extend(ax.plot([radius], [0]))
        #-------------------------------------------------------- sleep removed

    angle = (frame % 50) * dp
    circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
    plots[-1].set_data(*zip(*circles[-1]))
    return plots[-1]

# Slightly changed
animation = FuncAnimation(f, update, frames=frame_generator(), interval=INTERVAL, repeat=False)
plt.show()
应该有用

print(list(frame_generator()))
可能有助于澄清发生了什么。

您可以使用frame参数来控制动画。实际上,帧n之后的暂停与重复显示帧编号n相同,直到暂停结束。例如,如果以每秒1帧的速率运行动画,并希望在第二帧后暂停3秒,则可以提供

0, 1, 1, 1, 1, 2, 3, ....
作为帧,因此数字为1的帧显示四次

在代码中应用这个概念可以如下所示

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np

f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])

radius = 0
bu = 50
dp = 2*np.pi / bu
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])

def update(frame):
    global radius

    if frame % bu == 0:
        radius += 1
        circles.append([(radius, 0)])
        plots.extend(ax.plot([radius], [0]))

    angle = (frame % bu) * dp
    circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
    plots[-1].set_data(*zip(*circles[-1]))
    return plots[-1]


interval = 50 # milliseconds
pause = int(1 * 1000 / interval)
cycles = 4
frames = []
for c in range(cycles):
    frames.extend([np.arange(c*bu, (c+1)*bu), np.ones(pause)*((c+1)*bu)])
frames = np.concatenate(frames)

animation = FuncAnimation(f, update, frames=frames, interval=50, repeat=False)

### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
#     fh.write(animation.to_html5_video())
plt.show()
可以使用“帧”参数来控制动画。实际上,帧n之后的暂停与重复显示帧编号n相同,直到暂停结束。例如,如果以每秒1帧的速率运行动画,并希望在第二帧后暂停3秒,则可以提供

0, 1, 1, 1, 1, 2, 3, ....
作为帧,因此数字为1的帧显示四次

在代码中应用这个概念可以如下所示

from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np

f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])

radius = 0
bu = 50
dp = 2*np.pi / bu
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])

def update(frame):
    global radius

    if frame % bu == 0:
        radius += 1
        circles.append([(radius, 0)])
        plots.extend(ax.plot([radius], [0]))

    angle = (frame % bu) * dp
    circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
    plots[-1].set_data(*zip(*circles[-1]))
    return plots[-1]


interval = 50 # milliseconds
pause = int(1 * 1000 / interval)
cycles = 4
frames = []
for c in range(cycles):
    frames.extend([np.arange(c*bu, (c+1)*bu), np.ones(pause)*((c+1)*bu)])
frames = np.concatenate(frames)

animation = FuncAnimation(f, update, frames=frames, interval=50, repeat=False)

### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
#     fh.write(animation.to_html5_video())
plt.show()

延迟是你想要的帧速率的倍数吗?你能重复相同的帧进行一些更新迭代吗?您可能需要调整范围以使其正常工作,您甚至可以编写一个包装函数,将从FuncAnimation传递的帧映射到update调用,以便于调试并减少必要的更改数量。@jedwards理想情况下,它可以在任何延迟下工作,但如果由于技术细节的原因无法做到这一点对于导出视频格式,我想这是一个可以接受的要求。延迟是您帧速率的倍数吗?你能重复相同的帧进行一些更新迭代吗?您可能需要调整范围以使其正常工作,您甚至可以编写一个包装函数,将从FuncAnimation传递的帧映射到update调用,以便于调试并减少必要的更改数量。@jedwards理想情况下,它可以在任何延迟下工作,但如果由于技术细节的原因无法做到这一点我想这是一个可以接受的要求,非常好。我没有想过返回None来为下一帧保留相同的图像。非常好。我没有想过返回None来为下一帧保留相同的图像。