Python tkinter: 开发一个目标检测GUI小程序

ops/2024/9/18 13:32:01/ 标签: python, tkinter, GUI, 目标检测演示, YOLO

程序提供了一个用户友好的界面,允许用户选择图片或文件夹,使用行人检测模型进行处理,并在GUI中显示检测结果。用户可以通过点击画布上的检测结果来获取更多信息,并使用键盘快捷键来浏览不同的图片。

一. 基本功能介绍

  1. 界面布局:程序使用tkinter库创建一个窗口界面,包括标题栏、可调整大小的画布以及底部的操作按钮。

  2. 图片加载:用户可以通过点击“选择文件夹”或“选择图片”按钮来加载需要检测的图片。支持多种图片格式,如JPG、JPEG、PNG等。

  3. 目标检测:程序内置了一个检测器(例如YOLOv8),用于识别图片中的行人,并在图片上绘制矩形框和置信度标签。

  4. 进度显示:在处理多张图片时,程序会显示一个进度条,告知用户当前的检测进度。

  5. 结果展示:检测完成后,程序会在GUI中展示第一张图片的检测结果,并允许用户通过键盘操作(A键上一张,D键下一张)浏览所有图片。

  6. 交互反馈:用户点击画布上的检测框时,程序会弹出一个消息框显示选中目标的类别和置信度。

  7. 图片缩放:程序能够根据窗口大小调整图片的显示尺寸,确保图片在不同分辨率的屏幕上都能清晰显示。

  8. 日志记录:程序使用logging模块记录操作日志,便于问题追踪和调试。

  9. 多线程处理:为了不阻塞GUI操作,图片的检测处理在后台线程中进行。

  10. 配置灵活:程序允许用户通过参数配置检测器的行为,例如模型路径、图片尺寸、置信度阈值等。

二. 主要方法介绍

  1. __init__: 类的构造函数,用于初始化GUI窗口、设置窗口属性、创建组件和绑定事件。

  2. load_dir: 允许用户通过文件对话框选择一个文件夹,程序会加载该文件夹下的所有支持格式的图片。

  3. load_imgs: 使用文件对话框让用户选择一个或多个图片文件,并将这些文件的路径添加到图片列表中。

  4. show_progress_window: 显示进度条窗口,用于在处理图片时提供用户反馈。

  5. process_files: 在后台线程中处理所有选中的图片,对每张图片运行检测算法,并更新进度条。

  6. run_detect: 对单张图片运行检测器,返回检测结果。

  7. draw_result: 在原始图片上绘制检测结果,如边界框和置信度标签。

  8. draw_detections: 在GUI的画布上绘制检测结果,包括边界框和文本标签。

  9. remove_progress_bar: 在所有图片处理完毕后,移除进度条和相关组件。

  10. display_image: 在GUI中显示当前选中的图片及其检测结果。

  11. on_canvas_click: 绑定到画布的点击事件,用于检测用户点击的位置是否在检测框内,并显示选中目标的详细信息。

  12. win_change: 响应窗口大小变化事件,调整图片的显示大小以适应窗口。

  13. on_win_change: 处理窗口大小变化事件,调用win_change方法。

  14. on_key_press: 绑定到窗口的键盘按键事件,允许用户通过按键浏览图片。

  15. cv2pil: 将使用OpenCV加载的BGR格式图片转换为PIL图像,以便在Tkinter中显示。

三. 代码

