我正在尝试屏幕抓取并像录制一样快速显示图像。似乎所有功能都很好,除了显示窗口是"闪烁"偶尔用白色框架。它似乎不是每个更新或每隔一帧,而是每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()
答案 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())