睿智的目标检测——PyQt5搭建目标检测界面

睿智的目标检测——PyQt5搭建目标检测界面

学习前言

基于B导开源的YoloV4-Pytorch源码开发了戴口罩人脸检测系统(21年完成的本科毕设,较为老旧,可自行替换为最新的目标检测算法)。

源码下载

https://github.com/Egrt/YOLO_PyQt5
喜欢的可以点个star噢。

支持功能

  1. 支持读取本地图片
  2. 支持读取本地视频
  3. 支持打开摄像头实时检测
  4. 支持多线程,防止卡顿
  5. 支持检测到人脸未佩戴口罩时记录,并语音警告

界面展示

在这里插入图片描述

PyQt5

PyQt5是Python语言中一款流行的GUI(图形用户界面)开发框架,基于Qt GUI应用程序开发框架,提供了一个强大的工具集,用于创建各种桌面应用程序。PyQt5可以用于开发桌面应用程序、Web应用程序和移动应用程序,具有良好的跨平台性和丰富的功能。

信号与槽

信号和槽是PyQt5中一个重要的概念,是用于组织和管理GUI元素之间交互的机制。信号是GUI元素发出的事件或动作,槽是处理信号的函数。当信号发生时,与之相关联的槽将被自动调用。

下面是一个简单的示例代码,演示如何在PyQt5中使用信号和槽。这个示例创建了一个窗口,其中包含一个按钮和一个标签。当用户单击按钮时,标签的文本将会改变:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Signal and Slot')

        self.button = QPushButton('Click', self)
        self.button.move(100, 100)
        self.button.clicked.connect(self.changeText)

        self.label = QLabel('Hello World', self)
        self.label.move(110, 60)

    def changeText(self):
        self.label.setText('Button Clicked')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

在这个示例代码中,我们创建了一个名为MyWindow的窗口类,该类继承自QWidget。在MyWindow的构造函数中,我们创建了一个按钮和一个标签,并使用clicked信号将按钮的单击事件连接到changeText槽函数。当按钮被单击时,changeText槽函数将会被调用,该函数会改变标签的文本。

运行代码后,可以看到窗口上有一个按钮和一个标签,单击按钮后标签的文本会改变为“Button Clicked”。这个示例演示了如何使用PyQt5中的信号和槽来实现交互式GUI应用程序。

功能实现

界面设计

根据任务需求,可以将界面分为四部分:

  1. 最上方放置按钮来实现选择读取图片、视频、开启摄像头实时检测。
  2. 左侧放置目录控件,浏览本地文件。
  3. 中间显示YOLO处理后的图片。
  4. 在处理视频或实时读取摄像头检测时,如果多帧连续识别到不戴口罩人脸将其记录并发出语音警告。

因此编写代码如下:

class MyApp(QMainWindow):
    def __init__(self):
        super(MyApp, self).__init__()

        self.cap                 = cv2.VideoCapture()
        self.CAM_NUM             = 0
        self.thread_status       = False  # 判断识别线程是否开启
        self.tool_bar            = self.addToolBar('工具栏')
        self.action_right_rotate = QAction(
            QIcon("icons/右旋转.png"), "向右旋转90", self)
        self.action_left_rotate = QAction(
            QIcon("icons/左旋转.png"), "向左旋转90°", self)
        self.action_opencam = QAction(QIcon("icons/摄像头.png"), "开启摄像头", self)
        self.action_video   = QAction(QIcon("icons/video.png"), "加载视频", self)
        self.action_image   = QAction(QIcon("icons/图片.png"), "加载图片", self)
        self.action_right_rotate.triggered.connect(self.right_rotate)
        self.action_left_rotate.triggered.connect(self.left_rotate)
        self.action_opencam.triggered.connect(self.opencam)
        self.action_video.triggered.connect(self.openvideo)
        self.action_image.triggered.connect(self.openimage)
        self.tool_bar.addActions((self.action_left_rotate, self.action_right_rotate,
                                  self.action_opencam, self.action_video, self.action_image))
        self.stackedWidget      = StackedWidget(self)
        self.fileSystemTreeView = FileSystemTreeView(self)
        self.graphicsView       = GraphicsView(self)
        self.dock_file          = QDockWidget(self)
        self.dock_file.setWidget(self.fileSystemTreeView)
        self.dock_file.setTitleBarWidget(QLabel('目录'))
        self.dock_file.setFeatures(QDockWidget.NoDockWidgetFeatures)

        self.dock_attr = QDockWidget(self)
        self.dock_attr.setWidget(self.stackedWidget)
        self.dock_attr.setTitleBarWidget(QLabel('上报数据'))
        self.dock_attr.setFeatures(QDockWidget.NoDockWidgetFeatures)

        self.setCentralWidget(self.graphicsView)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_file)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_attr)

        self.setWindowTitle('口罩佩戴检测')
        self.setWindowIcon(QIcon('icons/mask.png'))
        self.src_img = None
        self.cur_img = None

