Compare commits

...

5 Commits

Author SHA1 Message Date
826ca326a9 [feat] 3.6 2025-12-04 20:44:42 +08:00
41a31473f3 [feat] 3.5 2025-12-03 19:55:35 +08:00
9a015f2358 [feat] 3.4 2025-11-29 13:17:04 +08:00
f4a4936b5c [feat] 3-test.py 2025-11-23 15:39:13 +08:00
f8fb6f8427 Create 3-3.py 2025-11-23 12:13:04 +08:00
10 changed files with 2298 additions and 1 deletions

275
3-3.py Normal file
View File

@@ -0,0 +1,275 @@
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(300, 150)
self.setWindowFlags(Qt.FramelessWindowHint | 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.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 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.setWindowTitle("Face Random Selector")
# 状态normal → random
self.state = "normal"
self.selected_face_index = -1
self.static_frame = None # 保存随机状态下的静态画面
self.all_faces_snapshot = [] # 保存所有人脸信息快照
# ---------------- 摄像头 ----------------
self.cap = cv2.VideoCapture(0)
# 设置摄像头分辨率
self.cap.set(3, 1280)
self.cap.set(4, 720)
# 设置窗口初始大小
self.resize(1280, 720)
# 更新加载页面文字,提示正在进行人脸模型初始化
self.loading_screen.loading_label.setText("正在初始化人脸检测模型...")
QApplication.processEvents()
# 使用长距离模型和更低的人脸检测阈值以提高检测率
# 这里会加载模型,可能需要一些时间
try:
self.detector = FaceDetector(minDetectionCon=0.25, modelSelection=1)
self.loading_screen.loading_label.setText("人脸模型加载完成!")
except Exception as e:
print(f"初始化人脸检测器时出错: {e}")
# 如果初始化失败,使用一个空的检测器
self.detector = None
self.loading_screen.loading_label.setText("人脸模型加载失败,使用基础模式")
# ---------------- 创建界面 ----------------
self.setup_ui()
self.faces = []
# 模拟加载完成
QTimer.singleShot(1500, self.finish_loading)
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()

339
3-4.py Normal file
View File

@@ -0,0 +1,339 @@
import sys
import os
import random
import cv2
import numpy as np
from PySide6.QtCore import QTimer, Qt
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)
# ======================================================
# YuNet 模型路径
# ======================================================
def get_yunet_model_path():
base_dir = os.path.dirname(os.path.abspath(__file__))
model_path = os.path.join(base_dir, "model", "face_detection_yunet_2023mar.onnx")
if not os.path.exists(model_path):
raise FileNotFoundError(f"YuNet 模型不存在: {model_path}")
return model_path
# ======================================================
# 主窗口
# ======================================================
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.detection_confidence = 0.6
self.faces = []
# 摄像头
self.cap = cv2.VideoCapture(0)
self.cap.set(3, 1280)
self.cap.set(4, 720)
# YuNet 加载
model_path = get_yunet_model_path()
self.detector = cv2.FaceDetectorYN.create(
model_path,
"",
(640, 480),
score_threshold=self.detection_confidence,
nms_threshold=0.3,
top_k=5000
)
# UI
self.setup_ui()
# 关闭加载界面并显示主窗口
self.loading_screen.close()
self.show()
# Timer
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
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)
# 捕获静态帧
ret, img = self.cap.read()
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
cropped = resized[y_start:y_start + target_h, x_start:x_start + target_w]
return cropped
# ======================================================
# 绘制人脸框和置信度 - 优化样式
# ======================================================
def draw_face_with_confidence(self, img, face, color, thickness=2):
"""绘制人脸框和置信度"""
x, y, w, h = map(int, face[:4])
score = face[4] # 置信度分数范围0-1
# 绘制人脸框
cv2.rectangle(img, (x, y), (x + w, y + h), color, thickness)
# 绘制置信度文本
confidence_text = f"{score:.2f}"
text_y = max(y - 10, 20) # 确保文本不超出图像顶部
# 绘制半透明文本背景
text_size = cv2.getTextSize(confidence_text, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
# 创建半透明覆盖层
overlay = img.copy()
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):
ret, img = self.cap.read()
if not ret:
return
img = cv2.flip(img, 1)
img = self.resize_cover(img, self.video_label.width(), self.video_label.height())
if self.state == "normal":
# 设置 YuNet 输入大小
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: # random 模式
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.isOpened():
self.cap.release()
event.accept()
# ======================================================
def main():
app = QApplication(sys.argv)
window = FaceRandomApp()
# 注意:这里不再调用 window.show(),因为 FaceRandomApp 内部会处理显示逻辑
sys.exit(app.exec())
if __name__ == "__main__":
main()

