人脸识别项目实战(三):PyQt界面模块实现
PyQt 界面模块是人脸识别系统与用户交互的桥梁,负责将人脸检测模块的功能以可视化的方式呈现给用户,并接收用户的操作指令。设计并实现直观友好的图形用户界面实现摄像头实时画面的采集与显示提供开始 / 停止检测、加载本地图片调用人脸检测模块对实时画面或本地图片进行处理在界面上实时标注检测到的人脸区域数据库人脸查看、搜索、删除技术栈:Python 3.10+、PyQt5、OpenCV。
·
人脸识别项目实战(三):PyQt界面模块实现
一、项目结构

二、模块概述
PyQt 界面模块是人脸识别系统与用户交互的桥梁,负责将人脸检测模块的功能以可视化的方式呈现给用户,并接收用户的操作指令。
本模块主要完成以下任务:
- 设计并实现直观友好的图形用户界面
- 实现摄像头实时画面的采集与显示
- 提供开始 / 停止检测、加载本地图片
- 调用人脸检测模块对实时画面或本地图片进行处理
- 在界面上实时标注检测到的人脸区域
- 数据库人脸查看、搜索、删除
技术栈:Python 3.10+、PyQt5、OpenCV
三、人脸识别模块需修改的代码
人脸保存
import cv2
import numpy as np
import torch
from facenet_pytorch import InceptionResnetV1
from ultralytics import YOLO
from FaceNet.DB import DB
class FaceSave:
def __init__(self):
# 初始化设备
self.device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {self.device}")
# 加载前面训练出来的模型,检测人脸
self.yolo_model = YOLO('../FaceDetection/runs/detect/train/weights/best.pt').to(self.device)
# 加载 FaceNet 模型,提取人脸特征
self.faceNet_model = InceptionResnetV1(pretrained='vggface2').eval()
# 定义数据库,连接数据库
self.db = DB()
# 对人脸图像进行预处理
def pretreatment_face(self, face):
pred_face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB)
# 调整大小,FaceNet对图像大小有要求
pred_face = cv2.resize(pred_face, (160, 160))
# 归一化
pred_face = (pred_face/255.0 - 0.5) / 0.5
# 将图像转换成张量
# 在 PyTorch 中,图像张量的标准格式通常要求通道维度在前
# permute(2, 0, 1)将图像的维度从HWC (高度、宽度、通道) 转换为CHW (通道、高度、宽度)
# 模型期望输入是4维的 [B, C, H, W]
pred_face = torch.tensor(pred_face).permute(2, 0, 1).float().unsqueeze(0)
return pred_face
def detect_and_features(self, name, img, ext):
results = self.yolo_model(img)
for result in results:
# 检测的人脸边框
boxes = result.boxes
# 处理人脸
for box in boxes:
# print(box)
x1, y1, x2, y2 = np.int32(box.xyxy.cpu()[0])
# 裁剪人脸
face = img[y1:y2, x1:x2]
# 预处理人脸
pred_face = self.pretreatment_face(face)
# 提取人脸特征
with torch.no_grad():
face_feature = self.faceNet_model(pred_face).detach()
# 保存
self.db.insert(name, face_feature, img, ext)
数据库
import datetime
import sqlite3
import cv2
import numpy as np
import pandas as pd
import torch
class DB:
def __init__(self):
# 数据库地址
self.db_path = "./face_database.db"
# 初始化数据库表结构
self.conn = None
self.cursor = None
self.open()
# 如果表不存在,创建人脸数据表
# 特征和图像用字节的方式保存
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS faces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
feature BLOB NOT NULL,
image BLOB NOT NULL
)
''')
self.conn.commit()
# 连接数据库
def open(self):
self.conn = sqlite3.connect(self.db_path)
self.cursor = self.conn.cursor()
# 将Tensor转换为字节
def tensor_to_bytes(self, tensor):
if tensor.dim() > 1:
tensor = tensor.squeeze()
# 保存为numpy数组的字节形式
np_array = tensor.cpu().numpy() if tensor.is_cuda else tensor.numpy()
return np_array.tobytes()
# 将字节转换成Tensor
def bytes_to_tensor(self, bytes_data):
# 从字节重建numpy数组
# np.frombuffer得到的是不可写的 NumPy 数组
np_array = np.frombuffer(bytes_data, dtype=np.float32)
# PyTorch 期望张量是可写的
# 创建可写副本
np_array = np_array.copy()
return torch.from_numpy(np_array).reshape(1, -1)
# 添加操作
def insert(self, name, feature, image, ext):
# 将特征张量转换为字节
feature_bytes = self.tensor_to_bytes(feature)
# # 得到图像和图片的扩展名(转字节需要)
# image = cv2.imread(image_path)
# ext = os.path.splitext(image_path)[1]
# 转换图像为字节
success, image_bytes = cv2.imencode(ext, image)
if not success:
raise ValueError("图像编码失败")
# 存入时间
created_time = datetime.datetime.now()
# 插入
self.cursor.execute('''
INSERT INTO faces (name, created_time, feature, image)
VALUES (?, ?, ?, ?)
''', (name, created_time, feature_bytes, image_bytes))
# 提交
self.conn.commit()
# 删除操作(根据Id)
def delete(self, id):
self.cursor.execute("DELETE FROM faces WHERE id = ?", (id,))
self.conn.commit()
# 查询操作(根据名字)
def select_one(self, name):
query = "SELECT * FROM faces WHERE name = ?"
data = pd.read_sql_query(query, self.conn, params=(name, ))
data.drop('feature', axis=1, inplace=True)
return data
# 获取数据库的特征内容
def select_features(self):
# 使用 pandas 读取数据
query = "SELECT * FROM faces"
datas = pd.read_sql_query(query, self.conn)
# 用一个字典存储
features = {}
if not datas.empty:
for name, feature in zip(datas.name, datas.feature):
features[name] = self.bytes_to_tensor(feature)
return features
# 查看数据库内容
def select_all(self):
# 使用 pandas 读取数据
query = "SELECT * FROM faces"
datas = pd.read_sql_query(query, self.conn)
datas.drop('feature', axis=1, inplace=True)
return datas
# 关闭数据库
def close(self):
self.cursor.close()
self.conn.close()
四、PyQt代码实现
1.启动程序
import sys
from PyQt5.QtWidgets import QApplication
from PyQt.controller import Controller
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Controller()
window.show()
sys.exit(app.exec_())
2.前端代码
很简洁,感兴趣的小伙伴自行美观
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.15.11
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1000, 721)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.navWidget = QtWidgets.QWidget(self.centralwidget)
self.navWidget.setMinimumSize(QtCore.QSize(150, 0))
self.navWidget.setMaximumSize(QtCore.QSize(150, 16777215))
self.navWidget.setObjectName("navWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.navWidget)
self.verticalLayout.setObjectName("verticalLayout")
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.btn_register = QtWidgets.QPushButton(self.navWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(10)
self.btn_register.setFont(font)
self.btn_register.setObjectName("btn_register")
self.verticalLayout.addWidget(self.btn_register)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem1)
self.btn_recognize = QtWidgets.QPushButton(self.navWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(10)
self.btn_recognize.setFont(font)
self.btn_recognize.setObjectName("btn_recognize")
self.verticalLayout.addWidget(self.btn_recognize)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem2)
self.btn_view = QtWidgets.QPushButton(self.navWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(10)
self.btn_view.setFont(font)
self.btn_view.setObjectName("btn_view")
self.verticalLayout.addWidget(self.btn_view)
spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem3)
self.horizontalLayout.addWidget(self.navWidget)
self.stacked_widget = QtWidgets.QStackedWidget(self.centralwidget)
self.stacked_widget.setObjectName("stacked_widget")
self.register_page = QtWidgets.QWidget()
self.register_page.setObjectName("register_page")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.register_page)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.registerTitle = QtWidgets.QLabel(self.register_page)
self.registerTitle.setAlignment(QtCore.Qt.AlignCenter)
self.registerTitle.setObjectName("registerTitle")
self.verticalLayout_2.addWidget(self.registerTitle)
self.imageWidget = QtWidgets.QWidget(self.register_page)
self.imageWidget.setObjectName("imageWidget")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.imageWidget)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.register_image_label = QtWidgets.QLabel(self.imageWidget)
self.register_image_label.setMinimumSize(QtCore.QSize(400, 300))
self.register_image_label.setAlignment(QtCore.Qt.AlignCenter)
self.register_image_label.setObjectName("register_image_label")
self.verticalLayout_3.addWidget(self.register_image_label)
self.verticalLayout_2.addWidget(self.imageWidget)
self.btnWidget1 = QtWidgets.QWidget(self.register_page)
self.btnWidget1.setObjectName("btnWidget1")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.btnWidget1)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.btn_select_image = QtWidgets.QPushButton(self.btnWidget1)
self.btn_select_image.setObjectName("btn_select_image")
self.horizontalLayout_2.addWidget(self.btn_select_image)
self.btn_open_camera = QtWidgets.QPushButton(self.btnWidget1)
self.btn_open_camera.setObjectName("btn_open_camera")
self.horizontalLayout_2.addWidget(self.btn_open_camera)
self.verticalLayout_2.addWidget(self.btnWidget1)
self.infoWidget = QtWidgets.QWidget(self.register_page)
self.infoWidget.setObjectName("infoWidget")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.infoWidget)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.nameWidget = QtWidgets.QWidget(self.infoWidget)
self.nameWidget.setObjectName("nameWidget")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.nameWidget)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.nameLabel = QtWidgets.QLabel(self.nameWidget)
self.nameLabel.setObjectName("nameLabel")
self.horizontalLayout_3.addWidget(self.nameLabel)
self.name_input = QtWidgets.QLineEdit(self.nameWidget)
self.name_input.setObjectName("name_input")
self.horizontalLayout_3.addWidget(self.name_input)
self.verticalLayout_4.addWidget(self.nameWidget)
self.idWidget = QtWidgets.QWidget(self.infoWidget)
self.idWidget.setObjectName("idWidget")
self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.idWidget)
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.btn_save_face = QtWidgets.QPushButton(self.idWidget)
self.btn_save_face.setObjectName("btn_save_face")
self.horizontalLayout_4.addWidget(self.btn_save_face)
self.verticalLayout_4.addWidget(self.idWidget)
self.verticalLayout_2.addWidget(self.infoWidget)
self.stacked_widget.addWidget(self.register_page)
self.recognize_page = QtWidgets.QWidget()
self.recognize_page.setObjectName("recognize_page")
self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.recognize_page)
self.verticalLayout_5.setObjectName("verticalLayout_5")
self.recognizeTitle = QtWidgets.QLabel(self.recognize_page)
self.recognizeTitle.setAlignment(QtCore.Qt.AlignCenter)
self.recognizeTitle.setObjectName("recognizeTitle")
self.verticalLayout_5.addWidget(self.recognizeTitle)
self.recImageWidget = QtWidgets.QWidget(self.recognize_page)
self.recImageWidget.setObjectName("recImageWidget")
self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.recImageWidget)
self.verticalLayout_6.setObjectName("verticalLayout_6")
self.recognize_image_label = QtWidgets.QLabel(self.recImageWidget)
self.recognize_image_label.setMinimumSize(QtCore.QSize(400, 300))
self.recognize_image_label.setAlignment(QtCore.Qt.AlignCenter)
self.recognize_image_label.setObjectName("recognize_image_label")
self.verticalLayout_6.addWidget(self.recognize_image_label)
self.verticalLayout_5.addWidget(self.recImageWidget)
self.resultWidget = QtWidgets.QWidget(self.recognize_page)
self.resultWidget.setObjectName("resultWidget")
self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.resultWidget)
self.verticalLayout_7.setObjectName("verticalLayout_7")
self.recognize_result = QtWidgets.QLabel(self.resultWidget)
self.recognize_result.setObjectName("recognize_result")
self.verticalLayout_7.addWidget(self.recognize_result)
self.detail_text = QtWidgets.QTextEdit(self.resultWidget)
self.detail_text.setObjectName("detail_text")
self.verticalLayout_7.addWidget(self.detail_text)
self.verticalLayout_5.addWidget(self.resultWidget)
self.btnWidget2 = QtWidgets.QWidget(self.recognize_page)
self.btnWidget2.setObjectName("btnWidget2")
self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.btnWidget2)
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.btn_select_image_rec = QtWidgets.QPushButton(self.btnWidget2)
self.btn_select_image_rec.setObjectName("btn_select_image_rec")
self.horizontalLayout_6.addWidget(self.btn_select_image_rec)
self.btn_open_camera_rec = QtWidgets.QPushButton(self.btnWidget2)
self.btn_open_camera_rec.setObjectName("btn_open_camera_rec")
self.horizontalLayout_6.addWidget(self.btn_open_camera_rec)
self.btn_recognize_face = QtWidgets.QPushButton(self.btnWidget2)
self.btn_recognize_face.setObjectName("btn_recognize_face")
self.horizontalLayout_6.addWidget(self.btn_recognize_face)
self.verticalLayout_5.addWidget(self.btnWidget2)
self.stacked_widget.addWidget(self.recognize_page)
self.view_page = QtWidgets.QWidget()
self.view_page.setObjectName("view_page")
self.verticalLayout_8 = QtWidgets.QVBoxLayout(self.view_page)
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.viewTitle = QtWidgets.QLabel(self.view_page)
self.viewTitle.setAlignment(QtCore.Qt.AlignCenter)
self.viewTitle.setObjectName("viewTitle")
self.verticalLayout_8.addWidget(self.viewTitle)
self.searchWidget = QtWidgets.QWidget(self.view_page)
self.searchWidget.setObjectName("searchWidget")
self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.searchWidget)
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.search_input = QtWidgets.QLineEdit(self.searchWidget)
self.search_input.setObjectName("search_input")
self.horizontalLayout_7.addWidget(self.search_input)
self.btn_search = QtWidgets.QPushButton(self.searchWidget)
self.btn_search.setObjectName("btn_search")
self.horizontalLayout_7.addWidget(self.btn_search)
self.btn_show_all = QtWidgets.QPushButton(self.searchWidget)
self.btn_show_all.setObjectName("btn_show_all")
self.horizontalLayout_7.addWidget(self.btn_show_all)
self.verticalLayout_8.addWidget(self.searchWidget)
self.faces_table = QtWidgets.QTableWidget(self.view_page)
self.faces_table.setColumnCount(5)
self.faces_table.setObjectName("faces_table")
self.faces_table.setRowCount(0)
item = QtWidgets.QTableWidgetItem()
self.faces_table.setHorizontalHeaderItem(0, item)
item = QtWidgets.QTableWidgetItem()
self.faces_table.setHorizontalHeaderItem(1, item)
item = QtWidgets.QTableWidgetItem()
self.faces_table.setHorizontalHeaderItem(2, item)
item = QtWidgets.QTableWidgetItem()
self.faces_table.setHorizontalHeaderItem(3, item)
item = QtWidgets.QTableWidgetItem()
self.faces_table.setHorizontalHeaderItem(4, item)
self.verticalLayout_8.addWidget(self.faces_table)
self.stacked_widget.addWidget(self.view_page)
self.horizontalLayout.addWidget(self.stacked_widget)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1000, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.stacked_widget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "人脸识别系统"))
self.btn_register.setText(_translate("MainWindow", "录入人脸"))
self.btn_recognize.setText(_translate("MainWindow", "识别人脸"))
self.btn_view.setText(_translate("MainWindow", "人脸查看"))
self.registerTitle.setStyleSheet(_translate("MainWindow", "font-size: 18pt; font-weight: bold; margin: 10px;"))
self.registerTitle.setText(_translate("MainWindow", "人脸录入"))
self.register_image_label.setStyleSheet(_translate("MainWindow", "border: 1px solid gray; background-color: #f0f0f0;"))
self.register_image_label.setText(_translate("MainWindow", "图像预览区域"))
self.btn_select_image.setText(_translate("MainWindow", "选择图片"))
self.btn_open_camera.setText(_translate("MainWindow", "打开摄像头"))
self.nameLabel.setText(_translate("MainWindow", "姓名:"))
self.name_input.setPlaceholderText(_translate("MainWindow", "请输入姓名"))
self.btn_save_face.setText(_translate("MainWindow", "保存人脸"))
self.recognizeTitle.setStyleSheet(_translate("MainWindow", "font-size: 18pt; font-weight: bold; margin: 10px;"))
self.recognizeTitle.setText(_translate("MainWindow", "人脸识别"))
self.recognize_image_label.setStyleSheet(_translate("MainWindow", "border: 1px solid gray; background-color: #f0f0f0;"))
self.recognize_image_label.setText(_translate("MainWindow", "图像预览区域"))
self.recognize_result.setStyleSheet(_translate("MainWindow", "font-size: 14pt; margin: 10px;"))
self.recognize_result.setText(_translate("MainWindow", "等待识别..."))
self.detail_text.setPlaceholderText(_translate("MainWindow", "识别详细信息将显示在这里..."))
self.btn_select_image_rec.setText(_translate("MainWindow", "选择图片"))
self.btn_open_camera_rec.setText(_translate("MainWindow", "打开摄像头"))
self.btn_recognize_face.setText(_translate("MainWindow", "识别人脸"))
self.viewTitle.setStyleSheet(_translate("MainWindow", "font-size: 18pt; font-weight: bold; margin: 10px;"))
self.viewTitle.setText(_translate("MainWindow", "人脸数据库"))
self.search_input.setPlaceholderText(_translate("MainWindow", "输入姓名搜索..."))
self.btn_search.setText(_translate("MainWindow", "搜索"))
self.btn_show_all.setText(_translate("MainWindow", "显示全部"))
item = self.faces_table.horizontalHeaderItem(0)
item.setText(_translate("MainWindow", "ID"))
item = self.faces_table.horizontalHeaderItem(1)
item.setText(_translate("MainWindow", "姓名"))
item = self.faces_table.horizontalHeaderItem(2)
item.setText(_translate("MainWindow", "录入时间"))
item = self.faces_table.horizontalHeaderItem(3)
item.setText(_translate("MainWindow", "图像"))
item = self.faces_table.horizontalHeaderItem(4)
item.setText(_translate("MainWindow", "操作"))
3.后端代码
初始化
def __init__(self):
super(Controller, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# 摄像头
self.camera = None
# 定时器
self.timer = QTimer(self)
# 摄像头暂存的最后一个画面
self.last_frame = None
# 打开的图片的文件地址
self.last_path = None
# 连接数据库
self.db = DB()
self.save = FaceSave()
self.recognition = FaceRecognition()
# 数据库中数据
self.datas = np.array(self.db.select_all())
# 当前显示的界面的图像显示区域
self.interface = self.ui.register_image_label
# 识别模式
self.pattern = False
# 显示所有数据
self.show_all()
# 连接信号
self.connect_signals()
切换界面
# 切换界面
def change_interface(self, index):
# 切换界面自动关闭摄像头
if self.camera is not None:
self.timer.stop()
self.camera.release()
self.camera = None
self.interface.clear()
self.ui.stacked_widget.setCurrentIndex(index)
if index == 0:
self.interface = self.ui.register_image_label
elif index == 1:
self.interface = self.ui.recognize_image_label
else:
self.interface = None
选择图片
# 选择图片
def select_image(self):
if self.camera is None:
# 打开文件对话框
file_path =QFileDialog.getOpenFileName(self, "选择图片文件", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif);;所有文件 (*)")
if len(file_path[0]) == 0:
return
# 如果用户选择了文件
if file_path:
# 获取文件路径
self.last_path = file_path[0]
img = cv2.imread(self.last_path)
self.show_frame(img)
else:
self.interface.setText("无法加载图片")
elif self.camera is not None and self.timer.isActive():
# 停止定时器
self.timer.stop()
if self.interface == self.ui.register_image_label:
self.ui.btn_select_image.setText("重新选择")
else:
self.ui.btn_select_image_rec.setText("重新选择")
else:
if self.interface == self.ui.register_image_label:
self.ui.btn_select_image.setText("选择图像")
else:
self.ui.btn_select_image_rec.setText("选择图像")
self.timer.start(30)
打开摄像头
# 打开摄像头
def open_camera(self):
if self.camera is None:
self.camera = cv2.VideoCapture(0)
if not self.camera.isOpened():
self.interface.setText("无法打开摄像头,请检查设备是否正常")
return
if self.interface == self.ui.register_image_label:
self.ui.btn_open_camera.setText("关闭摄像头")
else:
self.ui.btn_open_camera_rec.setText("关闭摄像头")
# 启用定时器,每隔30毫秒刷新一次画面
self.timer.start(30)
else:
# 停止定时器
self.timer.stop()
# 释放摄像头资源
self.camera.release()
self.camera = None
if self.interface == self.ui.register_image_label:
self.ui.btn_open_camera.setText("打开摄像头")
self.ui.btn_select_image.setText("选择图像")
else:
self.ui.btn_open_camera_rec.setText("打开摄像头")
self.ui.btn_select_image_rec.setText("选择图像")
self.interface.clear()
self.interface.setText("图像预览区域")
self.timer.timeout.connect(lambda: self.update_frame()) # 定时刷新画面
# self.update_frame(interface)
更新摄像头画面
# 更新摄像头画面
def update_frame(self):
# 读取摄像头帧
ret, frame = self.camera.read()
if not ret:
self.interface.setText("无法获取画面")
return
# 识别模式
if self.pattern:
# 识别
face_num, frame = self.recognition.detect_and_recognize(frame)
self.ui.detail_text.setText(f'识别了{face_num}张人脸')
# 翻转
# frame = cv2.flip(frame, 1)
self.last_frame = frame
self.show_frame(frame)
显示图像
# 显示图像
def show_frame(self, frame):
# 转换图像格式(OpenCV默认是BGR格式,需要转换为RGB)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 调整图像大小以适应显示区域
height, width, channel = frame.shape
bytes_per_line = channel * width
# 创建QImage对象并转换为QPixmap
q_image = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(q_image)
# 按比例缩放图片以适应标签
scaled_pixmap = pixmap.scaled(
self.interface.width(),
self.interface.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
# 设置图片
self.interface.setPixmap(scaled_pixmap)
self.interface.setText("") # 清空标签文本
人脸录入
# 录入人脸
def save_face(self):
if self.interface.text() == "图像预览区域" or self.timer.isActive():
QMessageBox.warning(
None,
"提示",
"请先选择图像",
QMessageBox.Ok
)
return
name = self.ui.name_input.text()
if name is None or name == '':
QMessageBox.warning(
None,
"提示",
"姓名不能为空",
QMessageBox.Ok
)
return
if self.camera is not None:
# 保存人脸
res = QMessageBox.warning(
None,
"提示",
"确认保存",
QMessageBox.Yes | QMessageBox.No
)
if res == QMessageBox.No:
return
else:
self.save.detect_and_features(name, self.last_frame, '.jpg')
QMessageBox.warning(
None,
"提示",
"保存成功!",
QMessageBox.Ok
)
else:
img = cv2.imread(self.last_path)
_, ext = os.path.splitext(self.last_path)
# 保存人脸
res = QMessageBox.warning(
None,
"提示",
"确认保存",
QMessageBox.Yes | QMessageBox.No
)
if res == QMessageBox.No:
return
else:
self.save.detect_and_features(name, img, ext)
QMessageBox.warning(
None,
"提示",
"保存成功!",
QMessageBox.Ok
)
self.show_all()
人脸识别
# 识别人脸
def recognize_face(self):
if self.interface.text() == "图像预览区域":
QMessageBox.warning(
None,
"提示",
"无图像可识别",
QMessageBox.Ok
)
return
if not self.pattern:
self.pattern = True
self.ui.btn_recognize_face.setText("取消识别")
self.ui.recognize_result.setText("识别中")
# self.ui.detail_text.setText()
else:
self.pattern = False
self.ui.btn_recognize_face.setText("识别人脸")
self.ui.recognize_result.setText("等待识别...")
self.ui.detail_text.setText("")
# 视频
if self.camera is not None:
self.update_frame()
# 图片
else:
img = cv2.imread(self.last_path)
if self.pattern:
face_num, img_recognition = self.recognition.detect_and_recognize(img)
self.ui.detail_text.setText(f'识别了{face_num}张人脸')
# 显示识别图片
self.show_frame(img_recognition)
else:
self.show_frame(img)
人脸查看
# 显示图像
def on_view_click(self, img_bytes):
# 将字节数据转换为 NumPy 数组
img_array = np.frombuffer(img_bytes, dtype=np.uint8)
# 使用 OpenCV 解码字节数组为图像
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
# cv2.imshow('view', img)
# cv2.waitKey(0)
# 创建弹窗显示详情
dialog = QDialog(self)
dialog.resize(600, 600)
layout = QVBoxLayout(dialog)
info_label = QLabel()
info_label.resize(600, 600)
info_label.setText("dsdad")
layout.addWidget(info_label)
self.interface = info_label
self.show_frame(img)
dialog.exec_()
# 删除数据
def on_delete_click(self, face_id):
# 删除
self.db.delete(face_id)
# 刷新
self.show_all()
# 为指定行添加操作按钮(查看 / 删除)
def add_action_buttons(self, row_idx, img_bytes, face_id):
# 创建按钮容器(避免按钮在单元格中拉伸变形)
btn_container1 = QWidget()
btn_layout1 = QVBoxLayout(btn_container1)
btn_layout1.setContentsMargins(2, 2, 2, 2) # 减小边距
btn_layout1.setSpacing(3) # 按钮间距
# 查看按钮
view_btn = QPushButton("查看")
view_btn.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
border-radius: 3px;
padding: 3px;
font-size: 12px;
}
QPushButton:hover {
background-color: #45a049;
}
""")
# 绑定点击事件(传递当前人脸ID)
view_btn.clicked.connect(lambda checked: self.on_view_click(img_bytes))
# 添加按钮到容器
btn_layout1.addWidget(view_btn)
# 将容器添加到表格单元格(第3列)
self.ui.faces_table.setCellWidget(row_idx, 3, btn_container1)
#删除按钮
# 创建按钮容器(避免按钮在单元格中拉伸变形)
btn_container2 = QWidget()
btn_layout2 = QVBoxLayout(btn_container2)
btn_layout2.setContentsMargins(2, 2, 2, 2) # 减小边距
btn_layout2.setSpacing(3) # 按钮间距
delete_btn = QPushButton("删除")
delete_btn.setStyleSheet("""
QPushButton {
background-color: #f44336;
color: white;
border-radius: 3px;
padding: 3px;
font-size: 12px;
}
QPushButton:hover {
background-color: #d32f2f;
}
""")
# 绑定点击事件
delete_btn.clicked.connect(lambda checked: self.on_delete_click(face_id))
# 添加按钮到容器
btn_layout2.addWidget(delete_btn)
# 将容器添加到表格单元格(第4列)
self.ui.faces_table.setCellWidget(row_idx, 4, btn_container2)
# 显示所有数据
def show_all(self):
# 当前的数据
self.datas = np.array(self.db.select_all())
# 设置行
self.ui.faces_table.setRowCount(len(self.datas))
# self.add_action_buttons(3, row)
for row, data in enumerate(self.datas):
self.ui.faces_table.setItem(row, 0, QTableWidgetItem(str(data[0])))
self.ui.faces_table.setItem(row, 1, QTableWidgetItem(str(data[1])))
self.ui.faces_table.setItem(row, 2, QTableWidgetItem(str(data[2])))
self.add_action_buttons(row, data[3], data[0])
# self.ui.faces_table.setItem(row, 3, QTableWidgetItem(self.add_action_buttons(3, row)))
# 查询
def search(self):
name = self.ui.search_input.text()
if name is None or len(name) == 0:
self.show_all()
return
# print(name)
# 当前的数据
self.datas = np.array(self.db.select_one(name))
# 设置行
self.ui.faces_table.setRowCount(len(self.datas))
# self.add_action_buttons(3, row)
for row, data in enumerate(self.datas):
self.ui.faces_table.setItem(row, 0, QTableWidgetItem(str(data[0])))
self.ui.faces_table.setItem(row, 1, QTableWidgetItem(str(data[1])))
self.ui.faces_table.setItem(row, 2, QTableWidgetItem(str(data[2])))
self.add_action_buttons(row, data[3], data[0])
连接信号
# 连接信号
def connect_signals(self):
self.ui.btn_select_image.clicked.connect(self.select_image)
self.ui.btn_open_camera.clicked.connect(self.open_camera)
self.ui.btn_save_face.clicked.connect(self.save_face)
self.ui.btn_register.clicked.connect(lambda: self.change_interface(0))
self.ui.btn_recognize.clicked.connect(lambda: self.change_interface(1))
self.ui.btn_view.clicked.connect(lambda: self.change_interface(2))
self.ui.btn_select_image_rec.clicked.connect(self.select_image)
self.ui.btn_open_camera_rec.clicked.connect(self.open_camera)
self.ui.btn_recognize_face.clicked.connect(self.recognize_face)
self.ui.btn_show_all.clicked.connect(self.show_all)
self.ui.btn_search.clicked.connect(self.search)
程序退出,关闭数据库
def closeEvent(self, event):
"""重写窗口关闭事件"""
self.db.close()
event.accept() # 接受关闭事件
完整代码(后端)
import os
import cv2
import numpy as np
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QTableWidgetItem, QPushButton, QWidget, QVBoxLayout, \
QDialog, QLabel
from FaceNet.DB import DB
from FaceNet.FaceRecognition import FaceRecognition
from FaceNet.FaceSave import FaceSave
from PyQt.index import Ui_MainWindow
class Controller(QMainWindow):
def __init__(self):
super(Controller, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# 摄像头
self.camera = None
# 定时器
self.timer = QTimer(self)
# 摄像头暂存的最后一个画面
self.last_frame = None
# 打开的图片的文件地址
self.last_path = None
# 连接数据库
self.db = DB()
self.save = FaceSave()
self.recognition = FaceRecognition()
# 数据库中数据
self.datas = np.array(self.db.select_all())
# 当前显示的界面的图像显示区域
self.interface = self.ui.register_image_label
# 识别模式
self.pattern = False
# 显示所有数据
self.show_all()
# 连接信号
self.connect_signals()
# 选择图片
def select_image(self):
if self.camera is None:
# 打开文件对话框
file_path =QFileDialog.getOpenFileName(self, "选择图片文件", "", "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif);;所有文件 (*)")
if len(file_path[0]) == 0:
return
# 如果用户选择了文件
if file_path:
# 获取文件路径
self.last_path = file_path[0]
img = cv2.imread(self.last_path)
self.show_frame(img)
else:
self.interface.setText("无法加载图片")
elif self.camera is not None and self.timer.isActive():
# 停止定时器
self.timer.stop()
if self.interface == self.ui.register_image_label:
self.ui.btn_select_image.setText("重新选择")
else:
self.ui.btn_select_image_rec.setText("重新选择")
else:
if self.interface == self.ui.register_image_label:
self.ui.btn_select_image.setText("选择图像")
else:
self.ui.btn_select_image_rec.setText("选择图像")
self.timer.start(30)
# 打开摄像头
def open_camera(self):
if self.camera is None:
self.camera = cv2.VideoCapture(0)
if not self.camera.isOpened():
self.interface.setText("无法打开摄像头,请检查设备是否正常")
return
if self.interface == self.ui.register_image_label:
self.ui.btn_open_camera.setText("关闭摄像头")
else:
self.ui.btn_open_camera_rec.setText("关闭摄像头")
# 启用定时器,每隔30毫秒刷新一次画面
self.timer.start(30)
else:
# 停止定时器
self.timer.stop()
# 释放摄像头资源
self.camera.release()
self.camera = None
if self.interface == self.ui.register_image_label:
self.ui.btn_open_camera.setText("打开摄像头")
self.ui.btn_select_image.setText("选择图像")
else:
self.ui.btn_open_camera_rec.setText("打开摄像头")
self.ui.btn_select_image_rec.setText("选择图像")
self.interface.clear()
self.interface.setText("图像预览区域")
self.timer.timeout.connect(lambda: self.update_frame()) # 定时刷新画面
# self.update_frame(interface)
# 更新摄像头画面
def update_frame(self):
# 读取摄像头帧
ret, frame = self.camera.read()
if not ret:
self.interface.setText("无法获取画面")
return
# 识别模式
if self.pattern:
# 识别
face_num, frame = self.recognition.detect_and_recognize(frame)
self.ui.detail_text.setText(f'识别了{face_num}张人脸')
# 翻转
# frame = cv2.flip(frame, 1)
self.last_frame = frame
self.show_frame(frame)
# 显示图像
def show_frame(self, frame):
# 转换图像格式(OpenCV默认是BGR格式,需要转换为RGB)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 调整图像大小以适应显示区域
height, width, channel = frame.shape
bytes_per_line = channel * width
# 创建QImage对象并转换为QPixmap
q_image = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(q_image)
# 按比例缩放图片以适应标签
scaled_pixmap = pixmap.scaled(
self.interface.width(),
self.interface.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.interface.setPixmap(scaled_pixmap)
self.interface.setText("") # 清空标签文本
# 录入人脸
def save_face(self):
if self.interface.text() == "图像预览区域" or self.timer.isActive():
QMessageBox.warning(
None,
"提示",
"请先选择图像",
QMessageBox.Ok
)
return
name = self.ui.name_input.text()
if name is None or name == '':
QMessageBox.warning(
None,
"提示",
"姓名不能为空",
QMessageBox.Ok
)
return
if self.camera is not None:
# 保存人脸
res = QMessageBox.warning(
None,
"提示",
"确认保存",
QMessageBox.Yes | QMessageBox.No
)
if res == QMessageBox.No:
return
else:
self.save.detect_and_features(name, self.last_frame, '.jpg')
QMessageBox.warning(
None,
"提示",
"保存成功!",
QMessageBox.Ok
)
else:
img = cv2.imread(self.last_path)
_, ext = os.path.splitext(self.last_path)
# 保存人脸
res = QMessageBox.warning(
None,
"提示",
"确认保存",
QMessageBox.Yes | QMessageBox.No
)
if res == QMessageBox.No:
return
else:
self.save.detect_and_features(name, img, ext)
QMessageBox.warning(
None,
"提示",
"保存成功!",
QMessageBox.Ok
)
self.show_all()
# 识别人脸
def recognize_face(self):
if self.interface.text() == "图像预览区域":
QMessageBox.warning(
None,
"提示",
"无图像可识别",
QMessageBox.Ok
)
return
if not self.pattern:
self.pattern = True
self.ui.btn_recognize_face.setText("取消识别")
self.ui.recognize_result.setText("识别中")
# self.ui.detail_text.setText()
else:
self.pattern = False
self.ui.btn_recognize_face.setText("识别人脸")
self.ui.recognize_result.setText("等待识别...")
self.ui.detail_text.setText("")
# 视频
if self.camera is not None:
self.update_frame()
# 图片
else:
img = cv2.imread(self.last_path)
if self.pattern:
face_num, img_recognition = self.recognition.detect_and_recognize(img)
self.ui.detail_text.setText(f'识别了{face_num}张人脸')
# 显示识别图片
self.show_frame(img_recognition)
else:
self.show_frame(img)
# 显示图像
def on_view_click(self, img_bytes):
# 将字节数据转换为 NumPy 数组
img_array = np.frombuffer(img_bytes, dtype=np.uint8)
# 使用 OpenCV 解码字节数组为图像
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
# cv2.imshow('view', img)
# cv2.waitKey(0)
# 创建弹窗显示详情
dialog = QDialog(self)
dialog.resize(600, 600)
layout = QVBoxLayout(dialog)
info_label = QLabel()
info_label.resize(600, 600)
info_label.setText("dsdad")
layout.addWidget(info_label)
self.interface = info_label
self.show_frame(img)
dialog.exec_()
# 删除数据
def on_delete_click(self, face_id):
# 删除
self.db.delete(face_id)
# 刷新
self.show_all()
# 为指定行添加操作按钮(查看 / 删除)
def add_action_buttons(self, row_idx, img_bytes, face_id):
# 创建按钮容器(避免按钮在单元格中拉伸变形)
btn_container1 = QWidget()
btn_layout1 = QVBoxLayout(btn_container1)
btn_layout1.setContentsMargins(2, 2, 2, 2) # 减小边距
btn_layout1.setSpacing(3) # 按钮间距
# 查看按钮
view_btn = QPushButton("查看")
view_btn.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
color: white;
border-radius: 3px;
padding: 3px;
font-size: 12px;
}
QPushButton:hover {
background-color: #45a049;
}
""")
# 绑定点击事件(传递当前人脸ID)
view_btn.clicked.connect(lambda checked: self.on_view_click(img_bytes))
# 添加按钮到容器
btn_layout1.addWidget(view_btn)
# 将容器添加到表格单元格(第3列)
self.ui.faces_table.setCellWidget(row_idx, 3, btn_container1)
#删除按钮
# 创建按钮容器(避免按钮在单元格中拉伸变形)
btn_container2 = QWidget()
btn_layout2 = QVBoxLayout(btn_container2)
btn_layout2.setContentsMargins(2, 2, 2, 2) # 减小边距
btn_layout2.setSpacing(3) # 按钮间距
delete_btn = QPushButton("删除")
delete_btn.setStyleSheet("""
QPushButton {
background-color: #f44336;
color: white;
border-radius: 3px;
padding: 3px;
font-size: 12px;
}
QPushButton:hover {
background-color: #d32f2f;
}
""")
# 绑定点击事件
delete_btn.clicked.connect(lambda checked: self.on_delete_click(face_id))
# 添加按钮到容器
btn_layout2.addWidget(delete_btn)
# 将容器添加到表格单元格(第4列)
self.ui.faces_table.setCellWidget(row_idx, 4, btn_container2)
# 显示所有数据
def show_all(self):
# 当前的数据
self.datas = np.array(self.db.select_all())
# 设置行
self.ui.faces_table.setRowCount(len(self.datas))
# self.add_action_buttons(3, row)
for row, data in enumerate(self.datas):
self.ui.faces_table.setItem(row, 0, QTableWidgetItem(str(data[0])))
self.ui.faces_table.setItem(row, 1, QTableWidgetItem(str(data[1])))
self.ui.faces_table.setItem(row, 2, QTableWidgetItem(str(data[2])))
self.add_action_buttons(row, data[3], data[0])
# self.ui.faces_table.setItem(row, 3, QTableWidgetItem(self.add_action_buttons(3, row)))
# 查询
def search(self):
name = self.ui.search_input.text()
if name is None or len(name) == 0:
self.show_all()
return
# print(name)
# 当前的数据
self.datas = np.array(self.db.select_one(name))
# 设置行
self.ui.faces_table.setRowCount(len(self.datas))
# self.add_action_buttons(3, row)
for row, data in enumerate(self.datas):
self.ui.faces_table.setItem(row, 0, QTableWidgetItem(str(data[0])))
self.ui.faces_table.setItem(row, 1, QTableWidgetItem(str(data[1])))
self.ui.faces_table.setItem(row, 2, QTableWidgetItem(str(data[2])))
self.add_action_buttons(row, data[3], data[0])
# 切换界面
def change_interface(self, index):
# 切换界面自动关闭摄像头
if self.camera is not None:
self.timer.stop()
self.camera.release()
self.camera = None
self.interface.clear()
self.ui.stacked_widget.setCurrentIndex(index)
if index == 0:
self.interface = self.ui.register_image_label
elif index == 1:
self.interface = self.ui.recognize_image_label
else:
self.interface = None
# 连接信号
def connect_signals(self):
self.ui.btn_select_image.clicked.connect(self.select_image)
self.ui.btn_open_camera.clicked.connect(self.open_camera)
self.ui.btn_save_face.clicked.connect(self.save_face)
self.ui.btn_register.clicked.connect(lambda: self.change_interface(0))
self.ui.btn_recognize.clicked.connect(lambda: self.change_interface(1))
self.ui.btn_view.clicked.connect(lambda: self.change_interface(2))
self.ui.btn_select_image_rec.clicked.connect(self.select_image)
self.ui.btn_open_camera_rec.clicked.connect(self.open_camera)
self.ui.btn_recognize_face.clicked.connect(self.recognize_face)
self.ui.btn_show_all.clicked.connect(self.show_all)
self.ui.btn_search.clicked.connect(self.search)
# 程序退出,关闭数据库
def closeEvent(self, event):
"""重写窗口关闭事件"""
self.db.close()
event.accept() # 接受关闭事件
五、演示

本项目及相关代码仅用于学习和研究目的,不保证其在任何场景下的准确性、完整性和可靠性。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)