槽函数

在初始化中配置窗口的界面并使用connect连接信号与槽函数,当信号发生时,与之相关联的槽将被自动调用。控制打开图片、视频与本地摄像头的槽函数分别为:

def openvideo(self):
   print(self.thread_status)
   if self.thread_status == False:

       fileName, filetype = QFileDialog.getOpenFileName(
           self, "选择视频", "D:/", "*.mp4;;*.flv;;All Files(*)")

       flag = self.cap.open(fileName)
       if flag == False:
           msg = QtWidgets.QMessageBox.warning(self, u"警告", u"请选择视频文件",
                                               buttons=QtWidgets.QMessageBox.Ok,
                                               defaultButton=QtWidgets.QMessageBox.Ok)
       else:
           self.detectThread = DetectThread(fileName)
           self.detectThread.Send_signal.connect(self.Display)
           self.detectThread.start()
           self.action_video.setText('关闭视频')
           self.thread_status = True
   elif self.thread_status == True:
       self.detectThread.terminate()
       if self.cap.isOpened():
           self.cap.release()
       self.action_video.setText('打开视频')
       self.thread_status = False

def openimage(self):
   if self.thread_status == False:
       fileName, filetype = QFileDialog.getOpenFileName(
           self, "选择图片", "D:/", "*.jpg;;*.png;;All Files(*)")
       if fileName != '':
           src_img = Image.open(fileName)
           r_image, predicted_class = yolo.detect_image(src_img)
           r_image = np.array(r_image)
           showImage = QtGui.QImage(
               r_image.data, r_image.shape[1], r_image.shape[0], QtGui.QImage.Format_RGB888)
           self.graphicsView.set_image(QtGui.QPixmap.fromImage(showImage))

def opencam(self):
   if self.thread_status == False:
       flag = self.cap.open(self.CAM_NUM)
       if flag == False:
           msg = QtWidgets.QMessageBox.warning(self, u"警告", u"请检测相机与电脑是否连接正确",
                                               buttons=QtWidgets.QMessageBox.Ok,
                                               defaultButton=QtWidgets.QMessageBox.Ok)
       else:
           self.detectThread = DetectThread(self.CAM_NUM)
           self.detectThread.Send_signal.connect(self.Display)
           self.detectThread.start()
           self.action_video.setText('关闭视频')
           self.thread_status = True
   else:
       self.detectThread.terminate()
       if self.cap.isOpened():
           self.cap.release()
       self.action_video.setText('打开视频')
       self.thread_status = False

多线程

在读取视频文件或摄像头时,为避免界面卡顿,使用了多线程进行处理,并在结束处理视频文件时需要关闭线程防止系统卡死,且在关闭摄像头时还需要使用self.cap.release()对摄像头进行释放。

在多线程处理连续帧时,采用了Qt自带的多线程库QThread:

