Tkinter双窗口Python应用中摄像头视频显示异常问题排查
问题分析与修复方案
嘿,我一眼就看出你代码里的两个关键问题,这就是为啥视频画面跑到第一个窗口的原因:
核心问题
多个主窗口冲突
你同时创建了两个tk.Tk()实例(主窗口Mafenetre和PhotoBoothApp里的self.root)。Tkinter设计上只推荐一个主窗口,多个Tk()会各自拥有独立的事件循环,不仅容易出bug,还会让控件默认绑定到第一个创建的主窗口上。控件父窗口未指定
你创建视频显示的Label(self.panel)时,没有明确指定它的父窗口是PhotoBoothApp里的self.root,所以Tkinter自动把它挂载到了第一个创建的主窗口Mafenetre上,这就导致视频画面出现在了错误的窗口。
另外,你的主窗口按钮代码还有个小语法错误:font= "Helvetica", 12)这里多了个逗号,应该写成font=("Helvetica", 12)。
修复后的完整代码
import tkinter as tk import threading import imutils import cv2 from PIL import Image from PIL import ImageTk from imutils.video import VideoStream class PhotoBoothApp: def __init__(self, parent, vs): # 存储视频流对象和父窗口 self.vs = vs self.parent = parent self.frame = None self.thread = None self.stopEvent_2 = None # 用Toplevel创建子窗口,而非新的Tk() self.root = tk.Toplevel(parent) self.root.title("Test thread") # 让子窗口始终在顶层 self.root.attributes("-topmost", True) self.panel = None self.user_input = tk.StringVar(self.root) self.but_frame = tk.Frame(self.root) w = 800 h = 500 ws = self.root.winfo_screenwidth() hs = self.root.winfo_screenheight() x = (ws/3) - (w/3) y = (hs/3) - (h/3) self.root.geometry('%dx%d+%d+%d' % (w, h, x, y)) # 创建按钮,父窗口是but_frame btn = tk.Button(self.but_frame, bd='5', text=" photo ") self.but_frame.pack(side="left") # 启动视频线程 self.stopEvent_2 = threading.Event() self.thread = threading.Thread(target=self.videoLoop, args=()) self.thread.start() # 绑定子窗口关闭事件 self.root.wm_protocol("WM_DELETE_WINDOW", self.onClose) def videoLoop(self): try: while not self.stopEvent_2.is_set(): self.frame = self.vs.read() self.frame = imutils.resize(self.frame, width=300) # 转换图像格式 image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB) image = Image.fromarray(image) image = ImageTk.PhotoImage(image) # 初始化panel时明确指定父窗口为self.root if self.panel is None: self.panel = tk.Label(self.root, image=image) self.panel.image = image self.panel.pack(side="left", padx=10, pady=10) else: self.panel.configure(image=image) self.panel.image = image except RuntimeError: print("[INFO] caught a RuntimeError") def onClose(self): print("[INFO] closing photo booth window...") self.stopEvent_2.set() self.vs.stop() self.root.destroy() # 主窗口 Mafenetre = tk.Tk() Mafenetre.title("GUI") Mafenetre['bg']='white' w = 500 h = 500 ws = Mafenetre.winfo_screenwidth() hs = Mafenetre.winfo_screenheight() x = (ws/2) - (w/2) y = (hs/2) - (h/2) Mafenetre.geometry('%dx%d+%d+%d' % (w, h, x, y)) # 修复按钮的font语法错误,并绑定打开窗口的事件 def open_photo_booth(): camera = VideoStream(0).start() PhotoBoothApp(Mafenetre, camera) reglage = tk.Button(Mafenetre, bd='5', text=" PARAM ", bg='#c42034', fg='white', font=("Helvetica", 12), command=open_photo_booth) reglage.pack(side=tk.TOP, padx=6, pady=35) Mafenetre.mainloop()
关键修复点说明
- 用
Toplevel替代Tk()创建第二个窗口:self.root = tk.Toplevel(parent),这样第二个窗口会依附于主窗口的事件循环,避免多主窗口的冲突。 - 明确指定控件的父窗口:创建
self.panel时加上self.root作为第一个参数,确保它属于第二个窗口。 - 修复按钮的语法错误:把
font= "Helvetica", 12)改成font=("Helvetica", 12),并给按钮绑定command事件,点击才打开摄像头窗口(原来的代码直接初始化了PhotoBoothApp,会导致程序启动就打开两个窗口,这应该不是你想要的)。 - 子窗口顶层属性:添加
self.root.attributes("-topmost", True)实现你想要的“始终在顶层”的效果。
这样修改后,摄像头视频就会正确显示在第二个窗口里啦!
内容的提问来源于stack exchange,提问作者meth