python">import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
import cv2
import math
import json
import time
import os
import threading
from PIL import Image, ImageTk
import logging
logging.basicConfig(level=logging.INFO)from person_detection import apiclass MyGUI(tk.Tk):def __init__(self, det_kwargs):super().__init__()self.title('行人检测小程序')self.geometry('600x400')self.resizable(width=True, height=True)self.label = tk.Label(self)self.bind('<Configure>', self.on_win_change)self.bind('<Key>', self.on_key_press)# 创建检测器self.detector = api.YOLOv8(**det_kwargs) if det_kwargs else Noneself.classes = {0: 'person'}# 用于记录当前的图片数据self.raw_img_file_path = Noneself.visual_image = Noneself.photo_image = None# 创建按钮框架self.button_frame = tk.Frame(self)self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, expand=False)# 创建按钮self.load_dir_button = tk.Button(self.button_frame, text="选择文件夹", command=self.load_dir)self.load_dir_button.pack(side=tk.LEFT, padx=10, pady=10, expand=False)self.load_imgs_button = tk.Button(self.button_frame, text="选择图片", command=self.load_imgs)self.load_imgs_button.pack(side=tk.LEFT, padx=10, pady=10, expand=False)# 创建用于显示图片的Canvasself.image_canvas = tk.Canvas(self, bg='white')self.image_canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)self.image_id = self.image_canvas.create_image(0, 0, image=self.photo_image, anchor='nw')# 创建进度条窗口和进度条self.bar_frame = Noneself.progress_window = Noneself.progress_bar = Noneself.processing_label = None# 为Canvas绑定鼠标点击事件self.image_canvas.bind("<Button-1>", self.on_canvas_click)# 为窗口绑定大小和位置变化事件的监听self.bind("<Configure>", self.on_win_change)# 记录每次加载的图片路径列表,检测结果,以及当前的图片索引self.img_list = []self.result_list = []self.index = 0# 初始化缩放比例属性self.scale_width = 1.0self.scale_height = 1.0def load_dir(self):"""选择文件夹,从中加载图片"""# 选择图片file_dir = filedialog.askdirectory(title='选择文件夹')logging.info("选择的文件夹: {}".format(file_dir))if file_dir != ():for i, file in enumerate(os.listdir(file_dir)):if file.endswith(('.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp')):img_path = os.path.join(file_dir, file)self.img_list.append(img_path)# 显示进度条窗口self.show_progress_window(self.img_list)def load_imgs(self):"""选择单个或多个文件以加载图片"""file_paths = filedialog.askopenfilenames(title='选择图片文件', filetypes=[('图像文件', '*.jpg *.jpeg *.png *.bmp *.tiff *.webp'), ('所有文件', '*.*')])logging.info("选择的文件: {}".format(file_paths))if file_paths != ():for img_path in file_paths:self.img_list.append(img_path)# 显示进度条窗口self.show_progress_window(self.img_list)def show_progress_window(self, img_list):# 创建进度条框架self.bar_frame = tk.Frame(self)self.bar_frame.pack(side=tk.TOP, fill=tk.Y, pady=20, expand=False)# 创建正在处理的文本标签和进度条self.processing_label = tk.Label(self.bar_frame, text="正在处理...")self.processing_label.pack(side=tk.BOTTOM, fill=tk.Y, pady=0, expand=False)self.progress_bar = ttk.Progressbar(self.bar_frame, orient='horizontal', length=300, mode='determinate')self.progress_bar.pack(side=tk.BOTTOM, pady=0, expand=False)# 设置最大值为文件数量total_files = len(img_list)self.progress_bar['maximum'] = total_files# 更新初始进度self.progress_bar['value'] = 0# 在新线程中处理文件以避免阻塞GUIthreading.Thread(target=self.process_files, args=(img_list,)).start()# 使用阻塞的方式处理文件# self.process_files(file_dir)def process_files(self, img_list):for i, img_path in enumerate(img_list):# 模拟处理文件的耗时操作logging.info("处理文件: {}".format(img_path))img = cv2.imread(img_path)self.result_list.append(self.run_detect(img))# 更新进度条self.progress_bar['value'] = i + 1  # 更新进度条# 处理完成后,显示消息框并移除进度条# self.after(100, self.remove_progress_bar)  # 稍后执行self.remove_progress_bar()  # 立即执行# 显示图片到Canvasself.display_image()# 调整图片至窗口大小self.win_change()def run_detect(self, img):return self.detector.detect([img])def draw_result(self, img, result):img = img.copy()dets = result['data'][0]['dets']for det in dets:id, score, bbox = det['id'], det['score'], det['bbox']x1, y1, x2, y2 = map(int, bbox)cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)cv2.putText(img, '%.2f' % score, (x1, y1 - 4),cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 255), thickness=1, lineType=cv2.LINE_AA)return imgdef draw_detections(self, result):dets = result['data'][0]['dets']for det in dets:id, score, bbox = det['id'], det['score'], det['bbox']x1, y1, x2, y2 = map(int, bbox)x1 = int(x1 / self.scale_width)y1 = int(y1 / self.scale_height)x2 = int(x2 / self.scale_width)y2 = int(y2 / self.scale_height)self.image_canvas.create_rectangle(x1, y1, x2, y2, outline='red', width=2)self.image_canvas.create_text(x1, y1 - 10, text=f'{score:.2f}', fill='red', font=('Arial', 8))def remove_progress_bar(self):"""移除进度条,进行一些销毁与重置的操作"""# 显示消息框messagebox.showinfo("完成", "文件处理完成!")# 从主窗体中移除进度条if self.progress_bar is not None:self.progress_bar.pack_forget()  # 隐藏进度条self.progress_bar.destroy()  # 销毁进度条对象# 从主窗体中移除进度条标签if hasattr(self, 'processing_label') and self.processing_label is not None:self.processing_label.pack_forget()  # 隐藏进度条self.processing_label.destroy()  # 销毁进度条对象# 从主窗体中移除进度条框架if self.bar_frame is not None:self.bar_frame.destroy()  # 销毁进度条框架def display_image(self):if len(self.img_list) == 0:returnimage = cv2.imread(self.img_list[self.index])image = self.draw_result(image, self.result_list[self.index])# 将OpenCV图像转换为PIL图像,然后转换为PhotoImagepil_image = self.cv2pil(image)self.photo_image = ImageTk.PhotoImage(pil_image)# 显示图片self.image_canvas.delete("all")  # 删除旧的图片self.image_id = self.image_canvas.create_image(0, 0, image=self.photo_image, anchor='nw')self.image_canvas.image = self.photo_image  # 保持对图像的引用def on_canvas_click(self, event):# 使用缩放比例将画布坐标转换为图像坐标canvas_x = event.xcanvas_y = event.yimg_x = int(canvas_x / self.scale_width)img_y = int(canvas_y / self.scale_height)# 初始化最近目标的距离和索引min_distance = float('inf')  # 正无穷大,用于比较closest_index = -1closest_id = Noneclosest_score = None# 检查点击坐标是否在检测结果的边界框内if len(self.result_list) > 0:dets = self.result_list[self.index]['data'][0]['dets']for i, det in enumerate(dets):id, score, bbox = det['id'], det['score'], det['bbox']x1, y1, x2, y2 = map(int, bbox)  # 边界框的坐标# 计算边界框的中心点坐标center_x = (x1 + x2) / 2center_y = (y1 + y2) / 2if x1 <= img_x <= x2 and y1 <= img_y <= y2:# 计算点击位置到边界框中心的欧氏距离distance = math.sqrt((img_x - center_x) ** 2 + (img_y - center_y) ** 2)# 更新最近目标的距离和索引if distance < min_distance:min_distance = distanceclosest_index = iclosest_id = idclosest_score = score# 如果找到最近的目标,则显示信息if closest_index != -1:messagebox.showinfo("检测到的目标", f"标签: {self.classes[closest_id]}\n置信度: {closest_score:.2f}")# else:#     messagebox.showinfo("点击区域", "未检测到目标")def win_change(self):if len(self.result_list) > 0:  # 确保图像不为空image = cv2.imread(self.img_list[self.index])visual_image = self.draw_result(image, self.result_list[self.index])# 保存原始图像尺寸和画布尺寸self.orig_width = visual_image.shape[1]self.orig_height = visual_image.shape[0]canvas_width = self.image_canvas.winfo_width()canvas_height = self.image_canvas.winfo_height()# 计算缩放比例self.scale_width = canvas_width / self.orig_widthself.scale_height = canvas_height / self.orig_height# 根据缩放比例调整图像大小new_width = int(self.orig_width * self.scale_width)new_height = int(self.orig_height * self.scale_height)resized_image = cv2.resize(visual_image, (new_width, new_height))# 显示调整大小后的图像pil_image = self.cv2pil(resized_image)self.photo_image = ImageTk.PhotoImage(pil_image)self.image_canvas.itemconfig(self.image_id, image=self.photo_image)def on_win_change(self, event):"""监控窗口大小和位置的变化:param event::return:"""self.win_change()def on_key_press(self, event):"""监控键盘按键的按下事件,根据按键进行index增减,以进行图片浏览切换设定规则:A:上一张D:下一张"""if len(self.result_list) == 0:returnif event.keysym in ['A', 'a']:if self.index == 0:messagebox.showinfo("提示", "已经是第一张图片了")self.index = max(0, self.index - 1)elif event.keysym in ['D', 'd']:if self.index == len(self.img_list) - 1:messagebox.showinfo("提示", "已经是最后一张图片了")self.index = min(len(self.img_list)-1, self.index + 1)else:messagebox.showinfo("提示", "请按 A 或 D 键进行图片上翻/下翻")self.display_image()self.win_change()def cv2pil(self, cv2_img):# 转换图片至RGB颜色空间image = cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB)# 转换图片至PIL格式return Image.fromarray(image)def get_filename(self, file_path):return os.path.splitext(os.path.split(file_path)[-1])[0]if __name__ == '__main__':det_kwargs = dict(model_path='/home/leon/Nextcloud/ChengDu_Research/computer_vision/algorithms/human/person_detection/1.0.1/person_detection_1.0.1.onnx',img_size=(640, 384),mode=api.ie.MODE_ORT,conf_thresh=0.5,providers=['CUDAExecutionProvider'],  # if no GPU, use 'CPUExecutionProvider'# half=True,)app = MyGUI(det_kwargs)app.mainloop()

