jiateng_ws/widgets/camera_display_widget.py

307 lines
12 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
import numpy as np
from ctypes import *
# 确定使用哪个UI框架
try:
from PySide6.QtWidgets import QWidget, QVBoxLayout, QFrame, QSizePolicy
from PySide6.QtCore import Qt, Signal, QSize, QTimer
from PySide6.QtGui import QPalette, QColor, QImage, QPixmap
except ImportError:
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QFrame, QSizePolicy
from PyQt5.QtCore import Qt, pyqtSignal as Signal, QSize
from PyQt5.QtGui import QPalette, QColor, QImage, QPixmap
# 添加相机模块路径
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
# 设置固定尺寸策略,防止超出父容器
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# 设置最小尺寸
self.setMinimumSize(QSize(320, 240))
# 添加本地图像处理功能
self._current_pixmap = None
self._is_local_frame_connected = False
self._connect_local_frame_signal()
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;")
# 设置框架的尺寸策略
self.frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# 添加到布局
layout.addWidget(self.frame)
# 设置布局
self.setLayout(layout)
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: 没有图像需要绘制")
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:
logging.info(f"相机画面显示开始,显示区域大小: {self.frame.width()}x{self.frame.height()}")
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)
# 记录大小变化
logging.debug(f"相机显示区域大小变化为: {self.width()}x{self.height()}")
# 重绘当前图像
self.update()
# 当尺寸变化超过一定阈值时,重新调整相机显示
if self.camera_manager.isGrabbing and not self.camera_manager.local_mode:
# 停止当前显示
self.stop_display()
# 使用新尺寸重新开始显示
self.start_display()
def closeEvent(self, event):
"""窗口关闭事件"""
self.stop_display()
super().closeEvent(event)