【搞事情】利用PyQt为目标检测SSD300添加界面(四)

2024-04-25 19:08

本文主要是介绍【搞事情】利用PyQt为目标检测SSD300添加界面(四),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【原创文章】欢迎正常授权转载(联系作者)
【反对恶意复制粘贴,如有发现必维权】
【微信公众号原文传送门】


​这篇文章将详细介绍利用多进程的实现—方案3(代码获取见文章末尾)。相比之前的稍微复杂一点,先看看demo的最终效果(视频)。


1 需求分析

首先看一下UI界面,界面上各个控件的详细信息如下表所示。
在这里插入图片描述

控件序号控件的类别Qobject_name功能
1QLabellabel_imgshow实时显示视频
2QLabellabel_imgshaow_res显示抽帧检测效果
3QTextEdittextEdit显示检测的目标信息
4QPushButtonpushButton_open打开视频文件
5QLineEditlineEdit_cameraIndex设置视频流URL;支持IP摄像头;数字为当前设备中摄像头索引(例:本人笔记本自带摄像头为0)
6QPushButtonpushButton_start开始检测并播放
7QPushButtonpushButton_pause暂停检测及播放
8QPushButtonpushButton_end停止检测及播放(全部重设)

结合上面的控件分析一下需求。方案3最初的设计需求是电脑的性能有限,无法做到实时检测并显示,我们希望做一个折中,不检测视频流中的每一帧图像,只是从中抽取部分来检测,检测时不打断画面的实时显示,在"后台"中尽可能多的检测视频流中的图像。之前的文章中也介绍过神经网络的预测过程是无法在线程中实现的,因此设计了一个如下图所示的方案,将图像检测神经网络放到一个子进程中负责在“后台”检测目标,主进程(UI进程)负责采集图像并实时显示在相应的控件上。
解决方案3流程
在实际的实现过程中面临以下几个关键点。

(1) 主进程(UI进程)采集到的图像数据如何传递给子进程检测?

Python多进程之间有一些简单的通讯方式,例如:Queue,好处是它是一个进程安全的队列,用户不需要关注变量的管理,但是实际用这种方式来传递图片,你就会发现速度慢呀!简直崩溃,最简单有效的还是通过“共享内存”,速度快很多(但是我还是觉得慢,我觉得主要是数据转来转去导致的),但需要对内存进行管理。

(2) 子进程检测的结果如何告知主进程并将结果显示出来?

最简单的就和上面一样,再使用一块“共享内存”来将绘制好检测结果的图片传递回主进程中,但是这显然不是最有效率的做法,毕竟使用“共享内存”耗时也挺长的,同时图片数据也挺大的,检测结果其实就是几个简单的数,没必要将整个图像都传递回去。我的处理方式是:主进程在“抽帧”时保存一个图像备份,子进程通过“Queue”将检测结果(相对图片数据小的多)传递回主进程,主进程收到结果后,将结果绘制在备份图像上并显示出来。

(3) 共享内存的管理。

使用共享内存时一定要注意这部分内存的管理,不能主进程“写”的同时你子进程在"读",否则数据不就错了嘛。严格点来说这里需要一个“互斥锁”(有兴趣的同学可以试试),我实在是比较懒,不想研究,直接创建了一个状态变量(“共享的”)来控制,主进程和子进程在读写“共享内存”前,通过判断状态变量的值来确定是否有“权利”使用该“共享内存”。


2 代码详解

(1) 构造函数

这部分需要注意的是:建立共享内存、检测子进程、消息接收线程,代码里面有详细的注释,这里不赘述。