请注意:在代码中,我用到了一个目标检测器,你需要替换为你自己的检测器,从而实现不同目标的检测!

代码中和检测相关方法/变量如下:

方法:

  1. run_detect(self, img): 使用检测器对提供的图像进行检测。
  2. draw_result(self, img, result): 在图像上绘制检测结果,如边界框和分数。
  3. process_files(self, img_list): 处理一个图片列表,对每张图片执行检测。
  4. display_image(self): 在GUI上显示当前选中的图片和其检测结果。
  5. draw_detections(self, result): 在Canvas上绘制检测结果。
  6. show_progress_window(self, img_list): 显示进度条窗口,准备开始处理图片列表。
  7. remove_progress_bar(self): 完成图片处理后,移除进度条。

变量:

  1. self.detector: 用于存储检测器实例,例如api.YOLOv8
  2. self.classes: 一个字典,用于将检测到的类别ID映射到类别名称。
  3. self.img_list: 存储加载的图片路径列表。
  4. self.result_list: 存储每张图片的检测结果。
  5. self.index: 当前显示图片的索引。
  6. self.raw_img_file_path: 记录当前处理的原始图片文件路径。
  7. self.visual_image: 用于存储绘制了检测结果的图像。
  8. self.photo_image: 用于存储Tkinter能够显示的图像对象。
  9. self.image_id: 存储Canvas上图像的ID,用于更新显示的图像。
  10. self.scale_width 和 self.scale_height: 存储图像的缩放比例。

