你在这里所做的有一些基本问题。基本前提是不正确的,因为您不应该继续销毁和重新创建标签小部件来显示图像。相反,只需通过使用新图像调用其configure()方法来更新附加到现有小部件的图像。这是一个性能修复,而不管这里有什么线程问题。一般来说,创建一次小部件并更新它们。这避免了在从UI树中移除和添加小部件时发生的一连串几何更改事件。在
这里的螺纹设计不正确。您不应该从工作线程进行Tk调用。Tk绑定到一个线程,只有事件在线程之间传递。为了说明如何更好地构造它,我修改了代码,使用queue.Queue()将图像帧从opencv读取器线程传递到Tk线程。我们可以发布一个自定义事件来通知UI有一个新的框架准备就绪(<>)。在
最后一个问题是,您应该保留对添加到Tk标签的图像的引用,否则当您不期望它时,它可能会被垃圾回收。因此,我们用每个新图像更新self.photo成员。在import sys
import cv2
import threading
import tkinter as tk
import tkinter.ttk as ttk
from queue import Queue
from PIL import Image
from PIL import ImageTk
class App(tk.Frame):
def __init__(self, parent, title):
tk.Frame.__init__(self, parent)
self.is_running = False
self.thread = None
self.queue = Queue()
self.photo = ImageTk.PhotoImage(Image.new("RGB", (800, 600), "white"))
parent.wm_withdraw()
parent.wm_title(title)
self.create_ui()
self.grid(sticky=tk.NSEW)
self.bind('<>', self.on_next_frame)
parent.wm_protocol("WM_DELETE_WINDOW", self.on_destroy)
parent.grid_rowconfigure(0, weight = 1)
parent.grid_columnconfigure(0, weight = 1)
parent.wm_deiconify()
def create_ui(self):
self.button_frame = ttk.Frame(self)
self.stop_button = ttk.Button(self.button_frame, text="Stop", command=self.stop)
self.stop_button.pack(side=tk.RIGHT)
self.start_button = ttk.Button(self.button_frame, text="Start", command=self.start)
self.start_button.pack(side=tk.RIGHT)
self.view = ttk.Label(self, image=self.photo)
self.view.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, expand=True)
def on_destroy(self):
self.stop()
self.after(20)
if self.thread is not None:
self.thread.join(0.2)
self.winfo_toplevel().destroy()
def start(self):
self.is_running = True
self.thread = threading.Thread(target=self.videoLoop, args=())
self.thread.daemon = True
self.thread.start()
def stop(self):
self.is_running = False
def videoLoop(self, mirror=False):
No=0
cap = cv2.VideoCapture(No)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 800)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 600)
while self.is_running:
ret, to_draw = cap.read()
if mirror is True:
to_draw = to_draw[:,::-1]
image = cv2.cvtColor(to_draw, cv2.COLOR_BGR2RGB)
self.queue.put(image)
self.event_generate('<>')
def on_next_frame(self, eventargs):
if not self.queue.empty():
image = self.queue.get()
image = Image.fromarray(image)
self.photo = ImageTk.PhotoImage(image)
self.view.configure(image=self.photo)
def main(args):
root = tk.Tk()
app = App(root, "OpenCV Image Viewer")
root.mainloop()
if __name__ == '__main__':
sys.exit(main(sys.argv))
我应该补充一点,如果你想在按下停止按钮后显示一个空白图像,你可以将viewlabel小部件设置为一个新的空白图像,如构造函数中所示。在