jiateng_ws/widgets/camera_settings_widget.py

506 lines
22 KiB
Python
Raw Normal View History

2025-06-07 10:45:09 +08:00
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
2025-06-07 10:45:09 +08:00
from PySide6.QtCore import Qt, Signal
USE_PYSIDE6 = True
except ImportError:
from PyQt5.QtWidgets import QMessageBox
2025-06-07 10:45:09 +08:00
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
2025-06-27 15:14:30 +08:00
from PySide6.QtCore import QObject
class CameraSettingsWidget(QObject):
"""相机设置控制器,管理相机设置并提供与主窗口相机显示部分的通信
注意这是一个QObject控制器不是QWidget只处理逻辑UI控件需要从父组件获取"""
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
# 定义信号
2025-06-07 10:45:09 +08:00
signal_camera_connection = Signal(bool, str) # 相机连接状态信号 (是否连接, 错误消息)
signal_camera_params_changed = Signal(float, float, float) # 相机参数变化信号 (曝光, 增益, 帧率)
signal_camera_error = Signal(str) # 相机错误信号
settings_changed = Signal() # 设置变更信号,与 SettingsWindow 兼容
2025-06-07 10:45:09 +08:00
def __init__(self, parent=None):
2025-06-27 15:14:30 +08:00
"""初始化相机设置控制器
Args:
parent: 父组件需要包含所有必要的UI控件
"""
# 作为QObject初始化
2025-06-07 10:45:09 +08:00
super().__init__(parent)
2025-06-27 15:14:30 +08:00
# 记录父组件引用
self.parent = parent
2025-06-07 10:45:09 +08:00
# 获取相机管理器实例
self.camera_manager = CameraManager.get_instance()
2025-06-27 15:14:30 +08:00
# 初始化日志记录
logging.debug("CameraSettingsWidget初始化开始")
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
# 从父组件获取所有需要的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
2025-06-07 10:45:09 +08:00
# 连接信号和槽
self.connect_signals()
2025-06-27 15:14:30 +08:00
# 初始化相机参数范围 - 注意:曝光参数的实际范围已改为线性映射
2025-06-07 10:45:09 +08:00
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) - 调整后的最大值
2025-06-07 10:45:09 +08:00
self.gain_min = 0.0
self.gain_max = 15.0
# 枚举设备
self.refresh_devices()
def connect_signals(self):
"""连接信号和槽"""
# 设备选择和刷新
2025-06-27 15:14:30 +08:00
if hasattr(self, 'refresh_button'):
self.refresh_button.clicked.connect(self.refresh_devices)
2025-06-07 10:45:09 +08:00
# 相机控制按钮
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)
2025-06-27 15:14:30 +08:00
2025-06-07 10:45:09 +08:00
def refresh_devices(self):
"""刷新设备列表"""
2025-06-27 15:14:30 +08:00
logging.info("【设备刷新】开始...")
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
# 直接检查是否能访问正确的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}")
2025-06-07 10:45:09 +08:00
def get_selected_device_index(self):
2025-06-27 15:14:30 +08:00
"""获取当前选中的设备索引参考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'}'")
2025-06-07 10:45:09 +08:00
return -1
def update_controls(self):
"""更新控件状态"""
is_open = self.camera_manager.isOpen
is_grabbing = self.camera_manager.isGrabbing
2025-06-27 15:14:30 +08:00
# 使用安全的方式访问控件
if hasattr(self, 'refresh_button') and self.refresh_button is not None:
self.refresh_button.setEnabled(not is_open)
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
if hasattr(self, 'camera_combo') and self.camera_combo is not None:
self.camera_combo.setEnabled(not is_open)
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
has_valid_selection = self.get_selected_device_index() != -1
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
if hasattr(self, 'open_button') and self.open_button is not None:
self.open_button.setEnabled(not is_open and has_valid_selection)
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
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)
2025-06-07 10:45:09 +08:00
def open_camera(self):
2025-06-27 15:14:30 +08:00
"""打开相机参考BasicDemo.py实现"""
2025-06-07 10:45:09 +08:00
if self.camera_manager.isOpen:
return
device_index = self.get_selected_device_index()
if device_index < 0:
# 使用父组件作为消息框的父级
parent_widget = self.parent if hasattr(self, "parent") else None
QMessageBox.warning(parent_widget, "错误", "请先选择一个有效的相机设备!")
2025-06-07 10:45:09 +08:00
return
2025-06-27 15:14:30 +08:00
self.open_button.setEnabled(False)
self.open_button.setText("打开中...")
2025-06-07 10:45:09 +08:00
2025-06-27 15:14:30 +08:00
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, "打开相机失败")
parent_widget = self.parent if hasattr(self, "parent") else None
QMessageBox.warning(parent_widget, "错误", "打开相机失败")
2025-06-27 15:14:30 +08:00
except Exception as e:
parent_widget = self.parent if hasattr(self, "parent") else None
QMessageBox.critical(parent_widget, "错误", f"打开相机时发生异常: {str(e)}")
2025-06-27 15:14:30 +08:00
finally:
self.open_button.setText("打开相机")
2025-06-07 10:45:09 +08:00
self.update_controls()
def close_camera(self):
"""关闭相机"""
if not self.camera_manager.isOpen:
return
2025-06-27 15:14:30 +08:00
self.camera_manager.close_device()
self.signal_camera_connection.emit(False, "")
2025-06-07 10:45:09 +08:00
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())
2025-06-27 15:14:30 +08:00
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}")
2025-06-27 15:14:30 +08:00
self.update_controls()
2025-06-07 10:45:09 +08:00
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")
2025-06-07 10:45:09 +08:00
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)
2025-06-07 10:45:09 +08:00
# 其他参数仍使用原始映射方法
2025-06-27 15:14:30 +08:00
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))
2025-06-07 10:45:09 +08:00
logging.debug(f"获取相机参数: 曝光时间={exposure_time}μs -> 滑块值={exposure_slider_value}, 增益={gain}dB, 帧率={frame_rate}fps")
2025-06-07 10:45:09 +08:00
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
2025-06-07 10:45:09 +08:00
# 根据滑块值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
# 增益和帧率保持原来的映射方式
2025-06-07 10:45:09 +08:00
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")
2025-06-27 15:14:30 +08:00
self.camera_manager.set_parameters(frame_rate, exposure_time, gain)
self.signal_camera_params_changed.emit(exposure_time, gain, frame_rate)
2025-06-07 10:45:09 +08:00
def save_camera_params(self):
"""保存相机参数到配置文件"""
2025-06-07 10:45:09 +08:00
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
2025-06-27 15:14:30 +08:00
if self.camera_manager.save_params_to_config(exposure, gain, frame_rate):
QMessageBox.information(parent_widget, "成功", "相机参数已保存")
2025-06-27 15:14:30 +08:00
self.settings_changed.emit()
else:
QMessageBox.critical(parent_widget, "错误", "保存相机参数失败")
2025-06-07 10:45:09 +08:00
def closeEvent(self, event):
"""窗口关闭事件"""
if self.camera_manager.isOpen:
self.camera_manager.close_device()
2025-06-27 15:14:30 +08:00
super().closeEvent(event)