373
3-5.py Normal file
View File

@@ -0,0 +1,373 @@
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()

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()

551
3-control.py Normal file
View File

@@ -0,0 +1,551 @@
import sys
import random
import cv2
import numpy as np
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, QHBoxLayout,
QSlider, QGroupBox, QDoubleSpinBox)
class LoadingScreen(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("加载中...")
self.setFixedSize(300, 150)
self.setWindowFlags(Qt.FramelessWindowHint | 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.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 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.setWindowTitle("Face Random Selector")
# 状态normal → random
self.state = "normal"
self.selected_face_index = -1
self.static_frame = None # 保存随机状态下的静态画面
self.all_faces_snapshot = [] # 保存所有人脸信息快照
# 检测参数
self.detection_confidence = 0.15 # 降低置信度阈值以提高检测率
self.model_selection = 1 # 长距离模型
self.control_panel_visible = False # 控制面板显示状态
self.multi_scale_enabled = True # 启用多尺度检测
# ---------------- 摄像头 ----------------
self.cap = cv2.VideoCapture(0)
# 设置更高分辨率以捕捉更多细节
self.cap.set(3, 1920) # 宽度
self.cap.set(4, 1080) # 高度
# 设置窗口初始大小
self.resize(1280, 720)
# 更新加载页面文字,提示正在进行人脸模型初始化
self.loading_screen.loading_label.setText("正在初始化人脸检测模型...")
QApplication.processEvents()
# 使用优化的参数
try:
self.detector = FaceDetector(
minDetectionCon=self.detection_confidence,
modelSelection=self.model_selection
)
self.loading_screen.loading_label.setText("人脸模型加载完成!")
except Exception as e:
print(f"初始化人脸检测器时出错: {e}")
# 如果初始化失败,使用一个空的检测器
self.detector = None
self.loading_screen.loading_label.setText("人脸模型加载失败,使用基础模式")
# ---------------- 创建界面 ----------------
self.setup_ui()
self.faces = []
# 模拟加载完成
QTimer.singleShot(1500, self.finish_loading)
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.control_btn = QPushButton("控制", self)
self.control_btn.setFixedSize(80, 40)
self.control_btn.setStyleSheet("""
QPushButton {
background-color: rgba(0, 0, 0, 120);
color: white;
font-size: 16px;
padding: 5px 10px;
border-radius: 6px;
}
QPushButton:hover {
background-color: rgba(30, 30, 30, 180);
}
""")
self.control_btn.clicked.connect(self.toggle_control_panel)
# 控制面板 - 新增
self.control_panel = QWidget(self)
self.control_panel.setFixedSize(300, 180) # 减小高度
self.control_panel.setStyleSheet("""
QWidget {
background-color: rgba(0, 0, 0, 180);
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 100);
}
""")
self.control_panel.hide() # 初始隐藏
# 控制面板布局
control_layout = QVBoxLayout(self.control_panel)
control_layout.setContentsMargins(15, 15, 15, 15)
control_layout.setSpacing(10)
# 置信度控制
confidence_layout = QHBoxLayout()
confidence_label = QLabel("置信度:")
confidence_label.setStyleSheet("color: white; font-size: 14px;")
self.confidence_slider = QSlider(Qt.Horizontal)
self.confidence_slider.setRange(5, 50) # 调整范围以适应教室环境
self.confidence_slider.setValue(15) # 默认0.15
self.confidence_slider.valueChanged.connect(self.on_confidence_changed)
self.confidence_slider.setStyleSheet("""
QSlider::groove:horizontal {
background: rgba(255, 255, 255, 100);
height: 6px;
border-radius: 3px;
}
QSlider::handle:horizontal {
background: white;
width: 16px;
height: 16px;
border-radius: 8px;
margin: -5px 0;
}
""")
self.confidence_value = QLabel("0.15")
self.confidence_value.setStyleSheet("color: white; font-size: 14px; min-width: 40px;")
confidence_layout.addWidget(confidence_label)
confidence_layout.addWidget(self.confidence_slider)
confidence_layout.addWidget(self.confidence_value)
control_layout.addLayout(confidence_layout)
# 多尺度检测选项
multiscale_layout = QHBoxLayout()
multiscale_label = QLabel("多尺度检测:")
multiscale_label.setStyleSheet("color: white; font-size: 14px;")
self.multiscale_check = QPushButton("启用")
self.multiscale_check.setCheckable(True)
self.multiscale_check.setChecked(True)
self.multiscale_check.setFixedHeight(30)
self.multiscale_check.setStyleSheet("""
QPushButton {
background-color: rgba(255, 255, 255, 50);
color: white;
border: 1px solid rgba(255, 255, 255, 100);
border-radius: 4px;
font-size: 12px;
}
QPushButton:checked {
background-color: rgba(30, 144, 255, 200);
border: 1px solid rgba(255, 255, 255, 200);
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 80);
}
""")
self.multiscale_check.clicked.connect(self.on_multiscale_changed)
multiscale_layout.addWidget(multiscale_label)
multiscale_layout.addWidget(self.multiscale_check)
multiscale_layout.addStretch()
control_layout.addLayout(multiscale_layout)
# 模型选择
model_layout = QHBoxLayout()
model_label = QLabel("检测模型:")
model_label.setStyleSheet("color: white; font-size: 14px;")
self.model_short_btn = QPushButton("短距离")
self.model_long_btn = QPushButton("长距离")
for btn in [self.model_short_btn, self.model_long_btn]:
btn.setCheckable(True)
btn.setFixedHeight(30)
btn.setStyleSheet("""
QPushButton {
background-color: rgba(255, 255, 255, 50);
color: white;
border: 1px solid rgba(255, 255, 255, 100);
border-radius: 4px;
font-size: 12px;
}
QPushButton:checked {
background-color: rgba(30, 144, 255, 200);
border: 1px solid rgba(255, 255, 255, 200);
}
QPushButton:hover {
background-color: rgba(255, 255, 255, 80);
}
""")
self.model_short_btn.clicked.connect(lambda: self.on_model_changed(0))
self.model_long_btn.clicked.connect(lambda: self.on_model_changed(1))
# 默认选择长距离模型
self.model_long_btn.setChecked(True)
model_layout.addWidget(model_label)
model_layout.addWidget(self.model_short_btn)
model_layout.addWidget(self.model_long_btn)
model_layout.addStretch()
control_layout.addLayout(model_layout)
control_layout.addStretch()
# 初始按钮位置
self.update_button_position()
def toggle_control_panel(self):
"""切换控制面板显示状态"""
self.control_panel_visible = not self.control_panel_visible
if self.control_panel_visible:
self.control_panel.show()
else:
self.control_panel.hide()
def on_confidence_changed(self, value):
"""置信度滑块值改变"""
confidence = value / 100.0
self.confidence_value.setText(f"{confidence:.2f}")
self.detection_confidence = confidence
if self.detector:
self.detector.minDetectionCon = confidence
def on_multiscale_changed(self):
"""多尺度检测选项改变"""
self.multi_scale_enabled = self.multiscale_check.isChecked()
def on_model_changed(self, model_selection):
"""模型选择改变"""
self.model_selection = model_selection
if model_selection == 0:
self.model_short_btn.setChecked(True)
self.model_long_btn.setChecked(False)
else:
self.model_short_btn.setChecked(False)
self.model_long_btn.setChecked(True)
# 重新初始化检测器
try:
self.detector = FaceDetector(
minDetectionCon=self.detection_confidence,
modelSelection=model_selection
)
except Exception as e:
print(f"重新初始化检测器失败: {e}")
def multi_scale_detection(self, img):
"""多尺度人脸检测,提高小尺寸人脸的检测率"""
if not self.multi_scale_enabled or self.detector is None:
return self.detector.findFaces(img, draw=False) if self.detector else (img, [])
faces = []
scales = [1.0, 0.75, 0.5] # 多尺度因子
for scale in scales:
# 缩放图像
if scale != 1.0:
h, w = img.shape[:2]
new_w, new_h = int(w * scale), int(h * scale)
scaled_img = cv2.resize(img, (new_w, new_h))
else:
scaled_img = img.copy()
# 在当前尺度下检测人脸
_, scaled_faces = self.detector.findFaces(scaled_img, draw=False)
# 将检测到的人脸坐标转换回原始尺度
if scaled_faces:
for face in scaled_faces:
bbox = face["bbox"]
# 调整边界框坐标
face["bbox"] = [
int(bbox[0] / scale),
int(bbox[1] / scale),
int(bbox[2] / scale),
int(bbox[3] / scale)
]
# 只添加新检测到的人脸(避免重复)
if not self.is_duplicate_face(face, faces):
faces.append(face)
return img, faces
def is_duplicate_face(self, new_face, existing_faces, threshold=0.5):
"""检查是否重复检测到同一个人脸"""
if not existing_faces:
return False
new_x, new_y, new_w, new_h = new_face["bbox"]
new_center = (new_x + new_w/2, new_y + new_h/2)
for face in existing_faces:
x, y, w, h = face["bbox"]
center = (x + w/2, y + h/2)
# 计算两个边界框中心点的距离
distance = np.sqrt((new_center[0] - center[0])**2 + (new_center[1] - center[1])**2)
# 如果距离小于阈值,认为是同一个人脸
if distance < max(new_w, new_h) * threshold:
return True
return False
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
)
# 控制按钮位置(左上角)
control_btn_w = 80
control_btn_h = 40
self.control_btn.setGeometry(
margin,
margin,
control_btn_w,
control_btn_h
)
# 控制面板位置(控制按钮下方)
panel_width = 300
panel_height = 180
self.control_panel.setGeometry(
margin,
margin + control_btn_h + 10,
panel_width,
panel_height
)
# ---------------------------------------------------------
# 逻辑按钮:随机 ↔ 重置
# ---------------------------------------------------------
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:
if self.multi_scale_enabled:
img, self.faces = self.multi_scale_detection(img)
else:
img, self.faces = self.detector.findFaces(img, draw=False)
self.faces = self.faces if self.faces else []
else:
self.faces = []
# 绘制所有人脸为绿色
for face in self.faces:
x, y, w, h = face["bbox"]
score = face["score"][0]
# 根据置信度调整边框粗细
thickness = max(1, int(3 * score))
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), thickness)
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) # 红色
thickness = 4
else:
color = (0, 255, 0) # 绿色
thickness = 2
cv2.rectangle(img, (x, y), (x + w, y + h), color, thickness)
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()

