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

基于Tkinter的视觉系统App无输出问题求助

Tkinter目标检测App图片不显示、检测无结果问题修复

问题描述

开发了一款基于Tkinter的目标检测App,支持本地导入图片/视频,使用自定义YOLOv8 ONNX模型(命令行预测正常)。程序运行无报错,但导入图片后画布空白无显示,点击Detect按钮也无检测结果输出。模型输入分辨率640x640,待检测图片尺寸3840x2160。

核心问题分析

  1. PhotoImage对象被垃圾回收:Tkinter的ImageTk.PhotoImage对象如果没有被实例变量引用,会被Python垃圾回收机制销毁,导致画布无法显示图片。
  2. YOLOv8 ONNX输出格式解析错误:YOLOv8的ONNX输出张量格式为(1, 84, 8400)(84=4框坐标+80类别),当前代码的循环逻辑错误,无法正确提取检测框信息。
  3. 检测框坐标缩放逻辑错误:YOLOv8输出的是归一化坐标,当前代码直接用原始图片尺寸缩放,未考虑模型输入resize时的宽高比,导致框位置偏移或无法显示。
  4. 重复初始化模型:每次点击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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 13:19:50