如何交互式地更新matplotlib的imshow()窗口?

61

我正在开发一些计算机视觉算法,想展示numpy数组在每个步骤中如何变化。

现在可以通过在代码末尾简单使用imshow( array )来显示窗口并展示最终图像。

但是我想要的是,在每次迭代中更新和显示imshow窗口以显示图像的变化。

例如,我想要做到:

import numpy as np
import matplotlib.pyplot as plt
import time

array = np.zeros( (100, 100), np.uint8 )

for i in xrange( 0, 100 ):
    for j in xrange( 0, 50 ):
        array[j, i] = 1

        #_show_updated_window_briefly_
        plt.imshow( array )
        time.sleep(0.1)

问题在于采用这种方式后,Matplotlib窗口不会被激活,直到整个计算过程完成为止。

我尝试了原生的Matplotlib和pyplot,但结果是一样的。对于绘图命令,我找到了一个.ion()开关,但它似乎在这里不起作用。

问题1:持续显示对numpy数组(实际上是uint8灰度图像)的更新的最佳方法是什么?

问题2:是否可能使用动画函数来实现此功能,就像动态图像示例中所示?我想在循环内调用一个函数,因此我不知道如何使用动画函数实现这一点。


1
可能取决于您使用的后端,但在开始循环之前,请尝试调用至少一个 show()draw() -- 参见此答案 - Bonlenfum
这对我有用。 - Esteban M. Correa
6个回答

55

你不需要一直调用imshow,使用对象的 set_data方法速度更快:

myobj = imshow(first_image)
for pixel in pixels:
    addpixel(pixel)
    myobj.set_data(segmentedimg)
    draw()

draw() 应确保后端更新图像。

更新:您的问题已经被重大修改。在这种情况下,最好提出另一个问题。以下是处理您的第二个问题的方法:

Matplotlib 的动画只处理一个递增的维度(时间),因此您的双重循环不起作用。您需要将索引转换为单个索引。以下是一个示例:

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

nx = 150
ny = 50

fig = plt.figure()
data = np.zeros((nx, ny))
im = plt.imshow(data, cmap='gist_gray_r', vmin=0, vmax=1)

def init():
    im.set_data(np.zeros((nx, ny)))

def animate(i):
    xi = i // ny
    yi = i % ny
    data[xi, yi] = 1
    im.set_data(data)
    return im

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=nx * ny,
                               interval=50)

1
不幸的是它不起作用,同样的事情发生了。也许我应该使用动画函数,就像在动态图像示例中一样:http://matplotlib.org/examples/animation/dynamic_image.html 但我不知道如何将其转换为基于循环的代码。 - hyperknot
@zsero:如果简化版本不起作用,我想更复杂的动画是否可以工作。我刚刚添加了一个在我这里可以工作的示例(matplotlib 1.2),看看它是否适用于您。 - tiago
4
我尝试修改您的示例,我认为 im = imshow(data, ...) 应更改为 im = plt.imshow(data, ...)。此外,要运行动画需要使用 plt.show()。干杯。 - Chrigi
@Chrigi 你说得对。我通常都会使用 --pylab,所以我没有看到这个问题。我已经更新了答案。 - tiago

14

我曾经为了解决这个问题而苦苦挣扎,因为许多帖子都谈到了这个问题,但似乎没有人提供一个可用的示例。然而,在这种情况下,原因是不同的:

  • 我无法使用Tiago或Bily的答案,因为它们与问题所在的范式不同。在问题中,刷新是由算法本身调度的,而使用funcanimation或videofig,则处于事件驱动的范式中。事件驱动编程对于现代用户界面编程是不可避免的,但当您从一个复杂的算法开始时,将其转换为事件驱动方案可能很困难,并且我也想能够以传统的过程式范式进行操作。
  • Bub Espinja的回答还存在另一个问题:我没有在jupyter笔记本的上下文中尝试过它,但重复调用imshow是错误的,因为每次都会创建新的数据结构,这会导致重要的内存泄漏并减慢整个显示过程。

Tiago还提到了调用draw(),但没有说明从哪里获取它-顺便说一句,您不需要它。你真正需要调用的函数是flush_event()。有时它可以工作,但这是因为它已经从其他地方触发了。您不能指望它。真正棘手的问题是,如果在空表上调用imshow(),则需要指定vmin和vmax,否则它将无法初始化其颜色映射,并且set_data也将失败。

以下是一个可行的解决方案:

IMAGE_SIZE = 500
import numpy as np
import matplotlib.pyplot as plt


plt.ion()

fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
fig3, ax3 = plt.subplots()