对于我的检测器,这里贴出来一个输出示例:

python">{"code": "0","message": "","data": [{"dets": [{"id": 0,"score": 0.7589585781097412,"bbox": [873.7188720703125,236.35150146484375,910.048095703125,335.6061706542969]},{"id": 0,"score": 0.716355562210083,"bbox": [447.7972717285156,278.9081726074219,521.7301025390625,421.3373718261719]}]}]
}

参考:

tkinter — Python interface to Tcl/Tk — Python 3.12.4 documentation


http://www.ppmy.cn/ops/55392.html

相关文章

通义灵码入选 2024 世界人工智能大会最高荣誉「镇馆之宝」

7 月 4 日&#xff0c;2024 上海世界人工智能大会正式开幕&#xff0c;并揭晓了今年的「镇馆之宝」名单&#xff0c;通义灵码入选&#xff0c;是首个入围该名单的 AI 编程助手。 镇馆之宝是世界人工智能大会展览的最高荣誉&#xff0c;从科技含量、市场前景、创新性以及社会经济…

关于echarts中使用到的图例、颜色设置、设置tooltip换行显示等问题

最近使用echarts中用到图例随机生成&#xff0c;颜色多不好设置的问题&#xff0c;图例多展示出现不全&#xff0c;不能根据颜色判断图例和数据的问题等总结一下 原始代码&#xff1a; that_ge.charts echarts.init(document.getElementById(paramenterEcharts));that_ge.al…