class DetectThread(QThread):
    Send_signal = pyqtSignal(np.ndarray, int)

    def __init__(self, fileName):
        super(DetectThread, self).__init__()
        self.capture = cv2.VideoCapture(fileName)
        self.count = 0
        self.warn = False  # 是否发送警告信号

    def run(self):
        ret, self.frame = self.capture.read()
        while ret:
            ret, self.frame = self.capture.read()
            self.detectCall()

    def detectCall(self):
        fps = 0.0
        t1 = time.time()
        # 读取某一帧
        frame = self.frame
        # 格式转变,BGRtoRGB
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        # 转变成Image
        frame = Image.fromarray(np.uint8(frame))
        # 进行检测
        frame_new, predicted_class = yolo.detect_image(frame)
        frame = np.array(frame_new)
        if predicted_class == "face":
            self.count = self.count+1
        else:
            self.count = 0
        # RGBtoBGR满足opencv显示格式
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
        fps = (fps + (1./(time.time()-t1))) / 2
        print("fps= %.2f" % (fps))
        frame = cv2.putText(frame, "fps= %.2f" % (
            fps), (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
        if self.count > 30:
            self.count = 0
            self.warn = True
        else:
            self.warn = False
        # 发送pyqt信号
        self.Send_signal.emit(frame, self.warn)

信息记录

如果连续30帧识别到未佩戴口罩的人脸时,将发送信号在右侧列表中显示,并记录当前帧画面:

def add_item(self, image):
    # 总Widget
    wight = QWidget()
    # 总体横向布局
    layout_main = QHBoxLayout()
    map_l = QLabel()  # 图片显示
    map_l.setFixedSize(60, 40)
    map_l.setPixmap(image.scaled(60, 40))
    # 右边的纵向布局
    layout_right = QVBoxLayout()
    # 右下的的横向布局
    layout_right_down = QHBoxLayout()  # 右下的横向布局
    layout_right_down.addWidget(
        QLabel(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))

    # 按照从左到右, 从上到下布局添加
    layout_main.addWidget(map_l)  # 最左边的图片
    layout_right.addWidget(QLabel('警告!检测到未佩戴口罩'))  # 右边的纵向布局
    layout_right.addLayout(layout_right_down)  # 右下角横向布局
    layout_main.addLayout(layout_right)  # 右边的布局
    wight.setLayout(layout_main)  # 布局给wight
    item = QListWidgetItem()  # 创建QListWidgetItem对象
    item.setSizeHint(QSize(300, 80))  # 设置QListWidgetItem大小
    self.stackedWidget.addItem(item)  # 添加item
    self.stackedWidget.setItemWidget(item, wight)  # 为item设置widget

关闭系统

在关闭系统时,需要确保关闭了多线程,且关闭了已经打开的摄像头,否则在退出时也将造成卡顿:

def Display(self, frame, warn):
        im = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        showImage = QtGui.QImage(
            im.data, im.shape[1], im.shape[0], QtGui.QImage.Format_RGB888)
        self.graphicsView.set_image(QtGui.QPixmap.fromImage(showImage))

def closeEvent(self, event):
    ok = QtWidgets.QPushButton()
    cacel = QtWidgets.QPushButton()
    msg = QtWidgets.QMessageBox(
        QtWidgets.QMessageBox.Warning, u"关闭", u"确定退出?")
    msg.addButton(ok, QtWidgets.QMessageBox.ActionRole)
    msg.addButton(cacel, QtWidgets.QMessageBox.RejectRole)
    ok.setText(u'确定')
    cacel.setText(u'取消')
    if msg.exec_() == QtWidgets.QMessageBox.RejectRole:
        event.ignore()
    else:
        if self.thread_status == True:
            self.detectThread.terminate()
        if self.cap.isOpened():
            self.cap.release()
        event.accept()

最终完整的代码如下:

import ctypes
import sys
import time

import cv2
import numpy as np
import qdarkstyle
from PIL import Image
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import QThread
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

from custom.graphicsView import GraphicsView
from custom.listWidgets import *
from custom.stackedWidget import *
from custom.treeView import FileSystemTreeView
from yolo import YOLO

ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")

# 多线程实时检测
class DetectThread(QThread):
    Send_signal = pyqtSignal(np.ndarray, int)

    def __init__(self, fileName):
        super(DetectThread, self).__init__()
        self.capture = cv2.VideoCapture(fileName)
        self.count = 0
        self.warn = False  # 是否发送警告信号

    def run(self):
        ret, self.frame = self.capture.read()
        while ret:
            ret, self.frame = self.capture.read()
            self.detectCall()

    def detectCall(self):
        fps = 0.0
        t1 = time.time()
        # 读取某一帧
        frame = self.frame
        # 格式转变,BGRtoRGB
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        # 转变成Image
        frame = Image.fromarray(np.uint8(frame))
        # 进行检测
        frame_new, predicted_class = yolo.detect_image(frame)
        frame = np.array(frame_new)
        if predicted_class == "face":
            self.count = self.count+1
        else:
            self.count = 0
        # RGBtoBGR满足opencv显示格式
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
        fps = (fps + (1./(time.time()-t1))) / 2
        print("fps= %.2f" % (fps))
        frame = cv2.putText(frame, "fps= %.2f" % (
            fps), (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
        if self.count > 30:
            self.count = 0
            self.warn = True
        else:
            self.warn = False
        # 发送pyqt信号
        self.Send_signal.emit(frame, self.warn)

class MyApp(QMainWindow):
    def __init__(self):
        super(MyApp, self).__init__()

        self.cap                 = cv2.VideoCapture()
        self.CAM_NUM             = 0
        self.thread_status       = False  # 判断识别线程是否开启
        self.tool_bar            = self.addToolBar('工具栏')
        self.action_right_rotate = QAction(
            QIcon("icons/右旋转.png"), "向右旋转90", self)
        self.action_left_rotate = QAction(
            QIcon("icons/左旋转.png"), "向左旋转90°", self)
        self.action_opencam = QAction(QIcon("icons/摄像头.png"), "开启摄像头", self)
        self.action_video   = QAction(QIcon("icons/video.png"), "加载视频", self)
        self.action_image   = QAction(QIcon("icons/图片.png"), "加载图片", self)
        self.action_right_rotate.triggered.connect(self.right_rotate)
        self.action_left_rotate.triggered.connect(self.left_rotate)
        self.action_opencam.triggered.connect(self.opencam)
        self.action_video.triggered.connect(self.openvideo)
        self.action_image.triggered.connect(self.openimage)
        self.tool_bar.addActions((self.action_left_rotate, self.action_right_rotate,
                                  self.action_opencam, self.action_video, self.action_image))
        self.stackedWidget      = StackedWidget(self)
        self.fileSystemTreeView = FileSystemTreeView(self)
        self.graphicsView       = GraphicsView(self)
        self.dock_file          = QDockWidget(self)
        self.dock_file.setWidget(self.fileSystemTreeView)
        self.dock_file.setTitleBarWidget(QLabel('目录'))
        self.dock_file.setFeatures(QDockWidget.NoDockWidgetFeatures)

        self.dock_attr = QDockWidget(self)
        self.dock_attr.setWidget(self.stackedWidget)
        self.dock_attr.setTitleBarWidget(QLabel('上报数据'))
        self.dock_attr.setFeatures(QDockWidget.NoDockWidgetFeatures)

        self.setCentralWidget(self.graphicsView)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_file)
        self.addDockWidget(Qt.RightDockWidgetArea, self.dock_attr)

        self.setWindowTitle('口罩佩戴检测')
        self.setWindowIcon(QIcon('icons/mask.png'))
        self.src_img = None
        self.cur_img = None

    def update_image(self):
        if self.src_img is None:
            return
        img = self.process_image()
        self.cur_img = img
        self.graphicsView.update_image(img)

    def change_image(self, img):
        self.src_img = img
        img = self.process_image()
        self.cur_img = img
        self.graphicsView.change_image(img)

    def process_image(self):
        img = self.src_img.copy()
        for i in range(self.useListWidget.count()):
            img = self.useListWidget.item(i)(img)
        return img

    def right_rotate(self):
        self.graphicsView.rotate(90)

    def left_rotate(self):
        self.graphicsView.rotate(-90)

    def add_item(self, image):
        # 总Widget
        wight = QWidget()
        # 总体横向布局
        layout_main = QHBoxLayout()
        map_l = QLabel()  # 图片显示
        map_l.setFixedSize(60, 40)
        map_l.setPixmap(image.scaled(60, 40))
        # 右边的纵向布局
        layout_right = QVBoxLayout()
        # 右下的的横向布局
        layout_right_down = QHBoxLayout()  # 右下的横向布局
        layout_right_down.addWidget(
            QLabel(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))

        # 按照从左到右, 从上到下布局添加
        layout_main.addWidget(map_l)  # 最左边的图片
        layout_right.addWidget(QLabel('警告!检测到未佩戴口罩'))  # 右边的纵向布局
        layout_right.addLayout(layout_right_down)  # 右下角横向布局
        layout_main.addLayout(layout_right)  # 右边的布局
        wight.setLayout(layout_main)  # 布局给wight
        item = QListWidgetItem()  # 创建QListWidgetItem对象
        item.setSizeHint(QSize(300, 80))  # 设置QListWidgetItem大小
        self.stackedWidget.addItem(item)  # 添加item
        self.stackedWidget.setItemWidget(item, wight)  # 为item设置widget

    def openvideo(self):
        print(self.thread_status)
        if self.thread_status == False:

            fileName, filetype = QFileDialog.getOpenFileName(
                self, "选择视频", "D:/", "*.mp4;;*.flv;;All Files(*)")

            flag = self.cap.open(fileName)
            if flag == False:
                msg = QtWidgets.QMessageBox.warning(self, u"警告", u"请选择视频文件",
                                                    buttons=QtWidgets.QMessageBox.Ok,
                                                    defaultButton=QtWidgets.QMessageBox.Ok)
            else:
                self.detectThread = DetectThread(fileName)
                self.detectThread.Send_signal.connect(self.Display)
                self.detectThread.start()
                self.action_video.setText('关闭视频')
                self.thread_status = True
        elif self.thread_status == True:
            self.detectThread.terminate()
            if self.cap.isOpened():
                self.cap.release()
            self.action_video.setText('打开视频')
            self.thread_status = False

    def openimage(self):
        if self.thread_status == False:
            fileName, filetype = QFileDialog.getOpenFileName(
                self, "选择图片", "D:/", "*.jpg;;*.png;;All Files(*)")
            if fileName != '':
                src_img = Image.open(fileName)
                r_image, predicted_class = yolo.detect_image(src_img)
                r_image = np.array(r_image)
                showImage = QtGui.QImage(
                    r_image.data, r_image.shape[1], r_image.shape[0], QtGui.QImage.Format_RGB888)
                self.graphicsView.set_image(QtGui.QPixmap.fromImage(showImage))

    def opencam(self):
        if self.thread_status == False:
            flag = self.cap.open(self.CAM_NUM)
            if flag == False:
                msg = QtWidgets.QMessageBox.warning(self, u"警告", u"请检测相机与电脑是否连接正确",
                                                    buttons=QtWidgets.QMessageBox.Ok,
                                                    defaultButton=QtWidgets.QMessageBox.Ok)
            else:
                self.detectThread = DetectThread(self.CAM_NUM)
                self.detectThread.Send_signal.connect(self.Display)
                self.detectThread.start()
                self.action_video.setText('关闭视频')
                self.thread_status = True
        else:
            self.detectThread.terminate()
            if self.cap.isOpened():
                self.cap.release()
            self.action_video.setText('打开视频')
            self.thread_status = False

    def Display(self, frame, warn):
        im = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        showImage = QtGui.QImage(
            im.data, im.shape[1], im.shape[0], QtGui.QImage.Format_RGB888)
        self.graphicsView.set_image(QtGui.QPixmap.fromImage(showImage))

    def closeEvent(self, event):
        ok = QtWidgets.QPushButton()
        cacel = QtWidgets.QPushButton()
        msg = QtWidgets.QMessageBox(
            QtWidgets.QMessageBox.Warning, u"关闭", u"确定退出?")
        msg.addButton(ok, QtWidgets.QMessageBox.ActionRole)
        msg.addButton(cacel, QtWidgets.QMessageBox.RejectRole)
        ok.setText(u'确定')
        cacel.setText(u'取消')
        if msg.exec_() == QtWidgets.QMessageBox.RejectRole:
            event.ignore()
        else:
            if self.thread_status == True:
                self.detectThread.terminate()
            if self.cap.isOpened():
                self.cap.release()
            event.accept()


if __name__ == "__main__":
    # 初始化yolo模型
    yolo = YOLO()
    app  = QApplication(sys.argv)
    app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

  • 14
    点赞
  • 209
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
PyQt5可以用于搭建Yolo目标检测界面。你可以使用PyQt5的GUI开发框架来创建一个具有按钮和标签的窗口。当按钮被点击时,可以通过信号和槽机制来实现目标检测功能,并将结果显示在标签上。 首先,你需要导入PyQt5库,并创建一个继承自QtWidgets.QMainWindow的类。在这个类中,你可以定义界面的布局和组件,比如按钮和标签。你可以使用QtDesigner来设计界面,然后将生成的.ui文件转换为Python代码。 接下来,你可以在按钮的点击事件中调用目标检测的函数。你可以使用OpenCV库来进行目标检测,通过调用cv2.VideoCapture()函数来打开摄像头,并在每一帧上进行目标检测。然后,你可以将检测结果显示在标签上,可以使用cv2.cvtColor()函数将图像从BGR格式转换为RGB格式,然后使用QtGui.QImage和QtGui.QPixmap来显示图像。 最后,在关闭窗口时,你需要确保关闭多线程和已经打开的摄像头,以避免程序卡顿。你可以在closeEvent()函数中实现这个功能,通过调用terminate()函数来终止多线程,使用release()函数来释放摄像头。 综上所述,你可以使用PyQt5搭建一个Yolo目标检测界面,通过信号和槽机制实现交互功能,并使用OpenCV库进行目标检测。 #### 引用[.reference_title] - *1* *2* *3* [睿智目标检测——PyQt5搭建目标检测界面](https://blog.csdn.net/weixin_43293172/article/details/129465120)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_白鹭先生_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值