基于Tkinter的视觉系统App无输出问题求助
Tkinter目标检测App图片不显示、检测无结果问题修复
问题描述
开发了一款基于Tkinter的目标检测App,支持本地导入图片/视频,使用自定义YOLOv8 ONNX模型(命令行预测正常)。程序运行无报错,但导入图片后画布空白无显示,点击Detect按钮也无检测结果输出。模型输入分辨率640x640,待检测图片尺寸3840x2160。
核心问题分析
- PhotoImage对象被垃圾回收:Tkinter的
ImageTk.PhotoImage对象如果没有被实例变量引用,会被Python垃圾回收机制销毁,导致画布无法显示图片。 - YOLOv8 ONNX输出格式解析错误:YOLOv8的ONNX输出张量格式为
(1, 84, 8400)(84=4框坐标+80类别),当前代码的循环逻辑错误,无法正确提取检测框信息。 - 检测框坐标缩放逻辑错误:YOLOv8输出的是归一化坐标,当前代码直接用原始图片尺寸缩放,未考虑模型输入resize时的宽高比,导致框位置偏移或无法显示。
- 重复初始化模型:每次点击Detect都重新创建ONNX会话,既浪费资源也可能导致潜在问题。
修复步骤
- 保存PhotoImage引用:在显示图片的方法中,将
img_tk赋值给App实例的属性,避免被垃圾回收。 - 修正输出解析逻辑:对YOLOv8的ONNX输出进行维度转换和筛选,提取置信度>0.2的检测框,并将中心坐标+宽高转换为左上角/右下角坐标。
- 修复坐标映射:先将模型输出的归一化坐标还原到原始图片尺寸,再缩放至画布大小,保证框位置准确。
- 提前加载模型:在App初始化时加载Detector实例,避免重复创建。
完整修复代码
import tkinter as tk from tkinter import filedialog import cv2 import numpy as np import onnxruntime from PIL import Image, ImageTk class ObjectDetector: def __init__(self, model_path): self.session = onnxruntime.InferenceSession(model_path) self.input_name = self.session.get_inputs()[0].name self.output_name = self.session.get_outputs()[0].name self.input_shape = self.session.get_inputs()[0].shape[2:] # (640, 640) def detect_objects(self, image): # 保存原始图片尺寸 orig_h, orig_w = image.shape[:2] # 预处理图片:resize、转NCHW、归一化 input_image = cv2.resize(image, (self.input_shape[1], self.input_shape[0])) input_image = np.transpose(input_image, (2, 0, 1)) # HWC -> CHW input_image = np.expand_dims(input_image, axis=0).astype(np.float32) input_image /= 255.0 # 推理 outputs = self.session.run([self.output_name], {self.input_name: input_image}) output = outputs[0][0] # 去除batch维度,得到(84, 8400) # 解析YOLOv8输出 detections = [] for det in output.T: # 转置为(8400, 84),遍历每个检测 scores = det[4:] max_score = np.max(scores) if max_score > 0.2: # 提取框坐标:x_center, y_center, w, h(归一化) xc, yc, w, h = det[:4] # 转换为xmin, ymin, xmax, ymax xmin = (xc - w/2) * orig_w ymin = (yc - h/2) * orig_h xmax = (xc + w/2) * orig_w ymax = (yc + h/2) * orig_h detections.append([xmin, ymin, xmax, ymax, max_score]) return detections class App: def __init__(self, root): self.root = root self.root.title("Object Detection App") # 提前加载模型 self.detector = ObjectDetector("C:/Software/anaconda22/envs/yolov8_custom/yolov8m_custom.onnx") self.image_path = None self.current_img = None # 保存PhotoImage引用,防止GC self.create_widgets() def create_widgets(self): self.select_button = tk.Button(self.root, text="Select Image or Video", command=self.select_file) self.select_button.pack() self.detect_button = tk.Button(self.root, text="Detect Objects", command=self.detect_objects, state=tk.DISABLED) self.detect_button.pack() self.canvas = tk.Canvas(self.root, bg="white", width=800, height=600) self.canvas.pack() def select_file(self): self.image_path = filedialog.askopenfilename() if self.image_path.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')): self.detect_button.config(state="normal") self.display_image() else: self.detect_button.config(state="disabled") def display_image(self): if self.image_path: image = cv2.imread(self.image_path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 缩放图片到画布尺寸 img_resized = cv2.resize(image, (800, 600)) self.current_img = ImageTk.PhotoImage(image=Image.fromarray(img_resized)) # 清空画布再绘制 self.canvas.delete("all") self.canvas.create_image(0, 0, anchor=tk.NW, image=self.current_img) def detect_objects(self): if self.image_path: if self.image_path.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')): image = cv2.imread(self.image_path) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = self.detector.detect_objects(image_rgb) self.draw_bounding_boxes(image_rgb, results) # 缩放带框的图片到画布尺寸 img_resized = cv2.resize(image_rgb, (800, 600)) self.current_img = ImageTk.PhotoImage(image=Image.fromarray(img_resized)) self.canvas.delete("all") self.canvas.create_image(0, 0, anchor=tk.NW, image=self.current_img) else: print("Unsupported file format. Please select an image file.") def draw_bounding_boxes(self, image, results): orig_h, orig_w = image.shape[:2] # 计算画布缩放比例 scale_w = 800 / orig_w scale_h = 600 / orig_h for det in results: xmin, ymin, xmax, ymax, score = det # 转换为画布坐标 xmin_canvas = int(xmin * scale_w) ymin_canvas = int(ymin * scale_h) xmax_canvas = int(xmax * scale_w) ymax_canvas = int(ymax * scale_h) cv2.rectangle(image, (xmin_canvas, ymin_canvas), (xmax_canvas, ymax_canvas), (0, 255, 0), 2) # 添加置信度标签 cv2.putText(image, f"{score:.2f}", (xmin_canvas, ymin_canvas-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2) if __name__ == "__main__": root = tk.Tk() app = App(root) root.mainloop()
内容的提问来源于stack exchange,提问作者ThunderBolt_23
相关产品推荐
相关产品推荐

