2025-11-16 12:04:48 +08:00
|
|
|
|
import sys
|
|
|
|
|
|
import random
|
|
|
|
|
|
import cv2
|
|
|
|
|
|
from cvzone.FaceDetectionModule import FaceDetector
|
2025-11-22 21:03:45 +08:00
|
|
|
|
from PySide6.QtCore import QTimer
|
|
|
|
|
|
from PySide6.QtGui import QImage, QPixmap
|
|
|
|
|
|
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton
|
2025-11-16 12:04:48 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FaceRandomApp(QWidget):
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
|
|
self.setWindowTitle("Face Random Selector")
|
|
|
|
|
|
self.resize(1280, 720)
|
|
|
|
|
|
|
|
|
|
|
|
# 状态: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.detector = FaceDetector(minDetectionCon=0.25, modelSelection=1)
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------- 视频区域 ----------------
|
|
|
|
|
|
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.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()
|
|
|
|
|
|
|
|
|
|
|
|
# 定时刷新视频
|
|
|
|
|
|
self.timer = QTimer()
|
|
|
|
|
|
self.timer.timeout.connect(self.update_frame)
|
|
|
|
|
|
self.timer.start(30)
|
|
|
|
|
|
|
|
|
|
|
|
self.faces = []
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
# 自动适配窗口大小时重新定位视频与按钮
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
def resizeEvent(self, event):
|
|
|
|
|
|
self.video_label.setGeometry(0, 0, self.width(), self.height())
|
|
|
|
|
|
self.update_button_position()
|
|
|
|
|
|
|
|
|
|
|
|
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()))
|
|
|
|
|
|
|
|
|
|
|
|
# 检测人脸
|
|
|
|
|
|
img, faces = self.detector.findFaces(img, draw=False)
|
|
|
|
|
|
self.faces = faces if faces else []
|
|
|
|
|
|
|
|
|
|
|
|
# 绘制所有人脸为绿色
|
|
|
|
|
|
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
|
|
|
|
|
|
qimg = QImage(rgb.data, w, h, ch * w, 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()
|
|
|
|
|
|
window.show()
|
|
|
|
|
|
sys.exit(app.exec())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|