From f4a4936b5c93d5ff4c55945dfbe2ceb17fbc78d8 Mon Sep 17 00:00:00 2001 From: Kegongteng Date: Sun, 23 Nov 2025 15:39:13 +0800 Subject: [PATCH] [feat] 3-test.py --- 3-test.py | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 17 ++- 2 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 3-test.py diff --git a/3-test.py b/3-test.py new file mode 100644 index 0000000..ae16d1d --- /dev/null +++ b/3-test.py @@ -0,0 +1,310 @@ +import sys +import random +import cv2 +from cvzone.FaceDetectionModule import FaceDetector +from PySide6.QtCore import QTimer, Qt +from PySide6.QtGui import QImage, QPixmap +from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QProgressBar + + +class LoadingScreen(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("加载中...") + self.setFixedSize(400, 250) # 增大窗口大小以容纳错误信息 + self.setWindowFlags(Qt.WindowStaysOnTopHint) # 保留置顶但移除无边框,方便关闭 + self.setStyleSheet("background-color: #1E90FF;") # 蓝色背景 + + # 创建布局 + layout = QVBoxLayout() + layout.setAlignment(Qt.AlignCenter) + layout.setSpacing(15) + + # 加载提示文字 + self.loading_label = QLabel("人脸模型加载中") + self.loading_label.setAlignment(Qt.AlignCenter) + self.loading_label.setStyleSheet("color: white; font-size: 18px; font-weight: bold;") + layout.addWidget(self.loading_label) + + # 错误信息标签 + self.error_label = QLabel("") + self.error_label.setAlignment(Qt.AlignCenter) + self.error_label.setStyleSheet("color: #FFD700; font-size: 12px; font-weight: normal;") + self.error_label.setWordWrap(True) + self.error_label.setHidden(True) # 初始隐藏 + layout.addWidget(self.error_label) + + # 进度条 - 设置为不确定模式,显示加载动画 + self.progress_bar = QProgressBar() + self.progress_bar.setRange(0, 0) # 设置为0,0表示不确定模式 + self.progress_bar.setFixedHeight(10) + layout.addWidget(self.progress_bar) + + # 关闭按钮 - 初始隐藏 + self.close_button = QPushButton("关闭") + self.close_button.setFixedSize(100, 35) + self.close_button.setStyleSheet(""" + QPushButton { + background-color: #FF6347; + color: white; + font-size: 14px; + border-radius: 5px; + } + QPushButton:hover { + background-color: #FF4500; + } + """) + self.close_button.clicked.connect(self.close) + self.close_button.setHidden(True) # 初始隐藏 + layout.addWidget(self.close_button) + + self.setLayout(layout) + + def show_error(self, error_message): + """显示错误信息并显示关闭按钮,隐藏进度条""" + self.loading_label.setText("人脸模型加载失败") + self.error_label.setText(f"错误详情: {error_message}") + self.error_label.setHidden(False) + self.progress_bar.setHidden(True) + self.close_button.setHidden(False) + self.setWindowTitle("加载失败") + # 调整窗口大小以适应错误信息 + self.adjustSize() + QApplication.processEvents() + + +class FaceRandomApp(QWidget): + def __init__(self): + super().__init__() + + # 先显示加载页面 + self.loading_screen = LoadingScreen() + self.loading_screen.show() + + # 强制立即处理GUI事件,确保加载页面能立即显示 + QApplication.processEvents() + + # 延迟初始化主界面,让加载页面先显示 + QTimer.singleShot(100, self.initialize_app) + + def initialize_app(self): + # 状态:normal → random + self.state = "normal" + self.selected_face_index = -1 + self.static_frame = None # 保存随机状态下的静态画面 + self.all_faces_snapshot = [] # 保存所有人脸信息快照 + + # 更新加载页面文字,提示正在进行人脸模型初始化 + self.loading_screen.loading_label.setText("正在初始化人脸检测模型...") + QApplication.processEvents() + + # 使用长距离模型和更低的人脸检测阈值以提高检测率 + # 这里会加载模型,可能需要一些时间 + try: + self.detector = FaceDetector(minDetectionCon=0.25, modelSelection=1) + self.loading_screen.loading_label.setText("人脸模型加载完成!") + + # 只有在检测器初始化成功后才继续初始化 + # 设置窗口标题 + self.setWindowTitle("Face Random Selector") + + # ---------------- 摄像头 ---------------- + self.cap = cv2.VideoCapture(0) + + # 设置摄像头分辨率 + self.cap.set(3, 1280) + self.cap.set(4, 720) + + # 设置窗口初始大小 + self.resize(1280, 720) + + # ---------------- 创建界面 ---------------- + self.setup_ui() + + self.faces = [] + + # 模拟加载完成 + QTimer.singleShot(1500, self.finish_loading) + + except Exception as e: + # 捕获并显示详细的错误信息(仅显示真实的错误内容) + error_type = type(e).__name__ + error_message = str(e) + # 只显示真实的错误类型和错误消息 + detailed_error = f"{error_type}: {error_message}" + + print(f"初始化人脸检测器时出错: {detailed_error}") + # 显示错误信息并停止继续初始化 + self.loading_screen.show_error(detailed_error) + # 不再继续初始化主界面 + + def finish_loading(self): + # 关闭加载页面 + self.loading_screen.close() + # 显示主窗口 + self.show() + + # 启动定时器更新视频帧 + self.timer = QTimer() + self.timer.timeout.connect(self.update_frame) + self.timer.start(30) + + def setup_ui(self): + # 视频标签 - 填充整个窗口 + self.video_label = QLabel(self) + self.video_label.setStyleSheet("background:#000;") + self.video_label.setGeometry(0, 0, self.width(), self.height()) + + # 按钮 - 叠加在视频上 + self.btn = QPushButton("随机", self) + self.btn.setFixedSize(140, 55) + self.btn.setStyleSheet(""" + QPushButton { + background-color: rgba(0, 0, 0, 120); + color: white; + font-size: 20px; + padding: 10px 20px; + border-radius: 8px; + } + QPushButton:hover { + background-color: rgba(30, 30, 30, 180); + } + """) + self.btn.clicked.connect(self.button_clicked) + + # 初始按钮位置 + self.update_button_position() + + def resizeEvent(self, event): + """窗口大小改变时自动调整视频和按钮位置""" + self.video_label.setGeometry(0, 0, self.width(), self.height()) + self.update_button_position() + super().resizeEvent(event) + + def update_button_position(self): + """更新按钮位置""" + btn_w = 140 + btn_h = 55 + margin = 20 + self.btn.setGeometry( + margin, + self.height() - btn_h - margin, + btn_w, + btn_h + ) + + # --------------------------------------------------------- + # 逻辑按钮:随机 ↔ 重置 + # --------------------------------------------------------- + def button_clicked(self): + if self.state == "normal": + # 切换到随机状态 + self.state = "random" + self.btn.setText("重置") + + # 随机选择一个人脸 + if len(self.faces) > 0: + self.selected_face_index = random.randint(0, len(self.faces) - 1) + + # 保存当前帧作为静态画面 + ret, img = self.cap.read() + if ret: + img = cv2.flip(img, 1) + # 调整图像大小以适应窗口 + img = cv2.resize(img, (self.video_label.width(), self.video_label.height())) + self.static_frame = img.copy() + self.all_faces_snapshot = self.faces.copy() + else: # self.state == "random" + # 切换回初始状态 + self.state = "normal" + self.selected_face_index = -1 + self.static_frame = None + self.all_faces_snapshot = [] + self.btn.setText("随机") + + # --------------------------------------------------------- + # 刷视频帧 + # --------------------------------------------------------- + def update_frame(self): + # ============================ + # 状态:normal(检测所有人脸并显示绿框) + # ============================ + if self.state == "normal": + ret, img = self.cap.read() + if not ret: + return + + img = cv2.flip(img, 1) + + # 调整图像大小以适应窗口 + img = cv2.resize(img, (self.video_label.width(), self.video_label.height())) + + # 检测人脸 + if self.detector is not None: + img, faces = self.detector.findFaces(img, draw=False) + self.faces = faces if faces else [] + else: + self.faces = [] + + # 绘制所有人脸为绿色 + for face in self.faces: + x, y, w, h = face["bbox"] + score = face["score"][0] + cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 3) + cv2.putText(img, f"{score:.2f}", (x, y - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) + + # 显示图像 + self.display_image(img) + + # ============================ + # 状态:random(显示静态画面,选中的人脸为红色,其他为绿色) + # ============================ + elif self.state == "random" and self.static_frame is not None: + img = self.static_frame.copy() + + # 绘制所有人脸,选中的为红色,其他为绿色 + for i, face in enumerate(self.all_faces_snapshot): + x, y, w, h = face["bbox"] + score = face["score"][0] + + if i == self.selected_face_index: + color = (0, 0, 255) # 红色 + else: + color = (0, 255, 0) # 绿色 + + cv2.rectangle(img, (x, y), (x + w, y + h), color, 3) + cv2.putText(img, f"{score:.2f}", (x, y - 10), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) + + # 显示图像 + self.display_image(img) + + # --------------------------------------------------------- + # 显示图像到 QLabel + # --------------------------------------------------------- + def display_image(self, img): + rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + h, w, ch = rgb.shape + bytes_per_line = ch * w + qimg = QImage(rgb.data, w, h, bytes_per_line, QImage.Format.Format_RGB888) + self.video_label.setPixmap(QPixmap.fromImage(qimg)) + + # --------------------------------------------------------- + def closeEvent(self, event): + if self.cap.isOpened(): + self.cap.release() + event.accept() + + +def main(): + app = QApplication(sys.argv) + + # 直接创建主应用窗口,它内部会处理加载页面 + window = FaceRandomApp() + + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/readme.md b/readme.md index e9503b2..f23851d 100644 --- a/readme.md +++ b/readme.md @@ -1 +1,16 @@ -nuitka --standalone --include-package=cv2 --enable-plugin=pyside6 --windows-console-mode=disable --windows-icon-from-ico=./icon.ico --include-data-dir=F:\python310\lib\site-packages\cv2\data=cv2\data --output-dir=dist32 --remove-output 3-2.py \ No newline at end of file +# Face Random + +## 1.软件说明 +FaceRandomSelector 是一款人脸随机选择工具。软件采用opencv的人脸检测算法,能够快速准确地识别摄像头中的人脸,并提供高效的随机选择功能,帮助教师轻松实现课堂互动。 + +## 2.打包指令 +` nuitka --standalone --include-package=cv2 --enable-plugin=pyside6 --windows-console-mode=disable --windows-icon-from-ico=./icon.ico --include-data-dir=F:\python310\lib\site-packages\cv2\data=cv2\data --output-dir=dist32 --remove-output 3-2.py ` + +## 3.版本说明 + +1. 1.py 2.py:非稳定版本 +2. 3-1.py:存在UI不美观 窗口大小 摄像头清晰度等问题 +3. 3-2.py:解决3-1.py的相关问题 +4. 3-3.py:添加加载进度条 +5. 3-test.py:由3-3.py改编 给出具体的[人脸识别模型无法获取]的相关报错 +> 软件务必保存在纯英文路径中! \ No newline at end of file