【问题标题】:How to animate Poly3DCollection using FuncAnimation with blit=True?如何使用具有 blit=True 的 FuncAnimation 为 Poly3DCollection 设置动画?
【发布时间】:2021-05-12 17:50:30
【问题描述】:

我正在尝试为旋转的立方体设置动画。为此,我使用 Poly3DCollection 并使用 FuncAnimation 对其进行动画处理:

anim = animation.FuncAnimation(fig, visualize_rotation, fargs=[collection],
                               init_func=partial(init_func, ax, collection),
                               frames=360, interval=1000 / 30)

但它渲染每一帧的速度非常慢,所以我每秒只能得到几帧。为了解决这个问题,我尝试添加参数blit=True,希望它能提高渲染速度,但这样我就看不到立方体了。

这是我在窗口中看到的:

奇怪的是,保存图形时立方体是可见的。这是我得到的结果:

我确保visualize_rotation 返回blit=True 所需的艺术家列表[collection],如this question 中所述,但立方体仍然不可见。

那么,在这种情况下,我如何使用blit 标志,同时能够在动画期间看到立方体?

完整代码:

import math
from functools import partial

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import animation
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

def visualize_rotation(frame, collection):
    angle = math.radians(2) * frame

    points = np.array([[-1, -1, -1],
                       [1, -1, -1],
                       [1, 1, -1],
                       [-1, 1, -1],
                       [-1, -1, 1],
                       [1, -1, 1],
                       [1, 1, 1],
                       [-1, 1, 1]])

    Z = np.zeros((8, 3))
    for i in range(8):
        Z[i, :] = [
            math.cos(angle) * points[i, 0] - math.sin(angle) * points[i, 1],
            math.sin(angle) * points[i, 0] + math.cos(angle) * points[i, 1],
            points[i, 2]
        ]
    Z = 10.0 * Z

    # list of sides' polygons of figure
    vertices = [[Z[0], Z[1], Z[2], Z[3]],
                [Z[4], Z[5], Z[6], Z[7]],
                [Z[0], Z[1], Z[5], Z[4]],
                [Z[2], Z[3], Z[7], Z[6]],
                [Z[1], Z[2], Z[6], Z[5]],
                [Z[4], Z[7], Z[3], Z[0]]]

    # plot sides
    collection.set_verts(vertices)
    print(frame)

    return [collection]

def init_func(ax, collection):
    ax.set_xlim(-15, 15)
    ax.set_ylim(-15, 15)
    ax.set_zlim(-15, 15)

    ax.set_box_aspect(np.ptp([ax.get_xlim(), ax.get_ylim(), ax.get_zlim()], axis=1))

    return [collection]

def animate_rotation():

    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d', proj_type='persp')

    collection = Poly3DCollection([[np.zeros(3)]], facecolors='white',
                                  linewidths=1, edgecolors='r', alpha=0.8)
    ax.add_collection3d(collection)

    # noinspection PyUnusedLocal
    anim = animation.FuncAnimation(fig, visualize_rotation, fargs=[collection],
                                   init_func=partial(init_func, ax, collection),
                                   frames=360, interval=1000 / 30, blit=True)

    plt.show()

编辑:

我添加了每秒帧数的计算并绘制出来:

timestamps = []

def visualize_rotation(frame, collection):
    ...

    # plot sides
    collection.set_verts(vertices)
    global timestamps

    timestamps.append(time.time())
    print(round(1 / np.mean(np.diff(timestamps[-1000:])), 1))

    return [collection]

def animate_rotation():
    ...

    plt.plot(np.diff(timestamps))
    plt.ylim([0, 0.1])
    plt.show()

当窗口处于正常大小并且绘制速度很慢(以秒为单位的时间与帧数)时会发生这种情况:

这是窗口很小时的情节:

绘图的开头显示了窗口大小的调整。在第二种情况下,仅丢弃了 2 帧(大约在 50 和 150 处),并且总帧速率约为 30 fps。当窗口大小正常时,我正在寻找相同的行为。当我打开blit 时,情节看起来不错,但问题是立方体不可见。

【问题讨论】:

  • This is the rotation speed 没有 blit=True 对我来说。你的速度明显变慢还是你要求提高这个速度?
  • 我的速度有点慢。但它明显慢于我的目标(interval=1000 / 30)30 fps。我注意到的一件事是速度取决于图形大小。如果窗口被调整到一个很小的大小,那么动画速度是正确的,动画是漂亮和流畅的。但是对于默认窗口大小,它大约慢 2 倍。所以,我要求提高速度。
  • 在我的环境中,我无法重现立方体的动画,但我认为您可以通过不使用初始化功能并将间隔值设置为最小值1来提高速度。速度在当前代码中显示print(frame)的位置也通过感觉得到了改进。
  • @r-beginners 当我将间隔设置为 1 时,visualize_rotation 每秒被调用大约 150 次,但大多数帧都被丢弃了。我不确定初始化函数如何影响速度,因为它只被调用一次。我通过添加帧速率图更新了问题。
  • 调用visualize_rotation根本不是瓶颈,因为平均每帧需要1毫秒,但瓶颈是matplotlib内部的渲染

标签: python matplotlib animation blit matplotlib-animation


【解决方案1】:

我为您找到了一个单行修复:在更新顶点后添加 do_3d_projection。

...
# plot sides
collection.set_verts(vertices)
collection.do_3d_projection(collection.axes.get_figure().canvas.get_renderer())
print(frame)

return [collection]

当 blit=True 时,底层代码没有调用它可能是一个错误。

此外,还会弹出另一个错误;当动画在 blit=True 模式下重复时,最后一帧会以某种方式被延续。要解决此问题,请在 init_func 中添加 ax.clear() 和 ax.add_collection3d():

def init_func(ax, collection):
    ax.clear()
    ax.add_collection3d(collection)
    ax.set_xlim(-15, 15)
    ax.set_ylim(-15, 15)
    ...

【讨论】:

  • 谢谢!添加do_3d_projection 解决了这个问题。在我的情况下,代码无需添加 ax.clear() 即可工作,但每次重复动画时,立方体都会消失一帧。将ax.set_limax.set_box_aspect 移出init_func 解决了这个问题(立方体不会消失,但帧会被丢弃)
  • 不客气!使用 blit=True 后,我的 fps 提高了很多,希望你也是。
  • @MarkH 渲染器 arg 现在已弃用,do_3d_projection 对我不起作用:我有一半绘制的补丁 here - 这个 twitter 链接链接到 YouTube 渲染和源代码以防万一你有兴趣。干杯!
  • @Colin 看起来您至少完成了动画。在新代码中,do_3d_projection 中的 _proj_transform_vec 操作在 v3.4 中已更改为使用 self.axes.M 而不是 self.renderer.M(本例中的 self 是 Poly3DCollection 对象),所以如果我要强制修复将是确保 Poly3DCollection 中的轴对象的 M 属性与渲染器对象匹配并且逐帧正确更新(或者您可以将其修改回来并将底层代码更改回使用渲染器。 M - 不知道这是否真的有效)。
  • @MarkH 感谢您的回复 - 我现在在代码中看到了 - 非常微妙!我发现我遇到的问题是set_verts 和我raised an issue 的错误。会看看有没有什么结果 - 我对它的了解还不够,无法帮助进一步减轻压力。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-08
  • 2013-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多