Как анимировать Poly3DCollection с помощью FuncAnimation с blit=True?

Я пытаюсь оживить вращающийся куб. Для этого я использую 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 возвращает список художников [collection], который требуется blit=True, как указано в этот вопрос, но куб по-прежнему не виден.

Итак, как я могу использовать флаг 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 кадров в секунду, как и хотелось. Я ищу такое же поведение, когда окно нормального размера. Когда я включаю blit, график выглядит нормально, но проблема в том, что куб не виден. введите здесь описание изображения


person fdermishin    schedule 09.02.2021    source источник
comment
Это скорость вращения без blit=True для меня. У вас значительно медленнее или вы просите увеличить эту скорость?   -  person Mr. T    schedule 11.02.2021
comment
Моя чуть медленнее. Но это заметно медленнее, чем 30 кадров в секунду, к которым я стремлюсь (interval=1000 / 30). Одна вещь, которую я заметил, это то, что скорость зависит от размера фигуры. Если размер окна уменьшен до крошечного, то скорость анимации правильная, а анимация приятная и плавная. Но для размера окна по умолчанию это примерно в 2 раза медленнее. Поэтому прошу увеличить скорость.   -  person fdermishin    schedule 11.02.2021
comment
В моей среде мне не удалось воспроизвести анимацию куба, но я думаю, что вы можете улучшить скорость, не используя функцию инициализации и установив значение интервала в минимальное значение 1. Скорость, с которой print(frame) отображается в текущем код также был улучшен на ощупь.   -  person r-beginners    schedule 12.02.2021
comment
@r-beginners Когда я устанавливаю интервал равным 1, то visualize_rotation вызывается примерно 150 раз в секунду, но большинство кадров отбрасываются. Я не уверен, как функция инициализации может повлиять на скорость, потому что она вызывается только один раз. Я обновил вопрос, добавив графики частоты кадров.   -  person fdermishin    schedule 12.02.2021
comment
Вызов visualize_rotation вовсе не является узким местом, потому что в среднем занимает 1 миллисекунду на кадр, но узким местом является рендеринг внутри matplotlib   -  person fdermishin    schedule 12.02.2021
comment
Если медленным фактором является рендеринг внутри matplotlib, улучшить скорость сложно. Вот руководство для matplotlib в формате PDF. Пожалуйста, обратитесь к руководству, в котором также упоминается «блиттинг». (стр. 219)   -  person r-beginners    schedule 12.02.2021
comment
Вы можете предварительно вычислить коллекции, тогда это станет немного быстрее.   -  person Jens Munk    schedule 16.02.2021
comment
Я сомневаюсь в этом. Все улучшения, которые я внес в приведенный выше код, чтобы избежать циклов и векторизовать функции numpy, существенно не увеличили скорость. Я прекратил свои попытки, когда увидел это от ImportanceOfBeingEarnest.   -  person Mr. T    schedule 16.02.2021


Ответы (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. Чтобы это исправить, добавьте ax.clear() и ax.add_collection3d() в свой init_func:

def init_func(ax, collection):
    ax.clear()
    ax.add_collection3d(collection)
    ax.set_xlim(-15, 15)
    ax.set_ylim(-15, 15)
    ...
person Mark H    schedule 17.02.2021
comment
Спасибо! Добавление do_3d_projection решило проблему. В моем случае код работает без добавления ax.clear(), но куб пропадает на один кадр при каждом повторе анимации. Перемещение ax.set_lim и ax.set_box_aspect из init_func исправило это (куб не исчезает, но кадр пропадает) - person fdermishin; 17.02.2021
comment
Пожалуйста! Я получаю намного более высокий fps с blit=True, и я надеюсь, что вы тоже. - person Mark H; 17.02.2021