首页 > 解决方案 > 将 C 或 numpy 数组转换为具有最少副本数的 Tkinter PhotoImage

问题描述

我知道通过 Tkinter 将 MxNx3 numpy 数组显示为 RGB 图像的配方,但我的配方在此过程中制作了该数组的多个副本:

a = np.random.randint(low=255, size=(100, 100, 3), dtype=np.uint8) # Original
ppm_header = b'P6\n%i %i\n255\n'%(a.shape[0], a.shape[1])
a_bytes = a.tobytes() # First copy
ppm_bytes = ppm_header + a_bytes # Second copy https://en.wikipedia.org/wiki/Netpbm_format
root = tk.Tk()
img = tk.PhotoImage(data=ppm_bytes) # Third and fourth copies?
canvas = tk.Canvas(root, width=a.shape[0], height=a.shape[1])
canvas.pack()
canvas.create_image(0, 0, anchor=tk.NW, image=img) # Fifth copy?
root.mainloop()

我怎样才能以最少的份数获得相同的结果?

理想情况下,我会创建一个 numpy 数组,它是 TkinterPhotoImage对象正在使用的相同字节的视图,有效地为我提供PhotoImage了可变像素值,并使更新 Tkinter 显示变得便宜且快速。我不知道如何从 Tkinter 中提取这个指针。

也许有一种通过 ctypes 的方法,正如这里所暗示的那样?

PhotoImage.put()方法似乎很慢,但也许我错了,那是前进的道路?

我尝试制作一个bytearray()包含 ppm 标头和图像像素值,然后使用numpy.frombuffer()将图像像素值查看为 numpy 数组,但我认为PhotoImage构造函数需要一个bytes()对象,而不是bytearray()对象,而且我认为 Tkinter 复制了将其data输入到其内部格式(32 位 RGBA?)。我想与上面的食谱相比,这可以为我节省一份副本?

标签: pythonnumpytkintertcltk

解决方案


我可以使用 PIL 和标签将其减少到 1 个(可能是 2 个)副本:

import numpy as np
import tkinter as tk
from PIL import Image, ImageTk

a = np.random.randint(low=255, size=(100, 100, 3), dtype=np.uint8) # Original
root = tk.Tk()
img = ImageTk.PhotoImage(Image.fromarray(a)) # First and maybe second copy.
lbl = tk.Label(root, image=img)
lbl.pack()
root.mainloop()

但是,这仍然不是可变的。如果你想要的话,我认为你需要自己在画布上放置一个像素来重塑图像。我在这个项目中做过一次,发现最快的更新是 matplotlib 动画,因为你已经在使用 np 数组,所以它对你非常有效。

我使用tk.CanvasPIL 图像(使用 putpixel())和matplotlib的代码。


推荐阅读