[feat] 3.6

This commit is contained in:
He
2025-12-04 20:33:34 +08:00
parent 41a31473f3
commit 826ca326a9
2 changed files with 426 additions and 0 deletions

425
3-6.py Normal file
View File

@@ -0,0 +1,425 @@
import sys
import os
import random
import cv2
import numpy as np
from PySide6.QtCore import QTimer, Qt, QThread, Signal
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QSlider, QVBoxLayout, QProgressBar
# ======================================================
# 加载动画界面
# ======================================================
class LoadingScreen(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Face Random Selector")
self.setFixedSize(300, 150)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setStyleSheet("""
background-color: #1E90FF;
border-radius: 10px;
""")
# 创建布局
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.progress_bar = QProgressBar()
self.progress_bar.setRange(0, 0) # 设置为0,0表示不确定模式
self.progress_bar.setFixedHeight(10)
self.progress_bar.setStyleSheet("""
QProgressBar {
border: 1px solid rgba(255, 255, 255, 100);
border-radius: 5px;
background-color: rgba(255, 255, 255, 50);
}
QProgressBar::chunk {
background-color: white;
border-radius: 4px;
}
""")
layout.addWidget(self.progress_bar)
self.setLayout(layout)
# ======================================================
# 模型加载线程
# ======================================================
class ModelLoader(QThread):
loaded = Signal(object, str) # 模型加载完成信号
progress = Signal(str) # 进度更新信号
def __init__(self, model_path):
super().__init__()
self.model_path = model_path
def run(self):
try:
# 模拟进度更新
self.progress.emit("正在初始化模型...")
# 启用 OpenCV 的优化并设置线程数,减少模型加载/运算开销
try:
cv2.setUseOptimized(True)
except Exception:
pass
# 延迟加载检测器。
detector = cv2.FaceDetectorYN.create(
self.model_path,
"",
(320, 240),
score_threshold=0.6,
nms_threshold=0.3,
top_k=5000
)
self.progress.emit("模型加载完成")
self.loaded.emit(detector, "")
except Exception as e:
self.loaded.emit(None, str(e))
# ======================================================
# 主窗口
# ======================================================
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):
"""初始化应用程序"""
# 状态
self.state = "normal"
self.selected_face_index = -1
self.static_frame = None
self.faces_snapshot = []
self.faces = []
self.detection_confidence = 0.6
self.is_static_mode = False
# 初始化摄像头
self.cap = cv2.VideoCapture(0)
if self.cap.isOpened():
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
# 提高图像质量设置
self.cap.set(cv2.CAP_PROP_FPS, 30)
# UI
self.setup_ui()
# 启动模型加载
self.start_model_loading()
def start_model_loading(self):
"""启动模型加载"""
model_path = self.get_yunet_model_path()
if model_path:
self.loader = ModelLoader(model_path)
self.loader.loaded.connect(self.on_model_loaded)
self.loader.progress.connect(self.on_loading_progress)
self.loader.start()
else:
# 如果没有模型,跳过模型加载
self.loading_screen.close()
self.show()
self.timer = QTimer()
self.timer.timeout.connect(self.update_frame)
self.timer.start(30)
def get_yunet_model_path(self):
"""获取模型路径"""
base_dir = os.path.dirname(os.path.abspath(__file__))
model_path = os.path.join(base_dir, "model", "face_detection_yunet_2023mar.onnx")
return model_path if os.path.exists(model_path) else None
def on_loading_progress(self, message):
"""加载进度更新"""
self.loading_screen.loading_label.setText(message)
def on_model_loaded(self, detector, error):
"""模型加载完成回调"""
if detector and not error:
self.detector = detector
# 更新置信度
self.detector.setScoreThreshold(self.detection_confidence)
else:
print(f"模型加载失败: {error}")
# 关闭加载界面并显示主窗口
self.loading_screen.close()
self.show()
# 启动视频更新定时器
self.timer = QTimer()
self.timer.timeout.connect(self.update_frame)
self.timer.start(30)
# ======================================================
# UI
# ======================================================
def setup_ui(self):
self.setWindowTitle("Face Random Selector")
self.resize(1280, 720)
self.video_label = QLabel(self)
self.video_label.setGeometry(0, 0, self.width(), self.height())
self.video_label.setStyleSheet("background: black;")
# 随机按钮
self.btn = QPushButton("随机", self)
self.btn.setFixedSize(140, 55)
self.btn.move(20, self.height() - 75)
self.btn.clicked.connect(self.on_random_clicked)
self.btn.setStyleSheet("""
QPushButton {
background-color: rgba(0, 0, 0, 180);
color: white;
font-size: 20px;
font-weight: bold;
border: none;
border-radius: 8px;
}
QPushButton:hover {
background-color: rgba(30, 30, 30, 200);
}
QPushButton:pressed {
background-color: rgba(50, 50, 50, 220);
}
""")
# 置信度滑条
self.slider = QSlider(Qt.Horizontal, self)
self.slider.setRange(30, 90)
self.slider.setValue(int(self.detection_confidence * 100))
self.slider.setFixedWidth(200)
self.slider.move(120, 20)
self.slider.valueChanged.connect(self.on_confidence_change)
self.slider.setStyleSheet("""
QSlider::groove:horizontal {
background: rgba(0, 0, 0, 120);
height: 4px;
border-radius: 2px;
}
QSlider::handle:horizontal {
background: white;
width: 14px;
height: 14px;
border-radius: 7px;
margin: -5px 0;
}
""")
# 置信度标签
self.conf_label = QLabel("置信度", self)
self.conf_label.move(20, 20)
self.conf_label.setStyleSheet("""
color: white;
font-size: 16px;
font-weight: bold;
background: transparent;
""")
# 静止按钮 (圆形半透明)
self.static_btn = QPushButton("", self)
self.static_btn.setFixedSize(55, 55)
self.static_btn.move(self.btn.x() + self.btn.width() + 20, self.height() - 75)
self.static_btn.clicked.connect(self.on_static_clicked)
self.static_btn.setStyleSheet("""
QPushButton {
background-color: rgba(0, 0, 0, 140);
color: white;
font-size: 18px;
font-weight: bold;
border: none;
border-radius: 27px;
}
QPushButton:hover {
background-color: rgba(30, 30, 30, 180);
}
QPushButton:pressed {
background-color: rgba(50, 50, 50, 200);
}
""")
def resizeEvent(self, event):
self.video_label.setGeometry(0, 0, self.width(), self.height())
self.btn.move(20, self.height() - 75)
if hasattr(self, 'static_btn'):
self.static_btn.move(self.btn.x() + self.btn.width() + 20, self.height() - 75)
super().resizeEvent(event)
def on_confidence_change(self, value):
self.detection_confidence = value / 100.0
if hasattr(self, 'detector'):
self.detector.setScoreThreshold(self.detection_confidence)
def on_random_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)
# 捕获静态帧
if self.cap and self.cap.isOpened():
ret, img = self.cap.read()
if ret:
img = cv2.flip(img, 1)
img = self.resize_cover(img, self.video_label.width(), self.video_label.height())
self.static_frame = img.copy()
self.faces_snapshot = self.faces.copy()
else:
self.state = "normal"
self.btn.setText("随机")
self.selected_face_index = -1
self.static_frame = None
def on_static_clicked(self):
""" 静止模式 """
if not getattr(self, 'is_static_mode', False):
# 静止模式:保留已加载的模型,不释放摄像头,只停止读取/检测
self.is_static_mode = True
try:
self.static_btn.setText("恢复")
except Exception:
pass
else:
# 退出静止模式,恢复摄像头和模型
self.is_static_mode = False
try:
self.static_btn.setText("")
except Exception:
pass
# 重新打开摄像头(仅在当前没有打开时)
if not hasattr(self, 'cap') or self.cap is None or not self.cap.isOpened():
self.cap = cv2.VideoCapture(0)
if self.cap.isOpened():
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
self.cap.set(cv2.CAP_PROP_FPS, 30)
# 保留 detector如果之前加载过则直接可用无需重新加载。
# 重新打开摄像头,检测器若存在则继续使用;否则重新加载模型。
if not hasattr(self, 'detector') or self.detector is None:
# 仅在没有 detector 时重新加载
self.start_model_loading()
def resize_cover(self, img, target_w, target_h):
h, w = img.shape[:2]
scale = max(target_w / w, target_h / h)
new_w = int(w * scale)
new_h = int(h * scale)
resized = cv2.resize(img, (new_w, new_h))
x_start = (new_w - target_w) // 2
y_start = (new_h - target_h) // 2
return resized[y_start:y_start + target_h, x_start:x_start + target_w]
def draw_face_with_confidence(self, img, face, color, thickness=2):
"""绘制人脸框和置信度"""
x, y, w, h = map(int, face[:4])
# 仅绘制人脸矩形框,不显示置信度文本
cv2.rectangle(img, (x, y), (x + w, y + h), color, thickness)
def update_frame(self):
# 如果处于静止模式,不使用摄像头和模型,摄像头区域黑屏以节省资源
if getattr(self, 'is_static_mode', False):
h = self.video_label.height()
w = self.video_label.width()
black = np.zeros((h, w, 3), dtype=np.uint8)
self.display(black)
return
# 非静止模式,需要摄像头
if not self.cap or not self.cap.isOpened():
return
ret, img = self.cap.read()
if not ret:
return
# 镜像翻转
img = cv2.flip(img, 1)
# resize_cover方法保持视野大小
img = self.resize_cover(img, self.video_label.width(), self.video_label.height())
if self.state == "normal":
if hasattr(self, 'detector') and self.detector is not None:
# 设置检测器输入大小
self.detector.setInputSize((img.shape[1], img.shape[0]))
# 检测人脸
_, detected = self.detector.detect(img)
self.faces = detected if detected is not None else []
# 绘制检测结果(正常模式绘制全部绿色框)
for face in self.faces:
self.draw_face_with_confidence(img, face, (0, 255, 0), 2)
else:
# 如果没有模型,只显示原始图像
pass
else: # random 模式
if self.static_frame is not None:
img = self.static_frame.copy()
# 仅绘制被选中的红色框,其他不显示
if self.selected_face_index != -1 and len(self.faces_snapshot) > self.selected_face_index:
face = self.faces_snapshot[self.selected_face_index]
self.draw_face_with_confidence(img, face, (0, 0, 255), 3)
self.display(img)
def display(self, img):
# 图像显示
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w, c = rgb.shape
qimg = QImage(rgb.data, w, h, c * w, QImage.Format_RGB888)
self.video_label.setPixmap(QPixmap.fromImage(qimg))
def closeEvent(self, event):
if self.cap and self.cap.isOpened():
self.cap.release()
if hasattr(self, 'loader') and self.loader.isRunning():
self.loader.quit()
self.loader.wait()
event.accept()
# ======================================================
def main():
app = QApplication(sys.argv)
window = FaceRandomApp()
# 注意:这里不再调用 window.show(),因为 FaceRandomApp 内部会处理显示逻辑
sys.exit(app.exec())
if __name__ == "__main__":
main()

View File

@@ -21,4 +21,5 @@ FaceRandomSelector 是一款人脸随机选择工具。软件采用opencv的人
6. 3-control.py由3-3.py改编 用户可以修改相关参数
7. 3-4.py使用YuNet模型 加强对小人脸识别准确度
8. 3-5.py优化加载性能
9. 3-6.py优化加载性能 添加静止模式 删去置信度与选中绿框
> 软件务必保存在纯英文路径中!