DDMA信号处理以及数据处理的流程---聚类

Hello,大家好,我是Xiaojie,好久不见,欢迎大家能够和Xiaojie一起学习毫米波雷达知识,Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程,本系列文章将从目标生成、信号仿真、测距、测速、cfar检测、测角、目标聚类、目标跟踪这几个模块逐步介绍,这个系列的…

时域自相关计算中,使用卷积而不是点积操作的原因

自相关&#xff08;Auto-Correlation&#xff09;和卷积&#xff08;Convolution&#xff09;是信号处理中的两个重要操作&#xff0c;它们在时域中的应用有所不同。尽管在概念上有所关联&#xff0c;但它们用于不同的目的&#xff0c;尤其是在处理时间序列数据时。以下是自相关…

【算法:贪心】:贪心算法介绍+基础题(四个步骤);柠檬水找零(交换论证法)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 前言&#xff1a; 暑假马上就要留校学习算法了&#xff0c;现在先学习一下基本的算法打打基础。本篇要讲的是…

默安逐日实验室:XDP的应用实践

1. 网络数据包是如何进入进计算机的 众所周知&#xff0c;网络数据包通常需要在TCP/IP协议栈中进行处理&#xff0c;但网络数据包并不直接进入TCP/IP协议栈&#xff1b;相反&#xff0c;他们直接进入网络接口。因此&#xff0c;在数据包进入 TCP/IP 堆栈之前&#xff0c;它们已…

Artificial Intelligence Self-study

Artificial Intelligence Self-study Traditional AI (Symbolic AI) 基于&#xff1a;符号表示 数理逻辑 搜索 - 有明确规则&#xff0c;依靠算力。Appliance &#xff1a; 数学难题(Heuristic Algorithm)&#xff0c;棋牌对抗(围棋)&#xff0c;专家系统(输入病症&#xf…

ElementUI的基本搭建

目录 1&#xff0c;首先在控制终端中输入下面代码&#xff1a;npm i element-ui -S 安装element UI 2&#xff0c;构架登录页面&#xff0c;login.vue​编辑 3&#xff0c;在官网获取对应所需的代码直接复制粘贴到对应位置 4&#xff0c;在继续完善&#xff0c;从官网添加…

荣耀电脑误删U盘文件?别慌,这里有找回方法

荣耀电脑误删U盘文件怎么找回&#xff1f;在日常工作和生活中&#xff0c;U盘是我们存储和传输数据的重要工具之一。然而&#xff0c;在使用荣耀电脑时&#xff0c;如果不小心误删了U盘中的文件&#xff0c;可能会给我们带来不小的困扰。但是&#xff0c;别慌&#xff01;本文将…

SpringBoot 参数校验