310
3-test.py Normal file
View File

@@ -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()

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

View File

@@ -1 +1,25 @@
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
# Face Random
## 1.软件说明
FaceRandomSelector 是一款人脸随机选择工具。软件采用opencv的人脸检测算法能够快速准确地识别摄像头中的人脸并提供高效的随机选择功能帮助教师轻松实现课堂互动。
## 2.打包指令
2.4版本之前:
` nuitka --standalone --include-package=cv2 --enable-plugin=pyside6 --windows-console-mode=disable --windows-icon-from-ico=./text.ico --include-data-dir=F:\python310\lib\site-packages\cv2\data=cv2\data --output-dir=dist3c --remove-output 3-control.py `
2.4版本之后:
` nuitka --standalone --include-package=cv2 --enable-plugin=pyside6 --windows-console-mode=disable --windows-icon-from-ico=./text.ico --include-data-files=model/face_detection_yunet_2023mar.onnx=model/face_detection_yunet_2023mar.onnx --output-dir=dist34 --remove-output 3-5.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改编 给出具体的人脸识别模型无法获取的相关报错
6. 3-control.py由3-3.py改编 用户可以修改相关参数
7. 3-4.py使用YuNet模型 加强对小人脸识别准确度
8. 3-5.py优化加载性能
9. 3-6.py优化加载性能 添加静止模式 删去置信度与选中绿框
> 软件务必保存在纯英文路径中!

BIN
text.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB