jiateng_ws/widgets/camera_settings_widget.py
2025-06-27 15:14:30 +08:00

459 lines
20 KiB
Python
Raw 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
from PySide6.QtCore import Qt, Signal
USE_PYSIDE6 = True
except ImportError:
from PyQt5.QtWidgets import QMessageBox
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 PySide6.QtCore import QObject
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 兼容
def __init__(self, parent=None):
"""初始化相机设置控制器
Args:
parent: 父组件需要包含所有必要的UI控件
"""
# 作为QObject初始化
super().__init__(parent)
# 记录父组件引用
self.parent = parent
# 获取相机管理器实例
self.camera_manager = CameraManager.get_instance()
# 初始化日志记录
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)
# 检查是否成功获取到了所有必要的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.connect_signals()
# 初始化相机参数范围
self.frame_rate_min = 1.0
self.frame_rate_max = 60.0
self.exposure_min = 20.0
self.exposure_max = 1000000.0
self.gain_min = 0.0
self.gain_max = 15.0
# 枚举设备
self.refresh_devices()
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)
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:
devList.append(device["display"])
logging.info(f"【设备刷新】找到 {len(devList)} 个设备: {devList}")
else:
devList.append("未发现相机设备")
logging.info(f"【设备刷新】将显示默认值 '未发现相机设备'")
# 3. 更新UI上的下拉列表
try:
if hasattr(self, 'camera_combo') and self.camera_combo is not None:
logging.info(f"【设备刷新】开始更新下拉列表,当前状态: 项目数={self.camera_combo.count()}, 是否可见={self.camera_combo.isVisible()}")
self.camera_combo.blockSignals(True)
self.camera_combo.clear()
# 确保项目数清零
if self.camera_combo.count() > 0:
logging.warning(f"【设备刷新】clear()后项目数仍为 {self.camera_combo.count()}")
# 直接添加项目 - 单个添加,避免批量添加可能的问题
for item in devList:
self.camera_combo.addItem(item)
logging.debug(f"【设备刷新】已添加项目: {item}")
# 确保设置当前项目
if self.camera_combo.count() > 0:
self.camera_combo.setCurrentIndex(0)
logging.info(f"【设备刷新】已设置当前索引为0显示文本: {self.camera_combo.currentText()}")
else:
logging.error("【设备刷新】未能添加任何项目到下拉列表")
self.camera_combo.blockSignals(False)
# 强制更新UI
self.camera_combo.update()
self.camera_combo.repaint()
# 确保ComboBox有足够的尺寸显示内容
self.camera_combo.adjustSize()
logging.info(f"【设备刷新】下拉列表更新完成。当前项目数: {self.camera_combo.count()}, 当前文本: {self.camera_combo.currentText()}")
else:
logging.error("【设备刷新】无法更新下拉列表camera_combo不存在")
except Exception as e:
logging.error(f"【设备刷新】更新下拉列表时发生错误: {e}")
# 4. 更新其他控件的状态
try:
self.update_controls()
logging.info("【设备刷新】控件状态已更新。")
except Exception as e:
logging.error(f"【设备刷新】更新控件状态时发生错误: {e}")
# 5. 如果下拉列表仍然为空,尝试最后一次强制添加
try:
if hasattr(self, 'camera_combo') and self.camera_combo is not None:
if self.camera_combo.count() == 0:
logging.warning("【设备刷新】下拉列表仍然为空,尝试强制添加项目")
self.camera_combo.addItem("未发现相机设备(强制添加)")
self.camera_combo.setCurrentIndex(0)
self.camera_combo.update()
self.camera_combo.repaint()
except Exception as e:
logging.error(f"【设备刷新】强制添加项目时发生错误: {e}")
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):
"""打开相机参考BasicDemo.py实现"""
if self.camera_manager.isOpen:
return
device_index = self.get_selected_device_index()
if device_index < 0:
QMessageBox.warning(self, "错误", "请先选择一个有效的相机设备!")
return
self.open_button.setEnabled(False)
self.open_button.setText("打开中...")
try:
success = self.camera_manager.open_device(device_index)
if success:
self.get_camera_params()
self.signal_camera_connection.emit(True, "")
else:
self.signal_camera_connection.emit(False, "打开相机失败")
QMessageBox.warning(self, "错误", "打开相机失败")
except Exception as e:
QMessageBox.critical(self, "错误", f"打开相机时发生异常: {str(e)}")
finally:
self.open_button.setText("打开相机")
self.update_controls()
def close_camera(self):
"""关闭相机"""
if not self.camera_manager.isOpen:
return
self.camera_manager.close_device()
self.signal_camera_connection.emit(False, "")
self.update_controls()
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:
QMessageBox.warning(self, "错误", f"开始预览失败: {e}")
self.update_controls()
def update_exposure_value(self, value):
"""更新曝光值显示"""
min_log = log10(self.exposure_min)
max_log = log10(self.exposure_max)
log_range = max_log - min_log
log_value = min_log + (value / 100.0) * log_range
actual_value = 10 ** log_value
self.exposure_value.setText(f"{actual_value:.1f} μ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_log_exp = log10(self.exposure_min)
max_log_exp = log10(self.exposure_max)
self.exposure_slider.setValue(int(((log10(exposure_time) - min_log_exp) / (max_log_exp - min_log_exp)) * 100))
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))
self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate)
def set_camera_params(self):
"""设置相机参数"""
if not self.camera_manager.isOpen:
return
min_log = log10(self.exposure_min)
max_log = log10(self.exposure_max)
log_range = max_log - min_log
log_value = min_log + (self.exposure_slider.value() / 100.0) * log_range
exposure_time = 10 ** log_value
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)
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()
if self.camera_manager.save_params_to_config(exposure, gain, frame_rate):
QMessageBox.information(self, "成功", "相机参数已保存")
self.settings_changed.emit()
else:
QMessageBox.critical(self, "错误", "保存相机参数失败")
def closeEvent(self, event):
"""窗口关闭事件"""
if self.camera_manager.isOpen:
self.camera_manager.close_device()
super().closeEvent(event)