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

程序提供了一个用户友好的界面,允许用户选择图片或文件夹,使用行人检测模型进行处理,并在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中显示。

三. 代码

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: 存储图像的缩放比例。

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

{"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.mzph.cn/pingmian/38639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智芯开发板----时钟的使用

一、开发板时钟概述 介绍 Z20K11xM 的时钟结构&#xff0c;分布以及各个外设时钟源的选择。SCC 模块用于选择系统时钟&#xff0c;产生 core clock、bus clock 和 flash clock&#xff0c;分 别用于驱动 core 及高速外设、普通外设和 flash。PARCC 模块用于单独设置 每个外设的…

中霖教育怎么样?税务师通过率高吗?

中霖教育怎么样?税务师通过率高吗? 我们在税务师考试培训方面有着不错的成绩&#xff0c;这都是老师与学员共同努力的结果。 采用小班教学模式&#xff0c;确保每位学员都能得到足够的关注和指导&#xff0c;在学习过程中针对学员的薄弱环节进行专项突破。 因为大部分学员…

【C语言】常见的字符串函数

©作者:末央&#xff06; ©系列:C语言初阶(适合小白入门) ©说明:以凡人之笔墨&#xff0c;书写未来之大梦 目录 strlen函数模拟实现 strstr子串查找函数模拟实现 strtok字符串分割 strlen函数 strlen函数是一个用于求字符串长度的库函数。它的参数是被求长度的字…

【爱上C++】详解string类2:模拟实现、深浅拷贝

在上一篇文章中我们介绍了string类的基本使用&#xff0c;本篇文章我们将讲解string类一些常用的模拟实现&#xff0c;其中有很多细小的知识点值得我们深入学习。Let’s go&#xff01; 文章目录 类声明默认成员函数构造函数析构函数拷贝构造函数深浅拷贝问题传统写法现代写法…

泛型的使用(<T>)

文章目录 前言一、泛型是什么&#xff1f;二、泛型的使用 1.定义泛型类2.泛型的常规用法总结 前言 强制类型转换存在一定隐患&#xff0c;如数据丢失、内存溢出、运行时错误、程序逻辑错误等。所以提供了泛型机制&#xff0c;使程序员可以定义安全的数据类型进行操作。通俗的理…

CEPH 系统盘挂了,如何使用数据盘恢复

硬盘损坏是早晚的时&#xff0c;CEHP数据盘坏了&#xff0c;使用CEPH的基本都轻车熟路了&#xff0c;如果系统盘坏了呢&#xff1f;不知道的可能会采取整个系统盘全做的方式 前提条件&#xff1a;使用cephadm搭建集群 如果换服务器&#xff0c;请确保CEPH数据盘放到其它服务器上…

Python基础教程——一次搞懂 Python 字典!Python字典的20种神奇用法

Python 字典&#xff08;Dictionary&#xff09;是数据结构中的一种重要类型。它以键值对的形式存储数据&#xff0c;具有快速查找的特性。今天我们将通过生动有趣的案例&#xff0c;来探讨字典的20个经典操作&#xff0c;帮助大家深入理解和掌握这些概念。 1. 创建字典 字典…

Python从0到100(三十四):Python中的urllib模块使用指南

1. urllib模块概述 在Python中&#xff0c;除了广泛使用的requests模块之外&#xff0c;urllib模块也是处理HTTP请求的重要工具。urllib模块在Python 2中分为urllib和urllib2两个模块&#xff0c;而在Python 3中&#xff0c;它们被合并为一个urllib模块。本文将重点介绍Python…

【鸿蒙学习笔记】Column迭代完备

属性含义介绍 Column({ space: 10 }) {Row() {Text(文本描述).size({ width: 80%, height: 60 }).backgroundColor(Color.Red)}.width(90%).height(90).backgroundColor(Color.Yellow) } .width(100%) // 宽度 .height(200) // 高度 .backgroundColor(Color.Pink) // 背景色 .…

pcap包常见拆分方法

文章目录 Wireshark 拆分流量包SplitCap使用简介魔数报错示例结果 在进行流量分析时&#xff0c;经常需要分析pcap流量包。但是体积过大的流量包不容易直接分析&#xff0c;经常需要按照一定的规则把它拆分成小的数据包。 这里统一选择cic数据集里的Thursday-WorkingHours.pcap…

二、 操作系统知识(考点篇)

一、操作系统概述 操作系统定义&#xff1a; 能有效地组织和管理系统中的各种软/硬件资源&#xff0c;合理地组织计算机系统工作流程&#xff0c;控制程序的执行&#xff0c;并且向用户提供一个良好的工作环境和友好的接口。 操作系统有三个重要的作用&#xff1a; 第一&am…

【办公软件使用分享—Word篇】实用技巧 一学就会 沈阳电脑办公软件基础培训

在平时的工作学习中&#xff0c;Word真真是让很多人头疼的一件事&#xff0c;今天给大家分享20个案例&#xff0c;感受下Word真正的力量&#xff01; 1.插入自动目录 没有目录的文档不是一份合格的文档&#xff0c;很多人认为在Word里插入目录是一件很麻烦的事&#xff0c;其…

Soul打造安全社交元宇宙环境,全力守护用户线上社交安全

在数字化时代的浪潮中,智能安全线上社交正成为人们日常生活中的重要组成部分。随着人们对社交媒体和在线平台依赖程度的不断增加,保障个人信息安全和网络安全变得至关重要。在此背景下,社交平台致力于采取多种措施来保障用户的隐私安全,提升社交体验的质量和安全性。而Soul全方…

咖啡消费旺季到来 为何想转让的库迪联营商却越来越多

文 | 智能相对论 作者 | 霖霖 去年还在朝“三年万店”计划狂奔的库迪&#xff0c;今年已出现明显“失速”。 早在今年2月&#xff0c;库迪就官宣其门店数已超过7000家&#xff0c;如今4个多月过去&#xff0c;据极海品牌监测数据显示&#xff0c;截至6月27日&#xff0c;其总…

[Shell编程学习路线]——shell脚本中case语句多分支选择详解

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f6e0;️Shell编程专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月21日16点30分 &#x1f004;️文章质量&#xff1a;95分 ————前言———— 在Shell编程中&#xff0c;处理多种条件…

基于人脸68特征点识别的美颜算法(一) 大眼算法 C++

1、加载一张原图&#xff0c;并识别人脸的68个特征点 cv::Mat img cv::imread("5.jpg");// 人脸68特征点的识别函数vector<Point2f> points_vec dectectFace68(img);// 大眼效果函数Mat dst0 on_BigEye(800, img, points_vec);2、函数 vector<Point2f&g…

动手学深度学习(Pytorch版)代码实践 -计算机视觉-38实战Kaggle比赛:图像分类 (CIFAR-10)

38实战Kaggle比赛&#xff1a;图像分类 (CIFAR-10) 比赛链接&#xff1a;CIFAR-10 - Object Recognition in Images | Kaggle 导入包 import os import glob import pandas as pd import numpy as np import torch import torchvision from torch.utils.data import Dataset…

R语言数据分析案例39-合肥市AQI聚类和多元线性回归

一、研究背景 随着全球工业化和城市化的迅速发展&#xff0c;空气污染问题日益凸显&#xff0c;已成为影响人类健康和环境质量的重大挑战。空气污染不仅会引发呼吸系统、心血管系统等多种疾病&#xff0c;还会对生态系统造成不可逆转的损害。因此&#xff0c;空气质量的监测和…

MySQL高阶:事务和并发

事务和并发 1. 事务创建事务 2. 并发和锁定并发问题 3. 事务隔离等级3.1 读取未提交隔离级别3.2 读取已提交隔离级别3.3 重复读取隔离级别3.4 序列化隔离级别 4. 死锁 1. 事务 事务&#xff08;trasaction&#xff09;是完成一个完整事件的一系列SQL语句。这一组SQL语句是一条…

经典小游戏(一)C实现——三子棋

switch(input){case 1:printf("三子棋\n");//这里先测试是否会执行成功break;case 0:printf("退出游戏\n");break;default :printf("选择错误&#xff0c;请重新选择!\n");break;}}while(input);//直到输入的结果为假&#xff0c;循环才会结束} …