Compare commits
7 Commits
cf5f4e9c22
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
826ca326a9
|
|||
|
41a31473f3
|
|||
|
9a015f2358
|
|||
|
f4a4936b5c
|
|||
|
f8fb6f8427
|
|||
|
c1a39143c0
|
|||
|
69dff66d5a
|
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
3-2.py
|
||||
8
.idea/face-random.iml
generated
Normal file
8
.idea/face-random.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/face-random.iml" filepath="$PROJECT_DIR$/.idea/face-random.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -2,9 +2,9 @@ import sys
|
||||
import random
|
||||
import cv2
|
||||
from cvzone.FaceDetectionModule import FaceDetector
|
||||
from PyQt6.QtCore import QTimer
|
||||
from PyQt6.QtGui import QImage, QPixmap
|
||||
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton
|
||||
from PySide6.QtCore import QTimer
|
||||
from PySide6.QtGui import QImage, QPixmap
|
||||
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton
|
||||
|
||||
|
||||
class FaceRandomApp(QWidget):
|
||||
207
2.py
Normal file
207
2.py
Normal file
@@ -0,0 +1,207 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import random
|
||||
import os
|
||||
import sys
|
||||
from collections import deque
|
||||
|
||||
class FaceSelector:
|
||||
def __init__(self):
|
||||
# 获取模型文件的正确路径
|
||||
cascade_path = self.get_cascade_path()
|
||||
self.face_cascade = cv2.CascadeClassifier(cascade_path)
|
||||
|
||||
# 检查模型是否加载成功
|
||||
if self.face_cascade.empty():
|
||||
fallback_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
|
||||
self.face_cascade = cv2.CascadeClassifier(fallback_path)
|
||||
|
||||
if self.face_cascade.empty():
|
||||
sys.exit(1)
|
||||
|
||||
# 状态变量
|
||||
self.random_mode = False
|
||||
self.selected_face = None
|
||||
self.static_frame = None
|
||||
self.static_faces = None
|
||||
|
||||
# 用于平滑检测的队列
|
||||
self.face_queue = deque(maxlen=5)
|
||||
|
||||
# 创建窗口
|
||||
self.window_name = "Face Selector"
|
||||
cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL)
|
||||
|
||||
# 添加窗口关闭事件处理
|
||||
self.running = True
|
||||
|
||||
def get_cascade_path(self):
|
||||
"""获取模型文件的正确路径"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
base_path = sys._MEIPASS
|
||||
else:
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
possible_paths = [
|
||||
os.path.join(base_path, 'haarcascade_frontalface_default.xml'),
|
||||
os.path.join(base_path, 'cv2', 'data', 'haarcascade_frontalface_default.xml'),
|
||||
os.path.join(base_path, 'Library', 'etc', 'haarcascades', 'haarcascade_frontalface_default.xml'),
|
||||
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
|
||||
]
|
||||
|
||||
for path in possible_paths:
|
||||
if os.path.isfile(path):
|
||||
return path
|
||||
|
||||
return possible_paths[0]
|
||||
|
||||
def detect_faces(self, frame):
|
||||
"""检测人脸并返回位置"""
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
faces = self.face_cascade.detectMultiScale(
|
||||
gray,
|
||||
scaleFactor=1.05,
|
||||
minNeighbors=6,
|
||||
minSize=(40, 40),
|
||||
flags=cv2.CASCADE_SCALE_IMAGE
|
||||
)
|
||||
return faces
|
||||
|
||||
def draw_faces(self, frame, faces):
|
||||
"""在帧上绘制人脸框"""
|
||||
# 在随机模式下,只绘制选中的红框
|
||||
if self.random_mode and self.selected_face is not None and self.static_faces is not None:
|
||||
if self.selected_face < len(self.static_faces):
|
||||
x, y, w, h = self.static_faces[self.selected_face]
|
||||
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 3) # 选中的为红色
|
||||
else:
|
||||
# 在正常模式下,绘制所有人脸框
|
||||
for (x, y, w, h) in faces:
|
||||
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) # 绿色
|
||||
|
||||
return frame
|
||||
|
||||
def draw_button(self, frame):
|
||||
"""绘制按钮"""
|
||||
button_text = "Reset" if self.random_mode else "Random"
|
||||
button_color = (0, 0, 255) if self.random_mode else (0, 255, 0)
|
||||
|
||||
# 绘制按钮背景
|
||||
cv2.rectangle(frame, (10, 10), (150, 50), button_color, -1)
|
||||
cv2.rectangle(frame, (10, 10), (150, 50), (255, 255, 255), 2)
|
||||
|
||||
# 绘制按钮文字
|
||||
cv2.putText(frame, button_text, (20, 35), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
|
||||
|
||||
return frame
|
||||
|
||||
def toggle_random_mode(self, frame, faces):
|
||||
"""切换随机模式"""
|
||||
if not self.random_mode:
|
||||
# 进入随机模式
|
||||
self.random_mode = True
|
||||
if len(faces) > 0:
|
||||
self.selected_face = random.randint(0, len(faces)-1)
|
||||
self.static_frame = frame.copy()
|
||||
self.static_faces = faces.copy() # 保存静态状态下的人脸位置
|
||||
else:
|
||||
self.selected_face = None
|
||||
self.static_frame = None
|
||||
self.static_faces = None
|
||||
else:
|
||||
# 退出随机模式
|
||||
self.random_mode = False
|
||||
self.selected_face = None
|
||||
self.static_frame = None
|
||||
self.static_faces = None
|
||||
|
||||
def process_frame(self, frame):
|
||||
"""处理每一帧"""
|
||||
# 检测人脸
|
||||
faces = self.detect_faces(frame)
|
||||
|
||||
# 更新人脸队列
|
||||
if len(faces) > 0:
|
||||
self.face_queue.append(faces)
|
||||
|
||||
# 使用队列中最新的人脸数据
|
||||
current_faces = faces
|
||||
if self.face_queue:
|
||||
current_faces = self.face_queue[-1]
|
||||
|
||||
# 处理随机模式
|
||||
if self.random_mode:
|
||||
if self.static_frame is not None:
|
||||
# 使用静态帧,只绘制选中的红框
|
||||
display_frame = self.static_frame.copy()
|
||||
display_frame = self.draw_faces(display_frame, current_faces)
|
||||
else:
|
||||
display_frame = frame.copy()
|
||||
else:
|
||||
# 在实时帧上绘制所有人脸
|
||||
display_frame = self.draw_faces(frame.copy(), current_faces)
|
||||
|
||||
# 绘制按钮
|
||||
display_frame = self.draw_button(display_frame)
|
||||
|
||||
return display_frame
|
||||
|
||||
def run(self):
|
||||
"""运行主程序"""
|
||||
cap = cv2.VideoCapture(0)
|
||||
|
||||
if not cap.isOpened():
|
||||
return
|
||||
|
||||
# 获取摄像头分辨率并设置窗口大小
|
||||
ret, frame = cap.read()
|
||||
if ret:
|
||||
height, width = frame.shape[:2]
|
||||
cv2.resizeWindow(self.window_name, width, height)
|
||||
|
||||
while self.running:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
cv2.waitKey(1000)
|
||||
continue
|
||||
|
||||
# 处理帧
|
||||
display_frame = self.process_frame(frame)
|
||||
|
||||
# 显示结果
|
||||
cv2.imshow(self.window_name, display_frame)
|
||||
|
||||
# 处理鼠标点击
|
||||
def mouse_callback(event, x, y, flags, param):
|
||||
if event == cv2.EVENT_LBUTTONDOWN:
|
||||
# 检查是否点击了按钮区域
|
||||
if 10 <= x <= 150 and 10 <= y <= 50:
|
||||
faces = self.detect_faces(frame)
|
||||
self.toggle_random_mode(frame, faces)
|
||||
|
||||
cv2.setMouseCallback(self.window_name, mouse_callback)
|
||||
|
||||
# 处理键盘输入和窗口关闭事件
|
||||
key = cv2.waitKey(1) & 0xFF
|
||||
if key == ord('q') or key == 27: # 'q' 或 ESC 键退出
|
||||
self.running = False
|
||||
break
|
||||
|
||||
# 检查窗口是否被关闭
|
||||
try:
|
||||
if cv2.getWindowProperty(self.window_name, cv2.WND_PROP_VISIBLE) <= 0:
|
||||
self.running = False
|
||||
break
|
||||
except:
|
||||
self.running = False
|
||||
break
|
||||
|
||||
# 释放资源
|
||||
cap.release()
|
||||
cv2.destroyAllWindows()
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = FaceSelector()
|
||||
app.run()
|
||||
199
3-1.py
Normal file
199
3-1.py
Normal file
@@ -0,0 +1,199 @@
|
||||
import sys
|
||||
import random
|
||||
import cv2
|
||||
from cvzone.FaceDetectionModule import FaceDetector
|
||||
from PySide6.QtCore import QTimer
|
||||
from PySide6.QtGui import QImage, QPixmap
|
||||
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton
|
||||
|
||||
|
||||
class FaceRandomApp(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
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.detector = FaceDetector(minDetectionCon=0.25, modelSelection=1)
|
||||
|
||||
# ---------------- 创建界面 ----------------
|
||||
self.setup_ui()
|
||||
|
||||
self.faces = []
|
||||
|
||||
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()))
|
||||
|
||||
# 检测人脸
|
||||
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
|
||||
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()
|
||||
window.show()
|
||||
|
||||
# 启动定时器
|
||||
timer = QTimer()
|
||||
timer.timeout.connect(window.update_frame)
|
||||
timer.start(30)
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
266
3-2.py
Normal file
266
3-2.py
Normal file
@@ -0,0 +1,266 @@
|
||||
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()
|
||||
|
||||
# 延迟初始化主界面,让加载页面先显示
|
||||
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)
|
||||
|
||||
# 使用长距离模型和更低的人脸检测阈值以提高检测率
|
||||
# 这里会加载模型,可能需要一些时间
|
||||
try:
|
||||
self.detector = FaceDetector(minDetectionCon=0.25, modelSelection=1)
|
||||
except Exception as e:
|
||||
print(f"初始化人脸检测器时出错: {e}")
|
||||
# 如果初始化失败,使用一个空的检测器
|
||||
self.detector = None
|
||||
|
||||
# ---------------- 创建界面 ----------------
|
||||
self.setup_ui()
|
||||
|
||||
self.faces = []
|
||||
|
||||
# 模拟加载完成
|
||||
QTimer.singleShot(2000, 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()
|
||||
275
3-3.py
Normal file
275
3-3.py
Normal 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
339
3-4.py
Normal 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
373
3-5.py
Normal 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
425
3-6.py
Normal 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
551
3-control.py
Normal 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
310
3-test.py
Normal 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
model/face_detection_yunet_2023mar.onnx
Normal file
BIN
model/face_detection_yunet_2023mar.onnx
Normal file
Binary file not shown.
25
readme.md
Normal file
25
readme.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 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:优化加载性能 添加静止模式 删去置信度与选中绿框
|
||||
> 软件务必保存在纯英文路径中!
|
||||
Reference in New Issue
Block a user