You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Tkinter双窗口Python应用中摄像头视频显示异常问题排查

问题分析与修复方案

嘿,我一眼就看出你代码里的两个关键问题,这就是为啥视频画面跑到第一个窗口的原因:

核心问题

  1. 多个主窗口冲突
    你同时创建了两个tk.Tk()实例(主窗口MafenetrePhotoBoothApp里的self.root)。Tkinter设计上只推荐一个主窗口,多个Tk()会各自拥有独立的事件循环,不仅容易出bug,还会让控件默认绑定到第一个创建的主窗口上。

  2. 控件父窗口未指定
    你创建视频显示的Labelself.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()

关键修复点说明

  1. Toplevel替代Tk()创建第二个窗口self.root = tk.Toplevel(parent),这样第二个窗口会依附于主窗口的事件循环,避免多主窗口的冲突。
  2. 明确指定控件的父窗口:创建self.panel时加上self.root作为第一个参数,确保它属于第二个窗口。
  3. 修复按钮的语法错误:把font= "Helvetica", 12)改成font=("Helvetica", 12),并给按钮绑定command事件,点击才打开摄像头窗口(原来的代码直接初始化了PhotoBoothApp,会导致程序启动就打开两个窗口,这应该不是你想要的)。
  4. 子窗口顶层属性:添加self.root.attributes("-topmost", True)实现你想要的“始终在顶层”的效果。

这样修改后,摄像头视频就会正确显示在第二个窗口里啦!

内容的提问来源于stack exchange,提问作者meth

火山引擎 最新活动