# this example doesn't work because array only contains zeroes
array = np.zeros(shape=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
axim1 = ax1.imshow(array)

# In order to solve this, one needs to set the color scale with vmin/vman
# I found this, thanks to @jettero's comment.
array = np.zeros(shape=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
axim2 = ax2.imshow(array, vmin=0, vmax=99)

# alternatively this process can be automated from the data
array[0, 0] = 99 # this value allow imshow to initialise it's color scale
axim3 = ax3.imshow(array)

del array

for _ in range(50):
    print(".", end="")
    matrix = np.random.randint(0, 100, size=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
    
    axim1.set_data(matrix)
    fig1.canvas.flush_events()
    
    axim2.set_data(matrix)
    fig1.canvas.flush_events()
    
    axim3.set_data(matrix)
    fig1.canvas.flush_events()
print()

更新:根据@Jettero的评论,我添加了基于vmin/vmax的解决方案(我一开始漏掉了它)。


在Jupyter Lab中测试了您的代码,并在for循环中添加了sleep(0.1)。似乎需要在for循环中加入plt.draw()才能更新图像。奇怪的是,它只会循环25次。我猜测draw()被认为是触发事件,触发下一个刷新和绘制...? - eliu
如果我没记错的话(我已经好几个月没用这个了),draw() 的问题在于它会在之前的对象上创建新的对象,所以在循环中使用它而不删除之前的对象会导致内存泄漏,这可能是你的程序在25次迭代后崩溃的原因。然而,我不能告诉你更多,因为我不是 Jupyter Lab 的用户。我只是在 Linux 上使用常规的 Python 解释器和 Idle 编写和测试了这段代码。 - Camion
不会崩溃,但只会重新绘制25次,这意味着循环50次,但刷新只有25次。 - eliu
这对我没有用,但是 fg.canvas.draw(); fg.canvas.flush_events() 有效。使用的是 Python 3.10.4 和 Jupyter-lab 中的 matplotlib 3.5.2。 - MRule
此解决方案不适用于Jupyter。 - Camion

9
如果你正在使用Jupyter,也许这个答案会对你有所帮助。我在这个网站上读到,clear_output的内嵌函数可以做到这一点。
%matplotlib inline
from matplotlib import pyplot as plt
from IPython.display import clear_output

plt.figure()
for i in range(len(list_of_frames)):
    plt.imshow(list_of_frames[i])
    plt.title('Frame %d' % i)
    plt.show()
    clear_output(wait=True)

虽然这种方法相当缓慢,但它可以用于测试目的。


我在回复中提到了它,但是重复使用imshow是错误的,因为它每次都会重新创建新的数据结构,这会导致重要的内存泄漏并减慢整个显示过程。 - Camion

3

我实现了一个方便的脚本,它正适合你的需求。你可以在这里试用一下。

一个展示自定义目录中图片的例子如下:

  import os
  import glob
  from scipy.misc import imread

  img_dir = 'YOUR-IMAGE-DIRECTORY'
  img_files = glob.glob(os.path.join(video_dir, '*.jpg'))

  def redraw_fn(f, axes):
    img_file = img_files[f]
    img = imread(img_file)
    if not redraw_fn.initialized:
      redraw_fn.im = axes.imshow(img, animated=True)
      redraw_fn.initialized = True
    else:
      redraw_fn.im.set_array(img)
  redraw_fn.initialized = False

  videofig(len(img_files), redraw_fn, play_fps=30)

他不是在询问如何更新一个图表,而是一个二维图像。 - Prof Huster
1
提供的脚本是通用的。您可以使用它来更新由matplotlib绘制的任何艺术家。示例已更新以显示如何更新2D图像。我已经使用它3年了,从未让我失望。希望它能帮助其他人。@ProfHuster - Bily

1
我遇到了类似的问题——想要更新图像,但不想重复替换坐标轴,然而 plt.imshow() (也不是 ax.imshow() )并没有更新显示的图形。最终我发现需要使用某种形式的 draw()。但无论是 fig.canvas.draw() 还是 ax.draw() 都不起作用。我最终在这里找到了解决方案:https://www.scivision.dev/fast-update-matplotlib-plots/
%matplotlib notebook  #If using Jupyter Notebook
import matplotlib.pyplot as plt
import numpy as np

imData    = np.array([[1,3],[3,1]])

  # Setup and plot image
fig = plt.figure()
ax  = plt.subplot(111)
im  = ax.imshow(imData)

  # Change image contents
newImData = np.array([[2,2],[2,2]])
im.set_data( newImData )
im.draw()

2
我正在使用Python3版本3.10.4和matplotlib 3.5.2,但是以下代码不再起作用:im.draw() [以及ax.draw()] 都需要一个参数"renderer",但我找不到如何检索该对象的清晰文档。 - MRule

0
import numpy as np
import matplotlib.pyplot as plt
k = 10
plt.ion()
array = np.zeros((k, k))
for i in range(k):
    for j in range(k):
        array[i, j] = 1
        plt.imshow(array)
        plt.show()
        plt.pause(0.001)
        plt.clf()

11
通过添加解释可以提高你的回答质量,从而使人们更易于理解和接受。仅有代码的回答并不能清楚地说明为什么这种解决方案比其他解决方案更好。请[编辑]以添加更多详细信息。 - Brian Tompsett - 汤莱恩

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接