在 Matplotlib 中快速渲染到缓冲区

Fast rendering to buffer in Matplotlib

我有一个 Kivy 应用程序,它使用 matplotlib 在应用程序 GUI 中渲染图形。这意味着应用程序创建一个 matplotlib 图并获取图的缓冲区以将其显示在 Image 小部件中。

现在,每次我想更新图形时,我都会重新创建一个图形并绘制所有内容,调用 refresh_gui_image

import matplotlib.pyplot as plt

def draw_matplotlib_buffer(image, *elements):
    fig = plt.figure(figsize=(5,5), dpi=200)
    ax = plt.Axes([0, 0, 1, 1])
    ax.set_axis_off()
    fig.add_axis(ax)
    ax.imshow(image)
    for elem in elements:
        # Suppose such a function exists and return a matplotlib.collection.PatchCollection
        patchCollection = elem.get_collection()
        ax.add_collection(patchCollection)
    
    buffer = fig.canvas.print_to_buffer()
    plt.close(fig)
    return buffer

# imageWidget is a kivy Widget instance
def refresh_gui_image(imageWidget, image, *elements):
    size = image.shape()
    imageBuffer = draw_matplotlib_buffer(image, *elements)
    imageWidget.texture.blit_buffer(imageBuffer, size=size, colorfmt='rgba', bufferfmt='ubyte')
    imageWidget.canvas.ask_update()

在上面的代码中,*elements代表多组对象。通常,我有 2 到 4 个集合,其中包含 10 到 2000 个对象。每个对象都用一个补丁表示,每个集合是图上的一个 PatchCollection。

效果很好。使用当前代码,每次调用 refresh_gui_image 时都会重新绘制每个补丁。当集合变大(如 2000 个)对象时,更新速度太慢(几秒钟)。我想用 matplotlib 进行更快的渲染,知道 一些集合不必重绘,图像留在背景中,也不必重绘.

我知道可以使用 blitting 和 animated artists,这是我尝试过的,遵循 matplotlib 文档的 this tutorial

import matplotlib.pyplot as plt

# fig and ax are now global variable
# bg holds the background that stays identical 
fig = None
ax = None
bg = None

def init_matplotlib_data(image, *elements):
    global fig, ax, bg
    fig = plt.figure(figsize=(5,5), dpi=200)
    ax = plt.Axes([0, 0, 1, 1])
    ax.set_axis_off()
    fig.add_axis(ax)
    ax.imshow(image)
    fig.canvas.draw() # I don't want a window to open, just want to have a cached renderer
    bg = fig.canvas.copy_from_bbox(fig.bbox)

    for elem in elements:
            # Suppose such a function exists and return a matplotlib.collection.PatchCollection
            patchCollection = elem.get_collection(animated=True)
            patchCollection.set_animated(True)
            ax.add_collection(patchCollection)

def draw_matplotlib_buffer(image, *artists_to_redraw):
    global fig, ax, bg
    fig.canvas.restore_region(bg)

    for artist in artists_to_redraw:
        ax.draw_artist(artist)
    
    fig.canvas.blit(fig.bbox)
    buffer = fig.canvas.print_to_buffer()
    return buffer

我调用 init_matplotlib_data 一次,refresh_gui_image 需要多少次就调用多少次,我需要更新艺术家。关键是我正确地获取了我的图像背景,但是我无法成功地获取 fig.canvas.print_to_buffer() 返回的缓冲区中的补丁集合。我取消设置集合的 animated 标志,这次它们正确显示。在我看来,经过一些测试 ax.draw_artist()fig.canvas.blit() 没有效果。我不明白的另一个行为是,如果我将 animated=True 传递给 ax.imshow(image),图像仍然被绘制。

为什么 ax.draw_artistfig.canvas.blit 函数没有按预期更新 fig.canvas.print_to_buffer 返回的缓冲区?

显然,blitting 是用于 GUI 的特殊功能。即使Agg后端支持blitting,也不代表blitting可以单独使用

我想出了一个解决方案,我可以存储我想画的每一位艺术家,并在需要时更改他们的数据。然后我使用 fig.canvas.print_to_buffer(),我不确定它到底做了什么,但我认为这个数字已经完全重绘了。它可能不像 blitting 那样快,但它的优点是无需为每次更新重新分配和重新创建每个艺术家。也可以通过调用艺术家的 remove() 方法将艺术家从 canvas 中移除,然后用 ax.add_artist(..).

重新放置。

我认为这个解决方案回答了我的问题,因为它是在将 canvas 转储到缓冲区时使用 matplotlib 进行动态绘图的最快解决方案。