You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter与OpenCV联动控制窗口触发致命错误问题求助

Tkinter与OpenCV窗口交互触发致命错误修复

问题描述

使用opencv-python 4.3.0,通过Tkinter按钮控制OpenCV窗口的创建与销毁。首次点击“Calibration”按钮时OpenCV窗口可正常打开,但点击Tkinter窗口任意位置后,程序短暂冻结并触发致命错误。

原代码

import cv2
import numpy as np
from tkinter import *


class visualRecognition():

    def __init__(self):
        self.cap = cv2.VideoCapture(0)
        self.windowOpen = True
        self.lastH_min, self.lastH_max, self.lastS_min, self.lastS_max, self.lastV_min, self.lastV_max = 0 , 0 , 0 , 0 , 0 , 0
    
        self.createWindow()

    def createWindow(self):
        self.windowOpen = True

    def destroyWindow(self):
        self.lastH_min, self.lastH_max, self.lastS_min, self.lastS_max, self.lastV_min, self.lastV_max = self.lastH_min, self.lastH_max, self.lastS_min, self.lastS_max, self.lastV_min, self.lastV_max
        cv2.destroyAllWindows()
        self.windowOpen = False
        
    def main(self):
        while True:
            ret, frame = self.cap.read()
            #code here to get results and draw a nice rectangle :)
            hStack = np.hstack([frame,results])

            if self.windowOpen == True:
                cv2.imshow("hello world", hStack)
                cv2.waitKey(1)


class GraphicalUserInterface():
    def __init__(self):
        
        #variables
        self.calibrationButtonClicked = False
        self.calibrationCounter = 0 
        #main window configurations and settings
        self.window = Tk()

        self.calibrationButton = Button(self.window,
                text = "Calibration",
                command = lambda: self.calibrationMode(),
                font = ("Comic Sans", 30),
                fg = "black",
                bg = "#eb7134",
                activebackground= "#c25d2b")
        self.calibrationButton.grid(row=2, column=6, pady=(40,0))

        self.window.mainloop()


    def calibrationMode(self):
        if self.calibrationCounter == 0:
            visualRec = visualRecognition()
            visualRec.main()
            self.calibrationButtonClicked = True
            self.calibrationCounter += 1
            print(f"Initial calibration in progress")
        else:
            if self.calibrationButtonClicked == False:
                self.calibrationButtonClicked = True
                visualRec.createWindow()
                print(f"Calibration in progress")

            elif self.calibrationButtonClicked == True:
                self.calibrationButtonClicked = False
                visualRec.destroyWindow()
                print(f"Calibration over")

if __name__ == "__main__":
    GraphicalUserInterface()

错误信息

Fatal Python error: PyEval_RestoreThread: NULL tstate
Python runtime state: initialized

Current thread 0x00005104 (most recent call first):
  File "main.py", line 94 in main
  File "main.py", line 196 in calibrationMode
  File "main.py", line 154 in <lambda>
  File "D:\Python\lib\tkinter\__init__.py", line 1883 in __call__
  File "D:\Python\lib\tkinter\__init__.py", line 1420 in mainloop
  File "main.py", line 176 in __init__
  File "main.py", line 222 in <module>

问题原因

  1. 线程阻塞:Tkinter的mainloop()和OpenCV的while True循环运行在同一个主线程,OpenCV的循环会完全占用线程,导致Tkinter无法处理用户点击等事件,引发线程状态混乱。
  2. 对象作用域问题:首次创建的visualRec对象仅存在于calibrationMode的局部作用域中,后续按钮点击无法访问该对象。
  3. 未定义变量:visualRec.main()中的results变量未定义,会触发NameError。

修复方案

  1. 使用threading模块将OpenCV的视频循环放到单独线程,避免阻塞Tkinter主循环。
  2. 将visualRec保存为GUI类的实例属性,确保后续按钮点击能访问。
  3. 修复results未定义问题,添加摄像头释放逻辑,避免资源泄漏。

修改后的完整代码

import cv2
import numpy as np
from tkinter import *
import threading


class visualRecognition():

    def __init__(self):
        self.cap = cv2.VideoCapture(0)
        self.windowOpen = True
        self.running = True  # 控制线程是否运行
        self.lastH_min, self.lastH_max, self.lastS_min, self.lastS_max, self.lastV_min, self.lastV_max = 0, 0, 0, 0, 0, 0

    def createWindow(self):
        self.windowOpen = True

    def destroyWindow(self):
        cv2.destroyAllWindows()
        self.windowOpen = False
        
    def main(self):
        while self.running:
            ret, frame = self.cap.read()
            if not ret:
                break
            # 模拟results变量,实际替换为你的处理逻辑
            results = np.zeros_like(frame)
            hStack = np.hstack([frame, results])

            if self.windowOpen:
                cv2.imshow("hello world", hStack)
                # 处理waitKey返回值,确保窗口响应
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break
        # 释放摄像头资源
        self.cap.release()
        cv2.destroyAllWindows()

    def stop(self):
        self.running = False


class GraphicalUserInterface():
    def __init__(self):
        self.calibrationButtonClicked = False
        self.calibrationCounter = 0 
        self.visualRec = None  # 保存visualRec实例
        self.video_thread = None  # 保存视频线程

        self.window = Tk()

        self.calibrationButton = Button(self.window,
                text = "Calibration",
                command = self.calibrationMode,
                font = ("Comic Sans", 30),
                fg = "black",
                bg = "#eb7134",
                activebackground= "#c25d2b")
        self.calibrationButton.grid(row=2, column=6, pady=(40,0))

        # 窗口关闭时清理资源
        self.window.protocol("WM_DELETE_WINDOW", self.on_closing)

        self.window.mainloop()

    def calibrationMode(self):
        if self.calibrationCounter == 0:
            # 创建实例并启动后台线程
            self.visualRec = visualRecognition()
            self.video_thread = threading.Thread(target=self.visualRec.main)
            self.video_thread.daemon = True  # 随主线程自动退出
            self.video_thread.start()
            self.calibrationButtonClicked = True
            self.calibrationCounter += 1
            print("Initial calibration in progress")
        else:
            if not self.calibrationButtonClicked:
                self.calibrationButtonClicked = True
                self.visualRec.createWindow()
                print("Calibration in progress")
            else:
                self.calibrationButtonClicked = False
                self.visualRec.destroyWindow()
                print("Calibration over")

    def on_closing(self):
        # 关闭窗口时停止视频线程并释放资源
        if self.visualRec:
            self.visualRec.stop()
        self.window.destroy()


if __name__ == "__main__":
    GraphicalUserInterface()

关键修改说明

  • 线程分离:用threading.Thread把OpenCV的视频循环放到后台线程,Tkinter主循环可正常处理用户事件。
  • 对象持久化:将visualRec和video_thread保存为GUI类属性,确保后续按钮点击能访问实例。
  • 资源清理:添加stop()方法终止视频循环,释放摄像头;窗口关闭时调用on_closing()清理线程资源。
  • 变量修复:添加results的模拟实现,避免NameError;处理cv2.waitKey()返回值,确保OpenCV窗口响应操作。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 11:47:54