无法从 PIL Image 构建 tkinter.PhotoImage

Cannot construct tkinter.PhotoImage from PIL Image

我尝试在按下按钮时在标签中显示图像,但图像太大,我已尝试调整图像大小。我创建了这个函数:

def image_resize(imageFile):
    width = 500
    height = 300
    image = Image.open(imageFile)
    im2 = image.resize((width, height), Image.ANTIALIAS)
    return im2

为了显示图像,我创建了这个函数:

def show_image():
    label_originalimage ['image'] = image_tk

以及带有 command=show_image 的按钮:

filename = 'bild_1.jpg'
image_resize = image_resize(filename)
image_tk = PhotoImage(image_resize)
button_open = Button(frame_open, text='Open Image', command=show_image)

我只得到这个:

TypeError : __str__ returned non-string (type instance)

来自 tkinterPhotoImage class 将文件名作为参数,并且由于它无法将 image 转换为字符串,它会抱怨。相反,使用 PIL.ImageTk 模块中的 PhotoImage class。这对我有用:

from tkinter import *
from PIL import ImageTk, Image

def image_resize(imageFile):
    width = 500
    height = 300
    image = Image.open(imageFile)
    im2 = image.resize((width,height), Image.ANTIALIAS)
    return im2

def show_image():
    label_originalimage ['image'] = image_tk

root = Tk()
filename = './Pictures/Space/AP923487321702.jpg'
image_resize = image_resize(filename)
image_tk = ImageTk.PhotoImage(image_resize)

label_originalimage = Label(root)
label_originalimage.pack()

button_open = Button(root, text='Open Image', command=show_image)
button_open.pack()

root.mainloop()

请注意从 image_tk = PhotoImage(image_resize)image_tk = ImageTk.PhotoImage(image_resize) 的变化。

我在尝试为 canvas 构建图像项时遇到了同样的问题 来自 tkinter PhotoImage 的 tkinter。后者是由一些 内存中的图像数据(在我的例子中是 opencv 图像)。一样的异常 如果我只是尝试将 PhotoImage 转换为字符串,就会发生这种情况。

估计PhotoImage的转换方法__str__有问题, 使其简单地 returns 图像源。如果从文件名构造 (见下文)这很好用。如果从一些图像数据构建,这不是 字符串类型并产生异常。

不幸的是,使用 PIL 的兼容 PhotoImage 像 matsjoyce 建议的 ImageTk 模块也没有帮助我,因为我遇到了一个更糟糕的问题,可能是平台或库版本相关的错误(我使用 OS X 10.11.6,python 3.5,tkinter 8.6, PIL 1.1.7):现在 python 脚本在使用 "Bus Error".

构建 canvas 图像项时崩溃

我知道的唯一解决方法是将图像数据存储到一个临时文件中,并使用从该文件名构造的 tkinter PhotoImage。 (尝试对 PIL PhotoImage 进行同样的操作仍然会崩溃。)

#!/usr/bin/env python3

import tkinter
import tempfile
import cv2

def opencv2photoimg(opencv_img):
    """Convert OpenCV (numpy) image to tkinter photo image."""
    # ugly workaround: store as file & load file, because direct
    # construction leads to a crash on my platform 
    tmpfile = tempfile.NamedTemporaryFile(suffix='.png', delete=True)
    # ^^^ I am using PNGs only, you might want to use another suffix
    cv2.imwrite(tmpfile.name, opencv_img)
    return tkinter.PhotoImage(file=tmpfile.name)

# load image
img = cv2.imread('test.png')
# do something w/ the image ...

# setup tk window w/ canvas containing an image
root = tkinter.Tk()
canvas = tkinter.Canvas(root, width=img.shape[1], height=img.shape[0])
canvas.pack()
# keep reference to PhotoImage to avoid it being garbage collected
# (well known tkinter bug for canvas image items)
photo_img = opencv2photoimg(img)
# create a canvas item 
img_item = canvas.create_image(0, 0, anchor=tkinter.NW, image=photo_img)

# display the window
tkinter.mainloop()

我不认为它很优雅,但它确实有效。

是的,它有效,但是 yeeeuchchh - 我必须这样做。

肯定有更好的方法。

这是我从 here 开始的测试代码...

import tkinter
from PIL import Image
import numpy
import time
import io
#python2 version (original) -> 120fps
#full physical file io and new image each cycle -> 130fps
#reuse PIL Image instead of create new each time -> 160fps

class mainWindow():
    times=1
    timestart=time.clock()
    data=numpy.array(numpy.random.random((400,500))*100,dtype=int)
    theimage = Image.frombytes('L', (data.shape[1],data.shape[0]),data.astype('b').tostring())



 def __init__(self):
        self.root = tkinter.Tk()
        self.frame = tkinter.Frame(self.root, width=500, height=400)
        self.frame.pack()
        self.canvas = tkinter.Canvas(self.frame, width=500,height=400)
        self.canvas.place(x=-2,y=-2)
        self.root.after(0,self.start) # INCREASE THE 0 TO SLOW IT DOWN
        self.root.mainloop()

    def start(self):
        global data
        global theimage
        self.theimage.frombytes(self.data.astype('b').tobytes())
        self.theimage.save('work.pgm')
        self.photo = tkinter.PhotoImage(file='work.pgm')
        self.canvas.create_image(0,0,image=self.photo,anchor=tkinter.NW)
        self.root.update()
        self.times+=1
        if self.times%33==0:
            print("%.02f FPS"%(self.times/(time.clock()-self.timestart)))
        self.root.after(10,self.start)
        self.data=numpy.roll(self.data,-1,1)

if __name__ == '__main__':
    x=mainWindow()

这里是:我发现 photoimage 的输入数据可以是一个字节数组,看起来像一个 ppm 文件,尽管它似乎只适用于合法 ppm 的一个子集(例如 16 位值不起作用)

所以为了以后参考......

import tkinter
import numpy
import time
#python2 version (original) -> 120fps
#full physical file io and new image each cycle -> 130fps
#reuse PIL Image instead of create new each time -> 160fps
#and... direct image into tkinter using ppm byte array -> 240 fps

class mainWindow():
    times=1
    timestart=time.clock()
    data=numpy.array(numpy.random.random((400,500))*900,dtype=numpy.uint16)

    def __init__(self):
        self.root = tkinter.Tk()
        self.frame = tkinter.Frame(self.root, width=500, height=400)
        self.frame.pack()
        self.canvas = tkinter.Canvas(self.frame, width=500,height=400)
        self.canvas.place(x=-2,y=-2)
        xdata = b'P5 500 400 255 ' + self.data.tobytes()
        self.photo = tkinter.PhotoImage(width=500, height=400, data=xdata, format='PPM')
        self.imid = self.canvas.create_image(0,0,image=self.photo,anchor=tkinter.NW)
        self.root.after(1,self.start) # INCREASE THE 0 TO SLOW IT DOWN
        self.root.mainloop()

    def start(self):
        global data
        xdata = b'P5 500 400 255 ' + numpy.clip(self.data,0,255).tobytes()
        self.photo = tkinter.PhotoImage(width=500, height=400, data=xdata, format='PPM')
        if True:
            self.canvas.itemconfig(self.imid, image = self.photo)
        else:
            self.canvas.delete(self.imid)
            self.imid = self.canvas.create_image(0,0,image=self.photo,anchor=tkinter.NW)
        self.times+=1
        if self.times%33==0:
            print("%.02f FPS"%(self.times/(time.clock()-self.timestart)))
        self.root.update()
        self.root.after(0,self.start)
        self.data=numpy.roll(self.data,-1,1)

if __name__ == '__main__':
    x=mainWindow()