def __init__(self, parent=None):super(MainWindow, self).__init__(parent)self.setupUi(self)# 图像大小self.img_shape = (480, 720)# 初始化界面self.label_imgshow.setScaledContents(True)          # 图片自适应显示self.label_imgshow_res.setScaledContents(True)      # 检测结果图片自适应显示self.img_none = np.ones((480, 720, 3), dtype=np.uint8)*255self.show_img(self.img_none)# SSD检测初始化self.weight_path = './ssd/weights/weights_SSD300.hdf5'self.weight_path = os.path.normpath(os.path.abspath(self.weight_path))self.obj_names = ['Aeroplane', 'Bicycle', 'Bird', 'Boat', 'Bottle','Bus', 'Car', 'Cat', 'Chair', 'Cow', 'Diningtable','Dog', 'Horse', 'Motorbike', 'Person', 'Pottedplant','Sheep', 'Sofa', 'Train', 'Tvmonitor']# 需要显示的目标list, 用于过滤self.include_class = self.obj_names# -----------检测子进程--------------# 子进程返回结果使用self.queue = Queue()# 多进程之间的共享图片内存,参数‘I’表示数据类型为 int # 后一个参数为内存的大小,这里Python不提供多个维度数据的共享内存# 只有数组类型满足使用需求,因此主进程中需先将图像数据变为数组的样子,在子进程中再恢复self.img_share = RawArray('I', self.img_shape[0] * self.img_shape[1] * 3)# 标识当前进程的状态,非0:保持检测;0:停止检测self.process_flg = RawValue('I', 1)# 当前图像共享内存 img_share 的状态,非0:主进程使用中;0:子进程使用中self.img_get_flg = RawValue('I', 1)# 创建检测子进程self.detector_process = Process(target=detector_process,args=(self.img_share,self.img_shape,self.process_flg,self.img_get_flg,self.queue))self.detector_process.start()    # 进程开始
# -------------------------------------------------------------# -----------接收检测结果的线程--------------# 主要考虑到Queue的get方法可能会阻塞,如果直接在计时器函数中调用# get会导致UI“假死”,卡着不动。# 虽然也可以设置阻塞时间,但是建议还是建立线程接收处理Queue中的结果# 接收检测结果的线程self.recv_thread = Recv_res(parent=self, queue=self.queue)# 连接信号        # 这个信号用于通知UI响应显示,接收线程中只负责接收转发结果,后面代码中有详细介绍self.recv_thread.res_signal.connect(self.show_res)self.recv_thread.start()
# -------------------------------------------------------------# 视频文件路径self.camera_index = 0self.FPS = None# 初始化计时器self.timer = QTimer(self)               # 更新计时器self.timer.timeout.connect(self.timer_update)       # 超时信号连接对应的槽函数# 等待加载模型self.textEdit.setText('正在加载模型,请稍后......')self.pushButton_start.setEnabled(False)self.pushButton_open.setEnabled(False)self.pushButton_pause.setEnabled(False)self.lineEdit_cameraIndex.setEnabled(False)# 暂停初始化为不暂停self.pause = False

(2) 检测子进程目标函数

下面是检测子进程的目标函数,检测子进程开始后执行的就是这个函数,首先是初始化SSD并加载权重,之后进入帧循环检测,子进程的消息(包括检测结果)通过Queue传递返回,包括两部分:状态量和消息内容,设置状态量的目的是为了下一步针对不同的消息做相应的处理。

def detector_process(img_share, img_shape, process_flg, img_get_flg, res_queue):"""SSD检测子进程目标函数:param img_share: 待检测图像数据,共享内存:param img_shape: 待检测图像的大小 (h, w):param process_flg: 子进程状态量  0:退出检测进程;非0:保持检测:param img_get_flg: 共享图像内存 的状态量     0:子进程占用共享内存   1:主进程占有内存:param res_queue: 返回检测结果的通道:return:"""# 初始化SSDweight_path = './ssd/weights/weights_SSD300.hdf5'weight_path = os.path.normpath(os.path.abspath(weight_path))obj_names = ['Aeroplane', 'Bicycle', 'Bird', 'Boat', 'Bottle','Bus', 'Car', 'Cat', 'Chair', 'Cow', 'Diningtable','Dog', 'Horse', 'Motorbike', 'Person', 'Pottedplant','Sheep', 'Sofa', 'Train', 'Tvmonitor']include_class = obj_namesssd = SSD_test(weight_path=weight_path, class_nam_list=obj_names)# 通知UI 模型加载成功res_queue.put((3, '模型加载成功'))# 构建检测循环while True:# print('process_flg:{}  img_get_flg:{}'.format(process_flg.value, img_get_flg.value))# 判断检测器状态,是否退出if process_flg.value == 0:print('安全退出检测进程!')res_queue.put((0, '检测进程已安全退出!'))break# 判断共享内存当前状态是否可以安全读取数据if img_get_flg.value == 0:# print('开始检测!')try:img = np.array(img_share[:], dtype=np.uint8)img_scr = np.reshape(img, (img_shape[0], img_shape[1], 3))# SSD检测preds = ssd.Predict(img_scr)# 结果过滤preds = filter(obj_names, preds, inclued_class=include_class)
​h, w = img_shape[:2]res = decode_preds(obj_names, preds, w=w, h=h)  # 列表# 管道返回检测结果res_queue.put((1, res))except:print('图片检测失败')res_queue.put((2, '当前图像检测失败!'))finally:# 释放图像共享内存占用,让主进程写入新的图像img_get_flg.value = 1

(3) 消息接收线程类

