大家好,我有一个从网络摄像头读取视频的代码,并使用计时器线程将其显示到tkinter窗口中。 当用户单击显示按钮时,应用程序创建一个线程并每隔x秒运行一次以逐帧显示。 问题是:每隔几帧的应用程序都会显示从视频源捕获的第一帧。 我知道那很奇怪,但我找不到原因! 这是我的代码:
import tkinter as tk
from tkinter import ttk
from tkinter import *
import threading
import cv2
import PIL.Image, PIL.ImageTk
from PIL import Image
from PIL import ImageTk
import time
class App(threading.Thread):
def __init__(self, root, window_title, video_source=0):
self.root = root
self.root.title(window_title)
self.video_source = video_source
self.show_switch=False
self.showButton = Button(self.root, text="PlayStream",command=self.showStram,width=15, padx="2", pady="3",compound=LEFT)
self.showButton.pack()
# Create a canvas that can fit the above video source size
self.canvas = tk.Canvas(root, width = 530, height = 397, cursor="crosshair")
self.canvas.pack()
self.root.mainloop()
def updateShow(self):
# Get a frame from the video source
cap=cv2.VideoCapture(0)
while True:
if(cap.isOpened()):
#read the frame from cap
ret, frame = cap.read()
if ret:
#show frame in main window
self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
else:
break
raise ValueError("Unable to open video source", video_source)
if self.show_switch==False:
cap.release()
return False
time.sleep(0.0416666666666667)
#release the cap
cap.release()
def showStram(self):
if self.show_switch:
self.showButton["text"]="StartStream"
# self.showButton.configure(image=self.playIcon)
self.show_switch=False
else:
self.showButton["text"]="StopStream"
self.show_switch=True
# self.showButton.configure(image=self.stopIcon)
self.showTimer=threading.Thread(target=self.updateShow,args=())
#self.showTimer.daemon=True
self.showTimer.start()
App(tk.Tk(), "Main")
答案 0 :(得分:0)
这是一个复杂的问题,因为您的示例代码是广泛的,并且结合了许多可能出错的内容。我无法以当前格式测试您的代码。
首先,您正在从不是MainThread的线程访问Tk实例。这可能会导致各种问题。在Tkinter和solution has not yet been merged中实现假定的线程安全的过程中也存在一些错误。如果确实需要在Tkinter中使用多个线程,请检出mtTkinter,即使这样,最好还是不要这样做,尤其是如果您要构建新的应用程序并可以选择使用Queue或其他系统,则不要这样做。 / p>
第二,您创建了threading.Thread
的(子类)实例,但是您从未在threading.Thread.__init__
中调用App.__init__
(如果要使用它作为线程,这是绝对要求! )。然后,您实际上已经有一个线程,而在Thread
中创建了一个新的def showStream(self)
。现在,这不会破坏您的代码,但是如果您不打算将您的类用作threading.Thread
,则不需要子类Thread
。在类中创建线程时,无需在类中创建线程。
然后,继续执行代码,您确实启动了线程,因此updateShow
得以运行。但是,在您的while循环中,存在一个问题:
while True:
if (cap.isOpened()):
ret, frame = cap.read()
if ret:
...
else:
break
# Error will never be raised because of break
# break exits the loop immediately, so the next line is never evaluated
raise ValueError()
cap.release()
# No notification of the loop having ended
这里可能有两件事出了问题。首先是您的循环可能由于break
子句中的else
而结束。 Break立即退出循环代码。紧随其后的所有内容将从不执行。如果由于无法获取下一帧而确实退出了循环,则无法确定,因为您不检查线程是否仍在运行(threading.Thread.is_alive
)或是否有任何打印语句来指示循环已结束
第二,当您从第二个线程访问Tkinter时,您的程序实际上可能崩溃。这样做会导致不确定的行为,包括奇怪的错误和Python解释器锁定,因为Tk解释器和Python解释器正在争夺死锁中的流控制(简而言之)。
最后但并非最不重要的一点是,创建图像的方式存在问题:
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
在此行中,您将在Canvas上创建一个新图像。但是,如果图像在创建新图像的位置的画布上已经存在,它将显示在已显示图像的 下。如果您不删除旧图像(在任何情况下都建议使用该图像,以防止发生大量内存泄漏),它将在“画布”上保持可见。
def __init__(...):
...
self.im = None
def updateShow(self):
...
while True:
if (cap.isOpened()):
...
if ret:
if self.im is not None:
self.canvas.delete(self.im)
...
self.im: str = self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)
总结:使用您当前发布的代码,可能会出错。没有附加信息,就不可能知道。但是,如果您修复了从其他线程访问Tkinter的问题,请调整while循环以使其不会中断但会引发错误,请调整Canvas图像创建代码,并且您的视频源确实可以正常工作,那么它应该可以解决。