Files
face-random/3-5.py
2025-12-03 19:55:35 +08:00

373 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sys
import os
import random
import cv2
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("正在初始化模型...")
# 延迟加载检测器
detector = cv2.FaceDetectorYN.create(
self.model_path,
"",
(640, 480),
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.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;
""")
def resizeEvent(self, event):
self.video_label.setGeometry(0, 0, self.width(), self.height())
self.btn.move(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 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])
score = face[4]
# 绘制人脸框
cv2.rectangle(img, (x, y), (x + w, y + h), color, thickness)
# 绘制置信度文本
confidence_text = f"{score:.2f}"
text_y = max(y - 10, 20)
# 创建半透明背景
overlay = img.copy()
text_size = cv2.getTextSize(confidence_text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
cv2.rectangle(overlay, (x, text_y - text_size[1] - 5),
(x + text_size[0] + 10, text_y + 5), color, -1)
# 应用透明度
alpha = 0.7
cv2.addWeighted(overlay, alpha, img, 1 - alpha, 0, img)
# 绘制文本
cv2.putText(img, confidence_text, (x + 5, text_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
def update_frame(self):
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'):
# 设置检测器输入大小
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()
for i, face in enumerate(self.faces_snapshot):
color = (0, 0, 255) if i == self.selected_face_index else (0, 255, 0)
thickness = 3 if i == self.selected_face_index else 2
self.draw_face_with_confidence(img, face, color, thickness)
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()