439 lines
18 KiB
Python
439 lines
18 KiB
Python
import cv2
|
|
import time
|
|
import ctypes
|
|
import sys
|
|
import traceback
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|
QHBoxLayout, QPushButton, QLabel, QSpinBox,
|
|
QStyle, QProgressBar, QSystemTrayIcon, QMenu, QMessageBox)
|
|
from PyQt5.QtCore import Qt, QTimer
|
|
from PyQt5.QtGui import QImage, QPixmap, QIcon, QFont
|
|
|
|
# 设置日志
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler('app.log'),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def handle_exception(exc_type, exc_value, exc_traceback):
|
|
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
|
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
|
|
sys.excepthook = handle_exception
|
|
|
|
try:
|
|
logger.info("Starting application...")
|
|
|
|
class WorkspaceMonitor(QMainWindow):
|
|
def __init__(self):
|
|
try:
|
|
super().__init__()
|
|
logger.info("Initializing WorkspaceMonitor...")
|
|
self.setWindowTitle("工位监控系统")
|
|
self.setMinimumSize(1000, 700)
|
|
|
|
# 系统托盘
|
|
self.tray_icon = QSystemTrayIcon(self)
|
|
self.tray_icon.setIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
|
|
self.create_tray_menu()
|
|
self.tray_icon.show()
|
|
|
|
# 初始化变量
|
|
self.running = False
|
|
self.timeout = 10
|
|
logger.info("Loading face detection model...")
|
|
# 使用OpenCV的人脸检测器
|
|
self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
|
if self.face_cascade.empty():
|
|
raise Exception("无法加载人脸检测模型")
|
|
|
|
logger.info("Opening camera...")
|
|
self.cap = cv2.VideoCapture(0)
|
|
if not self.cap.isOpened():
|
|
logger.error("Failed to open camera!")
|
|
raise Exception("无法打开摄像头")
|
|
|
|
self.last_face_time = time.time()
|
|
self.no_face_duration = 0
|
|
|
|
self.setup_ui()
|
|
logger.info("Initialization complete.")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in WorkspaceMonitor initialization: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
raise
|
|
|
|
def setup_ui(self):
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
main_layout = QVBoxLayout(central_widget)
|
|
|
|
# 标题
|
|
title_label = QLabel("工位监控系统")
|
|
title_label.setFont(QFont('Arial', 16, QFont.Bold))
|
|
title_label.setAlignment(Qt.AlignCenter)
|
|
main_layout.addWidget(title_label)
|
|
|
|
# 摄像头预览
|
|
preview_container = QWidget()
|
|
preview_container.setStyleSheet("background-color: #f0f0f0; border-radius: 10px;")
|
|
preview_layout = QVBoxLayout(preview_container)
|
|
|
|
self.camera_label = QLabel()
|
|
self.camera_label.setAlignment(Qt.AlignCenter)
|
|
self.camera_label.setMinimumHeight(480)
|
|
preview_layout.addWidget(self.camera_label)
|
|
|
|
main_layout.addWidget(preview_container)
|
|
|
|
# 控制面板
|
|
control_panel = QWidget()
|
|
control_panel.setStyleSheet("""
|
|
QWidget {
|
|
background-color: #ffffff;
|
|
border-radius: 10px;
|
|
padding: 10px;
|
|
}
|
|
QPushButton {
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
border-radius: 5px;
|
|
padding: 8px;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #45a049;
|
|
}
|
|
QLabel {
|
|
color: #333333;
|
|
}
|
|
""")
|
|
|
|
control_layout = QHBoxLayout(control_panel)
|
|
|
|
# 开始/停止按钮
|
|
self.start_stop_btn = QPushButton("开始监控")
|
|
self.start_stop_btn.clicked.connect(self.toggle_monitoring)
|
|
control_layout.addWidget(self.start_stop_btn)
|
|
|
|
# 超时设置
|
|
timeout_container = QWidget()
|
|
timeout_layout = QHBoxLayout(timeout_container)
|
|
|
|
timeout_label = QLabel("锁屏等待时间:")
|
|
self.timeout_spinbox = QSpinBox()
|
|
self.timeout_spinbox.setRange(5, 300)
|
|
self.timeout_spinbox.setValue(10)
|
|
self.timeout_spinbox.setSuffix(" 秒")
|
|
self.timeout_spinbox.valueChanged.connect(self.update_timeout)
|
|
|
|
timeout_layout.addWidget(timeout_label)
|
|
timeout_layout.addWidget(self.timeout_spinbox)
|
|
control_layout.addWidget(timeout_container)
|
|
|
|
# 状态显示
|
|
status_container = QWidget()
|
|
status_layout = QVBoxLayout(status_container)
|
|
|
|
self.status_label = QLabel("状态: 未开始监控")
|
|
self.status_label.setStyleSheet("""
|
|
QLabel {
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
color: #333333;
|
|
margin-bottom: 5px;
|
|
}
|
|
""")
|
|
|
|
self.confidence_label = QLabel("置信度: 0%")
|
|
self.confidence_label.setStyleSheet("""
|
|
QLabel {
|
|
color: #666666;
|
|
margin-bottom: 5px;
|
|
}
|
|
""")
|
|
|
|
# 进度条容器
|
|
progress_container = QWidget()
|
|
progress_container.setFixedHeight(45)
|
|
progress_container.setStyleSheet("""
|
|
QWidget {
|
|
background-color: transparent;
|
|
padding: 5px;
|
|
}
|
|
""")
|
|
progress_layout = QVBoxLayout(progress_container)
|
|
progress_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
# 进度条
|
|
self.progress_bar = QProgressBar()
|
|
self.progress_bar.setRange(0, 100)
|
|
self.progress_bar.setValue(0)
|
|
self.progress_bar.setFormat("离开时间: 0s")
|
|
self.progress_bar.setMinimumWidth(250)
|
|
self.progress_bar.setFixedHeight(20)
|
|
self.set_progress_bar_style()
|
|
|
|
progress_layout.addWidget(self.progress_bar)
|
|
|
|
status_layout.addWidget(self.status_label)
|
|
status_layout.addWidget(self.confidence_label)
|
|
status_layout.addWidget(progress_container)
|
|
|
|
control_layout.addWidget(status_container)
|
|
|
|
main_layout.addWidget(control_panel)
|
|
|
|
self.timer = QTimer()
|
|
self.timer.timeout.connect(self.update_frame)
|
|
|
|
def create_tray_menu(self):
|
|
menu = QMenu()
|
|
show_action = menu.addAction("显示主窗口")
|
|
show_action.triggered.connect(self.show)
|
|
quit_action = menu.addAction("退出")
|
|
quit_action.triggered.connect(self.close)
|
|
self.tray_icon.setContextMenu(menu)
|
|
self.tray_icon.activated.connect(self.tray_icon_activated)
|
|
|
|
def tray_icon_activated(self, reason):
|
|
if reason == QSystemTrayIcon.DoubleClick:
|
|
self.show()
|
|
|
|
def update_frame(self):
|
|
# 检查摄像头是否连接
|
|
if not self.cap.isOpened():
|
|
self.handle_camera_disconnected()
|
|
return
|
|
|
|
ret, frame = self.cap.read()
|
|
if not ret:
|
|
self.handle_camera_disconnected()
|
|
return
|
|
|
|
# 转换为灰度图进行人脸检测
|
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
faces = self.face_cascade.detectMultiScale(
|
|
gray,
|
|
scaleFactor=1.1,
|
|
minNeighbors=5,
|
|
minSize=(30, 30)
|
|
)
|
|
|
|
current_time = time.time()
|
|
max_confidence = 0
|
|
|
|
if len(faces) > 0:
|
|
# 简单地使用检测到的人脸数量作为置信度的基础
|
|
max_confidence = min(len(faces) * 0.5, 1.0) # 限制最大值为1.0
|
|
|
|
for (x, y, w, h) in faces:
|
|
# 绘制人脸框
|
|
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
|
# 显示置信度
|
|
cv2.putText(frame, f"置信度: {max_confidence:.2f}",
|
|
(x, y-10), cv2.FONT_HERSHEY_SIMPLEX,
|
|
0.5, (0, 255, 0), 2)
|
|
|
|
self.last_face_time = current_time
|
|
self.no_face_duration = 0
|
|
self.status_label.setText("状态: 检测到人脸")
|
|
self.confidence_label.setText(f"置信度: {max_confidence*100:.1f}%")
|
|
self.progress_bar.setValue(0)
|
|
self.progress_bar.setFormat("离开时间: 0s")
|
|
self.set_progress_bar_style("#4CAF50")
|
|
else:
|
|
self.no_face_duration = current_time - self.last_face_time
|
|
self.status_label.setText(f"状态: 未检测到人脸 ({int(self.no_face_duration)}秒)")
|
|
self.confidence_label.setText("置信度: 0%")
|
|
|
|
# 计算进度并更新进度条
|
|
progress = min(int(self.no_face_duration / self.timeout * 100), 100)
|
|
self.progress_bar.setValue(progress)
|
|
self.progress_bar.setFormat(f"离开时间: {int(self.no_face_duration)}s")
|
|
|
|
# 根据进度设置颜色
|
|
if progress > 70:
|
|
self.set_progress_bar_style("#FF4444") # 红色
|
|
elif progress > 30:
|
|
self.set_progress_bar_style("#FFA726") # 橙色
|
|
else:
|
|
self.set_progress_bar_style("#4CAF50") # 绿色
|
|
|
|
if self.no_face_duration > self.timeout:
|
|
self.status_label.setText(f"状态: 锁定屏幕 - {datetime.now()}")
|
|
self.tray_icon.showMessage("工位监控", "系统即将锁定", QSystemTrayIcon.Warning)
|
|
self.lock_windows()
|
|
# 重置所有状态
|
|
self.last_face_time = time.time()
|
|
self.no_face_duration = 0
|
|
self.progress_bar.setValue(0)
|
|
self.progress_bar.setFormat("离开时间: 0s")
|
|
self.set_progress_bar_style("#4CAF50") # 重置为绿色
|
|
time.sleep(1)
|
|
|
|
# 显示图像
|
|
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
h, w, ch = rgb_frame.shape
|
|
bytes_per_line = ch * w
|
|
qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format.RGB888)
|
|
scaled_pixmap = QPixmap.fromImage(qt_image).scaled(
|
|
self.camera_label.size(),
|
|
Qt.KeepAspectRatio
|
|
)
|
|
self.camera_label.setPixmap(scaled_pixmap)
|
|
|
|
def toggle_monitoring(self):
|
|
if not self.running:
|
|
# 开始监控时重置所有计时相关变量
|
|
self.last_face_time = time.time()
|
|
self.no_face_duration = 0
|
|
self.progress_bar.setValue(0)
|
|
self.progress_bar.setFormat("离开时间: 0s")
|
|
self.set_progress_bar_style("#4CAF50")
|
|
|
|
self.running = True
|
|
self.start_stop_btn.setText("停止监控")
|
|
self.start_stop_btn.setStyleSheet("background-color: #ff4444;")
|
|
self.status_label.setText("状态: 正在监控")
|
|
self.timer.start(30)
|
|
self.tray_icon.showMessage("工位监控", "监控已开始", QSystemTrayIcon.Information)
|
|
self.tray_icon.showMessage("工位监控", "监控已开始", QSystemTrayIcon.MessageIcon.Information)
|
|
else:
|
|
self.running = False
|
|
self.start_stop_btn.setText("开始监控")
|
|
self.start_stop_btn.setStyleSheet("background-color: #4CAF50;")
|
|
self.status_label.setText("状态: 已停止监控")
|
|
self.timer.stop()
|
|
# 重置计时相关的变量
|
|
self.last_face_time = time.time()
|
|
self.no_face_duration = 0
|
|
self.progress_bar.setValue(0)
|
|
self.progress_bar.setFormat("离开时间: 0s")
|
|
self.set_progress_bar_style("#4CAF50") # 重置进度条颜色为绿色
|
|
self.confidence_label.setText("置信度: 0%")
|
|
self.tray_icon.showMessage("工位监控", "监控已停止", QSystemTrayIcon.MessageIcon.Information)
|
|
|
|
def update_timeout(self, value):
|
|
self.timeout = value
|
|
|
|
def lock_windows(self):
|
|
ctypes.windll.user32.LockWorkStation()
|
|
|
|
def closeEvent(self, event):
|
|
if self.running:
|
|
self.tray_icon.showMessage("工位监控", "程序已最小化到系统托盘",
|
|
QSystemTrayIcon.MessageIcon.Information)
|
|
self.hide()
|
|
event.ignore()
|
|
else:
|
|
self.running = False
|
|
self.timer.stop()
|
|
self.cap.release()
|
|
self.tray_icon.hide()
|
|
event.accept()
|
|
|
|
def handle_camera_disconnected(self):
|
|
if self.running:
|
|
self.running = False
|
|
self.timer.stop()
|
|
self.start_stop_btn.setText("开始监控")
|
|
self.start_stop_btn.setStyleSheet("background-color: #4CAF50;")
|
|
self.status_label.setText("状态: 摄像头已断开")
|
|
self.confidence_label.setText("置信度: 0%")
|
|
self.progress_bar.setValue(0)
|
|
self.progress_bar.setFormat("摄像头未连接")
|
|
self.tray_icon.showMessage(
|
|
"工位监控",
|
|
"摄像头已断开,监控已停止",
|
|
QSystemTrayIcon.MessageIcon.Warning
|
|
)
|
|
|
|
# 尝试重新打开摄像头
|
|
self.try_reconnect_camera()
|
|
|
|
def try_reconnect_camera(self):
|
|
if self.cap.isOpened():
|
|
self.cap.release()
|
|
|
|
# 创建定时器尝试重新连接
|
|
self.reconnect_timer = QTimer()
|
|
self.reconnect_timer.timeout.connect(self.attempt_camera_reconnect)
|
|
self.reconnect_timer.start(2000) # 每2秒尝试重连
|
|
|
|
def attempt_camera_reconnect(self):
|
|
self.cap = cv2.VideoCapture(0)
|
|
if self.cap.isOpened():
|
|
self.reconnect_timer.stop()
|
|
self.status_label.setText("状态: 摄像头已重新连接")
|
|
self.progress_bar.setFormat("离开时间: %vs")
|
|
self.tray_icon.showMessage(
|
|
"工位监控",
|
|
"摄像头已重新连接,请手动开启监控",
|
|
QSystemTrayIcon.MessageIcon.Information
|
|
)
|
|
|
|
def set_progress_bar_style(self, color="#4CAF50"):
|
|
base_style = """
|
|
QProgressBar {
|
|
border: 1px solid #E0E0E0;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
background-color: #F5F5F5;
|
|
padding: 1px;
|
|
font-size: 12px;
|
|
}
|
|
QProgressBar::chunk {
|
|
background-color: %s;
|
|
border-radius: 3px;
|
|
margin: 0px;
|
|
}
|
|
"""
|
|
self.progress_bar.setStyleSheet(base_style % color)
|
|
|
|
def main():
|
|
try:
|
|
logger.info("Starting main function...")
|
|
app = QApplication(sys.argv)
|
|
app.setStyle('Fusion')
|
|
window = WorkspaceMonitor()
|
|
window.show()
|
|
logger.info("Application started successfully.")
|
|
sys.exit(app.exec())
|
|
except Exception as e:
|
|
logger.error(f"Error in main function: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
raise
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except Exception as e:
|
|
logger.error(f"Fatal error: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
# 显示错误消息框
|
|
if QApplication.instance() is None:
|
|
app = QApplication(sys.argv)
|
|
from PyQt6.QtWidgets import QMessageBox
|
|
msg = QMessageBox()
|
|
msg.setIcon(QMessageBox.Icon.Critical)
|
|
msg.setText("程序发生错误")
|
|
msg.setInformativeText(str(e))
|
|
msg.setWindowTitle("错误")
|
|
msg.setDetailedText(traceback.format_exc())
|
|
msg.exec()
|
|
sys.exit(1)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fatal error outside main: {str(e)}")
|
|
logger.error(traceback.format_exc())
|
|
sys.exit(1) |