jiateng_ws/widgets/camera_settings_widget.py

1067 lines
46 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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 sys
import os
import logging
from ctypes import *
from math import log10
from camera.CameraParams_const import *
# 添加相机模块路径
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "camera"))
try:
from PySide6.QtWidgets import QMessageBox, QFileDialog
from PySide6.QtCore import Qt, Signal
USE_PYSIDE6 = True
except ImportError:
from PyQt5.QtWidgets import QMessageBox, QFileDialog
from PyQt5.QtCore import Qt
from PyQt5.QtCore import pyqtSignal as Signal
USE_PYSIDE6 = False
# 导入相机相关模块
from camera.MvCameraControl_class import MvCamera
from camera.CamOperation_class import CameraOperation
from camera.MvErrorDefine_const import *
from camera.CameraParams_header import *
from ui.settings_ui import SettingsUI
# 导入相机管理器
from widgets.camera_manager import CameraManager
# 导入本地图像播放器
from utils.local_image_player import LocalImagePlayer
# 导入配置加载器
from utils.config_loader import ConfigLoader
from PySide6.QtCore import QObject
import json
class CameraSettingsWidget(QObject):
"""相机设置控制器,管理相机设置并提供与主窗口相机显示部分的通信
注意这是一个QObject控制器不是QWidget只处理逻辑UI控件需要从父组件获取"""
# 定义信号
signal_camera_connection = Signal(bool, str) # 相机连接状态信号 (是否连接, 错误消息)
signal_camera_params_changed = Signal(float, float, float) # 相机参数变化信号 (曝光, 增益, 帧率)
signal_camera_error = Signal(str) # 相机错误信号
settings_changed = Signal() # 设置变更信号,与 SettingsWindow 兼容
signal_local_mode_changed = Signal(bool) # 本地图像模式变更信号 (是否启用)
def __init__(self, parent=None):
"""初始化相机设置控制器
Args:
parent: 父组件需要包含所有必要的UI控件
"""
# 作为QObject初始化
super().__init__(parent)
# 记录父组件引用
self.parent = parent
# 获取相机管理器实例
self.camera_manager = CameraManager.get_instance()
# 加载配置
try:
self.config_loader = ConfigLoader()
self.camera_config = self.config_loader.get_config("camera")
# 使用 .get() 来安全地访问 'display'
self.display_config = self.camera_config.get("display", {})
except Exception as e:
logging.error(f"加载相机配置文件失败: {e}")
self.camera_config = {}
self.display_config = {}
# 创建本地图像播放器
self.local_player = LocalImagePlayer()
# 本地图像模式状态
self.local_mode_enabled = False
self.is_playing = False
# 初始化日志记录
logging.debug("CameraSettingsWidget初始化开始")
# 从父组件获取所有需要的UI控件
if parent is not None:
logging.info(f"父控件类型: {type(parent).__name__}")
# 从父组件获取所有需要的UI控件
self.camera_combo = getattr(parent, 'camera_combo', None)
self.refresh_button = getattr(parent, 'refresh_button', None)
self.open_button = getattr(parent, 'open_button', None)
self.close_button = getattr(parent, 'close_button', None)
self.test_button = getattr(parent, 'test_button', None)
self.exposure_slider = getattr(parent, 'exposure_slider', None)
self.gain_slider = getattr(parent, 'gain_slider', None)
self.framerate_slider = getattr(parent, 'framerate_slider', None)
self.exposure_value = getattr(parent, 'exposure_value', None)
self.gain_value = getattr(parent, 'gain_value', None)
self.framerate_value = getattr(parent, 'framerate_value', None)
self.get_params_button = getattr(parent, 'get_params_button', None)
self.set_params_button = getattr(parent, 'set_params_button', None)
self.save_camera_button = getattr(parent, 'save_camera_button', None)
self.preview_frame = getattr(parent, 'preview_frame', None)
self.preview_status = getattr(parent, 'preview_status', None)
# 本地图像模式相关控件
self.local_mode_check = getattr(parent, 'local_mode_check', None)
self.folder_path_edit = getattr(parent, 'folder_path_edit', None)
self.folder_path_button = getattr(parent, 'folder_path_button', None)
self.local_framerate_slider = getattr(parent, 'local_framerate_slider', None)
self.local_framerate_value = getattr(parent, 'local_framerate_value', None)
self.loop_playback_check = getattr(parent, 'loop_playback_check', None)
self.play_button = getattr(parent, 'play_button', None)
self.stop_button = getattr(parent, 'stop_button', None)
# 检查是否成功获取到了所有必要的UI控件
if self.camera_combo is None:
logging.error("未能从父组件获取camera_combo")
else:
logging.info(f"获取到父组件的camera_combo: ID={id(self.camera_combo)}, 项目数={self.camera_combo.count()}")
# 测试向下拉框添加项目
try:
self.camera_combo.addItem("控制器初始化测试项")
logging.info(f"成功添加测试项到camera_combo当前项目数={self.camera_combo.count()}")
except Exception as e:
logging.error(f"添加测试项失败: {e}")
else:
logging.error("CameraSettingsWidget必须有一个包含UI控件的父组件")
# 创建缺少的UI控件引用这些引用将为None
self.camera_combo = None
self.refresh_button = None
self.open_button = None
self.close_button = None
self.test_button = None
self.exposure_slider = None
self.gain_slider = None
self.framerate_slider = None
self.exposure_value = None
self.gain_value = None
self.framerate_value = None
self.get_params_button = None
self.set_params_button = None
self.save_camera_button = None
self.preview_frame = None
self.preview_status = None
self.local_mode_check = None
self.folder_path_edit = None
self.folder_path_button = None
self.local_framerate_slider = None
self.local_framerate_value = None
self.loop_playback_check = None
self.play_button = None
self.stop_button = None
# 连接本地图像播放器信号
self.connect_local_player_signals()
# 连接信号和槽
self.connect_signals()
# 初始化相机参数范围 - 注意:曝光参数的实际范围已改为线性映射
self.frame_rate_min = 1.0
self.frame_rate_max = 60.0
# 以下曝光范围仅用于兼容旧代码实际的映射在update_exposure_value方法中实现
self.exposure_min = 0.0 # 0 μs (最小值)
self.exposure_max = 20000.0 # 20000 μs (20ms) - 调整后的最大值
self.gain_min = 0.0
self.gain_max = 15.0
# 从配置加载本地模式设置
self.load_local_mode_settings()
# 更新本地模式UI
self.update_local_mode_ui()
# 枚举设备
self.refresh_devices()
def connect_local_player_signals(self):
"""连接本地图像播放器的信号"""
if self.local_player:
self.local_player.signal_status.connect(self.handle_local_player_status)
self.local_player.signal_error.connect(self.handle_local_player_error)
self.local_player.signal_progress.connect(self.handle_local_player_progress)
self.local_player.signal_frame_ready.connect(self.handle_local_player_frame)
self.local_player.signal_completed.connect(self.handle_local_player_completed)
def connect_signals(self):
"""连接信号和槽"""
# 设备选择和刷新
if hasattr(self, 'refresh_button'):
self.refresh_button.clicked.connect(self.refresh_devices)
# 相机控制按钮
self.open_button.clicked.connect(self.open_camera)
self.close_button.clicked.connect(self.close_camera)
self.test_button.clicked.connect(self.test_camera)
# 参数滑块
self.exposure_slider.valueChanged.connect(self.update_exposure_value)
self.gain_slider.valueChanged.connect(self.update_gain_value)
self.framerate_slider.valueChanged.connect(self.update_frame_rate_value)
# 参数操作按钮
self.get_params_button.clicked.connect(self.get_camera_params)
self.set_params_button.clicked.connect(self.set_camera_params)
self.save_camera_button.clicked.connect(self.save_camera_params)
# 本地图像模式相关按钮和控件
if self.local_mode_check:
self.local_mode_check.stateChanged.connect(self.toggle_local_mode)
if self.folder_path_button:
self.folder_path_button.clicked.connect(self.choose_image_folder)
if self.local_framerate_slider:
self.local_framerate_slider.valueChanged.connect(self.update_local_framerate_value)
if self.loop_playback_check:
self.loop_playback_check.stateChanged.connect(self.update_loop_playback)
if self.play_button:
self.play_button.clicked.connect(self.play_local_images)
if self.stop_button:
self.stop_button.clicked.connect(self.stop_local_images)
def refresh_devices(self):
"""刷新设备列表"""
logging.info("【设备刷新】开始...")
# 直接检查是否能访问正确的combobox
if not hasattr(self, 'camera_combo') or self.camera_combo is None:
logging.error("【设备刷新】无法访问camera_combo尝试从父组件获取")
# 尝试从父组件获取
if hasattr(self, 'parent') and self.parent is not None:
if hasattr(self.parent, 'camera_combo'):
self.camera_combo = self.parent.camera_combo
logging.info(f"【设备刷新】已从父组件获取camera_combo: {id(self.camera_combo)}")
else:
logging.error("【设备刷新】父组件也没有camera_combo")
return
else:
logging.error("【设备刷新】无法获取camera_combo且没有父组件")
return
# 直接测试下拉框是否可用
try:
# 先清理
self.camera_combo.clear()
# 添加一个测试项
self.camera_combo.addItem("刷新中...")
logging.info(f"【设备刷新】成功添加测试项,当前项目数={self.camera_combo.count()}")
# 更新UI
self.camera_combo.update()
self.camera_combo.repaint()
except Exception as e:
logging.error(f"【设备刷新】测试下拉框失败: {e}")
# 1. 枚举设备
try:
devices_info = self.camera_manager.enum_devices()
if not devices_info:
logging.warning("【设备刷新】未发现任何相机设备。")
devices_info = []
except Exception as e:
logging.error(f"【设备刷新】枚举设备时发生错误: {e}")
devices_info = []
# 2. 准备要显示到下拉列表的数据
devList = []
if devices_info:
for device in devices_info:
# 修复:安全获取 display 键,如果不存在则使用默认值
if isinstance(device, dict):
display_name = device.get("display", f"设备 {len(devList)}")
devList.append(display_name)
else:
# 如果整个设备对象不是字典,则添加一个默认名称
devList.append(f"设备 {len(devList)}")
logging.info(f"【设备刷新】找到 {len(devList)} 个设备: {devList}")
else:
devList = ["未发现相机设备"]
# 3. 清空并更新下拉列表
self.camera_combo.clear()
for dev in devList:
self.camera_combo.addItem(dev)
# 4. 更新UI状态
self.update_controls()
def get_selected_device_index(self):
"""获取当前选中的设备索引参考BasicDemo.py的TxtWrapBy实现"""
try:
if not hasattr(self, 'camera_combo') or self.camera_combo is None:
logging.error("无法获取设备索引camera_combo不存在")
return -1
if self.camera_combo.count() == 0:
logging.warning("设备下拉列表为空")
return -1
if self.camera_combo.currentText() == "未发现相机设备":
return -1
current_text = self.camera_combo.currentText()
start = current_text.find("[") + 1
end = current_text.find("]", start)
if start <= 0 or end <= 0 or start >= end:
logging.error(f"设备文本格式不正确: '{current_text}'")
return -1
return int(current_text[start:end])
except Exception as e:
logging.error(f"获取设备索引时出错: {e}, 文本: '{self.camera_combo.currentText() if hasattr(self, 'camera_combo') and self.camera_combo is not None else 'N/A'}'")
return -1
def update_controls(self):
"""更新控件状态"""
is_open = self.camera_manager.isOpen
is_grabbing = self.camera_manager.isGrabbing
# 使用安全的方式访问控件
if hasattr(self, 'refresh_button') and self.refresh_button is not None:
self.refresh_button.setEnabled(not is_open)
if hasattr(self, 'camera_combo') and self.camera_combo is not None:
self.camera_combo.setEnabled(not is_open)
has_valid_selection = self.get_selected_device_index() != -1
if hasattr(self, 'open_button') and self.open_button is not None:
self.open_button.setEnabled(not is_open and has_valid_selection)
if hasattr(self, 'close_button') and self.close_button is not None:
self.close_button.setEnabled(is_open)
if hasattr(self, 'test_button') and self.test_button is not None:
self.test_button.setEnabled(is_open and not is_grabbing)
if hasattr(self, 'get_params_button') and self.get_params_button is not None:
self.get_params_button.setEnabled(is_open)
if hasattr(self, 'set_params_button') and self.set_params_button is not None:
self.set_params_button.setEnabled(is_open)
if hasattr(self, 'save_camera_button') and self.save_camera_button is not None:
self.save_camera_button.setEnabled(is_open)
if hasattr(self, 'exposure_slider') and self.exposure_slider is not None:
self.exposure_slider.setEnabled(is_open)
if hasattr(self, 'gain_slider') and self.gain_slider is not None:
self.gain_slider.setEnabled(is_open)
if hasattr(self, 'framerate_slider') and self.framerate_slider is not None:
self.framerate_slider.setEnabled(is_open)
def open_camera(self):
"""打开相机"""
try:
device_index = self.get_selected_device_index()
if device_index < 0:
error_msg = "未选择相机设备"
self.signal_camera_error.emit(error_msg)
return False
logging.info(f"尝试打开相机设备,索引: {device_index}")
# 使用相机管理器打开设备
success = self.camera_manager.open_device(device_index)
if success:
logging.info(f"相机已成功打开")
# 更新按钮状态
self.update_controls()
# 向父窗口发送连接状态变化信号
self.signal_camera_connection.emit(True, "")
# 更新配置
config_loader = ConfigLoader.get_instance()
config_loader.set_value('camera.enabled', True)
config_loader.save_config()
# 通知设置已更改
self.settings_changed.emit()
return True
else:
error_msg = "打开相机失败"
self.signal_camera_error.emit(error_msg)
return False
except Exception as e:
error_msg = f"打开相机时发生异常: {str(e)}"
logging.error(error_msg)
self.signal_camera_error.emit(error_msg)
return False
def close_camera(self):
"""关闭相机"""
try:
logging.info("尝试关闭相机")
# 使用相机管理器关闭设备
self.camera_manager.close_device()
# 更新按钮状态
self.update_controls()
# 向父窗口发送连接状态变化信号
self.signal_camera_connection.emit(False, "")
# 更新配置
config_loader = ConfigLoader.get_instance()
config_loader.set_value('camera.enabled', False)
config_loader.save_config()
# 通知设置已更改
self.settings_changed.emit()
logging.info("相机已关闭")
return True
except Exception as e:
error_msg = f"关闭相机时发生异常: {str(e)}"
logging.error(error_msg)
self.signal_camera_error.emit(error_msg)
return False
def test_camera(self):
"""测试相机(在预览窗口显示图像)"""
if not self.camera_manager.isOpen:
return
if self.camera_manager.isGrabbing:
self.camera_manager.stop_grabbing()
self.test_button.setText("开始预览")
else:
try:
window_id = int(self.preview_frame.winId())
if self.camera_manager.start_grabbing(window_id):
self.test_button.setText("停止预览")
except Exception as e:
parent_widget = self.parent if hasattr(self, "parent") else None
QMessageBox.warning(parent_widget, "错误", f"开始预览失败: {e}")
self.update_controls()
def update_exposure_value(self, value):
"""更新曝光值显示(使用线性映射)"""
# 直接使用滑块值0-100映射到曝光范围1000-50000 μs
# 使用UI中设置的实际曝光范围
min_exp = 0 # 0 μs
max_exp = 50000
# 线性映射
actual_value = min_exp + (value * (max_exp - min_exp) / 100.0)
# 防止溢出
if actual_value > max_exp:
actual_value = max_exp
logging.warning(f"曝光值过大,已限制为{actual_value}μs")
# 显示整数值μs级别不需要小数点
self.exposure_value.setText(f"{int(actual_value)} μs")
logging.debug(f"曝光滑块值: {value}, 映射后曝光值: {int(actual_value)}μs")
def update_gain_value(self, value):
"""更新增益值显示"""
actual_value = self.gain_min + (value / 100.0) * (self.gain_max - self.gain_min)
self.gain_value.setText(f"{actual_value:.1f} dB")
def update_frame_rate_value(self, value):
"""更新帧率值显示"""
actual_value = self.frame_rate_min + (value / 100.0) * (self.frame_rate_max - self.frame_rate_min)
self.framerate_value.setText(f"{actual_value:.1f} fps")
def get_camera_params(self):
"""获取相机参数"""
if not self.camera_manager.isOpen:
return
params = self.camera_manager.get_parameters()
if not params:
return
exposure_time, gain, frame_rate = params
# 使用线性映射计算曝光滑块值
min_exp = 0 # 0 μs
max_exp = 20000 # 20000 μs
# 将获取到的曝光时间限制在有效范围内
if exposure_time < min_exp:
exposure_time = min_exp
elif exposure_time > max_exp:
exposure_time = max_exp
# 线性映射到滑块值0-100
exposure_slider_value = int(((exposure_time - min_exp) / (max_exp - min_exp)) * 100)
# 确保滑块值在有效范围内
if exposure_slider_value < 0:
exposure_slider_value = 0
elif exposure_slider_value > 100:
exposure_slider_value = 100
self.exposure_slider.setValue(exposure_slider_value)
# 其他参数仍使用原始映射方法
self.gain_slider.setValue(int(((gain - self.gain_min) / (self.gain_max - self.gain_min)) * 100))
self.framerate_slider.setValue(int(((frame_rate - self.frame_rate_min) / (self.frame_rate_max - self.frame_rate_min)) * 100))
logging.debug(f"获取相机参数: 曝光时间={exposure_time}μs -> 滑块值={exposure_slider_value}, 增益={gain}dB, 帧率={frame_rate}fps")
self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate)
def set_camera_params(self):
"""设置相机参数"""
if not self.camera_manager.isOpen:
return
# 使用线性映射计算曝光时间
min_exp = 0 # 0 μs
max_exp = 50000 # 50000 μs
# 根据滑块值0-100线性映射到曝光时间
slider_value = self.exposure_slider.value()
exposure_time = min_exp + (slider_value * (max_exp - min_exp) / 100.0)
# 确保曝光时间在有效范围内
if exposure_time < min_exp:
exposure_time = min_exp
elif exposure_time > max_exp:
exposure_time = max_exp
# 增益和帧率保持原来的映射方式
gain = self.gain_min + (self.gain_slider.value() / 100.0) * (self.gain_max - self.gain_min)
frame_rate = self.frame_rate_min + (self.framerate_slider.value() / 100.0) * (self.frame_rate_max - self.frame_rate_min)
logging.debug(f"设置相机参数: 曝光滑块值={slider_value} -> 曝光时间={exposure_time}μs, 增益={gain}dB, 帧率={frame_rate}fps")
self.camera_manager.set_parameters(frame_rate, exposure_time, gain)
self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate)
def save_camera_params(self):
"""保存相机参数到配置文件"""
if not self.camera_manager.isOpen:
return
exposure = self.exposure_slider.value()
gain = self.gain_slider.value()
frame_rate = self.framerate_slider.value()
parent_widget = self.parent if hasattr(self, "parent") else None
if self.camera_manager.save_params_to_config(exposure, gain, frame_rate):
QMessageBox.information(parent_widget, "成功", "相机参数已保存")
self.settings_changed.emit()
else:
QMessageBox.critical(parent_widget, "错误", "保存相机参数失败")
def closeEvent(self, event):
"""窗口关闭事件"""
if self.camera_manager.isOpen:
self.camera_manager.close_device()
super().closeEvent(event)
def load_local_mode_settings(self):
"""加载本地图像模式的配置"""
try:
# 使用 .get() 防止因缺少 local_mode 键而崩溃
config = self.camera_config.get("local_mode", {})
self.local_mode_enabled = config.get("enabled", False)
self.local_player.framerate = config.get("framerate", 15)
self.local_player.loop = config.get("loop", True)
folder_path = config.get("folder_path", "")
# 更新UI组件
if self.local_mode_check:
self.local_mode_check.setChecked(self.local_mode_enabled)
if self.local_framerate_slider:
self.local_framerate_slider.setValue(self.local_player.framerate)
if self.loop_playback_check:
self.loop_playback_check.setChecked(self.local_player.loop)
if self.folder_path_edit:
self.folder_path_edit.setText(folder_path)
self.local_player.folder_path = folder_path
logging.info(f"加载本地图像模式设置: 启用={self.local_mode_enabled}, 帧率={self.local_player.framerate}, 循环={self.local_player.loop}")
# 触发模式变更信号,通知主窗口和其他组件
if self.local_mode_enabled:
self.signal_local_mode_changed.emit(True)
except Exception as e:
logging.error(f"加载本地图像模式设置时发生错误: {e}")
def save_local_mode_settings(self):
"""保存本地图像模式的配置"""
try:
# 获取当前设置
enabled = self.local_mode_enabled
folder_path = self.folder_path_edit.text() if self.folder_path_edit else ""
framerate = self.local_framerate_slider.value() if self.local_framerate_slider else 15
loop = self.loop_playback_check.isChecked() if self.loop_playback_check else True
# 创建配置字典
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "app_config.json")
# 读取现有配置(如果存在)
config = {}
if os.path.exists(config_path):
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
except json.JSONDecodeError:
logging.warning(f"配置文件格式错误: {config_path}")
config = {}
except Exception as e:
logging.warning(f"读取配置文件失败: {e}")
config = {}
# 更新本地图像模式设置
if "local_image_mode" not in config:
config["local_image_mode"] = {}
config["local_image_mode"].update({
"enabled": enabled,
"folder_path": folder_path,
"framerate": framerate,
"loop": loop
})
# 保存配置
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=4, ensure_ascii=False)
logging.info(f"已保存本地图像模式设置: enabled={enabled}, folder='{folder_path}', "
f"framerate={framerate}, loop={loop}")
return True
except Exception as e:
logging.error(f"保存本地图像模式设置失败: {e}")
return False
def update_local_mode_ui(self):
"""更新本地图像模式UI状态"""
# 获取当前复选框状态(如果复选框存在)
enabled = self.local_mode_check.isChecked() if self.local_mode_check else self.local_mode_enabled
# 更新内部状态
if enabled != self.local_mode_enabled:
logging.info(f"本地图像模式状态变化: {self.local_mode_enabled} -> {enabled}")
self.local_mode_enabled = enabled
# 打印日志,帮助调试
logging.debug(f"更新本地图像模式UI: 启用={enabled}, 按钮状态={self.folder_path_button is not None}")
# 更新相关控件状态
if self.folder_path_edit:
self.folder_path_edit.setEnabled(enabled)
logging.debug(f"文件夹路径编辑框已设置为: {enabled}")
if self.folder_path_button:
self.folder_path_button.setEnabled(enabled)
logging.debug(f"文件夹选择按钮已设置为: {enabled}")
if self.local_framerate_slider:
self.local_framerate_slider.setEnabled(enabled)
if self.loop_playback_check:
self.loop_playback_check.setEnabled(enabled)
if self.play_button:
has_folder = bool(self.folder_path_edit and self.folder_path_edit.text())
self.play_button.setEnabled(enabled and has_folder)
logging.debug(f"播放按钮已设置为: {enabled and has_folder}, 有文件夹={has_folder}")
if self.stop_button:
self.stop_button.setEnabled(enabled and self.is_playing)
# 强制更新UI
if self.parent:
self.parent.update()
def toggle_local_mode(self, state):
"""切换本地图像模式"""
# 修复直接比较整数值Qt.Checked.value 是 2
is_checked = (state == 2) # 或者 state == Qt.Checked.value
# 停止可能正在进行的播放
if self.is_playing:
self.stop_local_images()
# 打印详细日志,帮助调试
logging.info(f"切换本地图像模式: {state} -> {is_checked}")
# 先设置相机管理器的本地模式
self.camera_manager.set_local_mode(is_checked)
# 更新本地模式状态
self.local_mode_enabled = is_checked
# 更新UI状态
self.update_local_mode_ui()
# 如果打开本地模式,关闭相机;如果关闭本地模式,尝试打开之前的相机
if is_checked:
# 如果相机正在工作,先关闭它
if self.camera_manager.isOpen and not self.camera_manager.local_mode:
self.close_camera()
else:
# 如果存在实际相机,尝试打开最后选择的相机
if self.camera_manager.has_real_camera:
# 获取当前选择的设备索引
device_index = self.get_selected_device_index()
if device_index >= 0:
# 关闭虚拟相机
if self.camera_manager.isOpen:
self.camera_manager.close_device()
# 开启实际相机
self.open_camera()
# 发送模式变更信号
self.signal_local_mode_changed.emit(is_checked)
# 保存设置
self.save_local_mode_settings()
logging.info(f"本地图像模式已{'启用' if is_checked else '禁用'}")
# 刷新设备列表
self.refresh_devices()
def choose_image_folder(self):
"""选择图像文件夹"""
# 检查复选框状态而不是类属性
is_enabled = self.local_mode_check.isChecked() if self.local_mode_check else False
# 即使控件状态可能不对,也尝试打开文件选择对话框
logging.info(f"选择图像文件夹: 本地模式启用={is_enabled}, 按钮状态={self.folder_path_button is not None}")
# 获取当前设置的文件夹路径作为初始目录
current_path = self.folder_path_edit.text() if self.folder_path_edit else ""
if not current_path or not os.path.isdir(current_path):
current_path = os.path.expanduser("~") # 默认使用用户主目录
# 打开文件夹选择对话框
folder_path = QFileDialog.getExistingDirectory(self.parent, "选择图像文件夹", current_path)
if folder_path:
logging.info(f"已选择图像文件夹: {folder_path}")
# 更新路径显示
if self.folder_path_edit:
self.folder_path_edit.setText(folder_path)
# 设置到本地播放器
if self.local_player:
self.local_player.set_folder(folder_path)
# 更新UI状态
self.update_local_mode_ui()
# 保存设置
self.save_local_mode_settings()
else:
logging.info("未选择任何文件夹")
def update_local_framerate_value(self, value):
"""更新本地模式帧率显示"""
if self.local_framerate_value:
self.local_framerate_value.setText(f"{value} fps")
# 更新本地播放器帧率
if self.local_player:
self.local_player.set_framerate(value)
def update_loop_playback(self, state):
"""更新循环播放设置"""
loop = (state == Qt.Checked)
# 更新本地播放器设置
if self.local_player:
self.local_player.set_loop(loop)
# 保存设置
self.save_local_mode_settings()
def play_local_images(self):
"""播放本地图像序列"""
if not self.local_mode_enabled:
return
if self.is_playing:
# 如果正在播放,则暂停/恢复
if self.local_player:
if self.local_player.is_paused:
self.local_player.resume_playback()
self.play_button.setText("暂停")
else:
self.local_player.pause_playback()
self.play_button.setText("继续")
else:
# 开始播放
if self.local_player:
folder_path = self.folder_path_edit.text() if self.folder_path_edit else ""
if not folder_path:
QMessageBox.warning(self.parent, "错误", "请先选择图像文件夹")
return
# 设置播放参数
framerate = self.local_framerate_slider.value() if self.local_framerate_slider else 15
loop = self.loop_playback_check.isChecked() if self.loop_playback_check else True
self.local_player.set_framerate(framerate)
self.local_player.set_loop(loop)
# 确保文件夹路径已设置
if not self.local_player.folder_path:
self.local_player.set_folder(folder_path)
# 开始播放
if self.local_player.start_playback():
self.is_playing = True
self.play_button.setText("暂停")
self.stop_button.setEnabled(True)
# 更新预览状态
if self.preview_status:
self.preview_status.setText("播放中...")
# 关键修复发送信号通知主窗口更新UI显示相机画面
logging.warning("====> 开始播放本地图像序列发送信号通知主窗口更新UI")
# 先确保本地模式已启用
self.camera_manager.set_local_mode(True)
# 发送信号通知主窗口更新UI
self.signal_local_mode_changed.emit(True)
logging.info(f"开始播放本地图像序列,帧率={framerate} fps循环={loop}")
def stop_local_images(self):
"""停止播放本地图像序列"""
if self.local_player and self.is_playing:
self.local_player.stop_playback()
self.is_playing = False
self.play_button.setText("播放")
self.stop_button.setEnabled(False)
# 更新预览状态
if self.preview_status:
self.preview_status.setText("已停止")
logging.info("停止播放本地图像序列")
def handle_local_player_status(self, message):
"""处理本地播放器状态消息"""
if self.preview_status:
self.preview_status.setText(message)
def handle_local_player_error(self, message):
"""处理本地播放器错误消息"""
logging.error(f"本地图像播放器错误: {message}")
if self.preview_status:
self.preview_status.setText(f"错误: {message}")
QMessageBox.warning(self.parent, "错误", message)
def handle_local_player_progress(self, current, total):
"""处理本地播放器进度消息"""
if self.preview_status:
self.preview_status.setText(f"播放中... {current}/{total}")
def handle_local_player_frame(self, frame):
"""处理本地播放器帧准备好消息"""
# 确保本地模式已启用
if not self.local_mode_enabled or not self.local_player:
return
# 添加明显的调试日志
logging.warning(f"====> 接收到本地图像帧: {frame.shape if frame is not None else 'None'}")
# 获取窗口ID (默认方法)
window_id = int(self.preview_frame.winId()) if self.preview_frame else 0
# 将帧传递给camera_manager处理
logging.warning(f"====> 传递给camera_manager处理窗口ID: {window_id}")
success = self.camera_manager.handle_local_frame(frame, window_id)
if not success:
logging.error("相机管理器处理本地图像帧失败")
# 修复:查找主窗口的改进方式
try:
# 获取应用实例
from PySide6.QtWidgets import QApplication
app = QApplication.instance()
# 查找主窗口
from widgets.main_window import MainWindow
main_window = None
# 遍历所有顶层窗口
for window in app.topLevelWidgets():
logging.warning(f"====> 找到顶层窗口: {window.__class__.__name__}")
if isinstance(window, MainWindow):
main_window = window
logging.warning("====> 找到 MainWindow 实例")
break
# 如果通过顶层窗口没找到,尝试通过父窗口查找
if not main_window and hasattr(self, 'parent') and self.parent:
parent = self.parent
# 循环查找父窗口,直到找到 MainWindow 或达到顶层
while parent:
logging.warning(f"====> 检查父窗口: {parent.__class__.__name__}")
if isinstance(parent, MainWindow):
main_window = parent
logging.warning("====> 在父窗口链中找到 MainWindow 实例")
break
if hasattr(parent, 'parent'):
parent = parent.parent()
else:
break
# 如果找到了主窗口,更新相机显示
if main_window:
logging.warning(f"====> MainWindow 实例 ID: {id(main_window)}")
# 添加详细调试日志,查看主窗口的属性
logging.warning(f"====> MainWindow 的属性: {dir(main_window)}")
# 检查 camera_display 是否存在
has_camera_display = hasattr(main_window, 'camera_display')
logging.warning(f"====> MainWindow 是否有 camera_display 属性: {has_camera_display}")
if has_camera_display:
camera_display = main_window.camera_display
logging.warning(f"====> camera_display 对象 ID: {id(camera_display) if camera_display else 'None'}")
logging.warning(f"====> camera_display 是否为 None: {camera_display is None}")
# 检查 material_placeholder 是否存在
has_placeholder = hasattr(main_window, 'material_placeholder')
logging.warning(f"====> MainWindow 是否有 material_placeholder 属性: {has_placeholder}")
# 检查camera_enabled状态
current_camera_enabled = getattr(main_window, 'camera_enabled', False)
logging.warning(f"====> 当前 camera_enabled 状态: {current_camera_enabled}")
# 关键修复确保camera_enabled为True
if hasattr(main_window, 'camera_enabled'):
logging.warning("====> 设置 camera_enabled 为 True")
main_window.camera_enabled = True
# 检查是否已调用过 init_camera_display 方法
if hasattr(main_window, 'init_camera_display'):
logging.warning("====> MainWindow 有 init_camera_display 方法")
# 尝试再次调用初始化方法
logging.warning("====> 尝试再次调用 init_camera_display 方法")
main_window.init_camera_display()
else:
logging.error("MainWindow 没有 init_camera_display 方法")
# 再次检查camera_display是否已创建
if hasattr(main_window, 'camera_display') and main_window.camera_display:
logging.warning("====> init_camera_display后camera_display已创建")
# 确保相机显示组件可见
main_window.camera_display.setVisible(True)
main_window.camera_display.raise_()
# 隐藏占位符
if hasattr(main_window, 'material_placeholder') and main_window.material_placeholder:
main_window.material_placeholder.setVisible(False)
# 调用update_local_frame方法
if hasattr(main_window.camera_display, 'update_local_frame'):
main_window.camera_display.update_local_frame(frame)
logging.warning("====> 直接调用camera_display.update_local_frame成功")
else:
logging.error("camera_display没有update_local_frame方法")
# 强制更新UI
main_window.update_camera_ui(True)
else:
logging.error("MainWindow中没有找到camera_display组件或它仍然是None")
else:
logging.error("未找到MainWindow实例")
except Exception as e:
logging.error(f"尝试直接更新主窗口相机组件失败: {str(e)}")
import traceback
logging.error(traceback.format_exc())
# 保留原来的第二种方法作为备选
try:
# 尝试使用Qt方式更新图像
if not success:
from PySide6.QtGui import QImage, QPixmap
# 将OpenCV的RGB图像转换为Qt的QImage
height, width, channel = frame.shape
bytes_per_line = channel * width
q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888)
# 创建QPixmap
pixmap = QPixmap.fromImage(q_img)
# 获取主窗口实例
from PySide6.QtWidgets import QApplication
app = QApplication.instance()
from widgets.main_window import MainWindow
for window in app.topLevelWidgets():
if isinstance(window, MainWindow):
main_window = window
# 确保camera_enabled为True
if hasattr(main_window, 'camera_enabled'):
main_window.camera_enabled = True
# 尝试初始化相机显示
if hasattr(main_window, 'init_camera_display'):
main_window.init_camera_display()
if hasattr(main_window, 'camera_display') and main_window.camera_display:
# 确保相机显示组件可见
main_window.camera_display.setVisible(True)
main_window.camera_display.raise_()
# 给相机显示组件设置属性,强制更新
main_window.camera_display._current_pixmap = pixmap
main_window.camera_display.update() # 强制重绘
logging.warning("====> 通过设置_current_pixmap并强制更新成功")
# 隐藏占位符
if hasattr(main_window, 'material_placeholder'):
main_window.material_placeholder.setVisible(False)
# 强制更新UI
main_window.update_camera_ui(True)
return
except Exception as e:
logging.error(f"尝试使用Qt方式更新图像失败: {e}")
import traceback
logging.error(traceback.format_exc())
def handle_local_player_completed(self):
"""处理本地播放器播放完成消息"""
if not self.local_player.loop:
self.is_playing = False
self.play_button.setText("播放")
# 更新预览状态
if self.preview_status:
self.preview_status.setText("播放完成")