Python tkinter将图像显示为电影流

时间:2016-10-13 11:14:26

标签: python tkinter pillow

我正在尝试屏幕抓取并像录制一样快速显示图像。似乎所有功能都很好,除了显示窗口是"闪烁"偶尔用白色框架。它似乎不是每个更新或每隔一帧,而是每5个左右。有关原因的任何想法?

from tkinter import *
from PIL import Image, ImageGrab, ImageTk
import threading
from collections import deque
from io import BytesIO

class buildFrame:
    def __init__(self):
        self.root = Tk()
        self.land = Canvas(self.root, width=800, height=600)
        self.land.pack()
        self.genObj()
        self.thsObj = self.land.create_image(0,0, anchor='nw', image=self.imgObj)
        self.sStream = deque()
        self.spinning = True

        prQ = threading.Thread(target=self.procQ)
        prQ.start()

        t1 = threading.Thread(target=self.snapS, args=[100])
        t1.start()

    def genObj(self):
        tmp = Image.new('RGBA', (800, 600), color=(0, 0, 0))
        self.imgObj = ImageTk.PhotoImage(image=tmp)

    def procQ(self):
        while self.spinning == True:
            if self.sStream:
                self.land.itemconfig(self.thsObj, image=self.sStream[0])
                self.sStream.popleft()

    def snapS(self, shtCount):
        quality_val = 70

        for i in range(shtCount):
            mem_file = BytesIO()
            ImageGrab.grab().save(mem_file, format="JPEG", quality=quality_val)
            mem_file.seek(0)
            tmp = Image.open(mem_file)
            tmp.thumbnail([800, 600])
            img = ImageTk.PhotoImage(tmp)
            self.sStream.append(img)

        mem_file.close()



world = buildFrame()
world.root.mainloop()

1 个答案:

答案 0 :(得分:0)

你应该避免在非GUI线程上进行Tk调用。如果你完全摆脱线程并使用after来安排图像捕获,这可以更顺利地工作。

from tkinter import *
from PIL import Image, ImageGrab, ImageTk
from io import BytesIO

class buildFrame:
    def __init__(self):
        self.root = Tk()
        self.land = Canvas(self.root, width=800, height=600)
        self.land.pack()
        tmp = Image.new('RGBA', (800, 600), color=(0, 0, 0))
        self.imgObj = ImageTk.PhotoImage(image=tmp)
        self.thsObj = self.land.create_image(0,0, anchor='nw', image=self.imgObj)
        self.root.after("idle", self.snapS)

    def snapS(self):
        quality_val = 70
        mem_file = BytesIO()
        ImageGrab.grab().save(mem_file, format="JPEG", quality=quality_val)
        mem_file.seek(0)
        tmp = Image.open(mem_file)
        tmp.thumbnail([800, 600])
        self.image = ImageTk.PhotoImage(tmp)
        self.land.itemconfig(self.thsObj, image=self.image)
        mem_file.close()
        self.root.after(10, self.snapS)

world = buildFrame()
world.root.mainloop()

如果你真的想使用线程,你应该对图像流进行排队,并让UI线程从流中反序列化tkinter图像并显示它。所以一个线程用于捕获,主线程正在显示。

修改

以下版本继续使用线程进行捕获并通过双端队列传递数据,但确保只有Tk UI线程对Tk对象进行操作。这需要一些工作来避免在队列中累积图像,但图像之间的延迟100毫秒现在工作正常。

from tkinter import *
from PIL import Image, ImageGrab, ImageTk
import sys, threading, time
from collections import deque
from io import BytesIO

class buildFrame:
    def __init__(self):
        self.root = Tk()
        self.root.wm_protocol("WM_DELETE_WINDOW", self.on_destroy)
        self.land = Canvas(self.root, width=800, height=600)
        self.land.pack()
        self.genObj()
        self.thsObj = self.land.create_image(0,0, anchor='nw', image=self.imgObj)
        self.sStream = deque()
        self.image_ready = threading.Event()
        self.spinning = True

        self.prQ = threading.Thread(target=self.procQ)
        self.prQ.start()

        self.t1 = threading.Thread(target=self.snapS, args=[100])
        self.t1.start()

    def on_destroy(self):
        self.spinning = False
        self.root.after_cancel(self.afterid)
        self.prQ.join()
        self.t1.join()
        self.root.destroy()

    def genObj(self):
        tmp = Image.new('RGBA', (800, 600), color=(0, 0, 0))
        self.imgObj = ImageTk.PhotoImage(image=tmp)

    def procQ(self):
        while self.spinning == True:
            if self.image_ready.wait(0.1):
                print(len(self.sStream))
                self.image_ready.clear()
                self.afterid = self.root.after(1, self.show_image)

    def show_image(self):
        stream = self.sStream[0]
        self.sStream.popleft()
        tmp = Image.open(stream)
        tmp.thumbnail([800, 600])
        self.image = ImageTk.PhotoImage(tmp)
        self.land.itemconfig(self.thsObj, image=self.image)
        stream.close()

    def snapS(self, shtCount):
        quality_val = 70

        while self.spinning:
            mem_file = BytesIO()
            ImageGrab.grab().save(mem_file, format="JPEG", quality=quality_val)
            mem_file.seek(0)
            self.sStream.append(mem_file)
            self.image_ready.set()
            time.sleep(0.1)

def main():
    world = buildFrame()
    world.root.mainloop()
    return 0

if __name__ == '__main__':
    sys.exit(main())