This commit is contained in:
He
2025-11-22 21:03:45 +08:00
parent cf5f4e9c22
commit 69dff66d5a
10 changed files with 450 additions and 3 deletions

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

8
.idea/face-random.iml generated Normal file
View 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>

View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -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
View 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.py Normal file
View 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()

1
readme.md Normal file
View File

@@ -0,0 +1 @@
nuitka --standalone --include-package=cv2 --enable-plugin=pyside6 --include-data-dir=F:\python310\lib\site-packages\cv2\data=cv2\data --output-dir=dist3 --remove-output 3.py