posedet/control_panel_widget.py
2025-06-22 16:51:48 +08:00

404 lines
12 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import tempfile
import cv2
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QComboBox, QRadioButton, QButtonGroup,
QFileDialog, QTextEdit, QGroupBox, QFrame, QScrollArea)
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
from PyQt5.QtGui import QFont, QPalette
class ControlPanelWidget(QScrollArea):
video_selected = pyqtSignal(str)
start_comparison = pyqtSignal(str)
stop_comparison = pyqtSignal()
initialize_system = pyqtSignal()
def __init__(self, motion_app):
super().__init__()
self.motion_app = motion_app
self.current_video_path = None
self.setup_ui()
def setup_ui(self):
# Create main widget for scroll area
main_widget = QWidget()
main_layout = QVBoxLayout(main_widget)
main_layout.setSpacing(10)
main_layout.setContentsMargins(15, 15, 15, 15)
# Title
title_label = QLabel("🎛️ 控制面板")
title_font = QFont()
title_font.setPointSize(14)
title_font.setBold(True)
title_label.setFont(title_font)
main_layout.addWidget(title_label)
# Add separator
separator = QFrame()
separator.setFrameShape(QFrame.HLine)
separator.setFrameShadow(QFrame.Sunken)
main_layout.addWidget(separator)
# Display settings group
self.setup_display_settings(main_layout)
# Video source selection group
self.setup_video_source(main_layout)
# System initialization group
self.setup_system_init(main_layout)
# System status group
self.setup_system_status(main_layout)
# Stretch to push everything to top
main_layout.addStretch()
# Set scroll area properties
self.setWidget(main_widget)
self.setWidgetResizable(True)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# Style the scroll area
self.setStyleSheet("""
QScrollArea {
border: none;
background-color: #ffffff;
}
QGroupBox {
font-weight: bold;
border: 2px solid #cccccc;
border-radius: 5px;
margin-top: 10px;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 10px 0 10px;
}
""")
def setup_display_settings(self, parent_layout):
group = QGroupBox("显示设置")
layout = QVBoxLayout(group)
# Resolution selection
res_label = QLabel("显示分辨率:")
self.resolution_combo = QComboBox()
self.resolution_combo.addItems([
"高清 (1280x800)",
"中等 (960x720)",
"标准 (640x480)"
])
self.resolution_combo.setCurrentIndex(1) # Default to medium
self.resolution_combo.currentTextChanged.connect(self.on_resolution_changed)
layout.addWidget(res_label)
layout.addWidget(self.resolution_combo)
parent_layout.addWidget(group)
def setup_video_source(self, parent_layout):
group = QGroupBox("视频来源")
layout = QVBoxLayout(group)
# Radio buttons for video source
self.video_source_group = QButtonGroup()
self.preset_radio = QRadioButton("预设视频")
self.upload_radio = QRadioButton("上传视频")
self.preset_radio.setChecked(True)
self.video_source_group.addButton(self.preset_radio, 0)
self.video_source_group.addButton(self.upload_radio, 1)
layout.addWidget(self.preset_radio)
layout.addWidget(self.upload_radio)
# Preset video selection
self.preset_combo = QComboBox()
# --- FIX STARTS HERE ---
# The incorrect call to self.load_preset_videos() has been removed from here.
# --- FIX ENDS HERE ---
# Upload button
self.upload_button = QPushButton("选择文件...")
self.upload_button.clicked.connect(self.upload_video)
self.upload_button.setEnabled(False)
# Video info display - This is now created BEFORE it is used.
self.video_info = QLabel("未选择视频")
self.video_info.setWordWrap(True)
self.video_info.setStyleSheet("color: #666; font-size: 9pt;")
layout.addWidget(self.preset_combo)
layout.addWidget(self.upload_button)
layout.addWidget(self.video_info)
# Connect signals
self.preset_radio.toggled.connect(self.on_video_source_changed)
self.preset_combo.currentTextChanged.connect(self.on_preset_video_changed)
parent_layout.addWidget(group)
# Load available presets after all UI elements are set up. This is the correct place.
self.load_preset_videos()
def setup_system_init(self, parent_layout):
group = QGroupBox("⚙️ 系统初始化")
layout = QVBoxLayout(group)
self.init_button = QPushButton("🚀 初始化系统")
self.init_button.clicked.connect(self.initialize_system_clicked)
self.init_button.setMinimumHeight(40)
self.init_button.setStyleSheet("""
QPushButton {
background-color: #0086d3;
color: white;
border: none;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #006ba3;
}
QPushButton:pressed {
background-color: #004d73;
}
""")
# Control buttons
button_layout = QHBoxLayout()
self.preview_button = QPushButton("📷 预览")
self.start_button = QPushButton("🚀 开始比较")
self.preview_button.clicked.connect(self.preview_camera)
self.start_button.clicked.connect(self.start_comparison_clicked)
self.preview_button.setEnabled(False)
self.start_button.setEnabled(False)
button_layout.addWidget(self.preview_button)
button_layout.addWidget(self.start_button)
layout.addWidget(self.init_button)
layout.addLayout(button_layout)
parent_layout.addWidget(group)
def setup_system_status(self, parent_layout):
group = QGroupBox(" 系统状态")
layout = QVBoxLayout(group)
self.status_info = QTextEdit()
self.status_info.setMaximumHeight(100)
self.status_info.setReadOnly(True)
self.update_system_status()
layout.addWidget(self.status_info)
parent_layout.addWidget(group)
def load_preset_videos(self):
preset_videos = {
"六字诀": "liuzi.mp4",
"六字诀精简": "liuzi-short.mp4",
}
preset_folder = "preset_videos"
if os.path.exists(preset_folder):
available_presets = []
for display_name, filename in preset_videos.items():
full_path = os.path.join(preset_folder, filename)
if os.path.exists(full_path):
available_presets.append(display_name)
self.preset_combo.clear() # Clear previous items before adding new ones
if available_presets:
self.preset_combo.addItems(available_presets)
# Set default selection
if available_presets:
self.on_preset_video_changed(available_presets[0])
else:
self.preset_combo.addItem("无可用预设视频")
else:
self.preset_combo.addItem("preset_videos 文件夹不存在")
def on_video_source_changed(self, checked):
if self.preset_radio.isChecked():
self.preset_combo.setEnabled(True)
self.upload_button.setEnabled(False)
if self.preset_combo.count() > 0 and self.preset_combo.currentText() != "无可用预设视频":
self.on_preset_video_changed(self.preset_combo.currentText())
else:
self.preset_combo.setEnabled(False)
self.upload_button.setEnabled(True)
self.current_video_path = None
self.video_info.setText("请选择上传文件")
def on_preset_video_changed(self, display_name):
preset_videos = {
"六字诀": "liuzi.mp4",
"六字诀精简": "liuzi-short.mp4",
}
if display_name in preset_videos:
filename = preset_videos[display_name]
video_path = os.path.join("preset_videos", filename)
if os.path.exists(video_path):
self.current_video_path = video_path
self.update_video_info()
self.video_selected.emit(video_path)
else:
self.video_info.setText("视频文件不存在")
def upload_video(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "选择视频文件", "",
"Video Files (*.mp4 *.avi *.mov *.mkv);;All Files (*)"
)
if file_path:
self.current_video_path = file_path
self.update_video_info()
self.video_selected.emit(file_path)
def update_video_info(self):
if self.current_video_path:
try:
cap = cv2.VideoCapture(self.current_video_path)
if cap.isOpened():
fps = cap.get(cv2.CAP_PROP_FPS)
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
duration = frame_count / fps if fps > 0 else 0
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
info_text = f"时长: {duration:.1f}s\n帧率: {fps:.1f}\n分辨率: {width}×{height}"
self.video_info.setText(info_text)
cap.release()
else:
self.video_info.setText("无法读取视频信息")
except Exception as e:
self.video_info.setText(f"错误: {str(e)}")
else:
self.video_info.setText("未选择视频")
def on_resolution_changed(self, text):
resolution_map = {
"高清 (1280x800)": "high",
"中等 (960x720)": "medium",
"标准 (640x480)": "low"
}
mode = resolution_map.get(text, "medium")
self.motion_app.display_settings['resolution_mode'] = mode
def initialize_system_clicked(self):
self.init_button.setEnabled(False)
self.init_button.setText("正在初始化...")
# Enable this after successful initialization
QTimer.singleShot(100, self.do_initialize)
def do_initialize(self):
success = self.motion_app.initialize_all()
if success:
self.init_button.setText("✅ 初始化完成")
self.preview_button.setEnabled(True)
if self.current_video_path:
self.start_button.setEnabled(True)
self.update_system_status()
else:
self.init_button.setText("❌ 初始化失败")
self.init_button.setEnabled(True)
def preview_camera(self):
# Emit signal to show camera preview
pass
def start_comparison_clicked(self):
if self.current_video_path:
self.start_comparison.emit(self.current_video_path)
def update_system_status(self):
import torch
from config import REALSENSE_AVAILABLE, PYGAME_AVAILABLE
status_text = f"""计算设备: {'GPU (CUDA)' if torch.cuda.is_available() else 'CPU'}
摄像头: {'RealSense' if REALSENSE_AVAILABLE else 'USB 摄像头'}
音频: {'启用' if PYGAME_AVAILABLE else '禁用'}
检测器: {'已初始化' if self.motion_app.body_detector else '未初始化'}"""
self.status_info.setText(status_text)