Ultralytics+Tkinter+PyInstaller打包EXE后加载YOLO模型异常
问题描述
基于Tkinter和Ultralytics YOLO开发的程序经PyInstaller打包为EXE后,加载YOLO模型时出现以下异常:
- 主窗口冻结
- 控制台短暂弹出
- 生成一个相同的新窗口,关闭新窗口后程序可正常运行,无其他报错
调试环境(VS2022)中无此问题,仅打包后出现,所有模型文件与EXE处于同一目录。
复现代码:
from io import StringIO memory_buffer = StringIO() sys.stdout = memory_buffer import tkinter as tk from ultralytics import YOLO import cv2 as cv global testimg testimg = cv.imread("testimg.jpg") class MyGUI: def __init__(self): self.root = tk.Tk() # root here is window name now self.root.geometry("600x400") self.root.columnconfigure(0, weight = 1) self.root.rowconfigure(0, weight = 1) self.root.rowconfigure(1, weight = 1) self.root.rowconfigure(2, weight = 1) self.buttonmodel1 = tk.Button(self.root, text = "Model 1", bg = "#c9c9c9", bd= 5, width = 200, command = self.button1pressed) self.buttonmodel1.grid(row =0, column=0, sticky= "WENS", padx = 3) self.buttonmodel2 = tk.Button(self.root, text = "Model 2", bg = "#c9c9c9", bd= 5, width = 200) self.buttonmodel2.grid(row =1, column=0, sticky= "WENS", padx = 3) self.buttonmodel3 = tk.Button(self.root, text = "Model 3", bg = "#c9c9c9", bd= 5, width = 200) self.buttonmodel3.grid(row =2, column=0, sticky= "WENS", padx = 3) self.root.mainloop() def button1pressed(self): global testimg model1 = YOLO("model1.pt") model2 = YOLO("model2.pt") model3 = YOLO("model3.pt") resultsmodel1 = model1.predict(testimg) print("model1") resultsmodel2 = model2.predict(testimg) print("model2") resultsmodel3 = model3.predict(testimg) print("model3") MyGUI()
打包命令:
pyinstaller --collect-data ultralytics Model_Test.py --onefile --windowed
解决建议
1. 用线程隔离模型操作,避免阻塞Tkinter主循环
Tkinter是单线程框架,耗时的模型加载、推理会阻塞主循环导致窗口冻结,甚至触发异常窗口。将模型操作放到子线程中执行:
修改后代码:
from io import StringIO import sys import os import threading import tkinter as tk from ultralytics import YOLO import cv2 as cv # 禁用YOLO冗余日志,避免触发控制台 os.environ["YOLO_VERBOSE"] = "false" # 重定向stdout和stderr,彻底屏蔽输出 memory_buffer = StringIO() sys.stdout = memory_buffer sys.stderr = memory_buffer class MyGUI: def __init__(self): self.root = tk.Tk() self.root.geometry("600x400") self.root.columnconfigure(0, weight=1) for i in range(3): self.root.rowconfigure(i, weight=1) # 改用类内变量存储图像,避免全局变量问题 self.testimg = cv.imread("testimg.jpg") self.buttonmodel1 = tk.Button( self.root, text="Model 1", bg="#c9c9c9", bd=5, width=200, command=self.run_model_task ) self.buttonmodel1.grid(row=0, column=0, sticky="WENS", padx=3) self.buttonmodel2 = tk.Button(self.root, text="Model 2", bg="#c9c9c9", bd=5, width=200) self.buttonmodel2.grid(row=1, column=0, sticky="WENS", padx=3) self.buttonmodel3 = tk.Button(self.root, text="Model 3", bg="#c9c9c9", bd=5, width=200) self.buttonmodel3.grid(row=2, column=0, sticky="WENS", padx=3) self.root.mainloop() def run_model_task(self): # 启动子线程执行模型操作,不阻塞主循环 threading.Thread(target=self.process_models, daemon=True).start() def process_models(self): # 获取当前EXE绝对路径,避免打包后路径错误 current_dir = os.path.dirname(os.path.abspath(__file__)) # 加载模型时禁用verbose,减少内部输出 model1 = YOLO(os.path.join(current_dir, "model1.pt"), verbose=False) model2 = YOLO(os.path.join(current_dir, "model2.pt"), verbose=False) model3 = YOLO(os.path.join(current_dir, "model3.pt"), verbose=False) # 推理时禁用窗口显示,避免Ultralytics创建额外窗口 resultsmodel1 = model1.predict(self.testimg, show=False) resultsmodel2 = model2.predict(self.testimg, show=False) resultsmodel3 = model3.predict(self.testimg, show=False) MyGUI()
2. 优化PyInstaller打包参数
使用更严谨的打包命令,确保Ultralytics依赖资源完整打包,彻底禁用控制台:
pyinstaller --collect-data ultralytics --collect-binaries ultralytics --noconsole --onefile Model_Test.py
--collect-binaries ultralytics:确保Ultralytics依赖的二进制文件(如推理引擎)被正确打包--noconsole:彻底屏蔽控制台窗口,替代--windowed(部分场景下--windowed仍会触发临时控制台)
3. 排查额外窗口触发原因
如果问题仍存在,可检查Ultralytics的初始化逻辑,避免其内部意外创建Tkinter实例:
- 确保所有YOLO操作都在子线程中执行
- 禁用YOLO的任何可视化相关参数(如
show=False、verbose=False)
问题根源分析
- Tkinter单线程阻塞:耗时的模型操作阻塞主循环,导致窗口无响应,同时Ultralytics内部的日志或初始化操作可能意外触发新的Tkinter窗口。
- PyInstaller资源缺失:Ultralytics依赖的部分二进制文件未被正确打包,运行时触发异常流程。
- 未屏蔽完整输出:仅重定向stdout未处理stderr,Ultralytics的错误日志触发临时控制台窗口。
内容的提问来源于stack exchange,提问作者Kxpr
相关产品推荐
相关产品推荐