线程创建后会先进入构造函数,启动后执行run函数。主要的功能就是接收子进程通过Queue传递回来的消息,并通知UI做出相应的处理。接收到检测子进程退出的消息后,该线程也跳出循环结束生命周期。

class Recv_res(QThread):"""检测结果接收线程"""res_signal = pyqtSignal(list)@debug_class('Recv_res')def __init__(self, parent, queue:Queue):"""构造函数:param parent: 父实例 QObj ,Qt中父实例析构相应的子线程会安全退出,不用人工处理:param queue: 管道"""super(Recv_res, self).__init__(parent=parent)self.queue = queue
​def run(self):while True:flg, res = self.queue.get()print(flg, res)if flg == 0:    # 对应检测子进程已安全退出print('接收线程已安全退出!')self.res_signal.emit([0, res])breakelse:self.res_signal.emit([flg, res])

(4) 计时器超时槽函数

该函数主要是按时读取视频流中的图像并显示在控件上,每次判断检测共享内存的状态,如果子进程释放则将当前帧数据写入共享内存中,之后改变状态变量的值(释放对共享内存的占有)。

def timer_update(self):"""计时器槽函数:return:"""if self.cap.isOpened():# 读取图像ret, self.img_scr = self.cap.read()# ### 视频读取完毕if not ret:# 计时器停止计时self.timer.stop()# 不检测self.img_get_flg.value = 1# 对话框提示QMessageBox.information(self, '播放提示', '视频已播放完毕!')# 释放摄像头if hasattr(self, 'cap'):self.cap.release()del self.cap# 释放‘开始’按钮self.pushButton_start.setEnabled(True)# 禁止暂停并初始化其功能self.pause = Falseself.pushButton_pause.setText('暂停')self.pushButton_pause.setEnabled(False)# 释放视频流选择self.pushButton_open.setEnabled(True)self.lineEdit_cameraIndex.setEnabled(True)return# 图像预处理self.img_scr = cv2.resize(self.img_scr, (self.img_shape[1], self.img_shape[0]))# 转为RGBself.img_scr = cv2.cvtColor(self.img_scr, cv2.COLOR_BGR2RGB)if hasattr(self, 'detector_process'):# ### 抽帧if self.img_get_flg.value == 1:# print('开始抽帧')self.img_temp = self.img_scr.copy()         # 用于显示检测结果self.img_share[:] = self.img_scr.reshape(-1).tolist()  # 抽帧保存在中间缓存self.img_get_flg.value = 0  # 不再抽取 直到检测完成# print('结束抽帧')# 显示图像self.show_img(self.img_scr)# 响应UIQApplication.processEvents()else:self.textEdit.setText('数据流未打开!!!\n请检查')self.resst_detector()

(5) 窗口关闭事件函数

在关闭窗口之前需要关闭子进程,否则子进程会一直在后台运行,开一次软件创建一个,多次重复后电脑越来越卡。打开任务管理器后后发现有好多名叫“Python”的进程,这些就是创建后却没关闭的子进程。因此,在窗口关闭事件函数下改变检测进程的状态变量值,使子进程能够正常退出。

def closeEvent(self, a0):"""关闭窗口时间函数:param a0::return:"""self.process_flg.value = 0          # 退出子进程self.detector_process.join()

其他函数就不写了,非常简单。


由于本人能力有限,欢迎批评指正。
可以加我的QQ(1152291782)交流,请注明来意。

关注下方公众号,回复关键字即可获取下载地址。
  • 本文配套源代码下载地址:

    回复“SSD界面3”获取。


如果你读后有收获,欢迎关注我的微信公众号
上面有更多完全免费教程,我也会不定期更新
ღ ღ ღ 打开微信扫描下方二维码关注 ღ ღ ღ

在这里插入图片描述

这篇关于【搞事情】利用PyQt为目标检测SSD300添加界面(四)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/935542

相关文章

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用PyQt实现简易文本编辑器

《使用PyQt实现简易文本编辑器》这篇文章主要为大家详细介绍了如何使用PyQt5框架构建一个简单的文本编辑器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录分析主窗口类 (MyWindow)菜单操作语法高亮 (SyntaxHighlighter)运行程序主要组件代码图示分析实现

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Python中的可视化设计与UI界面实现

《Python中的可视化设计与UI界面实现》本文介绍了如何使用Python创建用户界面(UI),包括使用Tkinter、PyQt、Kivy等库进行基本窗口、动态图表和动画效果的实现,通过示例代码,展示... 目录从像素到界面:python带你玩转UI设计示例:使用Tkinter创建一个简单的窗口绘图魔法:用

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X