404 lines
12 KiB
Python
404 lines
12 KiB
Python
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)
|
||
|
||
|
||
|