参数校验 引入springvalidation依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId> </dependency>参数前添加Pattern public Result registry(Pattern(regexp &qu…

Nginx 常用配置与应用

Nginx 常用配置与应用 官网地址&#xff1a;https://nginx.org/en/docs/ 目录 Nginx 常用配置与应用 Nginx总架构 正向代理 反向代理 Nginx 基本配置反向代理案例 负载均衡 Nginx总架构 进程模型 正向代理 反向代理 Nginx 基本配置反向代理案例 负载均衡 Nginx 基本配置…

微信小程序的智慧物流平台-计算机毕业设计源码49796

目 录 摘要 1 绪论 1.1 研究背景 1.2 研究意义 1.3研究方法 1.4开发技术 1.4.1 微信开发者工具 1.4.2 Node.JS框架 1.4.3 MySQL数据库 1.5论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 用户登录流程 2.2.2 数据删除流程 2.3 系统功能分…

对标GPT-4o!不锁区、支持手机、免费使用,Moshi来啦!

7月4日凌晨&#xff0c;法国知名开源AI研究实验室Kyutai在官网发布了&#xff0c;具备看、听、说多模态大模型——Moshi。 Moshi功能与OpenAI在5月14日展示的最新模型GPT-4o差不多&#xff0c;可以听取人的语音提问后进行实时推理回答内容。但GPT-4o的语音模式要在秋天才能全面…

VMware中的三种虚拟网络模式

虚拟机网络模式 1 主机网络环境2 VMware中的三种虚拟网络模式2.1 桥接模式2.2 NAT模式2.3 仅主机模式 3 网络模式选择及配置NAT模式3.1 VMware虚拟网络配置3.2 虚拟机选择网络模式3.3 Windows主机网络配置 4 配置静态IP 虚拟机联网方式为桥接模式&#xff0c;这种模式下&#x…

不同于 iTunes 或 iCloud,iMazing 永远不会覆盖您的 iPhone 和 iPad 备份,它运行可靠、具有创新性。

iPhone 按您的时间表进行备份 配置每台 Apple 设备的备份时间和频率&#xff1a;每天、每周... 选择 iMazing Mini 应该保留多少备份&#xff1a;一个月或一个星期&#xff0c;或者如果您是一名收藏家&#xff0c;则保留全部。 落后计划则收到通知&#xff1a;iMazing Mini 将…

深入解析HDFS:定义、架构、原理、应用场景及常用命令

引言 Hadoop分布式文件系统&#xff08;HDFS&#xff0c;Hadoop Distributed File System&#xff09;是Hadoop框架的核心组件之一&#xff0c;它提供了高可靠性、高可用性和高吞吐量的大规模数据存储和管理能力。本文将从HDFS的定义、架构、工作原理、应用场景以及常用…

Java毕业设计 基于SSM vue电影院票务系统小程序 微信小程序

Java毕业设计 基于SSM vue电影院票务系统小程序 微信小程序 SSM 电影院票务系统小程序 功能介绍 用户 登录 注册 忘记密码 首页 电影信息 电影详情 预订 收藏 评论 支付 最新资讯 资讯详情 用户信息修改 我的收藏管理 用户充值 在线咨询 我的订单 管理员 登录 个人中心 修改…

Github忘记了Two-factor Authentication code

意外重置了edge浏览器 码农家园github自从开启开启了2FA认证&#xff0c;每次输入auth code确实麻烦&#xff0c;于是下载了浏览器插件 Open two factor authenticator&#xff0c; 最近edge频繁宕机&#xff0c;而且提示磁盘空间不足&#xff0c;要不要立即清理并重置浏览器临…

《大模型进化论》第2章2节:从神经网络到预训练——近十年的显著突破与进展

2.2 大模型的发展历程&#xff1a;从神经网到预训练大模型 1) 萌芽期&#xff08;1950年-2005年&#xff09;&#xff1a;以CNN为代表的传统神经网络模型阶段 在人工智能的早期阶段&#xff0c;机器学习和神经网络的概念开始萌芽。研究者们主要关注于构建简单的模型来处…

第一节-k8s架构图

一个Deployment&#xff0c;可以由多个不同Node下的Pod组成&#xff0c;每个Pod又由多个Container组成。 区分Deployment是用Labels(key:value)&#xff0c;区分Pod是用PodName&#xff0c;区分Container是用ContainerName。 一个Node可以包含多个不同Deployment中的pod&…