2025-06-07 10:45:09 +08:00
|
|
|
|
import sys
|
|
|
|
|
|
import os
|
|
|
|
|
|
import logging
|
2025-07-07 15:33:56 +08:00
|
|
|
|
import numpy as np
|
2025-06-07 10:45:09 +08:00
|
|
|
|
from ctypes import *
|
|
|
|
|
|
|
|
|
|
|
|
# 确定使用哪个UI框架
|
|
|
|
|
|
try:
|
2025-06-30 19:40:02 +08:00
|
|
|
|
from PySide6.QtWidgets import QWidget, QVBoxLayout, QFrame, QSizePolicy
|
2025-07-07 15:33:56 +08:00
|
|
|
|
from PySide6.QtCore import Qt, Signal, QSize, QTimer
|
|
|
|
|
|
from PySide6.QtGui import QPalette, QColor, QImage, QPixmap
|
2025-06-07 10:45:09 +08:00
|
|
|
|
except ImportError:
|
2025-06-30 19:40:02 +08:00
|
|
|
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QFrame, QSizePolicy
|
2025-06-30 18:40:50 +08:00
|
|
|
|
from PyQt5.QtCore import Qt, pyqtSignal as Signal, QSize
|
2025-07-07 15:33:56 +08:00
|
|
|
|
from PyQt5.QtGui import QPalette, QColor, QImage, QPixmap
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加相机模块路径
|
|
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "camera"))
|
|
|
|
|
|
|
|
|
|
|
|
# 导入相机相关模块
|
|
|
|
|
|
from camera.MvCameraControl_class import MvCamera
|
|
|
|
|
|
from camera.CamOperation_class import CameraOperation
|
|
|
|
|
|
from camera.MvErrorDefine_const import *
|
|
|
|
|
|
from camera.CameraParams_header import *
|
|
|
|
|
|
|
|
|
|
|
|
# 导入相机管理器
|
|
|
|
|
|
from widgets.camera_manager import CameraManager
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CameraDisplayWidget(QWidget):
|
|
|
|
|
|
"""相机显示组件,用于在主窗口显示相机图像"""
|
|
|
|
|
|
|
|
|
|
|
|
# 状态信号
|
|
|
|
|
|
signal_camera_status = Signal(bool, str) # 相机状态信号 (是否连接, 状态消息)
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取相机管理器实例
|
|
|
|
|
|
self.camera_manager = CameraManager.get_instance()
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化UI
|
|
|
|
|
|
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
# 设置显示事件处理
|
|
|
|
|
|
self.showEvent = self.on_show_event
|
|
|
|
|
|
|
|
|
|
|
|
# 设置大小变化事件处理
|
|
|
|
|
|
self.resizeEvent = self.on_resize_event
|
2025-06-30 18:40:50 +08:00
|
|
|
|
|
|
|
|
|
|
# 设置固定尺寸策略,防止超出父容器
|
2025-06-30 19:40:02 +08:00
|
|
|
|
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
2025-06-30 18:40:50 +08:00
|
|
|
|
|
|
|
|
|
|
# 设置最小尺寸
|
|
|
|
|
|
self.setMinimumSize(QSize(320, 240))
|
2025-07-07 15:33:56 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加本地图像处理功能
|
|
|
|
|
|
self._current_pixmap = None
|
|
|
|
|
|
self._is_local_frame_connected = False
|
|
|
|
|
|
self._connect_local_frame_signal()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def init_ui(self):
|
|
|
|
|
|
"""初始化UI - 只包含相机显示框架"""
|
|
|
|
|
|
# 创建布局
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
# 移除所有边距和间距
|
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
layout.setSpacing(0)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建相机画面显示框架
|
|
|
|
|
|
self.frame = QFrame()
|
|
|
|
|
|
# 去掉边框
|
|
|
|
|
|
self.frame.setFrameShape(QFrame.NoFrame)
|
|
|
|
|
|
# 设置黑色背景
|
|
|
|
|
|
self.frame.setStyleSheet("background-color: #000000;")
|
2025-06-30 18:40:50 +08:00
|
|
|
|
# 设置框架的尺寸策略
|
2025-06-30 19:40:02 +08:00
|
|
|
|
self.frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
# 添加到布局
|
|
|
|
|
|
layout.addWidget(self.frame)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置布局
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
def _connect_local_frame_signal(self):
|
|
|
|
|
|
"""连接本地图像帧信号"""
|
|
|
|
|
|
if not self._is_local_frame_connected:
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 尝试不同的方式获取LocalImagePlayer实例
|
|
|
|
|
|
|
|
|
|
|
|
# 方法1:从主窗口的SettingsWindow查找
|
|
|
|
|
|
from widgets.settings_window import SettingsWindow
|
|
|
|
|
|
for window in self.window().findChildren(SettingsWindow):
|
|
|
|
|
|
if hasattr(window, 'camera_settings'):
|
|
|
|
|
|
if hasattr(window.camera_settings, 'local_player'):
|
|
|
|
|
|
local_player = window.camera_settings.local_player
|
|
|
|
|
|
# 连接本地图像帧信号
|
|
|
|
|
|
if hasattr(local_player, 'signal_frame_ready'):
|
|
|
|
|
|
local_player.signal_frame_ready.connect(self.update_local_frame)
|
|
|
|
|
|
self._is_local_frame_connected = True
|
|
|
|
|
|
logging.info("成功连接本地图像帧信号 (方法1)")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 方法2:直接从相机管理器获取实例
|
|
|
|
|
|
from widgets.camera_settings_widget import CameraSettingsWidget
|
|
|
|
|
|
from widgets.main_window import MainWindow
|
|
|
|
|
|
|
|
|
|
|
|
for window in self.window().findChildren(MainWindow):
|
|
|
|
|
|
# 如果已初始化了设置窗口
|
|
|
|
|
|
if hasattr(window, 'settings_window'):
|
|
|
|
|
|
if hasattr(window.settings_window, 'camera_settings'):
|
|
|
|
|
|
camera_settings = window.settings_window.camera_settings
|
|
|
|
|
|
if hasattr(camera_settings, 'local_player'):
|
|
|
|
|
|
local_player = camera_settings.local_player
|
|
|
|
|
|
if hasattr(local_player, 'signal_frame_ready'):
|
|
|
|
|
|
local_player.signal_frame_ready.connect(self.update_local_frame)
|
|
|
|
|
|
self._is_local_frame_connected = True
|
|
|
|
|
|
logging.info("成功连接本地图像帧信号 (方法2)")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 如果以上方法都失败了,启动重试机制
|
|
|
|
|
|
from PySide6.QtCore import QTimer
|
|
|
|
|
|
self._retry_timer = QTimer(self)
|
|
|
|
|
|
self._retry_timer.timeout.connect(self._retry_connect_signal)
|
|
|
|
|
|
self._retry_timer.start(1000) # 1秒后重试
|
|
|
|
|
|
logging.info("未找到本地图像播放器,将在1秒后重试")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"连接本地图像帧信号失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def _retry_connect_signal(self):
|
|
|
|
|
|
"""重试连接本地图像帧信号"""
|
|
|
|
|
|
if self._is_local_frame_connected:
|
|
|
|
|
|
if hasattr(self, '_retry_timer'):
|
|
|
|
|
|
self._retry_timer.stop()
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 重新尝试连接
|
|
|
|
|
|
self._connect_local_frame_signal()
|
|
|
|
|
|
|
|
|
|
|
|
# 如果仍然未连接成功,停止重试计时器
|
|
|
|
|
|
if not self._is_local_frame_connected and hasattr(self, '_retry_counter'):
|
|
|
|
|
|
self._retry_counter = getattr(self, '_retry_counter', 0) + 1
|
|
|
|
|
|
if self._retry_counter >= 5: # 最多重试5次
|
|
|
|
|
|
logging.warning("连接本地图像帧信号重试5次后仍失败,停止重试")
|
|
|
|
|
|
self._retry_timer.stop()
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.info(f"第{self._retry_counter}次重试连接本地图像帧信号")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"重试连接本地图像帧信号失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def update_local_frame(self, frame):
|
|
|
|
|
|
"""处理并显示本地图像帧
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
frame: 本地图像帧数据(OpenCV RGB图像)
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 添加明显的调试日志,确认该方法被调用
|
|
|
|
|
|
logging.warning(f"====> CameraDisplayWidget.update_local_frame被调用,帧尺寸: {frame.shape if frame is not None else 'None'}")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
if frame is None:
|
|
|
|
|
|
logging.warning("接收到的本地图像帧为空")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 转换OpenCV的BGR图像为Qt的QImage
|
|
|
|
|
|
height, width, channel = frame.shape
|
|
|
|
|
|
bytes_per_line = channel * width
|
|
|
|
|
|
|
|
|
|
|
|
# 创建QImage (注意:OpenCV使用RGB格式,QImage使用RGB格式)
|
|
|
|
|
|
q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建QPixmap并设置到标签
|
|
|
|
|
|
pixmap = QPixmap.fromImage(q_img)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存当前图像
|
|
|
|
|
|
self._current_pixmap = pixmap
|
|
|
|
|
|
|
|
|
|
|
|
# 立即绘制
|
|
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
|
|
|
|
# 强制重绘
|
|
|
|
|
|
self.repaint()
|
|
|
|
|
|
|
|
|
|
|
|
# 记录日志
|
|
|
|
|
|
logging.warning(f"====> 成功更新本地图像帧: 图像大小={width}x{height},已调用update()和repaint()触发重绘")
|
|
|
|
|
|
|
|
|
|
|
|
# 确保相机区域可见
|
|
|
|
|
|
self.setVisible(True)
|
|
|
|
|
|
self.raise_()
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"处理本地图像帧时出错: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def paintEvent(self, event):
|
|
|
|
|
|
"""重写绘制事件,用于显示图像"""
|
|
|
|
|
|
# 调用父类的paintEvent,确保原有功能完整
|
|
|
|
|
|
super().paintEvent(event)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加调试日志,检查是否有图像需要绘制
|
|
|
|
|
|
if self._current_pixmap:
|
|
|
|
|
|
logging.warning(f"====> paintEvent: 绘制图像,尺寸={self._current_pixmap.width()}x{self._current_pixmap.height()}")
|
|
|
|
|
|
|
|
|
|
|
|
# 确保我们直接在这个窗口上绘制,不创建新组件
|
|
|
|
|
|
from PySide6.QtGui import QPainter
|
|
|
|
|
|
painter = QPainter(self)
|
|
|
|
|
|
|
|
|
|
|
|
# 按比例缩放图像,保持图像比例
|
|
|
|
|
|
scaled_pixmap = self._current_pixmap.scaled(
|
|
|
|
|
|
self.size(), # 使用整个控件尺寸
|
|
|
|
|
|
Qt.KeepAspectRatio,
|
|
|
|
|
|
Qt.SmoothTransformation
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算居中位置
|
|
|
|
|
|
x = (self.width() - scaled_pixmap.width()) // 2
|
|
|
|
|
|
y = (self.height() - scaled_pixmap.height()) // 2
|
|
|
|
|
|
|
|
|
|
|
|
# 绘制图像
|
|
|
|
|
|
painter.drawPixmap(x, y, scaled_pixmap)
|
|
|
|
|
|
|
|
|
|
|
|
# 记录绘制完成
|
|
|
|
|
|
logging.warning(f"====> 图像绘制完成: 控件尺寸={self.width()}x{self.height()}, 图像绘制位置=({x},{y}), 缩放后尺寸={scaled_pixmap.width()}x{scaled_pixmap.height()}")
|
|
|
|
|
|
|
|
|
|
|
|
painter.end()
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.debug("paintEvent: 没有图像需要绘制")
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
def start_display(self):
|
|
|
|
|
|
"""开始显示相机图像"""
|
|
|
|
|
|
if not self.camera_manager.isOpen:
|
|
|
|
|
|
logging.error("相机未打开,无法开始显示")
|
|
|
|
|
|
self.signal_camera_status.emit(False, "相机未打开")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取窗口句柄
|
|
|
|
|
|
try:
|
|
|
|
|
|
# PySide6
|
|
|
|
|
|
window_id = int(self.frame.winId())
|
|
|
|
|
|
except:
|
|
|
|
|
|
try:
|
|
|
|
|
|
# PyQt5
|
|
|
|
|
|
window_id = self.frame.winId().__int__()
|
|
|
|
|
|
except:
|
|
|
|
|
|
# 其他情况
|
|
|
|
|
|
window_id = int(self.frame.winId())
|
|
|
|
|
|
|
|
|
|
|
|
# 开始取图
|
|
|
|
|
|
success = self.camera_manager.start_grabbing(window_id)
|
|
|
|
|
|
|
|
|
|
|
|
if success:
|
2025-06-30 18:40:50 +08:00
|
|
|
|
logging.info(f"相机画面显示开始,显示区域大小: {self.frame.width()}x{self.frame.height()}")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
self.signal_camera_status.emit(True, "")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.error("开始显示相机画面失败")
|
|
|
|
|
|
self.signal_camera_status.emit(False, "开始显示失败")
|
|
|
|
|
|
|
|
|
|
|
|
return success
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"开始显示时发生异常: {str(e)}")
|
|
|
|
|
|
self.signal_camera_status.emit(False, str(e))
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def stop_display(self):
|
|
|
|
|
|
"""停止显示相机图像"""
|
|
|
|
|
|
if not self.camera_manager.isGrabbing:
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
success = self.camera_manager.stop_grabbing()
|
|
|
|
|
|
if not success:
|
|
|
|
|
|
logging.error("停止显示失败")
|
|
|
|
|
|
return success
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"停止显示时发生异常: {str(e)}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def on_show_event(self, event):
|
|
|
|
|
|
"""当组件显示时检查相机状态并自动开始显示"""
|
|
|
|
|
|
if self.camera_manager.isOpen and not self.camera_manager.isGrabbing:
|
|
|
|
|
|
self.start_display()
|
|
|
|
|
|
super().showEvent(event)
|
|
|
|
|
|
|
|
|
|
|
|
def on_resize_event(self, event):
|
|
|
|
|
|
"""处理大小变化事件,确保相机画面适配上料区"""
|
|
|
|
|
|
super().resizeEvent(event)
|
2025-06-30 18:40:50 +08:00
|
|
|
|
|
|
|
|
|
|
# 记录大小变化
|
|
|
|
|
|
logging.debug(f"相机显示区域大小变化为: {self.width()}x{self.height()}")
|
|
|
|
|
|
|
2025-07-07 15:33:56 +08:00
|
|
|
|
# 重绘当前图像
|
|
|
|
|
|
self.update()
|
|
|
|
|
|
|
2025-06-30 18:40:50 +08:00
|
|
|
|
# 当尺寸变化超过一定阈值时,重新调整相机显示
|
2025-07-07 15:33:56 +08:00
|
|
|
|
if self.camera_manager.isGrabbing and not self.camera_manager.local_mode:
|
2025-06-07 10:45:09 +08:00
|
|
|
|
# 停止当前显示
|
|
|
|
|
|
self.stop_display()
|
|
|
|
|
|
# 使用新尺寸重新开始显示
|
|
|
|
|
|
self.start_display()
|
|
|
|
|
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
|
|
"""窗口关闭事件"""
|
|
|
|
|
|
self.stop_display()
|
|
|
|
|
|
super().closeEvent(event)
|