Smart target detection – PyQt5 builds target detection interface
Learn the preface
Based on the open source YoloV4-Pytorch source code of B guide, a face detection system for wearing a mask was developed (the undergraduate project completed in 21 years, it is relatively old, and can be replaced by the latest target detection algorithm by itself).
Source code download
https://github.com/Egrt/YOLO_PyQt5
You can order a star if you like it.
Support features
- Support reading local pictures
- Support reading local video
- Support open camera real-time detection
- Support multi-threading to prevent stuck
- Support recording when a face is detected without a mask, and a voice warning
Interface display
PyQt5
PyQt5 is a popular GUI (Graphical User Interface) development framework in the Python language. Based on the Qt GUI application development framework, it provides a powerful tool set for creating various desktop applications. PyQt5 can be used to develop desktop applications, web applications and mobile applications, with good cross-platform and rich functions.
Signals and slots
Signals and slots are an important concept in PyQt5 and are mechanisms for organizing and managing interactions between GUI elements. Signals are events or actions emitted by GUI elements, and slots are functions that handle signals. When a signal occurs, the slot associated with it will be called automatically.
Below is a simple example code demonstrating how to use signals and slots in PyQt5. This example creates a window that contains a button and a label. When the user clicks the button, the label’s text will change:
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_())
In this sample code, we create a window class named MyWindow
, which inherits from QWidget
. In the MyWindow
constructor, we create a button and a label, and use the clicked
signal to connect the button’s click event to changeText
slot function. When the button is clicked, the changeText
slot function will be called, which will change the text of the label.
After running the code, you can see that there is a button and a label on the window, and the text of the label will change to “Button Clicked” after the button is clicked. This example demonstrates how to use signals and slots in PyQt5 to implement interactive GUI applications.
Function realization
Interface design
According to the task requirements, the interface can be divided into four parts:
- The button is placed on the top to realize the selection of reading pictures, videos, and opening the camera for real-time detection.
- A directory control is placed on the left to browse local files.
- The image processed by YOLO is shown in the middle.
- When processing video or reading camera detection in real time, if a face without a mask is continuously recognized in multiple frames, it will be recorded and a voice warning will be issued.
So write the code as follows:
class MyApp(QMainWindow): def __init__(self): super(MyApp, self).__init__() self.cap = cv2.VideoCapture() self.CAM_NUM = 0 self.thread_status = False # Determine whether the recognition thread is enabled self.tool_bar = self.addToolBar('Toolbar') self.action_right_rotate = QAction( QIcon("icons/rotate right.png"), "rotate right 90", self) self.action_left_rotate = QAction( QIcon("icons/left rotation.png"), "rotate left 90°", self) self.action_opencam = QAction(QIcon("icons/camera.png"), "open camera", self) self.action_video = QAction(QIcon("icons/video.png"), "load video", self) self.action_image = QAction(QIcon("icons/image.png"), "load image", 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('directory')) self.dock_file.setFeatures(QDockWidget.NoDockWidgetFeatures) self.dock_attr = QDockWidget(self) self.dock_attr.setWidget(self.stackedWidget) self.dock_attr.setTitleBarWidget(QLabel('report data')) 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('mask wearing detection') self.setWindowIcon(QIcon('icons/mask.png')) self.src_img = None self.cur_img = None
Slot function
Configure the interface of the window in the initialization and use connect
to connect the signal and the slot function. When the signal occurs, the slot associated with it will be called automatically. The slot functions that control opening pictures, videos and local cameras are:
def openvideo(self): print(self. thread_status) if self. thread_status == False: fileName, filetype = QFileDialog.getOpenFileName( self, "Select Video", "D:/", "*.mp4;;*.flv;;All Files(*)") flag = self.cap.open(fileName) if flag == False: msg = QtWidgets.QMessageBox.warning(self, u"Warning", u"Please select a video file", 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('close video') self. thread_status = True elif self. thread_status == True: self. detectThread. terminate() if self.cap.isOpened(): self.cap.release() self.action_video.setText('open video') self. thread_status = False def openimage(self): if self. thread_status == False: fileName, filetype = QFileDialog.getOpenFileName( self, "Select Image", "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"Warning", u"Please check whether the camera is connected to the computer correctly", 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('close video') self. thread_status = True else: self. detectThread. terminate() if self.cap.isOpened(): self.cap.release() self.action_video.setText('open video') self. thread_status = False
Multithreading
When reading video files or cameras, in order to avoid interface freezes, multi-threading is used for processing, and the thread needs to be closed to prevent the system from being stuck when the video file is finished processing, and self needs to be used when closing the camera. cap.release()
releases the camera.
When processing continuous frames with multiple threads, the multi-threaded library QThread that comes with Qt is used:
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 # Whether to send a warning signal 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() # read a frame frame = self.frame # Format conversion, BGRtoRGB frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Convert to Image frame = Image.fromarray(np.uint8(frame)) # check 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 meets the opencv display format 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 # Send pyqt signal self.Send_signal.emit(frame, self.warn)
Information record
If a face without a mask is recognized for 30 consecutive frames, a signal will be sent and displayed in the list on the right, and the current frame will be recorded:
def add_item(self, image): # Total Widget weight = QWidget() # overall horizontal layout layout_main = QHBoxLayout() map_l = QLabel() # picture display map_l. setFixedSize(60, 40) map_l.setPixmap(image.scaled(60, 40)) # vertical layout on the right layout_right = QVBoxLayout() # Horizontal layout for bottom right layout_right_down = QHBoxLayout() # The horizontal layout of the lower right layout_right_down.addWidget( QLabel(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))) # Add according to the layout from left to right, top to bottom layout_main.addWidget(map_l) # leftmost image layout_right.addWidget(QLabel('Warning! No mask was detected')) # vertical layout on the right layout_right.addLayout(layout_right_down) # Horizontal layout in the lower right corner layout_main.addLayout(layout_right) # layout on the right wight.setLayout(layout_main) # layout for weight item = QListWidgetItem() # create QListWidgetItem object item.setSizeHint(QSize(300, 80)) # Set the size of QListWidgetItem self.stackedWidget.addItem(item) # add item self.stackedWidget.setItemWidget(item, weight) # set widget for item
Shut down the system
When shutting down the system, you need to make sure that multi-threading is turned off, and the camera that has been opened is turned off, otherwise it will also cause a freeze when exiting:
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"Close", u"Are you sure to exit?") msg.addButton(ok, QtWidgets.QMessageBox.ActionRole) msg.addButton(cacel, QtWidgets.QMessageBox.RejectRole) ok.setText(u'OK') cacel.setText(u'Cancel') 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()
The final complete code is as follows:
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") # Multi-thread real-time detection 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 # Whether to send a warning signal 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() # read a frame frame = self.frame # Format conversion, BGRtoRGB frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Convert to Image frame = Image.fromarray(np.uint8(frame)) # check 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 meets the opencv display format 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 # Send pyqt signal 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 # Determine whether the recognition thread is enabled self.tool_bar = self.addToolBar('Toolbar') self.action_right_rotate = QAction( QIcon("icons/rotate right.png"), "rotate right 90", self) self.action_left_rotate = QAction( QIcon("icons/left rotation.png"), "rotate left 90°", self) self.action_opencam = QAction(QIcon("icons/camera.png"), "open camera", self) self.action_video = QAction(QIcon("icons/video.png"), "load video", self) self.action_image = QAction(QIcon("icons/image.png"), "load image", 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('directory')) self.dock_file.setFeatures(QDockWidget.NoDockWidgetFeatures) self.dock_attr = QDockWidget(self) self.dock_attr.setWidget(self.stackedWidget) self.dock_attr.setTitleBarWidget(QLabel('report data')) 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('mask wearing detection') 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): # Total Widget weight = QWidget() # overall horizontal layout layout_main = QHBoxLayout() map_l = QLabel() # picture display map_l. setFixedSize(60, 40) map_l.setPixmap(image.scaled(60, 40)) # vertical layout on the right layout_right = QVBoxLayout() # Horizontal layout for bottom right layout_right_down = QHBoxLayout() # The horizontal layout of the lower right layout_right_down.addWidget( QLabel(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))) # Add according to the layout from left to right, top to bottom layout_main.addWidget(map_l) # leftmost image layout_right.addWidget(QLabel('Warning! No mask was detected')) # vertical layout on the right layout_right.addLayout(layout_right_down) # Horizontal layout in the lower right corner layout_main.addLayout(layout_right) # layout on the right wight.setLayout(layout_main) # layout for weight item = QListWidgetItem() # create QListWidgetItem object item.setSizeHint(QSize(300, 80)) # Set the size of QListWidgetItem self.stackedWidget.addItem(item) # add item self.stackedWidget.setItemWidget(item, weight) # set widget for item def openvideo(self): print(self. thread_status) if self. thread_status == False: fileName, filetype = QFileDialog.getOpenFileName( self, "Select Video", "D:/", "*.mp4;;*.flv;;All Files(*)") flag = self.cap.open(fileName) if flag == False: msg = QtWidgets.QMessageBox.warning(self, u"Warning", u"Please select a video file", 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('close video') self. thread_status = True elif self. thread_status == True: self. detectThread. terminate() if self.cap.isOpened(): self.cap.release() self.action_video.setText('open video') self. thread_status = False def openimage(self): if self. thread_status == False: fileName, filetype = QFileDialog.getOpenFileName( self, "Select Image", "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"Warning", u"Please check whether the camera is connected to the computer correctly", 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('close video') self. thread_status = True else: self. detectThread. terminate() if self.cap.isOpened(): self.cap.release() self.action_video.setText('open video') 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"Close", u"Are you sure to exit?") msg.addButton(ok, QtWidgets.QMessageBox.ActionRole) msg.addButton(cacel, QtWidgets.QMessageBox.RejectRole) ok.setText(u'OK') cacel.setText(u'Cancel') 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__": # Initialize the yolo model yolo = YOLO() app = QApplication(sys. argv) app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) window = MyApp() window. show() sys. exit(app. exec_())