活动介绍

AttributeError: 'QLineEdit' object has no attribute 'valueChanged'

时间: 2024-09-20 13:00:52 浏览: 123
这个错误提示是说你在PyQt5中尝试访问一个`QLineEdit`对象(即单行文本输入框)的`valueChanged`属性,但是`QLineEdit`实际上并没有这样的属性。`valueChanged`通常用于那些可以改变值的对象,比如`QSlider`, `QDoubleSpinBox` 或者 `QCheckBox` 等,它们有值的变化事件。 `QLineEdit`的主要操作是获取和设置其文本内容,它的常用属性包括`text()`、`setText()` 和 `textEdited.connect(func)` 来连接文本变化的信号。如果你想要监听用户在`QLineEdit`中输入的变化,你应该使用`textChanged`信号,而不是`valueChanged`。 例如: ```python lineEdit = QLineEdit() lineEdit.textChanged.connect(your_function) # 当文本内容改变时,调用your_function ``` 在这个错误发生的地方,你需要确认你是不是误用了`valueChanged`,如果是想监听输入变化,应该替换为`textChanged`或者其他适合`QLineEdit`的相应信号或属性。
相关问题

view.addItem(container) AttributeError: 'QWidget' object has no attribute 'geometryChanged'

根据提供的引用内容,你遇到了两个错误: 1. 在文件`ui_logindialog.h`的第151行,出现了错误`'class QLineEdit' has no member named 'setPlaceholderText'`。这个错误意味着在`QLineEdit`类中没有名为`setPlaceholderText`的成员函数。 2. 在`Dialog`类的`on_horizontalSlider_valueChanged`函数中,出现了错误`AttributeError: 'QWidget' object has no attribute 'geometryChanged'`。这个错误意味着`QWidget`对象没有名为`geometryChanged`的属性。 以下是对这两个错误的解释和可能的解决方法: 1. `'class QLineEdit' has no member named 'setPlaceholderText'`错误表明你在使用`QLineEdit`类的`setPlaceholderText`函数时出现了问题。可能的原因是你的Qt版本较旧,不支持`setPlaceholderText`函数。解决方法是升级Qt版本或者使用其他方法来设置`QLineEdit`的占位文本。 2. `AttributeError: 'QWidget' object has no attribute 'geometryChanged'`错误表明你在访问`QWidget`对象的`geometryChanged`属性时出现了问题。可能的原因是你错误地访问了`QWidget`对象的属性,或者该属性在你的代码中不存在。解决方法是检查你的代码,确保正确访问了`QWidget`对象的属性,并且该属性存在。

Traceback (most recent call last): File "c:\Users\zhangyuxiang\Desktop\cloth_inspection\wanzheng.py", line 1149, in <module> detection_layout.addWidget(mainWindow.ui.bnSaveSample) AttributeError: 'Ui_MainWindow' object has no attribute 'bnSaveSample'

<think>我们遇到了一个新的错误:`Ui_MainWindow`对象没有`bnSaveSample`属性。这意味着在`Ui_MainWindow`类中也没有定义`bnSaveSample`按钮。 我们可以采用与之前`bnCheckPrint`相同的方法:动态创建缺失的按钮。 但是,考虑到代码中可能还有其他缺失的控件(如`bnPreviewSample`、`lblSamplePath`等),我们应该检查所有在代码中引用的UI控件是否在`Ui_MainWindow`中存在。如果不存在,我们需要动态创建它们。 然而,为了快速解决问题,我们可以逐个处理。现在,我们动态创建`bnSaveSample`按钮。 修改思路: 1. 在右侧控制面板的“检测工具”组中,我们需要添加三个按钮:`bnCheckPrint`(已处理)、`bnSaveSample`、`bnPreviewSample`,以及一个标签`lblSamplePath`。 2. 我们已经在之前动态创建了`bnCheckPrint`,现在同样动态创建`bnSaveSample`和`bnPreviewSample`,以及`lblSamplePath`。 注意:为了避免重复代码,我们可以在动态创建控件之前检查它们是否存在。 修改步骤: 在构建右侧控制面板时,依次检查这些控件是否存在,如果不存在则动态创建并添加到布局中。 具体修改如下(在之前修改的基础上继续): 在右侧控制面板的“检测工具”组布局代码中,添加对`bnSaveSample`、`bnPreviewSample`和`lblSamplePath`的动态创建。 同时,我们需要连接这些按钮的信号到相应的槽函数: - `bnSaveSample` 连接到 `save_sample_image` - `bnPreviewSample` 连接到 `preview_sample` 另外,`lblSamplePath`是一个标签,用于显示当前样本的路径。 修改后的代码片段(在检测工具组布局部分)如下: ```python # === 检测工具 === detection_group = QGroupBox("检测工具") detection_layout = QVBoxLayout(detection_group) # 动态添加缺失的按钮(如果UI中不存在) if not hasattr(mainWindow.ui, 'bnCheckPrint'): mainWindow.ui.bnCheckPrint = QPushButton("检测印花") mainWindow.ui.bnCheckPrint.setObjectName("bnCheckPrint") mainWindow.ui.bnCheckPrint.clicked.connect(check_print_with_sensor) if not hasattr(mainWindow.ui, 'bnSaveSample'): mainWindow.ui.bnSaveSample = QPushButton("保存样本") mainWindow.ui.bnSaveSample.setObjectName("bnSaveSample") mainWindow.ui.bnSaveSample.clicked.connect(save_sample_image) if not hasattr(mainWindow.ui, 'bnPreviewSample'): mainWindow.ui.bnPreviewSample = QPushButton("预览样本") mainWindow.ui.bnPreviewSample.setObjectName("bnPreviewSample") mainWindow.ui.bnPreviewSample.clicked.connect(preview_sample) if not hasattr(mainWindow.ui, 'lblSamplePath'): mainWindow.ui.lblSamplePath = QLabel("当前样本: 未设置样本") mainWindow.ui.lblSamplePath.setObjectName("lblSamplePath") # 添加检测按钮 detection_layout.addWidget(mainWindow.ui.bnCheckPrint) detection_layout.addWidget(mainWindow.ui.bnSaveSample) detection_layout.addWidget(mainWindow.ui.bnPreviewSample) detection_layout.addWidget(mainWindow.ui.lblSamplePath) ``` 这样,我们就动态创建了所有在检测工具组中缺失的控件。 另外,在代码的其他部分,我们使用了`mainWindow.ui.lblDiffThreshold`、`mainWindow.ui.sliderDiffThreshold`等控件,如果这些控件也不存在,同样会出现错误。因此,我们可能需要为所有缺失的控件做动态创建。 但是,由于时间关系,我们只处理当前报错的控件。如果后续出现其他缺失控件的错误,再按同样方法处理。 注意:在动态创建控件后,我们需要在`enable_controls`函数中更新这些控件的状态(例如`bnPreviewSample`的启用状态取决于是否有样本路径)。在动态创建时,我们设置了初始状态,但后续状态更新需要`enable_controls`函数处理。因此,我们还需要修改`enable_controls`函数,使其能处理动态创建的控件。 修改`enable_controls`函数中关于`bnPreviewSample`的部分: 原代码: mainWindow.ui.bnPreviewSample.setEnabled(bool(current_sample_path)) 由于`bnPreviewSample`是动态创建的,它已经存在,所以可以直接设置。 但是,如果动态创建了`bnPreviewSample`,那么它一定存在,所以代码可以保持不变。 因此,我们只需要确保在`enable_controls`函数被调用时,这些动态创建的控件已经被创建即可。由于我们在主窗口初始化时就已经动态创建了它们,所以没有问题。 完整修改后的`__main__`部分代码(只展示修改后的右侧控制面板部分,其他部分保持不变): 注意:由于代码较长,我们只展示修改后的部分。请将之前动态创建`bnCheckPrint`的代码扩展为同时创建其他控件。 另外,我们还需要注意:在动态创建控件后,需要更新`current_sample_path`的显示(调用`update_sample_display`)。我们在主窗口显示后可以调用一次`update_sample_display`。 但是,在`update_sample_display`函数中,我们使用了`mainWindow.ui.lblSamplePath`和`mainWindow.ui.bnPreviewSample`,现在它们都是动态创建的,所以我们可以直接调用。 在初始化后,我们可以调用: update_sample_display() 这样,标签就会显示初始状态(未设置样本)。 修改后的完整`__main__`部分(从创建右侧控制面板开始)如下: 注意:为了节省篇幅,我只写修改的部分,但会尽量给出上下文。 由于整个`__main__`部分代码很长,我将只展示修改后的右侧控制面板构建部分: ```python # ===== 右侧:控制面板 ===== # 创建右侧滚动区域 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setMinimumWidth(400) # 设置最小宽度 # 右侧容器 right_container = QWidget() scroll_area.setWidget(right_container) right_layout = QVBoxLayout(right_container) # === 检测工具 === detection_group = QGroupBox("检测工具") detection_layout = QVBoxLayout(detection_group) # 动态添加缺失的按钮(如果UI中不存在) if not hasattr(mainWindow.ui, 'bnCheckPrint'): mainWindow.ui.bnCheckPrint = QPushButton("检测印花") mainWindow.ui.bnCheckPrint.setObjectName("bnCheckPrint") mainWindow.ui.bnCheckPrint.clicked.connect(check_print_with_sensor) if not hasattr(mainWindow.ui, 'bnSaveSample'): mainWindow.ui.bnSaveSample = QPushButton("保存样本") mainWindow.ui.bnSaveSample.setObjectName("bnSaveSample") mainWindow.ui.bnSaveSample.clicked.connect(save_sample_image) if not hasattr(mainWindow.ui, 'bnPreviewSample'): mainWindow.ui.bnPreviewSample = QPushButton("预览样本") mainWindow.ui.bnPreviewSample.setObjectName("bnPreviewSample") mainWindow.ui.bnPreviewSample.clicked.connect(preview_sample) if not hasattr(mainWindow.ui, 'lblSamplePath'): mainWindow.ui.lblSamplePath = QLabel("当前样本: 未设置样本") mainWindow.ui.lblSamplePath.setObjectName("lblSamplePath") # 添加检测按钮 detection_layout.addWidget(mainWindow.ui.bnCheckPrint) detection_layout.addWidget(mainWindow.ui.bnSaveSample) detection_layout.addWidget(mainWindow.ui.bnPreviewSample) detection_layout.addWidget(mainWindow.ui.lblSamplePath) # 历史记录 detection_layout.addWidget(QLabel("历史记录:")) if hasattr(mainWindow.ui, 'cbHistory'): detection_layout.addWidget(mainWindow.ui.cbHistory) else: # 如果历史记录下拉框也不存在,动态创建 mainWindow.ui.cbHistory = QComboBox() detection_layout.addWidget(mainWindow.ui.cbHistory) right_layout.addWidget(detection_group) # ... 其他组的布局保持不变 ... ``` 注意:我们还为历史记录下拉框`cbHistory`做了动态创建,以防它也不存在。 最后,在显示主窗口后,我们调用`update_sample_display()`来初始化样本路径标签: ```python # 显示主窗口 mainWindow.show() # 初始化样本路径显示 update_sample_display() ``` 这样,我们就解决了`bnSaveSample`缺失的问题,并预防了其他控件缺失的问题。 请根据上述修改调整代码。如果还有其他缺失控件,同样方法处理。</think>这个错误表明 `Ui_MainWindow` 类中缺少 `bnSaveSample` 属性。与之前的 `bnCheckPrint` 问题类似,我们需要在代码中动态创建这个缺失的按钮。以下是完整的解决方案: ### 解决方法:动态创建所有缺失的按钮 ```python # 在 __main__ 部分,创建主窗口后添加以下代码 if __name__ == "__main__": # ... [其他初始化代码] ... # === 检测工具 === detection_group = QGroupBox("检测工具") detection_layout = QVBoxLayout(detection_group) # 动态创建所有缺失的按钮 if not hasattr(mainWindow.ui, 'bnCheckPrint'): mainWindow.ui.bnCheckPrint = QPushButton("检测印花") mainWindow.ui.bnCheckPrint.setObjectName("bnCheckPrint") mainWindow.ui.bnCheckPrint.clicked.connect(check_print_with_sensor) if not hasattr(mainWindow.ui, 'bnSaveSample'): mainWindow.ui.bnSaveSample = QPushButton("保存样本") mainWindow.ui.bnSaveSample.setObjectName("bnSaveSample") mainWindow.ui.bnSaveSample.clicked.connect(save_sample_image) if not hasattr(mainWindow.ui, 'bnPreviewSample'): mainWindow.ui.bnPreviewSample = QPushButton("预览样本") mainWindow.ui.bnPreviewSample.setObjectName("bnPreviewSample") mainWindow.ui.bnPreviewSample.clicked.connect(preview_sample) # 确保标签存在 if not hasattr(mainWindow.ui, 'lblSamplePath'): mainWindow.ui.lblSamplePath = QLabel("当前样本: 未设置样本") mainWindow.ui.lblSamplePath.setObjectName("lblSamplePath") # 确保历史记录下拉框存在 if not hasattr(mainWindow.ui, 'cbHistory'): mainWindow.ui.cbHistory = QComboBox() mainWindow.ui.cbHistory.setObjectName("cbHistory") # 添加检测按钮 detection_layout.addWidget(mainWindow.ui.bnCheckPrint) detection_layout.addWidget(mainWindow.ui.bnSaveSample) detection_layout.addWidget(mainWindow.ui.bnPreviewSample) # 样本路径显示 detection_layout.addWidget(mainWindow.ui.lblSamplePath) # 历史记录 detection_layout.addWidget(QLabel("历史记录:")) detection_layout.addWidget(mainWindow.ui.cbHistory) right_layout.addWidget(detection_group) # ... [其他代码] ... ``` ### 完整修改后的 `__main__` 部分: ```python if __name__ == "__main__": # 初始化UI app = QApplication(sys.argv) # 创建主窗口实例 mainWindow = MainWindow() # 扩大主窗口尺寸 mainWindow.resize(1600, 900) # 创建主水平布局 main_splitter = QSplitter(Qt.Horizontal) mainWindow.setCentralWidget(main_splitter) # ===== 左侧:图像显示区域 ===== left_container = QWidget() left_layout = QVBoxLayout(left_container) mainWindow.ui.widgetDisplay.setMinimumSize(800, 600) left_layout.addWidget(mainWindow.ui.widgetDisplay, 10) # 底部工具栏 bottom_toolbar = QToolBar() left_layout.addWidget(bottom_toolbar) # 添加相机控制按钮 bottom_toolbar.addWidget(mainWindow.ui.bnEnum) bottom_toolbar.addWidget(mainWindow.ui.bnOpen) bottom_toolbar.addWidget(mainWindow.ui.bnClose) bottom_toolbar.addWidget(mainWindow.ui.bnStart) bottom_toolbar.addWidget(mainWindow.ui.bnStop) bottom_toolbar.addWidget(mainWindow.ui.bnSoftwareTrigger) bottom_toolbar.addWidget(mainWindow.ui.bnSaveImage) # 参数控制 param_group = QGroupBox("相机参数") param_layout = QHBoxLayout(param_group) param_layout.addWidget(QLabel("帧率:")) param_layout.addWidget(mainWindow.ui.edtFrameRate) param_layout.addWidget(QLabel("曝光:")) param_layout.addWidget(mainWindow.ui.edtExposureTime) param_layout.addWidget(QLabel("增益:")) param_layout.addWidget(mainWindow.ui.edtGain) param_layout.addWidget(mainWindow.ui.bnGetParam) param_layout.addWidget(mainWindow.ui.bnSetParam) left_layout.addWidget(param_group) # ===== 右侧:控制面板 ===== scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setMinimumWidth(400) right_container = QWidget() scroll_area.setWidget(right_container) right_layout = QVBoxLayout(right_container) # === 检测工具 === detection_group = QGroupBox("检测工具") detection_layout = QVBoxLayout(detection_group) # 动态创建所有缺失的按钮和控件 if not hasattr(mainWindow.ui, 'bnCheckPrint'): mainWindow.ui.bnCheckPrint = QPushButton("检测印花") mainWindow.ui.bnCheckPrint.setObjectName("bnCheckPrint") mainWindow.ui.bnCheckPrint.clicked.connect(check_print_with_sensor) if not hasattr(mainWindow.ui, 'bnSaveSample'): mainWindow.ui.bnSaveSample = QPushButton("保存样本") mainWindow.ui.bnSaveSample.setObjectName("bnSaveSample") mainWindow.ui.bnSaveSample.clicked.connect(save_sample_image) if not hasattr(mainWindow.ui, 'bnPreviewSample'): mainWindow.ui.bnPreviewSample = QPushButton("预览样本") mainWindow.ui.bnPreviewSample.setObjectName("bnPreviewSample") mainWindow.ui.bnPreviewSample.clicked.connect(preview_sample) if not hasattr(mainWindow.ui, 'lblSamplePath'): mainWindow.ui.lblSamplePath = QLabel("当前样本: 未设置样本") mainWindow.ui.lblSamplePath.setObjectName("lblSamplePath") if not hasattr(mainWindow.ui, 'cbHistory'): mainWindow.ui.cbHistory = QComboBox() mainWindow.ui.cbHistory.setObjectName("cbHistory") # 添加检测按钮 detection_layout.addWidget(mainWindow.ui.bnCheckPrint) detection_layout.addWidget(mainWindow.ui.bnSaveSample) detection_layout.addWidget(mainWindow.ui.bnPreviewSample) detection_layout.addWidget(mainWindow.ui.lblSamplePath) detection_layout.addWidget(QLabel("历史记录:")) detection_layout.addWidget(mainWindow.ui.cbHistory) right_layout.addWidget(detection_group) # === 差异度调整 === diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) # 确保差异度控件存在 if not hasattr(mainWindow.ui, 'lblDiffThreshold'): mainWindow.ui.lblDiffThreshold = QLabel("差异度阈值:") if not hasattr(mainWindow.ui, 'sliderDiffThreshold'): mainWindow.ui.sliderDiffThreshold = QSlider(Qt.Horizontal) mainWindow.ui.sliderDiffThreshold.setRange(0, 100) mainWindow.ui.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) if not hasattr(mainWindow.ui, 'lblDiffValue'): mainWindow.ui.lblDiffValue = QLabel("0%") if not hasattr(mainWindow.ui, 'lblCurrentDiff'): mainWindow.ui.lblCurrentDiff = QLabel("当前差异度: -") if not hasattr(mainWindow.ui, 'lblDiffStatus'): mainWindow.ui.lblDiffStatus = QLabel("状态: 未检测") # 添加差异度控件 diff_layout.addWidget(mainWindow.ui.lblDiffThreshold) diff_layout.addWidget(mainWindow.ui.sliderDiffThreshold) diff_layout.addWidget(mainWindow.ui.lblDiffValue) diff_layout.addWidget(mainWindow.ui.lblCurrentDiff) diff_layout.addWidget(mainWindow.ui.lblDiffStatus) right_layout.addWidget(diff_group) # === 传感器控制 === sensor_group = QGroupBox("传感器控制") sensor_layout = QVBoxLayout(sensor_group) # 确保传感器控件存在 if not hasattr(mainWindow.ui, 'lblSensorType'): mainWindow.ui.lblSensorType = QLabel("传感器类型:") if not hasattr(mainWindow.ui, 'cbSensorType'): mainWindow.ui.cbSensorType = QComboBox() mainWindow.ui.cbSensorType.addItems(["串口传感器", "以太网传感器"]) # 传感器类型选择 sensor_type_layout = QHBoxLayout() sensor_type_layout.addWidget(mainWindow.ui.lblSensorType) sensor_type_layout.addWidget(mainWindow.ui.cbSensorType) sensor_layout.addLayout(sensor_type_layout) # 确保串口组存在 if not hasattr(mainWindow.ui, 'serialGroup'): mainWindow.ui.serialGroup = QGroupBox("串口参数") serial_layout = QGridLayout(mainWindow.ui.serialGroup) serial_layout.addWidget(QLabel("端口:"), 0, 0) serial_layout.addWidget(QLineEdit("COM1"), 0, 1) serial_layout.addWidget(QLabel("波特率:"), 1, 0) serial_layout.addWidget(QComboBox(), 1, 1) # 确保以太网组存在 if not hasattr(mainWindow.ui, 'ethernetGroup'): mainWindow.ui.ethernetGroup = QGroupBox("以太网参数") ether_layout = QGridLayout(mainWindow.ui.ethernetGroup) ether_layout.addWidget(QLabel("IP地址:"), 0, 0) ether_layout.addWidget(QLineEdit("192.168.1.100"), 0, 1) ether_layout.addWidget(QLabel("端口:"), 1, 0) ether_layout.addWidget(QLineEdit("502"), 1, 1) # 添加串口和以太网参数 sensor_layout.addWidget(mainWindow.ui.serialGroup) sensor_layout.addWidget(mainWindow.ui.ethernetGroup) # 确保延迟控件存在 if not hasattr(mainWindow.ui, 'lblDelay'): mainWindow.ui.lblDelay = QLabel("延迟时间(秒):") if not hasattr(mainWindow.ui, 'spinDelay'): mainWindow.ui.spinDelay = QSpinBox() mainWindow.ui.spinDelay.setRange(0, 60) # 延迟设置 delay_layout = QHBoxLayout() delay_layout.addWidget(mainWindow.ui.lblDelay) delay_layout.addWidget(mainWindow.ui.spinDelay) sensor_layout.addLayout(delay_layout) # 确保连接按钮存在 if not hasattr(mainWindow.ui, 'bnConnectSensor'): mainWindow.ui.bnConnectSensor = QPushButton("连接传感器") if not hasattr(mainWindow.ui, 'bnDisconnectSensor'): mainWindow.ui.bnDisconnectSensor = QPushButton("断开传感器") # 连接/断开按钮 btn_layout = QHBoxLayout() btn_layout.addWidget(mainWindow.ui.bnConnectSensor) btn_layout.addWidget(mainWindow.ui.bnDisconnectSensor) sensor_layout.addLayout(btn_layout) # 确保数据显示标签存在 if not hasattr(mainWindow.ui, 'lblSensorData'): mainWindow.ui.lblSensorData = QLabel("传感器数据: 未连接") # 传感器数据显示 sensor_layout.addWidget(mainWindow.ui.lblSensorData) right_layout.addWidget(sensor_group) # 添加拉伸 right_layout.addStretch() # ===== 添加到主分割布局 ===== main_splitter.addWidget(left_container) main_splitter.addWidget(scroll_area) # 设置分割比例 main_splitter.setSizes([int(mainWindow.width() * 0.8), int(mainWindow.width() * 0.2)]) # 绑定信号和槽 mainWindow.ui.bnEnum.clicked.connect(enum_devices) mainWindow.ui.bnOpen.clicked.connect(open_device) mainWindow.ui.bnClose.clicked.connect(close_device) mainWindow.ui.bnStart.clicked.connect(start_grabbing) mainWindow.ui.bnStop.clicked.connect(stop_grabbing) mainWindow.ui.bnSaveImage.clicked.connect(save_image_dialog) mainWindow.ui.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.ui.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.ui.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.ui.bnGetParam.clicked.connect(get_param) mainWindow.ui.bnSetParam.clicked.connect(set_param) mainWindow.ui.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) # 初始化控件状态 update_sample_display() enable_controls() # 显示主窗口 mainWindow.show() # 执行应用 app.exec_() # 关闭设备 close_device() # 断开传感器 disconnect_sensor() sys.exit() ``` ### 解释: 1. **问题原因**: - `Ui_MainWindow` 类中缺少多个属性(`bnSaveSample`, `bnPreviewSample`, `lblSamplePath` 等) - 代码尝试访问这些属性,但它们不存在于 UI 类中 2. **解决方案**: - 动态创建所有缺失的 UI 控件 - 为每个控件设置对象名称(ObjectName)以匹配代码中的属性名 - 将控件添加到正确的布局中 - 连接按钮的点击信号到相应的处理函数 3. **关键改进**: - **全面检查**:检查并动态创建所有可能缺失的控件 - **信号连接**:确保所有按钮连接到正确的处理函数 - **初始化状态**:调用 `update_sample_display()` 和 `enable_controls()` 初始化控件状态 - **错误预防**:为每个控件添加存在性检查,避免重复创建 这种方法允许你在不修改原始 UI 文件的情况下解决所有属性缺失问题,同时保持代码的其他部分不变。
阅读全文

相关推荐

Traceback (most recent call last): File "d:\海康\MVS\Development\Samples\Python\MvImport\wanzheng.py", line 700, in enum_devices dev_name = f"GigE: {st_gige_info.chUserDefinedName.decode('gbk', 'ignore')}" AttributeError: 'c_ubyte_Array_16' object has no attribute 'decode' 下面的程序出现了上面的问题,要求给出修复方案并完整展示 # -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np import time from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QGridLayout, QSpinBox ) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation #sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\wanzheng.py") import ctypes from ctypes import cast, POINTER from datetime import datetime import logging import socket import serial import skimage import platform from CameraConstants import * import threading import time class ManagedThread(threading.Thread): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._stop_event = threading.Event() # 设置为非守护线程 self.daemon = False def stop(self): """安全停止线程""" self._stop_event.set() def should_stop(self): """检查是否应该停止""" return self._stop_event.is_set() def worker(): """线程工作函数""" try: while not threading.current_thread().should_stop(): # 模拟工作 time.sleep(1) # 安全输出 sys.stdout.write("Working...\n") except Exception as e: # 避免在关闭时使用stderr pass def main(): # 创建并启动线程 threads = [] for _ in range(3): t = ManagedThread(target=worker) t.start() threads.append(t) try: # 主程序逻辑 time.sleep(5) finally: # 安全停止所有线程 for t in threads: t.stop() for t in threads: t.join(timeout=2.0) # 设置超时避免无限等待 # 确保所有输出完成 sys.stdout.flush() sys.stderr.flush() # 在导入部分添加 from CameraParams_header import ( MV_GIGE_DEVICE, MV_USB_DEVICE, MV_GENTL_CAMERALINK_DEVICE, MV_GENTL_CXP_DEVICE, MV_GENTL_XOF_DEVICE ) # 获取当前文件所在目录 current_dir = os.path.dirname(os.path.abspath(__file__)) # ===== 路径修复 ===== sdk_path = os.path.join(current_dir, "MvImport") if sdk_path not in sys.path: sys.path.append(sdk_path) def fix_sdk_path(): """修复海康SDK的加载路径""" if getattr(sys, 'frozen', False): # 打包模式 base_path = sys._MEIPASS # 添加DLL目录到系统路径 dll_path = os.path.join(base_path, "dlls") os.environ['PATH'] = dll_path + os.pathsep + os.environ['PATH'] try: # 直接加载DLL ctypes.WinDLL(os.path.join(dll_path, "MvCamCtrldll.dll")) ctypes.WinDLL(os.path.join(dll_path, "MvCameraControl.dll")) except OSError as e: logging.error(f"核心DLL加载失败: {e}") sys.exit(1) else: # 开发模式 # 确保SDK路径存在 if sdk_path not in sys.path: sys.path.append(sdk_path) # 添加DLL到系统路径 dll_dir = r"D:\海康\MVS\Runtime\Win64" if dll_dir not in os.environ['PATH']: os.environ['PATH'] = dll_dir + os.pathsep + os.environ['PATH'] # 立即执行路径修复 fix_sdk_path() # ===== 正确导入SDK模块 ===== try: from MvImport.MvCameraControl_class import MvCamera print("成功导入MvCamera类") from CameraParams_header import * from MvErrorDefine_const import * except ImportError as e: print(f"SDK导入失败: {e}") sys.exit(1) # 配置日志系统 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动") # 全局变量 current_sample_path = "" detection_history = [] isGrabbing = False isOpen = False obj_cam_operation = None frame_monitor_thread = None sensor_monitor_thread = None sensor_controller = None MV_OK = 0 MV_E_CALLORDER = -2147483647 # ==================== 传感器通讯模块 ==================== class SensorController: def __init__(self): self.connected = False self.running = False self.connection = None def connect(self, config): try: if config['type'] == 'serial': self.connection = serial.Serial( port=config['port'], baudrate=config['baudrate'], timeout=config.get('timeout', 1.0) ) else: self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connection.connect((config['ip'], config['port'])) self.connection.settimeout(config.get('timeout', 1.0)) self.connected = True self.running = True logging.info(f"传感器连接成功: {config}") return True except Exception as e: logging.error(f"传感器连接失败: {str(e)}") return False def disconnect(self): if self.connection: try: self.connection.close() except: pass self.connection = None self.connected = False self.running = False logging.info("传感器已断开") def read_data(self): if not self.connected: return None return { 'tension': np.random.uniform(10.0, 20.0), 'speed': np.random.uniform(1.0, 5.0), 'temperature': np.random.uniform(20.0, 30.0), 'humidity': np.random.uniform(40.0, 60.0) } def wait_for_material(self, delay_seconds=0): if not self.connected: logging.warning("未连接传感器,跳过等待") return False logging.info(f"等待布料到达,延迟 {delay_seconds} 秒") start_time = time.time() while time.time() - start_time < delay_seconds: QThread.msleep(100) if not self.running: return False logging.info("布料已到位,准备拍摄") return True class SensorMonitorThread(QThread): data_updated = pyqtSignal(dict) def __init__(self, sensor_controller): super().__init__() self.sensor_controller = sensor_controller self.running = True def run(self): while self.running: if self.sensor_controller and self.sensor_controller.connected: try: data = self.sensor_controller.read_data() if data: self.data_updated.emit(data) except Exception as e: logging.error(f"传感器数据读取错误: {str(e)}") QThread.msleep(500) def stop(self): self.running = False self.wait(2000) def wait_for_material(self, delay_seconds): return self.sensor_controller.wait_for_material(delay_seconds) # ==================== 相机帧监控线程 ==================== class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) # 用于发送状态消息的信号 def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True self.frame_count = 0 self.last_time = time.time() def run(self): """监控相机帧状态的主循环""" while self.running: try: if self.cam_operation and self.cam_operation.is_grabbing: # 获取帧统计信息 frame_info = self.get_frame_info() if frame_info: fps = frame_info.get('fps', 0) dropped = frame_info.get('dropped', 0) status = f"FPS: {fps:.1f} | 丢帧: {dropped}" self.frame_status.emit(status) else: self.frame_status.emit("取流中...") else: self.frame_status.emit("相机未取流") except Exception as e: self.frame_status.emit(f"监控错误: {str(e)}") # 每500ms检查一次 QThread.msleep(500) def stop(self): """停止监控线程""" self.running = False self.wait(1000) # 等待线程结束 def calculate_fps(self): """计算当前帧率""" current_time = time.time() elapsed = current_time - self.last_time if elapsed > 0: fps = self.frame_count / elapsed self.frame_count = 0 self.last_time = current_time return fps return 0 def get_frame_info(self): """获取帧信息""" try: # 更新帧计数 self.frame_count += 1 # 返回帧信息 return { 'fps': self.calculate_fps(), 'dropped': 0 # 实际应用中需要从相机获取真实丢帧数 } except Exception as e: logging.error(f"获取帧信息失败: {str(e)}") return None # ==================== 优化后的检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05, sensor_data=None): if sensor_data: speed_factor = min(1.0 + sensor_data['speed'] * 0.1, 1.5) env_factor = 1.0 + abs(sensor_data['temperature'] - 25) * 0.01 + abs(sensor_data['humidity'] - 50) * 0.005 adjusted_threshold = threshold * speed_factor * env_factor logging.info(f"根据传感器数据调整阈值: 原始={threshold:.4f}, 调整后={adjusted_threshold:.4f}") else: adjusted_threshold = threshold try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) try: orb = cv2.ORB_create(nfeatures=200) keypoints1, descriptors1 = orb.detectAndCompute(sample_image, None) keypoints2, descriptors2 = orb.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning("无法提取特征描述符,跳过配准") aligned_sample = sample_image else: bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) matches = sorted(matches, key=lambda x: x.distance) if len(matches) > 10: src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info("图像配准成功,使用配准后样本") else: aligned_sample = sample_image logging.warning("无法计算单应性矩阵,使用原始样本") else: aligned_sample = sample_image logging.warning("特征点匹配不足,跳过图像配准") except Exception as e: logging.error(f"图像配准失败: {str(e)}") aligned_sample = sample_image try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None try: from skimage.metrics import structural_similarity as compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True, data_range=255 ) except ImportError: from skimage.measure import compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) except Exception as e: logging.error(f"SSIM计算失败: {str(e)}") abs_diff = cv2.absdiff(aligned_sample, test_image_gray) ssim_diff = abs_diff.astype(np.float32) / 255.0 ssim_score = 1.0 - np.mean(ssim_diff) ssim_diff = (1 - ssim_diff) * 255 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) _, thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels is_qualified = diff_ratio <= adjusted_threshold marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: y, x = prop.centroid cv2.putText(marked_image, f"Defect", (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) return is_qualified, diff_ratio, marked_image # ==================== 传感器控制的质量检测流程 ==================== def sensor_controlled_check(): global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller logging.info("质量检测启动") sensor_data = None if sensor_controller and sensor_controller.connected: sensor_data = sensor_controller.read_data() if not sensor_data: QMessageBox.warning(mainWindow, "传感器警告", "无法读取传感器数据,将使用默认参数", QMessageBox.Ok) else: logging.info("未连接传感器,使用默认参数检测") check_print_with_sensor(sensor_data) def check_print_with_sensor(sensor_data=None): global isGrabbing, obj_cam_operation, current_sample_path, detection_history logging.info("检测印花质量按钮按下") if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return if not obj_cam_operation: QMessageBox.warning(mainWindow, "错误", "相机未正确初始化!", QMessageBox.Ok) return if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: test_image = obj_cam_operation.get_current_frame() progress.setValue(30) if test_image is None: QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return diff_threshold = mainWindow.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(50) is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, test_image, threshold=diff_threshold, sensor_data=sensor_data ) progress.setValue(70) if is_qualified is None: QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n差异占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'sensor_data': sensor_data if sensor_data else {} } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) finally: progress.close() def update_diff_display(diff_ratio, is_qualified): mainWindow.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") if is_qualified: mainWindow.lblDiffStatus.setText("状态: 合格") mainWindow.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.lblDiffStatus.setText("状态: 不合格") mainWindow.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") def update_diff_threshold(value): mainWindow.lblDiffValue.setText(f"{value}%") def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有可用帧 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() format_mapping = {".bmp": "bmp", ".png": "png", ".jpg": "jpg", ".jpeg": "jpg"} save_format = format_mapping.get(file_extension) if not save_format: QMessageBox.warning(mainWindow, "错误", "不支持的文件格式!", QMessageBox.Ok) return directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return try: ret = obj_cam_operation.save_image(file_path, save_format) if ret != MV_OK: strError = f"保存样本图像失败: {hex(ret)}" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) except Exception as e: QMessageBox.critical(mainWindow, "异常错误", f"保存图像时发生错误: {str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: img_data = np.fromfile(current_sample_path, dtype=np.uint8) sample_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if sample_img is None: raise Exception("无法加载图像") cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def update_sample_display(): global current_sample_path if current_sample_path: mainWindow.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") mainWindow.lblSamplePath.setToolTip(current_sample_path) mainWindow.bnPreviewSample.setEnabled(True) else: mainWindow.lblSamplePath.setText("当前样本: 未设置样本") mainWindow.bnPreviewSample.setEnabled(False) def update_history_display(): global detection_history mainWindow.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" mainWindow.cbHistory.addItem(f"[极客{timestamp}] {status} - 差异: {ratio}") def TxtWrapBy(start_str, end, all): start = all.find(start_str) if start >= 0: start += len(start_str) end = all.find(end, start) if end >= 0: return all[start:end].strip() def ToHexStr(num): if not isinstance(num, int): try: num = int(num) except: return f"<非整数:{type(num)}>" chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'} hexStr = "" if num < 0: num = num + 2 ** 32 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return "0x" + hexStr def decoding_char(c_ubyte_value): c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p) try: decode_str = c_char_p_value.value.decode('gbk') except UnicodeDecodeError: decode_str = str(c_char_p_value.value) return decode_str def enum_devices(): global deviceList, obj_cam_operation n_layer_type = ( MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE ) # 创建设备列表 deviceList = MV_CC_DEVICE_INFO_LIST() # 枚举设备 ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != MV_OK: error_msg = f"枚举设备失败! 错误码: 0x{ret:x}" logging.error(error_msg) QMessageBox.warning(mainWindow, "错误", error_msg, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, "提示", "未找到任何设备", QMessageBox.Ok) return MV_OK logging.info(f"找到 {deviceList.nDeviceNum} 个设备") # 处理设备信息 devList = [] for i in range(deviceList.nDeviceNum): # 获取设备信息 mvcc_dev_info = ctypes.cast( deviceList.pDeviceInfo[i], ctypes.POINTER(MV_CC_DEVICE_INFO) ).contents # 根据设备类型提取信息 if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE: st_gige_info = mvcc_dev_info.SpecialInfo.stGigEInfo ip_addr = ( f"{(st_gige_info.nCurrentIp >> 24) & 0xFF}." f"{(st_gige_info.nCurrentIp >> 16) & 0xFF}." f"{(st_gige_info.nCurrentIp >> 8) & 0xFF}." f"{st_gige_info.nCurrentIp & 0xFF}" ) dev_name = f"GigE: {st_gige_info.chUserDefinedName.decode('gbk', 'ignore')}" devList.append(f"[{i}] {dev_name} ({ip_addr})") elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: st_usb_info = mvcc_dev_info.SpecialInfo.stUsb3VInfo serial = bytes(st_usb_info.chSerialNumber).decode('ascii', 'ignore').rstrip('\x00') dev_name = f"USB: {st_usb_info.chUserDefinedName.decode('gbk', 'ignore')}" devList.append(f"[{i}] {dev_name} (SN: {serial})") else: devList.append(f"[{i}] 未知设备类型: {mvcc_dev_info.nTLayerType}") # 更新UI mainWindow.ComboDevices.clear() mainWindow.ComboDevices.addItems(devList) if devList: mainWindow.ComboDevices.setCurrentIndex(0) mainWindow.statusBar().showMessage(f"找到 {deviceList.nDeviceNum} 个设备", 3000) return MV_OK # ===== 关键改进:相机操作函数 ===== def open_device(): global deviceList, nSelCamIndex, obj_cam_operation, isOpen, frame_monitor_thread, mainWindow if isOpen: QMessageBox.warning(mainWindow, "Error", '相机已打开!', QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = mainWindow.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, "Error", '请选择相机!', QMessageBox.Ok) return MV_E_CALLORDER # 创建相机控制对象 cam = MvCamera() # 初始化相机操作对象 - 确保传入有效的相机对象 obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = "打开设备失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 创建并启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(mainWindow.statusBar().showMessage) frame_monitor_thread.start() def start_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def stop_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return # 关键改进:添加连接状态检查 if not hasattr(obj_cam_operation, 'connected') or not obj_cam_operation.connected: QMessageBox.warning(mainWindow, "Error", "相机未连接", QMessageBox.Ok) return ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = "停止取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() def close_device(): global isOpen, isGrabbing, obj_cam_operation, frame_monitor_thread if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) if isOpen and obj_cam_operation: # 关键改进:确保相机对象存在 if hasattr(obj_cam_operation, 'cam') and obj_cam_operation.cam: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() def set_continue_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = "设置连续模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(True) mainWindow.radioTriggerMode.setChecked(False) mainWindow.bnSoftwareTrigger.setEnabled(False) def set_software_trigger_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = "设置触发模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(False) mainWindow.radioTriggerMode.setChecked(True) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing) def trigger_once(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.trigger_once() if ret != 0: strError = "软触发失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 尝试捕获当前帧 frame = obj_cam_operation.capture_frame() if frame is None: QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 确保图像有效 if frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0: QMessageBox.warning(mainWindow, "无效图像", "捕获的图像无效,请检查相机设置!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return # 确保文件扩展名正确 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() # 创建目录(如果不存在) directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return # 保存图像 try: # 使用OpenCV保存图像 if not cv2.imwrite(file_path, frame): raise Exception("OpenCV保存失败") # 更新状态 current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) # 显示成功消息 QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) # 可选:自动预览样本 preview_sample() except Exception as e: logging.error(f"保存图像失败: {str(e)}") QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 直接使用OpenCV加载图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: raise Exception("无法加载图像") # 显示图像 cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def start_grabbing(): global obj_cam_operation, isGrabbing ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def is_float(str): try: float(str) return True except ValueError: return False def get_param(): try: ret = obj_cam_operation.get_parameters() if ret != MV_OK: strError = "获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: mainWindow.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time)) mainWindow.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain)) mainWindow.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate)) except Exception as e: error_msg = f"获取参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def set_param(): frame_rate = mainWindow.edtFrameRate.text() exposure = mainWindow.edtExposureTime.text() gain = mainWindow.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = "设置参数失败: 参数必须是有效的浮点数" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) return MV_E_PARAMETER try: ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = "设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def enable_controls(): global isGrabbing, isOpen mainWindow.groupGrab.setEnabled(isOpen) mainWindow.paramgroup.setEnabled(isOpen) mainWindow.bnOpen.setEnabled(not isOpen) mainWindow.bnClose.setEnabled(isOpen) mainWindow.bnStart.setEnabled(isOpen and (not isGrabbing)) mainWindow.bnStop.setEnabled(isOpen and isGrabbing) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing and mainWindow.radioTriggerMode.isChecked()) mainWindow.bnSaveImage.setEnabled(isOpen and isGrabbing) mainWindow.bnCheckPrint.setEnabled(isOpen and isGrabbing) mainWindow.bnSaveSample.setEnabled(isOpen and isGrabbing) mainWindow.bnPreviewSample.setEnabled(bool(current_sample_path)) def update_sensor_display(data): if not data: return text = (f"张力: {data['tension']:.2f}N | " f"速度: {data['speed']:.2f}m/s | " f"温度: {data['temperature']:.1f}°C | " f"湿度: {data['humidity']:.1f}%") mainWindow.lblSensorData.setText(text) def connect_sensor(): global sensor_monitor_thread, sensor_controller sensor_type = mainWindow.cbSensorType.currentText() if sensor_controller is None: sensor_controller = SensorController() if sensor_type == "串口": config = { 'type': 'serial', 'port': mainWindow.cbComPort.currentText(), 'baudrate': int(mainWindow.cbBaudrate.currentText()), 'timeout': 1.0 } else: config = { 'type': 'ethernet', 'ip': mainWindow.edtIP.text(), 'port': int(mainWindow.edtPort.text()), 'timeout': 1.0 } if sensor_controller.connect(config): mainWindow.bnConnectSensor.setEnabled(False) mainWindow.bnDisconnectSensor.setEnabled(True) sensor_monitor_thread = SensorMonitorThread(sensor_controller) sensor_monitor_thread.data_updated.connect(update_sensor_display) sensor_monitor_thread.start() def disconnect_sensor(): global sensor_monitor_thread if sensor_controller: sensor_controller.disconnect() mainWindow.bnConnectSensor.setEnabled(True) mainWindow.bnDisconnectSensor.setEnabled(False) if sensor_monitor_thread and sensor_monitor_thread.isRunning(): sensor_monitor_thread.stop() sensor_monitor_thread.wait(2000) sensor_monitor_thread = None mainWindow.lblSensorData.setText("传感器数据: 未连接") def update_sensor_ui(index): mainWindow.serialGroup.setVisible(index == 0) mainWindow.ethernetGroup.setVisible(index == 1) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("布料印花检测系统") self.resize(1200, 800) central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 设备枚举区域 device_layout = QHBoxLayout() self.ComboDevices = QComboBox() self.bnEnum = QPushButton("枚举设备") self.bnOpen = QPushButton("打开设备") self.bnClose = QPushButton("关闭设备") device_layout.addWidget(self.ComboDevices) device_layout.addWidget(self.bnEnum) device_layout.addWidget(self.bnOpen) device_layout.addWidget(self.bnClose) main_layout.addLayout(device_layout) # 取流控制组 self.groupGrab = QGroupBox("取流控制") grab_layout = QHBoxLayout(self.groupGrab) self.bnStart = QPushButton("开始取流") self.bnStop = QPushButton("停止取流") self.radioContinueMode = QRadioButton("连续模式") self.radioTriggerMode = QRadioButton("触发模式") self.bnSoftwareTrigger = QPushButton("软触发") grab_layout.addWidget(self.bnStart) grab_layout.addWidget(self.bnStop) grab_layout.addWidget(self.radioContinueMode) grab_layout.addWidget(self.radioTriggerMode) grab_layout.addWidget(self.bnSoftwareTrigger) main_layout.addWidget(self.groupGrab) # 参数设置组 self.paramgroup = QGroupBox("相机参数") param_layout = QGridLayout(self.paramgroup) self.edtExposureTime = QLineEdit() self.edtGain = QLineEdit() self.edtFrameRate = QLineEdit() self.bnGetParam = QPushButton("获取参数") self.bnSetParam = QPushButton("设置参数") self.bnSaveImage = QPushButton("保存图像") param_layout.addWidget(QLabel("曝光时间:"), 0, 0) param_layout.addWidget(self.edtExposureTime, 0, 1) param_layout.addWidget(self.bnGetParam, 0, 2) param_layout.addWidget(QLabel("增益:"), 1, 0) param_layout.addWidget(self.edtGain, 1, 1) param_layout.addWidget(self.bnSetParam, 1, 2) param_layout.addWidget(QLabel("帧率:"), 2, 0) param_layout.addWidget(self.edtFrameRate, 2, 1) param_layout.addWidget(self.bnSaveImage, 2, 2) main_layout.addWidget(self.paramgroup) # 图像显示区域 self.widgetDisplay = QLabel() self.widgetDisplay.setMinimumSize(640, 480) self.widgetDisplay.setStyleSheet("background-color: black;") self.widgetDisplay.setAlignment(Qt.AlignCenter) self.widgetDisplay.setText("相机预览区域") main_layout.addWidget(self.widgetDisplay, 1) # 状态栏 #self.statusBar = QStatusBar() #self.setStatusBar(self.statusBar) # 创建自定义UI组件 self.setup_custom_ui() def setup_custom_ui(self): # 工具栏 toolbar = self.addToolBar("检测工具") self.bnCheckPrint = QPushButton("检测印花质量") self.bnSaveSample = QPushButton("保存标准样本") self.bnPreviewSample = QPushButton("预览样本") self.cbHistory = QComboBox() self.cbHistory.setMinimumWidth(300) toolbar.addWidget(self.bnCheckPrint) toolbar.addWidget(self.bnSaveSample) toolbar.addWidget(self.bnPreviewSample) toolbar.addWidget(QLabel("历史记录:")) toolbar.addWidget(self.cbHistory) # 状态栏样本路径 self.lblSamplePath = QLabel("当前样本: 未设置样本") self.statusBar().addPermanentWidget(self.lblSamplePath) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 差异度调整组 diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) self.lblDiffThreshold = QLabel("差异度阈值 (0-100%):") self.sliderDiffThreshold = QSlider(Qt.Horizontal) self.sliderDiffThreshold.setRange(0, 100) self.sliderDiffThreshold.setValue(5) self.lblDiffValue = QLabel("5%") self.lblCurrentDiff = QLabel("当前差异度: -") self.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") self.lblDiffStatus = QLabel("状态: 未检测") self.lblDiffStatus.setStyleSheet("font-size: 12px;") diff_layout.addWidget(self.lblDiffThreshold) diff_layout.addWidget(self.sliderDiffThreshold) diff_layout.addWidget(self.lblDiffValue) diff_layout.addWidget(self.lblCurrentDiff) diff_layout.addWidget(self.lblDiffStatus) right_layout.addWidget(diff_group) # 传感器控制面板 sensor_panel = QGroupBox("传感器控制") sensor_layout = QVBoxLayout(sensor_panel) sensor_type_layout = QHBoxLayout() self.lblSensorType = QLabel("传感器类型:") self.cbSensorType = QComboBox() self.cbSensorType.addItems(["串口", "以太网"]) sensor_type_layout.addWidget(self.lblSensorType) sensor_type_layout.addWidget(self.cbSensorType) sensor_layout.addLayout(sensor_type_layout) # 串口参数 self.serialGroup = QGroupBox("串口参数") serial_layout = QVBoxLayout(self.serialGroup) self.lblComPort = QLabel("端口:") self.cbComPort = QComboBox() if platform.system() == 'Windows': ports = [f"COM{i}" for i in range(1, 21)] else: ports = [f"/dev/ttyS{i}" for i in range(0, 4)] + [f"/dev/ttyUSB{i}" for i in range(0, 4)] self.cbComPort.addItems(ports) self.lblBaudrate = QLabel("波特率:") self.cbBaudrate = QComboBox() self.cbBaudrate.addItems(["96000", "19200", "38400", "57600", "115200"]) self.cbBaudrate.setCurrentText("115200") serial_layout.addWidget(self.lblComPort) serial_layout.addWidget(self.cbComPort) serial_layout.addWidget(self.lblBaudrate) serial_layout.addWidget(self.cbBaudrate) sensor_layout.addWidget(self.serialGroup) # 以太网参数 self.ethernetGroup = QGroupBox("以太网参数") ethernet_layout = QVBoxLayout(self.ethernetGroup) self.lblIP = QLabel("IP地址:") self.edtIP = QLineEdit("192.168.1.100") self.lblPort = QLabel("端口:") self.edtPort = QLineEdit("502") ethernet_layout.addWidget(self.lblIP) ethernet_layout.addWidget(self.edtIP) ethernet_layout.addWidget(self.lblPort) ethernet_layout.addWidget(self.edtPort) sensor_layout.addWidget(self.ethernetGroup) # 连接按钮 self.bnConnectSensor = QPushButton("连接传感器") self.bnDisconnectSensor = QPushButton("断开传感器") self.bnDisconnectSensor.setEnabled(False) sensor_layout.addWidget(self.bnConnectSensor) sensor_layout.addWidget(self.bnDisconnectSensor) # 延迟设置 delay_layout = QHBoxLayout() self.lblDelay = QLabel("触发延迟(秒):") self.spinDelay = QSpinBox() self.spinDelay.setRange(0, 60) self.spinDelay.setValue(0) self.spinDelay.setToolTip("传感器检测到布料后延迟拍摄的时间") delay_layout.addWidget(self.lblDelay) delay_layout.addWidget(self.spinDelay) sensor_layout.addLayout(delay_layout) # 传感器数据 self.lblSensorData = QLabel("传感器数据: 未连接") self.lblSensorData.setStyleSheet("font-size: 10pt;") sensor_layout.addWidget(self.lblSensorData) right_layout.addWidget(sensor_panel) right_layout.addStretch(1) # 停靠窗口 dock = QDockWidget("检测控制面板", self) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.addDockWidget(Qt.RightDockWidgetArea, dock) def closeEvent(self, event): logging.info("主窗口关闭,执行清理...") close_device() disconnect_sensor() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) mainWindow = MainWindow() # 信号连接 mainWindow.cbSensorType.currentIndexChanged.connect(update_sensor_ui) update_sensor_ui(0) mainWindow.bnConnectSensor.clicked.connect(connect_sensor) mainWindow.bnDisconnectSensor.clicked.connect(disconnect_sensor) mainWindow.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) mainWindow.bnCheckPrint.clicked.connect(sensor_controlled_check) mainWindow.bnSaveSample.clicked.connect(save_sample_image) mainWindow.bnPreviewSample.clicked.connect(preview_sample) mainWindow.bnEnum.clicked.connect(enum_devices) mainWindow.bnOpen.clicked.connect(open_device) mainWindow.bnClose.clicked.connect(close_device) mainWindow.bnStart.clicked.connect(start_grabbing) mainWindow.bnStop.clicked.connect(stop_grabbing) mainWindow.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.bnGetParam.clicked.connect(get_param) mainWindow.bnSetParam.clicked.connect(set_param) mainWindow.bnSaveImage.clicked.connect(save_sample_image) main() mainWindow.show() app.exec_() close_device() disconnect_sensor() sys.exit()

# -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QCheckBox, QGridLayout, QSpinBox, QRadioButton) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal, QTimer from PyQt5.QtGui import QPixmap, QImage, QFont import time import datetime import logging import platform import random from skimage.metrics import structural_similarity as ssim import json import threading import ctypes # 尝试导入海康SDK try: from MvCameraControl_class import * except ImportError: logging.error("未找到海康SDK库,请安装MVS SDK") # 如果没有安装SDK,使用模拟模式 class MvCamera: MV_CC_DEVICE_INFO_LIST = type('MV_CC_DEVICE_INFO_LIST', (object,), {}) MV_GIGE_DEVICE = 1 MV_USB_DEVICE = 4 MV_ACCESS_Exclusive = 1 @staticmethod def MV_CC_EnumDevices(nTLayerType, stDeviceList): return 0 @staticmethod def MV_CC_CreateHandle(stDeviceInfo): return 0 @staticmethod def MV_CC_OpenDevice(stCamHandle, nAccessMode, nSwitchoverKey): return 0 @staticmethod def MV_CC_StartGrabbing(stCamHandle): return 0 @staticmethod def MV_CC_StopGrabbing(stCamHandle): return 0 @staticmethod def MV_CC_CloseDevice(stCamHandle): return 0 @staticmethod def MV_CC_DestroyHandle(stCamHandle): return 0 @staticmethod def MV_CC_RegisterImageCallBack(stCamHandle, cbOutput, pUser): return 0 # 配置日志系统 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动") # 全局变量 current_sample_path = "" # 当前使用的样本路径 detection_history = [] # 检测历史记录 is_processing = False # 防止重复处理 discovered_cameras = [] # 存储发现的相机列表 # ====================== 虚拟传感器类 ====================== class VirtualSensor: """模拟真实传感器输入的虚拟传感器""" def __init__(self): self.state = False # 传感器状态(触发/未触发) self.trigger_delay = 0.5 # 默认触发延迟(秒) self.trigger_count = 0 # 触发计数器 self.sensor_type = "光电传感器" # 传感器类型 self.mock_mode = False # 模拟模式 def trigger(self): """模拟传感器触发""" self.state = True self.trigger_count += 1 logging.info(f"传感器触发 #{self.trigger_count}") time.sleep(self.trigger_delay) self.state = False def set_delay(self, delay): """设置触发延迟时间""" self.trigger_delay = max(0.1, min(delay, 5.0)) # 限制在0.1-5秒之间 def set_type(self, sensor_type): """设置传感器类型""" self.sensor_type = sensor_type def enable_mock(self, enable): """启用/禁用模拟模式""" self.mock_mode = enable if enable: logging.info("传感器模拟模式已启用") def mock_trigger(self): """模拟传感器触发(随机间隔)""" if self.mock_mode: interval = random.uniform(0.5, 3.0) threading.Timer(interval, self.trigger).start() # 创建虚拟传感器实例 virtual_sensor = VirtualSensor() # ====================== 传感器信号处理线程 ====================== class SensorThread(QThread): """处理传感器信号的线程""" sensor_triggered = pyqtSignal() def __init__(self, sensor): super().__init__() self.sensor = sensor self.running = True self.mock_timer = QTimer() self.mock_timer.timeout.connect(self.mock_sensor_check) def run(self): while self.running: if self.sensor.state: self.sensor_triggered.emit() # 等待传感器复位 while self.sensor.state: time.sleep(0.01) time.sleep(0.05) # 减少CPU占用 def start_mock(self, interval=1000): """启动模拟传感器触发""" self.mock_timer.start(interval) def stop_mock(self): """停止模拟传感器触发""" self.mock_timer.stop() def mock_sensor_check(self): """检查并触发模拟传感器""" if self.sensor.mock_mode: self.sensor.trigger() # ====================== 图像处理线程 ====================== class ImageProcessingThread(QThread): """图像处理线程,避免阻塞UI""" processing_complete = pyqtSignal(bool, float, np.ndarray) def __init__(self, sample_path, test_image, threshold, use_ssim): super().__init__() self.sample_path = sample_path self.test_image = test_image self.threshold = threshold self.use_ssim = use_ssim def run(self): try: # 执行检测 is_qualified, diff_ratio, marked_image = self.check_print_quality( self.sample_path, self.test_image, self.threshold, self.use_ssim ) # 发出信号 self.processing_complete.emit(is_qualified, diff_ratio, marked_image) except Exception as e: logging.exception(f"图像处理线程错误: {str(e)}") self.processing_complete.emit(None, None, None) def check_print_quality(self, sample_image_path, test_image, threshold=0.05, use_ssim=True): """ 优化的布料印花检测算法 :param sample_image_path: 合格样本图像路径 :param test_image: 测试图像 (numpy数组) :param threshold: 差异阈值 :param use_ssim: 是否使用SSIM结构相似性指标 :return: 是否合格,差异值,标记图像 """ try: # 读取样本图像 sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None # 确保测试图像是灰度图 if len(test_image.shape) == 3: # 如果是彩色图像 test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() # 图像配准 - 使用特征匹配解决轻微位移问题 aligned_image = self.align_images(sample_image, test_image_gray) if aligned_image is None: aligned_image = test_image_gray # 配准失败则使用原始图像 logging.warning("图像配准失败,使用原始图像") # 确保两个图像大小一致 if aligned_image.shape != sample_image.shape: aligned_image = cv2.resize(aligned_image, (sample_image.shape[1], sample_image.shape[0])) # 方法1: 极速SSIM算法 (优化版) if use_ssim: # 使用优化的SSIM计算 score = self.fast_ssim(sample_image, aligned_image) diff_ratio = 1.0 - score # 差异比例 # 计算绝对差异作为差异图 diff = cv2.absdiff(sample_image, aligned_image) _, thresholded = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY) else: # 方法2: 传统绝对差异法 diff = cv2.absdiff(sample_image, aligned_image) # 自适应阈值处理 thresholded = cv2.adaptiveThreshold( diff, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2 ) # 计算差异比例 diff_pixels = np.count_nonzero(thresholded) total_pixels = sample_image.size diff_ratio = diff_pixels / total_pixels # 形态学操作去除噪声 kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) # 多尺度缺陷检测 marked_image = self.detect_defects(aligned_image, thresholded) # 判断是否合格 is_qualified = diff_ratio <= threshold return is_qualified, diff_ratio, marked_image except Exception as e: logging.exception(f"检测过程中发生错误: {str(e)}") return None, None, None def fast_ssim(self, img1, img2): """优化的SSIM计算,提高性能""" # 图像下采样以提高速度 if img1.shape[0] > 512 or img1.shape[1] > 512: scale = 0.5 img1 = cv2.resize(img1, (0, 0), fx=scale, fy=scale) img2 = cv2.resize(img2, (0, 0), fx=scale, fy=scale) # 计算SSIM score = ssim(img1, img2, win_size=3, data_range=img1.max() - img1.min()) return max(0.0, min(1.0, score)) # 确保在0-1范围内 def align_images(self, image1, image2): """ 使用特征匹配对齐两幅图像 :param image1: 参考图像 :param image2: 待对齐图像 :return: 对齐后的图像 """ # 使用ORB检测器(比SIFT更快) orb = cv2.ORB_create() # 查找关键点和描述符 kp1, des1 = orb.detectAndCompute(image1, None) kp2, des2 = orb.detectAndCompute(image2, None) # 如果关键点不足,尝试使用SIFT if des1 is None or des2 is None or len(des1) < 4 or len(des2) < 4: sift = cv2.SIFT_create() kp1, des1 = sift.detectAndCompute(image1, None) kp2, des2 = sift.detectAndCompute(image2, None) # 如果还是没有足够的关键点,返回None if des1 is None or des2 is None or len(des1) < 4 or len(des2) < 4: return None # 使用BFMatcher进行特征匹配 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) # 至少需要4个点计算变换矩阵 if len(matches) < 4: return None # 提取匹配点坐标 src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) # 计算变换矩阵(使用RANSAC) M, mask = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC, 5.0) # 应用变换 aligned_image = cv2.warpPerspective( image2, M, (image1.shape[1], image1.shape[0]), flags=cv2.INTER_LINEAR ) return aligned_image def detect_defects(self, image, mask): """ 多尺度缺陷检测和标记 :param image: 原始图像 :param mask: 差异掩码 :return: 标记后的图像 """ # 创建彩色标记图像 marked_image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) # 查找轮廓 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 设置最小缺陷尺寸(避免标记小噪点) min_defect_area = max(10, image.size * 0.0001) # 自适应最小面积 # 标记缺陷区域 defect_count = 0 for cnt in contours: area = cv2.contourArea(cnt) if area > min_defect_area: defect_count += 1 # 计算轮廓的边界框 x, y, w, h = cv2.boundingRect(cnt) # 绘制边界框 cv2.rectangle(marked_image, (x, y), (x+w, y+h), (0, 0, 255), 2) # 在缺陷中心添加文本标签 cv2.putText( marked_image, f"Defect {defect_count}: {area}px", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1 ) # 添加缺陷统计信息 cv2.putText( marked_image, f"Total Defects: {defect_count}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2 ) return marked_image # ====================== 网络配置检查 ====================== def ctypes_array_to_str(array): """将ctypes字符数组转换为Python字符串""" # 转换为字节串 byte_str = bytes(array) # 找到第一个空字节的位置 null_index = byte_str.find(b'\0') if null_index >= 0: # 截取到第一个空字节 byte_str = byte_str[:null_index] # 尝试UTF-8解码 try: return byte_str.decode('utf-8') except UnicodeDecodeError: # 尝试GBK解码(常见于中文设备) try: return byte_str.decode('gbk') except UnicodeDecodeError: # 最终回退方案 return byte_str.decode('latin1', errors='replace') def check_network_configuration(): """检查网络配置是否适合海康相机""" global discovered_cameras # 尝试使用海康SDK枚举设备 device_list = MV_CC_DEVICE_INFO_LIST() ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, device_list) if ret == 0 and device_list.nDeviceNum > 0: discovered_cameras = [] for i in range(device_list.nDeviceNum): # 获取设备信息指针 device_info_ptr = device_list.pDeviceInfo[i] # 解引用指针获取实际设备信息 device_info = device_info_ptr.contents # 检查设备类型 if device_info.nTLayerType == MV_GIGE_DEVICE: # 处理GigE设备 - 正确转换IP地址 ip_int = device_info.SpecialInfo.stGigEInfo.nCurrentIp # 将32位整数分解为4个字节 ip_bytes = [ (ip_int >> 24) & 0xFF, # 最高字节 (ip_int >> 16) & 0xFF, # 次高字节 (ip_int >> 8) & 0xFF, # 次低字节 ip_int & 0xFF # 最低字节 ] ip = ".".join(map(str, ip_bytes)) # 正确解码模型名和序列号 model = ctypes_array_to_str(device_info.SpecialInfo.stGigEInfo.chModelName) serial = ctypes_array_to_str(device_info.SpecialInfo.stGigEInfo.chSerialNumber) discovered_cameras.append({"ip": ip, "model": model, "serial": serial}) elif device_info.nTLayerType == MV_USB_DEVICE: # 处理USB设备 model = ctypes_array_to_str(device_info.SpecialInfo.stUsb3VInfo.chModelName) serial = ctypes_array_to_str(device_info.SpecialInfo.stUsb3VInfo.chSerialNumber) discovered_cameras.append({"ip": "USB", "model": model, "serial": serial}) logging.info(f"发现 {len(discovered_cameras)} 台真实相机") return True else: # 模拟相机发现 discovered_cameras = [ {"ip": "192.168.1.101", "model": "MV-CA016-10GC", "serial": "SN123456"}, {"ip": "192.168.1.102", "model": "MV-CA020-10GC", "serial": "SN789012"} ] logging.info(f"使用模拟相机数据: {len(discovered_cameras)} 台网络相机") return bool(discovered_cameras) # ====================== 主窗口类 ====================== class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # 初始化顺序保持不变 self.setup_ui() self.setup_variables() self.setup_connections() self.load_settings() def setup_variables(self): """初始化变量""" self.isOpen = False # 相机是否打开 self.isGrabbing = False # 相机是否在取流 self.cam = None # 相机对象 self.current_frame = None # 当前帧 self.settings = None # 用于存储设置 self.image_thread = None # 图像处理线程 self.progress = None # 进度对话框 # 创建传感器线程 self.sensor_thread = SensorThread(virtual_sensor) def setup_ui(self): """初始化用户界面""" # 主窗口设置 self.setWindowTitle("布料印花检测系统") self.setGeometry(100, 100, 1200, 800) # 创建主窗口部件和布局 central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # ===== 顶部工具栏 ===== toolbar = QToolBar("主工具栏") self.addToolBar(toolbar) # 创建工具栏按钮 self.bnEnum = QPushButton("枚举设备") self.bnOpen = QPushButton("打开设备") self.bnClose = QPushButton("关闭设备") self.bnStart = QPushButton("开始取流") self.bnStop = QPushButton("停止取流") self.bnSaveImage = QPushButton("保存图像") # 添加到工具栏 toolbar.addWidget(self.bnEnum) toolbar.addWidget(self.bnOpen) toolbar.addWidget(self.bnClose) toolbar.addWidget(self.bnStart) toolbar.addWidget(self.bnStop) toolbar.addWidget(self.bnSaveImage) # ===== 设备选择区域 ===== device_group = QGroupBox("设备控制") device_layout = QHBoxLayout(device_group) self.ComboDevices = QComboBox() self.ComboDevices.setMinimumWidth(300) device_layout.addWidget(QLabel("选择设备:")) device_layout.addWidget(self.ComboDevices) # 触发模式 trigger_group = QGroupBox("采集模式") trigger_layout = QVBoxLayout(trigger_group) self.radioContinueMode = QRadioButton("连续采集模式") self.radioTriggerMode = QRadioButton("触发采集模式") self.radioContinueMode.setChecked(True) trigger_layout.addWidget(self.radioContinueMode) trigger_layout.addWidget(self.radioTriggerMode) # 软件触发按钮 self.bnSoftwareTrigger = QPushButton("软触发") self.bnSoftwareTrigger.setEnabled(False) device_layout.addWidget(trigger_group) device_layout.addWidget(self.bnSoftwareTrigger) main_layout.addWidget(device_group) # ===== 图像显示区域 ===== image_group = QGroupBox("图像显示") image_layout = QHBoxLayout(image_group) self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet("background-color: black;") image_layout.addWidget(self.image_label) # 参数控制 param_group = QGroupBox("相机参数") param_layout = QGridLayout(param_group) param_layout.addWidget(QLabel("曝光时间(μs):"), 0, 0) self.edtExposureTime = QLineEdit("10000") param_layout.addWidget(self.edtExposureTime, 0, 1) param_layout.addWidget(QLabel("增益(dB):"), 1, 0) self.edtGain = QLineEdit("0") param_layout.addWidget(self.edtGain, 1, 1) param_layout.addWidget(QLabel("帧率(fps):"), 2, 0) self.edtFrameRate = QLineEdit("30") param_layout.addWidget(self.edtFrameRate, 2, 1) self.bnGetParam = QPushButton("获取参数") self.bnSetParam = QPushButton("设置参数") param_layout.addWidget(self.bnGetParam, 0, 2) param_layout.addWidget(self.bnSetParam, 1, 2) image_layout.addWidget(param_group) main_layout.addWidget(image_group) # ===== 检测控制区域 ===== detection_group = QGroupBox("检测控制") detection_layout = QGridLayout(detection_group) # 样本控制 self.bnSaveSample = QPushButton("保存样本") self.bnPreviewSample = QPushButton("预览样本") self.lblSamplePath = QLabel("当前样本: 未设置样本") detection_layout.addWidget(QLabel("样本控制:"), 0, 0) detection_layout.addWidget(self.bnSaveSample, 0, 1) detection_layout.addWidget(self.bnPreviewSample, 0, 2) detection_layout.addWidget(self.lblSamplePath, 0, 3) # 检测参数 detection_layout.addWidget(QLabel("差异阈值:"), 1, 0) self.sliderDiffThreshold = QSlider(Qt.Horizontal) self.sliderDiffThreshold.setRange(1, 20) # 1% - 20% self.sliderDiffThreshold.setValue(5) detection_layout.addWidget(self.sliderDiffThreshold, 1, 1, 1, 2) self.lblDiffValue = QLabel("5%") detection_layout.addWidget(self.lblDiffValue, 1, 3) # 检测方法 self.cbUseSSIM = QCheckBox("使用SSIM算法") self.cbUseSSIM.setChecked(True) detection_layout.addWidget(self.cbUseSSIM, 2, 0) # 检测按钮 self.bnCheckPrint = QPushButton("检测布料印花") detection_layout.addWidget(self.bnCheckPrint, 2, 1, 1, 3) # 检测结果显示 self.lblCurrentDiff = QLabel("当前差异度: --") self.lblDiffStatus = QLabel("状态: --") self.lblDiffStatus.setStyleSheet("font-weight: bold;") detection_layout.addWidget(self.lblCurrentDiff, 3, 0, 1, 2) detection_layout.addWidget(self.lblDiffStatus, 3, 2, 1, 2) main_layout.addWidget(detection_group) # ===== 传感器控制区域 ===== sensor_group = QGroupBox("传感器控制") sensor_layout = QGridLayout(sensor_group) # 传感器类型 sensor_layout.addWidget(QLabel("传感器类型:"), 0, 0) self.comboSensorType = QComboBox() self.comboSensorType.addItems(["光电传感器", "接近传感器", "编码器"]) sensor_layout.addWidget(self.comboSensorType, 0, 1) # 传感器延迟 sensor_layout.addWidget(QLabel("触发延迟(秒):"), 1, 0) self.edtSensorDelay = QLineEdit("0.5") sensor_layout.addWidget(self.edtSensorDelay, 1, 1) self.bnSetSensorDelay = QPushButton("设置延迟") sensor_layout.addWidget(self.bnSetSensorDelay, 1, 2) # 传感器模拟 self.cbMockSensor = QCheckBox("启用传感器模拟") self.cbMockSensor.setChecked(False) sensor_layout.addWidget(self.cbMockSensor, 2, 0) self.spinMockInterval = QSpinBox() self.spinMockInterval.setRange(500, 5000) self.spinMockInterval.setValue(1000) self.spinMockInterval.setSuffix("ms") sensor_layout.addWidget(self.spinMockInterval, 2, 1) self.bnStartMock = QPushButton("开始模拟") self.bnStartMock.setEnabled(False) self.bnStopMock = QPushButton("停止模拟") self.bnStopMock.setEnabled(False) sensor_layout.addWidget(self.bnStartMock, 2, 2) sensor_layout.addWidget(self.bnStopMock, 2, 3) # 手动触发 self.bnManualTrigger = QPushButton("手动触发传感器") sensor_layout.addWidget(self.bnManualTrigger, 3, 0, 1, 4) main_layout.addWidget(sensor_group) # ===== 历史记录区域 ===== history_group = QGroupBox("检测历史") history_layout = QVBoxLayout(history_group) self.cbHistory = QComboBox() history_layout.addWidget(self.cbHistory) main_layout.addWidget(history_group) # ===== 状态栏 ===== self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.statusBar.showMessage("就绪", 5000) # 帧状态标签 self.lblFrameStatus = QLabel("帧状态: --") self.statusBar.addPermanentWidget(self.lblFrameStatus) # 启用控件 self.enable_controls() # ====================== 相机操作方法 ====================== def enum_devices(self): """枚举设备""" self.ComboDevices.clear() # 多次尝试枚举 max_retries = 3 device_list = None for i in range(max_retries): device_list = MV_CC_DEVICE_INFO_LIST() ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, device_list) if ret == 0 and device_list.nDeviceNum > 0: break else: logging.warning(f"枚举设备失败 (尝试 {i+1}/{max_retries}), 错误码: {ret}") time.sleep(1) # 等待1秒后重试 if device_list is None or device_list.nDeviceNum == 0: self.statusBar.showMessage("未找到任何设备", 5000) return # 将设备添加到下拉框 for i in range(device_list.nDeviceNum): device_info_ptr = device_list.pDeviceInfo[i] device_info = device_info_ptr.contents # 检查设备类型并添加 if device_info.nTLayerType == MV_GIGE_DEVICE: ip_int = device_info.SpecialInfo.stGigEInfo.nCurrentIp ip_bytes = [(ip_int >> 24) & 0xFF, (ip_int >> 16) & 0xFF, (ip_int >> 8) & 0xFF, ip_int & 0xFF] ip = ".".join(map(str, ip_bytes)) model = ctypes_array_to_str(device_info.SpecialInfo.stGigEInfo.chModelName) serial = ctypes_array_to_str(device_info.SpecialInfo.stGigEInfo.chSerialNumber) device_str = f"[{i}]GigE: {model} ({ip})" self.ComboDevices.addItem(device_str, i) elif device_info.nTLayerType == MV_USB_DEVICE: model = ctypes_array_to_str(device_info.SpecialInfo.stUsb3VInfo.chModelName) serial = ctypes_array_to_str(device_info.SpecialInfo.stUsb3VInfo.chSerialNumber) device_str = f"[{i}]USB: {model} (SN:{serial})" self.ComboDevices.addItem(device_str, i) self.statusBar.showMessage(f"已发现 {device_list.nDeviceNum} 个设备", 5000) # 自动选择第一个设备 if device_list.nDeviceNum > 0: self.ComboDevices.setCurrentIndex(0) def open_device(self): """打开真实海康设备""" if self.isOpen: QMessageBox.warning(self, "错误", "设备已打开", QMessageBox.Ok) return if self.ComboDevices.currentIndex() < 0: QMessageBox.warning(self, "错误", "请先选择设备", QMessageBox.Ok) return # 重新枚举设备以确保列表最新 device_list = MV_CC_DEVICE_INFO_LIST() ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, device_list) if ret != 0: QMessageBox.warning(self, "错误", f"枚举设备失败! 错误码: {ret}", QMessageBox.Ok) return device_index = self.ComboDevices.currentIndex() if device_index >= device_list.nDeviceNum: QMessageBox.warning(self, "错误", "选择的设备索引无效", QMessageBox.Ok) return # 获取设备信息 device_info = device_list.pDeviceInfo[device_index] try: # 创建相机实例 self.cam = MvCamera() # 创建句柄 ret = self.cam.MV_CC_CreateHandle(device_info) if ret != 0: error_msg = self.get_camera_error(ret) QMessageBox.critical( self, "创建句柄失败", f"错误码: {ret}\n{error_msg}\n\n" "可能原因:\n" "1. 设备未正确连接\n" "2. 设备被其他程序占用\n" "3. 驱动程序未正确安装", QMessageBox.Ok ) self.cam = None return # 打开设备 ret = self.cam.MV_CC_OpenDevice(MV_ACCESS_Exclusive, 0) if ret != 0: error_msg = self.get_camera_error(ret) QMessageBox.critical( self, "打开设备失败", f"错误码: {ret}\n{error_msg}\n\n" "解决方案:\n" "1. 检查设备电源和连接\n" "2. 重启设备和计算机\n" "3. 确保设备未被其他程序占用", QMessageBox.Ok ) self.cam.MV_CC_DestroyHandle() self.cam = None return # 注册图像回调 - 修复语法错误的位置 ret = self.cam.MV_CC_RegisterImageCallBack(self.image_callback, None) if ret != 0: logging.warning(f"注册图像回调失败! 错误码: {ret}") # 回调注册失败不是致命错误,继续执行 # 设置基本参数 self.set_reasonable_default_params() self.isOpen = True self.enable_controls() self.statusBar.showMessage("设备已成功打开", 5000) except Exception as e: logging.exception(f"打开设备时发生异常: {str(e)}") QMessageBox.critical( self, "严重错误", f"打开设备时发生异常:\n{str(e)}\n\n" "请检查:\n" "1. SDK是否正确安装\n" "2. Python环境是否匹配SDK架构(32/64位)\n" "3. 依赖库是否完整", QMessageBox.Ok ) if self.cam: self.cam.MV_CC_DestroyHandle() self.cam = None def log_device_info(self, device_info): """记录设备详细信息用于调试""" if device_info.nTLayerType == MV_GIGE_DEVICE: ip_int = device_info.SpecialInfo.stGigEInfo.nCurrentIp ip_bytes = [ (ip_int >> 24) & 0xFF, (ip_int >> 16) & 0xFF, (ip_int >> 8) & 0xFF, ip_int & 0xFF ] ip = ".".join(map(str, ip_bytes)) model = ctypes_array_to_str(device_info.SpecialInfo.stGigEInfo.chModelName) serial = ctypes_array_to_str(device_info.SpecialInfo.stGigEInfo.chSerialNumber) logging.info(f"尝试打开设备: GigE相机 - 型号: {model}, IP: {ip}, SN: {serial}") elif device_info.nTLayerType == MV_USB_DEVICE: model = ctypes_array_to_str(device_info.SpecialInfo.stUsb3VInfo.chModelName) serial = ctypes_array_to_str(device_info.SpecialInfo.stUsb3VInfo.chSerialNumber) logging.info(f"尝试打开设备: USB相机 - 型号: {model}, SN: {serial}") def get_camera_error(self, error_code): """获取相机错误信息描述(增强版)""" error_map = { 0x80000000: "错误或无效参数", 0x80000001: "不支持的功能", 0x80000002: "相机未初始化", 0x80000003: "相机资源不足", 0x80000004: "设备未找到 - 请检查物理连接和IP配置", 0x80000005: "设备已被占用 - 关闭其他使用相机的程序", 0x80000006: "参数超出范围", 0x80000007: "参数类型错误", 0x80000008: "参数值错误", 0x80000009: "超时 - 操作未在规定时间内完成", 0x8000000A: "设备已打开", 0x8000000B: "设备未打开", 0x8000000C: "设备未连接 - 检查物理连接", 0x8000000D: "资源锁定 - 资源被其他操作占用", 0x8000000E: "无数据 - 没有可用的图像数据", 0x8000000F: "内存不足 - 系统内存不足", 0x80000010: "不支持的操作系统", 0x80000011: "内部错误 - SDK内部错误", 0x80000012: "文件访问错误", 0x80000013: "文件格式错误", 0x80000014: "访问被拒绝 - 权限不足", 0x80000015: "缓冲区太小 - 提供的缓冲区不足以容纳数据", 0x80000016: "无效句柄", 0x80000017: "无效ID", 0x80000018: "无效访问权限", 0x80000019: "无效IP配置 - 检查相机IP设置", 0x8000001A: "无效注册表类型", 0x8000001B: "无效注册表大小", 0x8000001C: "无效属性", 0x8000001D: "设备已被其他程序占用 - 关闭其他相机应用程序", } def set_reasonable_default_params(self): """设置合理的默认参数""" if not self.isOpen or self.cam is None: return try: # 设置合理的曝光时间 ret = self.cam.MV_CC_SetFloatValue("ExposureTime", 10000) if ret == 0: self.edtExposureTime.setText("10000") # 设置合理的增益 ret = self.cam.MV_CC_SetFloatValue("Gain", 0) if ret == 0: self.edtGain.setText("0") # 设置合理的帧率 ret = self.cam.MV_CC_SetFloatValue("AcquisitionFrameRate", 30) if ret == 0: self.edtFrameRate.setText("30") # 设置像素格式为Mono8(灰度) ret = self.cam.MV_CC_SetEnumValue("PixelFormat", PixelType_Gvsp_Mono8) if ret != 0: logging.warning(f"设置像素格式失败! 错误码: {ret}") # 设置自动白平衡(如果是彩色相机) ret = self.cam.MV_CC_SetEnumValue("BalanceWhiteAuto", 1) # 1 = Continuous if ret != 0: logging.debug("此相机可能不支持自动白平衡") # 设置自动曝光 ret = self.cam.MV_CC_SetEnumValue("ExposureAuto", 2) # 2 = Continuous if ret != 0: logging.debug("此相机可能不支持自动曝光") self.statusBar.showMessage("已设置默认参数", 2000) except Exception as e: logging.error(f"设置默认参数时出错: {str(e)}") def close_device(self): """关闭真实设备""" if not self.isOpen or self.cam is None: return # 停止取流 if self.isGrabbing: self.stop_grabbing() # 关闭设备 ret = self.cam.MV_CC_CloseDevice() if ret != 0: logging.error(f"关闭设备失败! 错误码: {ret}") # 销毁句柄 self.cam.MV_CC_DestroyHandle() self.cam = None self.isOpen = False self.isGrabbing = False self.enable_controls() self.statusBar.showMessage("设备已关闭", 3000) def start_grabbing(self): """开始真实取流""" if not self.isOpen: QMessageBox.warning(self, "错误", "请先打开设备", QMessageBox.Ok) return # 开始取流 ret = self.cam.MV_CC_StartGrabbing() if ret != 0: QMessageBox.warning(self, "错误", f"开始取流失败! 错误码: {ret}", QMessageBox.Ok) return self.isGrabbing = True self.enable_controls() self.statusBar.showMessage("已开始取流", 3000) def stop_grabbing(self): """停止真实取流""" if not self.isGrabbing or self.cam is None: return # 停止取流 ret = self.cam.MV_CC_StopGrabbing() if ret != 0: logging.error(f"停止取流失败! 错误码: {ret}") self.isGrabbing = False self.enable_controls() self.statusBar.showMessage("已停止取流", 3000) def set_continue_mode(self): """设置连续采集模式""" if self.isOpen: # 实际设置相机为连续模式 if self.cam: ret = self.cam.MV_CC_SetEnumValue("TriggerMode", 0) if ret == 0: self.statusBar.showMessage("已设置为连续采集模式", 3000) else: self.statusBar.showMessage(f"设置连续模式失败! 错误码: {ret}", 3000) self.bnSoftwareTrigger.setEnabled(False) def set_software_trigger_mode(self): """设置触发采集模式""" if self.isOpen: # 实际设置相机为软件触发模式 if self.cam: ret = self.cam.MV_CC_SetEnumValue("TriggerMode", 1) # 1 = On if ret == 0: self.statusBar.showMessage("已设置为触发采集模式", 3000) else: self.statusBar.showMessage(f"设置触发模式失败! 错误码: {ret}", 3000) self.bnSoftwareTrigger.setEnabled(self.isGrabbing) def trigger_once(self): """执行软触发""" if self.isOpen and self.isGrabbing and self.cam: # 执行软触发 ret = self.cam.MV_CC_SetCommandValue("TriggerSoftware") if ret == 0: self.statusBar.showMessage("已执行软触发", 3000) else: self.statusBar.showMessage(f"软触发失败! 错误码: {ret}", 3000) def get_param(self): """获取相机参数""" if not self.isOpen or self.cam is None: QMessageBox.warning(self, "错误", "设备未打开", QMessageBox.Ok) return try: # 获取曝光时间 exposure = ctypes.c_float() ret = self.cam.MV_CC_GetFloatValue("ExposureTime", exposure) if ret == 0: self.edtExposureTime.setText(f"{exposure.value:.2f}") # 获取增益 gain = ctypes.c_float() ret = self.cam.MV_CC_GetFloatValue("Gain", gain) if ret == 0: self.edtGain.setText(f"{gain.value:.2f}") # 获取帧率 frame_rate = ctypes.c_float() ret = self.cam.MV_CC_GetFloatValue("AcquisitionFrameRate", frame_rate) if ret == 0: self.edtFrameRate.setText(f"{frame_rate.value:.2f}") self.statusBar.showMessage("已获取相机参数", 3000) except Exception as e: logging.error(f"获取参数错误: {str(e)}") self.statusBar.showMessage("获取参数失败", 3000) def set_param(self): """设置相机参数""" if not self.isOpen or self.cam is None: QMessageBox.warning(self, "错误", "设备未打开", QMessageBox.Ok) return try: exposure = float(self.edtExposureTime.text()) gain = float(self.edtGain.text()) frame_rate = float(self.edtFrameRate.text()) # 验证参数范围 if not (5000 <= exposure <= 20000): raise ValueError("曝光时间应在5000-20000μs之间") if not (0 <= gain <= 20): raise ValueError("增益应在0-20dB之间") if not (10 <= frame_rate <= 60): raise ValueError("帧率应在10-60fps之间") # 设置曝光时间 ret = self.cam.MV_CC_SetFloatValue("ExposureTime", exposure) if ret != 0: logging.error(f"设置曝光失败! 错误码: {ret}") # 设置增益 ret = self.cam.MV_CC_SetFloatValue("Gain", gain) if ret != 0: logging.error(f"设置增益失败! 错误码: {ret}") # 设置帧率 ret = self.cam.MV_CC_SetFloatValue("AcquisitionFrameRate", frame_rate) if ret != 0: logging.error(f"设置帧率失败! 错误码: {ret}") self.statusBar.showMessage(f"已设置参数: 曝光={exposure}μs, 增益={gain}dB, 帧率={frame_rate}fps", 3000) except ValueError as e: QMessageBox.warning(self, "输入错误", str(e), QMessageBox.Ok) def save_image_dialog(self): """保存图像对话框""" if not self.isGrabbing: QMessageBox.warning(self, "错误", "请先开始取流", QMessageBox.Ok) return file_path, _ = QFileDialog.getSaveFileName( self, "保存图像", os.path.join(os.getcwd(), "capture.bmp"), "BMP Files (*.bmp);;All Files (*)" ) if file_path: # 在实际应用中这里会保存真实图像 # 需要从相机获取当前帧并保存 self.statusBar.showMessage(f"图像已保存至: {file_path}", 5000) def enable_controls(self): """设置控件状态""" # 相机控制 self.bnOpen.setEnabled(not self.isOpen) self.bnClose.setEnabled(self.isOpen) self.bnStart.setEnabled(self.isOpen and not self.isGrabbing) self.bnStop.setEnabled(self.isOpen and self.isGrabbing) self.bnSaveImage.setEnabled(self.isGrabbing) self.bnSoftwareTrigger.setEnabled(self.isGrabbing and self.radioTriggerMode.isChecked()) # 检测控制 self.bnCheckPrint.setEnabled(self.isGrabbing and bool(current_sample_path)) self.bnSaveSample.setEnabled(self.isGrabbing) self.bnPreviewSample.setEnabled(bool(current_sample_path)) # 参数控制 self.bnGetParam.setEnabled(self.isOpen) self.bnSetParam.setEnabled(self.isOpen) # ====================== 检测相关函数 ====================== def save_sample_image(self): """保存标准样本""" if not self.isGrabbing: QMessageBox.warning(self, "错误", "请先开始取流", QMessageBox.Ok) return file_path, _ = QFileDialog.getSaveFileName( self, "保存标准样本", os.path.join(os.getcwd(), "sample.bmp"), "BMP Files (*.bmp);;All Files (*)" ) if file_path: global current_sample_path current_sample_path = file_path # 保存当前帧作为样本 if hasattr(self, 'current_frame') and self.current_frame is not None: cv2.imwrite(file_path, self.current_frame) self.statusBar.showMessage(f"标准样本已保存: {file_path}", 5000) else: self.statusBar.showMessage("无法保存样本: 无有效图像", 5000) self.update_sample_display() self.save_settings() def preview_sample(self): """预览样本""" global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(self, "错误", "请先设置有效的标准样本图像", QMessageBox.Ok) return # 显示样本图像 sample_image = cv2.imread(current_sample_path) if sample_image is not None: # 转换为QPixmap并显示 if len(sample_image.shape) == 2: h, w = sample_image.shape bytes_per_line = w q_img = QImage(sample_image.data, w, h, bytes_per_line, QImage.Format_Grayscale8) else: h, w, ch = sample_image.shape bytes_per_line = ch * w q_img = QImage(sample_image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(q_img) self.lblImageDisplay.setPixmap(pixmap.scaled( self.lblImageDisplay.width(), self.lblImageDisplay.height(), Qt.KeepAspectRatio )) self.statusBar.showMessage("正在预览样本图像", 3000) else: QMessageBox.warning(self, "错误", "无法加载样本图像", QMessageBox.Ok) def update_diff_threshold(self, value): """更新差异度阈值显示""" self.lblDiffValue.setText(f"{value}%") def check_print(self): """执行检测""" global is_processing, current_sample_path, detection_history if is_processing: return is_processing = True # 检查条件 if not self.isGrabbing: QMessageBox.warning(self, "错误", "请先开始取流", QMessageBox.Ok) is_processing = False return if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(self, "错误", "请先设置有效的标准样本图像", QMessageBox.Ok) is_processing = False return # 获取当前帧 if not hasattr(self, 'current_frame') or self.current_frame is None: QMessageBox.warning(self, "错误", "无有效图像可用于检测", QMessageBox.Ok) is_processing = False return test_image = self.current_frame # 显示进度对话框 self.progress = QProgressDialog("正在检测布料质量...", "取消", 0, 100, self) self.progress.setWindowModality(Qt.WindowModal) self.progress.setValue(30) # 获取参数 diff_threshold = self.sliderDiffThreshold.value() / 100.0 use_ssim = self.cbUseSSIM.isChecked() # 启动图像处理线程 self.image_thread = ImageProcessingThread( current_sample_path, test_image, diff_threshold, use_ssim ) self.image_thread.processing_complete.connect(self.handle_processing_result) self.image_thread.start() # ====================== 传感器相关函数 ====================== def sensor_triggered(self): """处理传感器触发事件""" if not self.cbEnableSensor.isChecked(): logging.debug("传感器触发但未启用,忽略") return if not self.isGrabbing: logging.warning("传感器触发时相机未就绪") self.statusBar.showMessage("传感器触发但相机未就绪", 3000) return # 确保相机已连接 if not self.isOpen or self.cam is None: logging.warning("传感器触发时相机未连接") self.statusBar.showMessage("传感器触发但相机未连接", 3000) return # 设置触发模式(如果是触发模式) if self.radioTriggerMode.isChecked(): self.set_software_trigger_mode() # 在实际系统中,这里会确保布料移动到正确位置 logging.info("传感器触发 - 开始检测") self.statusBar.showMessage("传感器触发 - 开始检测", 3000) # 执行检测 self.check_print() def manual_sensor_trigger(self): """手动触发传感器""" virtual_sensor.trigger() self.statusBar.showMessage("手动触发传感器", 3000) def set_sensor_delay(self): """设置传感器触发延迟""" try: delay = float(self.edtSensorDelay.text()) virtual_sensor.set_delay(delay) self.save_settings() self.statusBar.showMessage(f"传感器延迟已设置为 {delay} 秒", 3000) except ValueError: QMessageBox.warning(self, "输入错误", "请输入有效的数字(0.1-5.0)", QMessageBox.Ok) def set_sensor_type(self, sensor_type): """设置传感器类型""" virtual_sensor.set_type(sensor_type) self.save_settings() self.statusBar.showMessage(f"传感器类型已设置为 {sensor_type}", 3000) def enable_sensor_mock(self, state): """启用/禁用传感器模拟""" virtual_sensor.enable_mock(state == Qt.Checked) self.bnStartMock.setEnabled(state == Qt.Checked) self.bnStopMock.setEnabled(state == Qt.Checked) self.spinMockInterval.setEnabled(state == Qt.Checked) def start_mock_sensor(self): """启动模拟传感器""" interval = self.spinMockInterval.value() self.sensor_thread.start_mock(interval) self.statusBar.showMessage(f"传感器模拟已启动,间隔 {interval}ms", 3000) def stop_mock_sensor(self): """停止模拟传感器""" self.sensor_thread.stop_mock() self.statusBar.showMessage("传感器模拟已停止", 3000) # ====================== 辅助方法 ====================== def load_settings(self): """加载应用程序设置""" self.settings = QSettings("ClothInspection", "CameraApp") # 加载样本路径 sample_path = self.settings.value("current_sample_path", "") if sample_path: global current_sample_path current_sample_path = sample_path self.update_sample_display() # 加载检测参数 diff_threshold = self.settings.value("diff_threshold", 5, type=int) self.sliderDiffThreshold.setValue(diff_threshold) self.update_diff_threshold(diff_threshold) # 加载传感器设置 sensor_delay = self.settings.value("sensor_delay", 0.5, type=float) self.edtSensorDelay.setText(str(sensor_delay)) virtual_sensor.set_delay(sensor_delay) sensor_type = self.settings.value("sensor_type", "光电传感器") self.comboSensorType.setCurrentText(sensor_type) virtual_sensor.set_type(sensor_type) def save_settings(self): """保存应用程序设置""" # 保存样本路径 self.settings.setValue("current_sample_path", current_sample_path) # 保存检测参数 self.settings.setValue("diff_threshold", self.sliderDiffThreshold.value()) # 保存传感器设置 self.settings.setValue("sensor_delay", float(self.edtSensorDelay.text())) self.settings.setValue("sensor_type", self.comboSensorType.currentText()) def update_sample_display(self): """更新样本路径显示""" global current_sample_path if current_sample_path: self.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") self.lblSamplePath.setToolTip(current_sample_path) else: self.lblSamplePath.setText("当前样本: 未设置样本") def update_history_display(self): """更新历史记录显示""" global detection_history self.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): # 显示最近10条记录 timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" self.cbHistory.addItem(f"[{timestamp}] {status} - 差异: {ratio}") def handle_processing_result(self, is_qualified, diff_ratio, marked_image): """处理检测结果""" global is_processing, detection_history self.progress.setValue(100) self.progress.close() if is_qualified is None: QMessageBox.critical(self, "检测错误", "检测过程中发生错误", QMessageBox.Ok) is_processing = False return # 更新UI显示 self.update_diff_display(diff_ratio, is_qualified) # 显示结果 result_text = f"布料印花 {'合格' if is_qualified else '不合格'}\n差异度: {diff_ratio*100:.2f}%\n阈值: {self.sliderDiffThreshold.value()}%" QMessageBox.information(self, "检测结果", result_text, QMessageBox.Ok) # 显示标记图像 if marked_image is not None: # 转换为QPixmap并显示 if len(marked_image.shape) == 2: h, w = marked_image.shape bytes_per_line = w q_img = QImage(marked_image.data, w, h, bytes_per_line, QImage.Format_Grayscale8) else: h, w, ch = marked_image.shape bytes_per_line = ch * w q_img = QImage(marked_image.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(q_img) self.lblImageDisplay.setPixmap(pixmap.scaled( self.lblImageDisplay.width(), self.lblImageDisplay.height(), Qt.KeepAspectRatio )) self.statusBar.showMessage("已显示缺陷标记图像", 5000) # 记录检测结果 detection_result = { 'timestamp': datetime.datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': self.sliderDiffThreshold.value() } detection_history.append(detection_result) self.update_history_display() is_processing = False def update_diff_display(self, diff_ratio, is_qualified): """更新差异度显示""" self.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") if is_qualified: self.lblDiffStatus.setText("状态: 合格") self.lblDiffStatus.setStyleSheet("color: green;") else: self.lblDiffStatus.setText("状态: 不合格") self.lblDiffStatus.setStyleSheet("color: red;") def image_callback(self, pData, pFrameInfo, pUser): """图像数据回调函数""" try: if pFrameInfo.contents.nFrameLen <= 0: return # 将原始数据转换为numpy数组 data = (ctypes.c_ubyte * pFrameInfo.contents.nFrameLen).from_address(pData) image = np.frombuffer(data, dtype=np.uint8) # 根据帧信息解码图像 if pFrameInfo.contents.enPixelType == PixelType_Gvsp_Mono8: # 单通道灰度图 image = image.reshape(pFrameInfo.contents.nHeight, pFrameInfo.contents.nWidth) elif pFrameInfo.contents.enPixelType == PixelType_Gvsp_RGB8_Packed: # RGB24 image = image.reshape(pFrameInfo.contents.nHeight, pFrameInfo.contents.nWidth, 3) image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) else: # 其他格式需要转换 image = cv2.imdecode(image, cv2.IMREAD_UNCHANGED) # 显示图像 self.display_real_image(image) # 保存当前帧用于检测 self.current_frame = image.copy() # 更新帧状态 self.lblFrameStatus.setText(f"帧状态: {pFrameInfo.contents.nWidth}x{pFrameInfo.contents.nHeight}") except Exception as e: logging.exception(f"图像回调错误: {str(e)}") def display_real_image(self, image): """显示真实相机图像""" if len(image.shape) == 2: # 灰度图 h, w = image.shape bytes_per_line = w q_img = QImage(image.data, w, h, bytes_per_line, QImage.Format_Grayscale8) elif len(image.shape) == 3: # 彩色图 h, w, ch = image.shape bytes_per_line = ch * w q_img = QImage(image.data, w, h, bytes_per_line, QImage.Format_RGB888) else: return pixmap = QPixmap.fromImage(q_img) self.lblImageDisplay.setPixmap(pixmap.scaled( self.lblImageDisplay.width(), self.lblImageDisplay.height(), Qt.KeepAspectRatio )) # ====================== 连接方法 ====================== def setup_connections(self): """连接信号和槽""" # 相机控制 self.bnEnum.clicked.connect(self.enum_devices) self.bnOpen.clicked.connect(self.open_device) self.bnClose.clicked.connect(self.close_device) self.bnStart.clicked.connect(self.start_grabbing) self.bnStop.clicked.connect(self.stop_grabbing) self.bnSaveImage.clicked.connect(self.save_image_dialog) # 参数控制 self.bnGetParam.clicked.connect(self.get_param) self.bnSetParam.clicked.connect(self.set_param) # 触发模式 self.radioContinueMode.clicked.connect(self.set_continue_mode) self.radioTriggerMode.clicked.connect(self.set_software_trigger_mode) self.bnSoftwareTrigger.clicked.connect(self.trigger_once) # 检测控制 self.bnCheckPrint.clicked.connect(self.check_print) self.bnSaveSample.clicked.connect(self.save_sample_image) self.bnPreviewSample.clicked.connect(self.preview_sample) self.sliderDiffThreshold.valueChanged.connect(self.update_diff_threshold) # 传感器控制 self.bnSetSensorDelay.clicked.connect(self.set_sensor_delay) self.bnManualTrigger.clicked.connect(self.manual_sensor_trigger) self.comboSensorType.currentTextChanged.connect(self.set_sensor_type) self.cbMockSensor.stateChanged.connect(self.enable_sensor_mock) self.bnStartMock.clicked.connect(self.start_mock_sensor) self.bnStopMock.clicked.connect(self.stop_mock_sensor) # 连接传感器信号 self.sensor_thread.sensor_triggered.connect(self.sensor_triggered) def closeEvent(self, event): """关闭应用程序时执行清理""" self.save_settings() # 停止传感器线程 if self.sensor_thread.isRunning(): self.sensor_thread.stop_mock() self.sensor_thread.quit() self.sensor_thread.wait(2000) # 关闭相机 if self.cam: self.close_device() event.accept() # ====================== 主程序入口 ====================== if __name__ == "__main__": # 首先检查网络配置 if not check_network_configuration(): # 创建临时QApplication用于显示错误消息 app_temp = QApplication(sys.argv) error_msg = "网络配置检查失败,无法检测到海康相机。请检查:\n\n" error_msg += "1. 相机是否已正确连接并上电\n" error_msg += "2. 计算机和相机是否在同一子网\n" error_msg += "3. 防火墙是否阻止了相机通信\n" error_msg += "4. 网线连接是否正常\n\n" # 添加发现的相机信息(如果有) if discovered_cameras: error_msg += "发现的相机:\n" for cam in discovered_cameras: error_msg += f"- {cam['model']} (IP: {cam['ip']}, SN: {cam['serial']})\n" QMessageBox.critical(None, "网络错误", error_msg, QMessageBox.Ok) sys.exit(1) # 如果网络检查通过,继续运行主应用 app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle("Fusion") # 创建主窗口 main_window = MainWindow() # 启动传感器线程 main_window.sensor_thread.start() # 显示主窗口 main_window.show() # 执行应用程序 sys.exit(app.exec_()) 这个程序出现下面的问题 Traceback (most recent call last): File "d:\海康\MVS\Development\Samples\Python\MvImport\2.py", line 1532, in <module> main_window = MainWindow() File "d:\海康\MVS\Development\Samples\Python\MvImport\2.py", line 449, in __init__ self.setup_ui() File "d:\海康\MVS\Development\Samples\Python\MvImport\2.py", line 665, in setup_ui self.enable_controls() File "d:\海康\MVS\Development\Samples\Python\MvImport\2.py", line 1088, in enable_controls self.bnOpen.setEnabled(not self.isOpen) AttributeError: 'MainWindow' object has no attribute 'isOpen'

ERROR: script ‘D:\海康\MVS\Development\Samples\Python\MvImport\cloth_inspection_main.py’ not found还是出现了这个问题 – coding: utf-8 – import sys import os import cv2 import numpy as np import time from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QGridLayout, QSpinBox ) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation sys.path.append(“D:\海康\MVS\Development\Samples\Python\wanzheng.py”) import ctypes from ctypes import cast, POINTER from datetime import datetime import logging import socket import serial import skimage import platform from CameraConstants import * import threading import time import sys import wanzheng from wanzheng import some_function class ManagedThread(threading.Thread): def init(self, *args, **kwargs): super().init(*args, **kwargs) self._stop_event = threading.Event() 设置为非守护线程 self.daemon = False def stop(self): “”“安全停止线程”“” self._stop_event.set() def should_stop(self): “”“检查是否应该停止”“” return self._stop_event.is_set() def worker(): “”“线程工作函数”“” try: while not threading.current_thread().should_stop(): 模拟工作 time.sleep(1) 安全输出 sys.stdout.write(“Working…\n”) except Exception as e: 避免在关闭时使用stderr pass def main(): 创建并启动线程 threads = [] for _ in range(3): t = ManagedThread(target=worker) t.start() threads.append(t) try: # 主程序逻辑 time.sleep(5) finally: # 安全停止所有线程 for t in threads: t.stop() for t in threads: t.join(timeout=2.0) # 设置超时避免无限等待 # 确保所有输出完成 sys.stdout.flush() sys.stderr.flush() 在导入部分添加 from CameraParams_header import ( MV_GIGE_DEVICE, MV_USB_DEVICE, MV_GENTL_CAMERALINK_DEVICE, MV_GENTL_CXP_DEVICE, MV_GENTL_XOF_DEVICE ) ===== 路径修复 ===== def fix_sdk_path(): “”“修复海康SDK的加载路径”“” if getattr(sys, ‘frozen’, False): # 打包模式 base_path = sys.MEIPASS mvimport_path = os.path.join(base_path, “MvImport”) if mvimport_path not in sys.path: sys.path.insert(0, mvimport_path) if sys.platform == ‘win32’: os.environ[‘PATH’] = base_path + os.pathsep + os.environ[‘PATH’] try: ctypes.WinDLL(os.path.join(base_path, “MvCamCtrldll.dll”)) except OSError as e: print(f"核心DLL加载失败: {e}“) sys.exit(1) else: # 开发模式 sys.path.insert(0, os.path.dirname(os.path.abspath(file))) 立即执行路径修复 fix_sdk_path() ===== 正确导入SDK模块 ===== try: from MvImport.MvCameraControl_class import MvCamera print(“成功导入MvCamera类”) from CameraParams_header import * from MvErrorDefine_const import * except ImportError as e: print(f"SDK导入失败: {e}”) sys.exit(1) 配置日志系统 logging.basicConfig( level=logging.DEBUG, format=‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’, handlers=[ logging.FileHandler(“cloth_inspection_debug.log”), logging.StreamHandler() ] ) logging.info(“布料印花检测系统启动”) 全局变量 current_sample_path = “” detection_history = [] isGrabbing = False isOpen = False obj_cam_operation = None frame_monitor_thread = None sensor_monitor_thread = None sensor_controller = None MV_OK = 0 MV_E_CALLORDER = -2147483647 ==================== 传感器通讯模块 ==================== class SensorController: def init(self): self.connected = False self.running = False self.connection = None def connect(self, config): try: if config[‘type’] == ‘serial’: self.connection = serial.Serial( port=config[‘port’], baudrate=config[‘baudrate’], timeout=config.get(‘timeout’, 1.0) ) else: self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connection.connect((config[‘ip’], config[‘port’])) self.connection.settimeout(config.get(‘timeout’, 1.0)) self.connected = True self.running = True logging.info(f"传感器连接成功: {config}“) return True except Exception as e: logging.error(f"传感器连接失败: {str(e)}”) return False def disconnect(self): if self.connection: try: self.connection.close() except: pass self.connection = None self.connected = False self.running = False logging.info(“传感器已断开”) def read_data(self): if not self.connected: return None return { ‘tension’: np.random.uniform(10.0, 20.0), ‘speed’: np.random.uniform(1.0, 5.0), ‘temperature’: np.random.uniform(20.0, 30.0), ‘humidity’: np.random.uniform(40.0, 60.0) } def wait_for_material(self, delay_seconds=0): if not self.connected: logging.warning(“未连接传感器,跳过等待”) return False logging.info(f"等待布料到达,延迟 {delay_seconds} 秒") start_time = time.time() while time.time() - start_time < delay_seconds: QThread.msleep(100) if not self.running: return False logging.info(“布料已到位,准备拍摄”) return True class SensorMonitorThread(QThread): data_updated = pyqtSignal(dict) def init(self, sensor_controller): super().init() self.sensor_controller = sensor_controller self.running = True def run(self): while self.running: if self.sensor_controller and self.sensor_controller.connected: try: data = self.sensor_controller.read_data() if data: self.data_updated.emit(data) except Exception as e: logging.error(f"传感器数据读取错误: {str(e)}“) QThread.msleep(500) def stop(self): self.running = False self.wait(2000) def wait_for_material(self, delay_seconds): return self.sensor_controller.wait_for_material(delay_seconds) ==================== 相机帧监控线程 ==================== class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) # 用于发送状态消息的信号 def init(self, cam_operation): super().init() self.cam_operation = cam_operation self.running = True self.frame_count = 0 self.last_time = time.time() def run(self): “”“监控相机帧状态的主循环””" while self.running: try: if self.cam_operation and self.cam_operation.is_grabbing: # 获取帧统计信息 frame_info = self.get_frame_info() if frame_info: fps = frame_info.get(‘fps’, 0) dropped = frame_info.get(‘dropped’, 0) status = f"FPS: {fps:.1f} | 丢帧: {dropped}" self.frame_status.emit(status) else: self.frame_status.emit(“取流中…”) else: self.frame_status.emit(“相机未取流”) except Exception as e: self.frame_status.emit(f"监控错误: {str(e)}“) # 每500ms检查一次 QThread.msleep(500) def stop(self): “”“停止监控线程””" self.running = False self.wait(1000) # 等待线程结束 def calculate_fps(self): “”“计算当前帧率”“” current_time = time.time() elapsed = current_time - self.last_time if elapsed > 0: fps = self.frame_count / elapsed self.frame_count = 0 self.last_time = current_time return fps return 0 def get_frame_info(self): “”“获取帧信息”“” try: # 更新帧计数 self.frame_count += 1 # 返回帧信息 return { ‘fps’: self.calculate_fps(), ‘dropped’: 0 # 实际应用中需要从相机获取真实丢帧数 } except Exception as e: logging.error(f"获取帧信息失败: {str(e)}“) return None ==================== 优化后的检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05, sensor_data=None): if sensor_data: speed_factor = min(1.0 + sensor_data[‘speed’] * 0.1, 1.5) env_factor = 1.0 + abs(sensor_data[‘temperature’] - 25) * 0.01 + abs(sensor_data[‘humidity’] - 50) * 0.005 adjusted_threshold = threshold * speed_factor * env_factor logging.info(f"根据传感器数据调整阈值: 原始={threshold:.4f}, 调整后={adjusted_threshold:.4f}”) else: adjusted_threshold = threshold try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}“) return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}”) return None, None, None if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) try: orb = cv2.ORB_create(nfeatures=200) keypoints1, descriptors1 = orb.detectAndCompute(sample_image, None) keypoints2, descriptors2 = orb.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning(“无法提取特征描述符,跳过配准”) aligned_sample = sample_image else: bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) matches = sorted(matches, key=lambda x: x.distance) if len(matches) > 10: src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info(“图像配准成功,使用配准后样本”) else: aligned_sample = sample_image logging.warning(“无法计算单应性矩阵,使用原始样本”) else: aligned_sample = sample_image logging.warning(“特征点匹配不足,跳过图像配准”) except Exception as e: logging.error(f"图像配准失败: {str(e)}“) aligned_sample = sample_image try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}”) return None, None, None try: from skimage.metrics import structural_similarity as compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True, data_range=255 ) except ImportError: from skimage.measure import compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) except Exception as e: logging.error(f"SSIM计算失败: {str(e)}“) abs_diff = cv2.absdiff(aligned_sample, test_image_gray) ssim_diff = abs_diff.astype(np.float32) / 255.0 ssim_score = 1.0 - np.mean(ssim_diff) ssim_diff = (1 - ssim_diff) * 255 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) , thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels is_qualified = diff_ratio <= adjusted_threshold marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: y, x = prop.centroid cv2.putText(marked_image, f"Defect", (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) return is_qualified, diff_ratio, marked_image ==================== 传感器控制的质量检测流程 ==================== def sensor_controlled_check(): global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller logging.info(“质量检测启动”) sensor_data = None if sensor_controller and sensor_controller.connected: sensor_data = sensor_controller.read_data() if not sensor_data: QMessageBox.warning(mainWindow, “传感器警告”, “无法读取传感器数据,将使用默认参数”, QMessageBox.Ok) else: logging.info(“未连接传感器,使用默认参数检测”) check_print_with_sensor(sensor_data) def check_print_with_sensor(sensor_data=None): global isGrabbing, obj_cam_operation, current_sample_path, detection_history logging.info(“检测印花质量按钮按下”) if not isGrabbing: QMessageBox.warning(mainWindow, “错误”, “请先开始取流并捕获图像!”, QMessageBox.Ok) return if not obj_cam_operation: QMessageBox.warning(mainWindow, “错误”, “相机未正确初始化!”, QMessageBox.Ok) return if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, “错误”, “请先设置有效的标准样本图像!”, QMessageBox.Ok) return progress = QProgressDialog(“正在检测…”, “取消”, 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: test_image = obj_cam_operation.get_current_frame() progress.setValue(30) if test_image is None: QMessageBox.warning(mainWindow, “错误”, “无法获取当前帧图像!”, QMessageBox.Ok) return diff_threshold = mainWindow.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}“) progress.setValue(50) is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, test_image, threshold=diff_threshold, sensor_data=sensor_data ) progress.setValue(70) if is_qualified is None: QMessageBox.critical(mainWindow, “检测错误”, “检测失败,请检查日志”, QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}”) progress.setValue(90) update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {‘合格’ if is_qualified else ‘不合格’}\n差异占比: {diff_ratio100:.2f}%\n阈值: {diff_threshold100:.2f}%" QMessageBox.information(mainWindow, “检测结果”, result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow(“缺陷标记结果”, marked_image) cv2.waitKey(0) cv2.destroyAllWindows() detection_result = { ‘timestamp’: datetime.now(), ‘qualified’: is_qualified, ‘diff_ratio’: diff_ratio, ‘threshold’: diff_threshold, ‘sensor_data’: sensor_data if sensor_data else {} } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception(“印花检测失败”) QMessageBox.critical(mainWindow, “检测错误”, f"检测过程中发生错误: {str(e)}“, QMessageBox.Ok) finally: progress.close() def update_diff_display(diff_ratio, is_qualified): mainWindow.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%”) if is_qualified: mainWindow.lblDiffStatus.setText(“状态: 合格”) mainWindow.lblDiffStatus.setStyleSheet(“color: green; font-size: 12px;”) else: mainWindow.lblDiffStatus.setText(“状态: 不合格”) mainWindow.lblDiffStatus.setStyleSheet(“color: red; font-size: 12px;”) def update_diff_threshold(value): mainWindow.lblDiffValue.setText(f"{value}%“) def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, “错误”, “请先开始取流并捕获图像!”, QMessageBox.Ok) return # 检查是否有可用帧 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, “无有效图像”, “未捕获到有效图像,请检查相机状态!”, QMessageBox.Ok) return settings = QSettings(“ClothInspection”, “CameraApp”) last_dir = settings.value(“last_save_dir”, os.path.join(os.getcwd(), “captures”)) timestamp = datetime.now().strftime(”%Y%m%d%H%M%S”) default_filename = f"sample" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, “保存标准样本图像”, os.path.join(last_dir, default_filename), “BMP Files (.bmp);;PNG Files (.png);;JPEG Files (.jpg);;所有文件 ()”, options=QFileDialog.DontUseNativeDialog ) if not file_path: return file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if “BMP” in selected_filter: file_path += “.bmp” elif “PNG” in selected_filter: file_path += “.png” elif “JPEG” in selected_filter or “JPG” in selected_filter: file_path += “.jpg” else: file_path += “.bmp” file_extension = os.path.splitext(file_path)[1].lower() format_mapping = {“.bmp”: “bmp”, “.png”: “png”, “.jpg”: “jpg”, “.jpeg”: “jpg”} save_format = format_mapping.get(file_extension) if not save_format: QMessageBox.warning(mainWindow, “错误”, “不支持的文件格式!”, QMessageBox.Ok) return directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, “目录创建错误”, f"无法创建目录 {directory}: {str(e)}“, QMessageBox.Ok) return try: ret = obj_cam_operation.save_image(file_path, save_format) if ret != MV_OK: strError = f"保存样本图像失败: {hex(ret)}” QMessageBox.warning(mainWindow, “错误”, strError, QMessageBox.Ok) else: QMessageBox.information(mainWindow, “成功”, f"标准样本已保存至:\n{file_path}“, QMessageBox.Ok) current_sample_path = file_path update_sample_display() settings.setValue(“last_save_dir”, os.path.dirname(file_path)) except Exception as e: QMessageBox.critical(mainWindow, “异常错误”, f"保存图像时发生错误: {str(e)}”, QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, “错误”, “请先设置有效的标准样本图像!”, QMessageBox.Ok) return try: img_data = np.fromfile(current_sample_path, dtype=np.uint8) sample_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if sample_img is None: raise Exception(“无法加载图像”) cv2.imshow(“标准样本预览”, sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, “错误”, f"预览样本失败: {str(e)}“, QMessageBox.Ok) def update_sample_display(): global current_sample_path if current_sample_path: mainWindow.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}”) mainWindow.lblSamplePath.setToolTip(current_sample_path) mainWindow.bnPreviewSample.setEnabled(True) else: mainWindow.lblSamplePath.setText(“当前样本: 未设置样本”) mainWindow.bnPreviewSample.setEnabled(False) def update_history_display(): global detection_history mainWindow.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): timestamp = result[‘timestamp’].strftime(“%H:%M:%S”) status = “合格” if result[‘qualified’] else “不合格” ratio = f"{result[‘diff_ratio’]*100:.2f}%" mainWindow.cbHistory.addItem(f"[极客{timestamp}] {status} - 差异: {ratio}“) def TxtWrapBy(start_str, end, all): start = all.find(start_str) if start >= 0: start += len(start_str) end = all.find(end, start) if end >= 0: return all[start:end].strip() def ToHexStr(num): if not isinstance(num, int): try: num = int(num) except: return f”<非整数:{type(num)}>" chaDic = {10: ‘a’, 11: ‘b’, 12: ‘c’, 13: ‘d’, 14: ‘e’, 15: ‘f’} hexStr = “” if num < 0: num = num + 2 ** 32 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return “0x” + hexStr def decoding_char(c_ubyte_value): c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p) try: decode_str = c_char_p_value.value.decode(‘gbk’) except UnicodeDecodeError: decode_str = str(c_char_p_value.value) return decode_str def enum_devices(): global deviceList, obj_cam_operation 使用正确的常量方式 n_layer_type = ( MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE ) 正确创建设备列表 deviceList = MV_CC_DEVICE_INFO_LIST() # 调用枚举函数 ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != 0: strError = “Enum devices fail! ret = :” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, “Info”, “Find no device”, QMessageBox.Ok) return ret print(f"Find {deviceList.nDeviceNum} devices!“) # 处理设备信息 devList = [] for i in range(0, deviceList.nDeviceNum): # 获取设备信息指针 device_info = deviceList.pDeviceInfo[i] # 转换为正确的结构体类型 mvcc_dev_info = cast(device_info, POINTER(MV_CC_DEVICE_INFO)).contents # 根据设备类型提取信息 - 关键修复:使用正确的结构体名称 if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE: # 处理GigE设备信息 - 使用正确的stGigEInfo try: # 尝试访问新版SDK的结构体 st_gige_info = mvcc_dev_info.SpecialInfo.stGigEInfo except AttributeError: try: # 尝试访问旧版SDK的结构体 st_gige_info = mvcc_dev_info.SpecialInfo.stGEInfo except AttributeError: # 如果两种结构体都不存在,使用默认值 devList.append(f”[{i}]GigE: Unknown Device") continue user_defined_name = decoding_char(st_gige_info.chUserDefinedName) model_name = decoding_char(st_gige_info.chModelName) nip1 = ((st_gige_info.nCurrentIp & 0xff000000) >> 24) nip2 = ((st_gige_info.nCurrentIp & 0x00ff0000) >> 16) nip3 = ((st_gige_info.nCurrentIp & 0x0000ff00) >> 8) nip4 = (st_gige_info.nCurrentIp & 0x000000ff) devList.append(f"[{i}]GigE: {user_defined_name} {model_name}({nip1}.{nip2}.{nip3}.{nip4})“) elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: # 处理USB设备信息 try: st_usb_info = mvcc_dev_info.SpecialInfo.stUsb3VInfo except AttributeError: try: st_usb_info = mvcc_dev_info.SpecialInfo.stUsbInfo except AttributeError: devList.append(f”[{i}]USB: Unknown Device") continue user_defined_name = decoding_char(st_usb_info.chUserDefinedName) model_name = decoding_char(st_usb_info.chModelName) strSerialNumber = “” for per in st_usb_info.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) devList.append(f"[{i}]USB: {user_defined_name} {model_name}({strSerialNumber})“) else: # 处理其他类型设备 devList.append(f”[{i}]Unknown Device Type: {mvcc_dev_info.nTLayerType}“) # 更新UI mainWindow.ComboDevices.clear() mainWindow.ComboDevices.addItems(devList) mainWindow.ComboDevices.setCurrentIndex(0) # 更新状态栏 mainWindow.statusBar().showMessage(f"找到 {deviceList.nDeviceNum} 个设备”, 3000) return MV_OK ===== 关键改进:相机操作函数 ===== def open_device(): global deviceList, nSelCamIndex, obj_cam_operation, isOpen, frame_monitor_thread, mainWindow if isOpen: QMessageBox.warning(mainWindow, “Error”, ‘相机已打开!’, QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = mainWindow.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, “Error”, ‘请选择相机!’, QMessageBox.Ok) return MV_E_CALLORDER # 创建相机控制对象 cam = MvCamera() # 初始化相机操作对象 - 确保传入有效的相机对象 obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = “打开设备失败 ret:” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 创建并启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(mainWindow.statusBar().showMessage) frame_monitor_thread.start() def start_grabbing(): global obj_cam_operation, isGrabbing 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, ‘cam’) or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, “Error”, “相机对象未正确初始化”, QMessageBox.Ok) return ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = “开始取流失败 ret:” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, “警告”, “开始取流后未接收到帧,请检查相机连接!”, QMessageBox.Ok) def stop_grabbing(): global obj_cam_operation, isGrabbing 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, ‘cam’) or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, “Error”, “相机对象未正确初始化”, QMessageBox.Ok) return 关键改进:添加连接状态检查 if not hasattr(obj_cam_operation, ‘connected’) or not obj_cam_operation.connected: QMessageBox.warning(mainWindow, “Error”, “相机未连接”, QMessageBox.Ok) return ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = “停止取流失败 ret:” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() def close_device(): global isOpen, isGrabbing, obj_cam_operation, frame_monitor_thread if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) if isOpen and obj_cam_operation: 关键改进:确保相机对象存在 if hasattr(obj_cam_operation, ‘cam’) and obj_cam_operation.cam: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() def set_continue_mode(): 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, ‘cam’) or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = “设置连续模式失败 ret:” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(True) mainWindow.radioTriggerMode.setChecked(False) mainWindow.bnSoftwareTrigger.setEnabled(False) def set_software_trigger_mode(): 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, ‘cam’) or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = “设置触发模式失败 ret:” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(False) mainWindow.radioTriggerMode.setChecked(True) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing) def trigger_once(): 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, ‘cam’) or not obj_cam_operation.cam: return ret = obj_cam_operation.trigger_once() if ret != 0: strError = “软触发失败 ret:” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, “错误”, “请先开始取流并捕获图像!”, QMessageBox.Ok) return # 尝试捕获当前帧 frame = obj_cam_operation.capture_frame() if frame is None: QMessageBox.warning(mainWindow, “无有效图像”, “未捕获到有效图像,请检查相机状态!”, QMessageBox.Ok) return # 确保图像有效 if frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0: QMessageBox.warning(mainWindow, “无效图像”, “捕获的图像无效,请检查相机设置!”, QMessageBox.Ok) return settings = QSettings(“ClothInspection”, “CameraApp”) last_dir = settings.value(“last_save_dir”, os.path.join(os.getcwd(), “captures”)) timestamp = datetime.now().strftime(“%Y%m%d_%H%M%S”) default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, “保存标准样本图像”, os.path.join(last_dir, default_filename), “BMP Files (.bmp);;PNG Files (.png);;JPEG Files (.jpg);;所有文件 ()”, options=QFileDialog.DontUseNativeDialog ) if not file_path: return # 确保文件扩展名正确 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if “BMP” in selected_filter: file_path += “.bmp” elif “PNG” in selected_filter: file_path += “.png” elif “JPEG” in selected_filter or “JPG” in selected_filter: file_path += “.jpg” else: file_path += “.bmp” file_extension = os.path.splitext(file_path)[1].lower() # 创建目录(如果不存在) directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, “目录创建错误”, f"无法创建目录 {directory}: {str(e)}“, QMessageBox.Ok) return # 保存图像 try: # 使用OpenCV保存图像 if not cv2.imwrite(file_path, frame): raise Exception(“OpenCV保存失败”) # 更新状态 current_sample_path = file_path update_sample_display() settings.setValue(“last_save_dir”, os.path.dirname(file_path)) # 显示成功消息 QMessageBox.information(mainWindow, “成功”, f"标准样本已保存至:\n{file_path}”, QMessageBox.Ok) # 可选:自动预览样本 preview_sample() except Exception as e: logging.error(f"保存图像失败: {str(e)}“) QMessageBox.critical(mainWindow, “保存错误”, f"保存图像时发生错误:\n{str(e)}”, QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, “错误”, “请先设置有效的标准样本图像!”, QMessageBox.Ok) return try: # 直接使用OpenCV加载图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: raise Exception(“无法加载图像”) # 显示图像 cv2.imshow(“标准样本预览”, sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, “错误”, f"预览样本失败: {str(e)}", QMessageBox.Ok) def start_grabbing(): global obj_cam_operation, isGrabbing ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = “开始取流失败 ret:” + ToHexStr(ret) QMessageBox.warning(mainWindow, “Error”, strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, “警告”, “开始取流后未接收到帧,请检查相机连接!”, QMessageBox.Ok) def is_float(str): try: float(str) return True except ValueError: return False def get_param(): try: ret = obj_cam_operation.get_parameters() if ret != MV_OK: strError = “获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, “错误”, strError, QMessageBox.Ok) else: mainWindow.edtExposureTime.setText(”{0:.2f}“.format(obj_cam_operation.exposure_time)) mainWindow.edtGain.setText(”{0:.2f}“.format(obj_cam_operation.gain)) mainWindow.edtFrameRate.setText(”{0:.2f}“.format(obj_cam_operation.frame_rate)) except Exception as e: error_msg = f"获取参数时发生错误: {str(e)}” QMessageBox.critical(mainWindow, “严重错误”, error_msg, QMessageBox.Ok) def set_param(): frame_rate = mainWindow.edtFrameRate.text() exposure = mainWindow.edtExposureTime.text() gain = mainWindow.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = “设置参数失败: 参数必须是有效的浮点数” QMessageBox.warning(mainWindow, “错误”, strError, QMessageBox.Ok) return MV_E_PARAMETER try: ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = “设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, “错误”, strError, QMessageBox.Ok) except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}” QMessageBox.critical(mainWindow, “严重错误”, error_msg, QMessageBox.Ok) def enable_controls(): global isGrabbing, isOpen mainWindow.groupGrab.setEnabled(isOpen) mainWindow.paramgroup.setEnabled(isOpen) mainWindow.bnOpen.setEnabled(not isOpen) mainWindow.bnClose.setEnabled(isOpen) mainWindow.bnStart.setEnabled(isOpen and (not isGrabbing)) mainWindow.bnStop.setEnabled(isOpen and isGrabbing) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing and mainWindow.radioTriggerMode.isChecked()) mainWindow.bnSaveImage.setEnabled(isOpen and isGrabbing) mainWindow.bnCheckPrint.setEnabled(isOpen and isGrabbing) mainWindow.bnSaveSample.setEnabled(isOpen and isGrabbing) mainWindow.bnPreviewSample.setEnabled(bool(current_sample_path)) def update_sensor_display(data): if not data: return text = (f"张力: {data[‘tension’]:.2f}N | " f"速度: {data[‘speed’]:.2f}m/s | " f"温度: {data[‘temperature’]:.1f}°C | " f"湿度: {data[‘humidity’]:.1f}%") mainWindow.lblSensorData.setText(text) def connect_sensor(): global sensor_monitor_thread, sensor_controller sensor_type = mainWindow.cbSensorType.currentText() if sensor_controller is None: sensor_controller = SensorController() if sensor_type == “串口”: config = { ‘type’: ‘serial’, ‘port’: mainWindow.cbComPort.currentText(), ‘baudrate’: int(mainWindow.cbBaudrate.currentText()), ‘timeout’: 1.0 } else: config = { ‘type’: ‘ethernet’, ‘ip’: mainWindow.edtIP.text(), ‘port’: int(mainWindow.edtPort.text()), ‘timeout’: 1.0 } if sensor_controller.connect(config): mainWindow.bnConnectSensor.setEnabled(False) mainWindow.bnDisconnectSensor.setEnabled(True) sensor_monitor_thread = SensorMonitorThread(sensor_controller) sensor_monitor_thread.data_updated.connect(update_sensor_display) sensor_monitor_thread.start() def disconnect_sensor(): global sensor_monitor_thread if sensor_controller: sensor_controller.disconnect() mainWindow.bnConnectSensor.setEnabled(True) mainWindow.bnDisconnectSensor.setEnabled(False) if sensor_monitor_thread and sensor_monitor_thread.isRunning(): sensor_monitor_thread.stop() sensor_monitor_thread.wait(2000) sensor_monitor_thread = None mainWindow.lblSensorData.setText(“传感器数据: 未连接”) def update_sensor_ui(index): mainWindow.serialGroup.setVisible(index == 0) mainWindow.ethernetGroup.setVisible(index == 1) class MainWindow(QMainWindow): def init(self): super().init() self.setWindowTitle(“布料印花检测系统”) self.resize(1200, 800) central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) 设备枚举区域 device_layout = QHBoxLayout() self.ComboDevices = QComboBox() self.bnEnum = QPushButton(“枚举设备”) self.bnOpen = QPushButton(“打开设备”) self.bnClose = QPushButton(“关闭设备”) device_layout.addWidget(self.ComboDevices) device_layout.addWidget(self.bnEnum) device_layout.addWidget(self.bnOpen) device_layout.addWidget(self.bnClose) main_layout.addLayout(device_layout) # 取流控制组 self.groupGrab = QGroupBox(“取流控制”) grab_layout = QHBoxLayout(self.groupGrab) self.bnStart = QPushButton(“开始取流”) self.bnStop = QPushButton(“停止取流”) self.radioContinueMode = QRadioButton(“连续模式”) self.radioTriggerMode = QRadioButton(“触发模式”) self.bnSoftwareTrigger = QPushButton(“软触发”) grab_layout.addWidget(self.bnStart) grab_layout.addWidget(self.bnStop) grab_layout.addWidget(self.radioContinueMode) grab_layout.addWidget(self.radioTriggerMode) grab_layout.addWidget(self.bnSoftwareTrigger) main_layout.addWidget(self.groupGrab) # 参数设置组 self.paramgroup = QGroupBox(“相机参数”) param_layout = QGridLayout(self.paramgroup) self.edtExposureTime = QLineEdit() self.edtGain = QLineEdit() self.edtFrameRate = QLineEdit() self.bnGetParam = QPushButton(“获取参数”) self.bnSetParam = QPushButton(“设置参数”) self.bnSaveImage = QPushButton(“保存图像”) param_layout.addWidget(QLabel(“曝光时间:”), 0, 0) param_layout.addWidget(self.edtExposureTime, 0, 1) param_layout.addWidget(self.bnGetParam, 0, 2) param_layout.addWidget(QLabel(“增益:”), 1, 0) param_layout.addWidget(self.edtGain, 1, 1) param_layout.addWidget(self.bnSetParam, 1, 2) param_layout.addWidget(QLabel(“帧率:”), 2, 0) param_layout.addWidget(self.edtFrameRate, 2, 1) param_layout.addWidget(self.bnSaveImage, 2, 2) main_layout.addWidget(self.paramgroup) # 图像显示区域 self.widgetDisplay = QLabel() self.widgetDisplay.setMinimumSize(640, 480) self.widgetDisplay.setStyleSheet(“background-color: black;”) self.widgetDisplay.setAlignment(Qt.AlignCenter) self.widgetDisplay.setText(“相机预览区域”) main_layout.addWidget(self.widgetDisplay, 1) # 状态栏 #self.statusBar = QStatusBar() #self.setStatusBar(self.statusBar) # 创建自定义UI组件 self.setup_custom_ui() def setup_custom_ui(self): # 工具栏 toolbar = self.addToolBar(“检测工具”) self.bnCheckPrint = QPushButton(“检测印花质量”) self.bnSaveSample = QPushButton(“保存标准样本”) self.bnPreviewSample = QPushButton(“预览样本”) self.cbHistory = QComboBox() self.cbHistory.setMinimumWidth(300) toolbar.addWidget(self.bnCheckPrint) toolbar.addWidget(self.bnSaveSample) toolbar.addWidget(self.bnPreviewSample) toolbar.addWidget(QLabel(“历史记录:”)) toolbar.addWidget(self.cbHistory) # 状态栏样本路径 self.lblSamplePath = QLabel(“当前样本: 未设置样本”) self.statusBar().addPermanentWidget(self.lblSamplePath) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 差异度调整组 diff_group = QGroupBox(“差异度调整”) diff_layout = QVBoxLayout(diff_group) self.lblDiffThreshold = QLabel(“差异度阈值 (0-100%)😊 self.sliderDiffThreshold = QSlider(Qt.Horizontal) self.sliderDiffThreshold.setRange(0, 100) self.sliderDiffThreshold.setValue(5) self.lblDiffValue = QLabel(“5%”) self.lblCurrentDiff = QLabel(“当前差异度: -”) self.lblCurrentDiff.setStyleSheet(“font-size: 14px; font-weight: bold;”) self.lblDiffStatus = QLabel(“状态: 未检测”) self.lblDiffStatus.setStyleSheet(“font-size: 12px;”) diff_layout.addWidget(self.lblDiffThreshold) diff_layout.addWidget(self.sliderDiffThreshold) diff_layout.addWidget(self.lblDiffValue) diff_layout.addWidget(self.lblCurrentDiff) diff_layout.addWidget(self.lblDiffStatus) right_layout.addWidget(diff_group) # 传感器控制面板 sensor_panel = QGroupBox(“传感器控制”) sensor_layout = QVBoxLayout(sensor_panel) sensor_type_layout = QHBoxLayout() self.lblSensorType = QLabel(“传感器类型:”) self.cbSensorType = QComboBox() self.cbSensorType.addItems([“串口”, “以太网”]) sensor_type_layout.addWidget(self.lblSensorType) sensor_type_layout.addWidget(self.cbSensorType) sensor_layout.addLayout(sensor_type_layout) # 串口参数 self.serialGroup = QGroupBox(“串口参数”) serial_layout = QVBoxLayout(self.serialGroup) self.lblComPort = QLabel(“端口:”) self.cbComPort = QComboBox() if platform.system() == ‘Windows’: ports = [f"COM{i}” for i in range(1, 21)] else: ports = [f"/dev/ttyS{i}" for i in range(0, 4)] + [f"/dev/ttyUSB{i}" for i in range(0, 4)] self.cbComPort.addItems(ports) self.lblBaudrate = QLabel(“波特率:”) self.cbBaudrate = QComboBox() self.cbBaudrate.addItems([“96000”, “19200”, “38400”, “57600”, “115200”]) self.cbBaudrate.setCurrentText(“115200”) serial_layout.addWidget(self.lblComPort) serial_layout.addWidget(self.cbComPort) serial_layout.addWidget(self.lblBaudrate) serial_layout.addWidget(self.cbBaudrate) sensor_layout.addWidget(self.serialGroup) # 以太网参数 self.ethernetGroup = QGroupBox(“以太网参数”) ethernet_layout = QVBoxLayout(self.ethernetGroup) self.lblIP = QLabel(“IP地址:”) self.edtIP = QLineEdit(“192.168.1.100”) self.lblPort = QLabel(“端口:”) self.edtPort = QLineEdit(“502”) ethernet_layout.addWidget(self.lblIP) ethernet_layout.addWidget(self.edtIP) ethernet_layout.addWidget(self.lblPort) ethernet_layout.addWidget(self.edtPort) sensor_layout.addWidget(self.ethernetGroup) # 连接按钮 self.bnConnectSensor = QPushButton(“连接传感器”) self.bnDisconnectSensor = QPushButton(“断开传感器”) self.bnDisconnectSensor.setEnabled(False) sensor_layout.addWidget(self.bnConnectSensor) sensor_layout.addWidget(self.bnDisconnectSensor) # 延迟设置 delay_layout = QHBoxLayout() self.lblDelay = QLabel("触发延迟(秒)😊 self.spinDelay = QSpinBox() self.spinDelay.setRange(0, 60) self.spinDelay.setValue(0) self.spinDelay.setToolTip(“传感器检测到布料后延迟拍摄的时间”) delay_layout.addWidget(self.lblDelay) delay_layout.addWidget(self.spinDelay) sensor_layout.addLayout(delay_layout) # 传感器数据 self.lblSensorData = QLabel(“传感器数据: 未连接”) self.lblSensorData.setStyleSheet(“font-size: 10pt;”) sensor_layout.addWidget(self.lblSensorData) right_layout.addWidget(sensor_panel) right_layout.addStretch(1) # 停靠窗口 dock = QDockWidget(“检测控制面板”, self) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.addDockWidget(Qt.RightDockWidgetArea, dock) def closeEvent(self, event): logging.info(“主窗口关闭,执行清理…”) close_device() disconnect_sensor() event.accept() if name == “main”: app = QApplication(sys.argv) mainWindow = MainWindow() 信号连接 mainWindow.cbSensorType.currentIndexChanged.connect(update_sensor_ui) update_sensor_ui(0) mainWindow.bnConnectSensor.clicked.connect(connect_sensor) mainWindow.bnDisconnectSensor.clicked.connect(disconnect_sensor) mainWindow.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) mainWindow.bnCheckPrint.clicked.connect(sensor_controlled_check) mainWindow.bnSaveSample.clicked.connect(save_sample_image) mainWindow.bnPreviewSample.clicked.connect(preview_sample) mainWindow.bnEnum.clicked.connect(enum_devices) mainWindow.bnOpen.clicked.connect(open_device) mainWindow.bnClose.clicked.connect(close_device) mainWindow.bnStart.clicked.connect(start_grabbing) mainWindow.bnStop.clicked.connect(stop_grabbing) mainWindow.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.bnGetParam.clicked.connect(get_param) mainWindow.bnSetParam.clicked.connect(set_param) mainWindow.bnSaveImage.clicked.connect(save_sample_image) main() mainWindow.show() app.exec_() close_device() disconnect_sensor() sys.exit() 这个是wanzheng.py的代码

# -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np import time from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QGridLayout, QSpinBox ) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\BasicDemo") import ctypes from ctypes import cast, POINTER from datetime import datetime import logging import socket import serial import skimage import platform from CameraConstants import * import threading import time import sys class ManagedThread(threading.Thread): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._stop_event = threading.Event() # 设置为非守护线程 self.daemon = False def stop(self): """安全停止线程""" self._stop_event.set() def should_stop(self): """检查是否应该停止""" return self._stop_event.is_set() def worker(): """线程工作函数""" try: while not threading.current_thread().should_stop(): # 模拟工作 time.sleep(1) # 安全输出 sys.stdout.write("Working...\n") except Exception as e: # 避免在关闭时使用stderr pass def main(): # 创建并启动线程 threads = [] for _ in range(3): t = ManagedThread(target=worker) t.start() threads.append(t) try: # 主程序逻辑 time.sleep(5) finally: # 安全停止所有线程 for t in threads: t.stop() for t in threads: t.join(timeout=2.0) # 设置超时避免无限等待 # 确保所有输出完成 sys.stdout.flush() sys.stderr.flush() # 在导入部分添加 from CameraParams_header import ( MV_GIGE_DEVICE, MV_USB_DEVICE, MV_GENTL_CAMERALINK_DEVICE, MV_GENTL_CXP_DEVICE, MV_GENTL_XOF_DEVICE ) # ===== 路径修复 ===== def fix_sdk_path(): """修复海康SDK的加载路径""" if getattr(sys, 'frozen', False): # 打包模式 base_path = sys._MEIPASS mvimport_path = os.path.join(base_path, "MvImport") if mvimport_path not in sys.path: sys.path.insert(0, mvimport_path) if sys.platform == 'win32': os.environ['PATH'] = base_path + os.pathsep + os.environ['PATH'] try: ctypes.WinDLL(os.path.join(base_path, "MvCamCtrldll.dll")) except OSError as e: print(f"核心DLL加载失败: {e}") sys.exit(1) else: # 开发模式 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # 立即执行路径修复 fix_sdk_path() # ===== 正确导入SDK模块 ===== try: from MvImport.MvCameraControl_class import MvCamera print("成功导入MvCamera类") from CameraParams_header import * from MvErrorDefine_const import * except ImportError as e: print(f"SDK导入失败: {e}") sys.exit(1) # 配置日志系统 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动") # 全局变量 current_sample_path = "" detection_history = [] isGrabbing = False isOpen = False obj_cam_operation = None frame_monitor_thread = None sensor_monitor_thread = None sensor_controller = None MV_OK = 0 MV_E_CALLORDER = -2147483647 # ==================== 传感器通讯模块 ==================== class SensorController: def __init__(self): self.connected = False self.running = False self.connection = None def connect(self, config): try: if config['type'] == 'serial': self.connection = serial.Serial( port=config['port'], baudrate=config['baudrate'], timeout=config.get('timeout', 1.0) ) else: self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connection.connect((config['ip'], config['port'])) self.connection.settimeout(config.get('timeout', 1.0)) self.connected = True self.running = True logging.info(f"传感器连接成功: {config}") return True except Exception as e: logging.error(f"传感器连接失败: {str(e)}") return False def disconnect(self): if self.connection: try: self.connection.close() except: pass self.connection = None self.connected = False self.running = False logging.info("传感器已断开") def read_data(self): if not self.connected: return None return { 'tension': np.random.uniform(10.0, 20.0), 'speed': np.random.uniform(1.0, 5.0), 'temperature': np.random.uniform(20.0, 30.0), 'humidity': np.random.uniform(40.0, 60.0) } def wait_for_material(self, delay_seconds=0): if not self.connected: logging.warning("未连接传感器,跳过等待") return False logging.info(f"等待布料到达,延迟 {delay_seconds} 秒") start_time = time.time() while time.time() - start_time < delay_seconds: QThread.msleep(100) if not self.running: return False logging.info("布料已到位,准备拍摄") return True class SensorMonitorThread(QThread): data_updated = pyqtSignal(dict) def __init__(self, sensor_controller): super().__init__() self.sensor_controller = sensor_controller self.running = True def run(self): while self.running: if self.sensor_controller and self.sensor_controller.connected: try: data = self.sensor_controller.read_data() if data: self.data_updated.emit(data) except Exception as e: logging.error(f"传感器数据读取错误: {str(e)}") QThread.msleep(500) def stop(self): self.running = False self.wait(2000) def wait_for_material(self, delay_seconds): return self.sensor_controller.wait_for_material(delay_seconds) # ==================== 相机帧监控线程 ==================== class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) # 用于发送状态消息的信号 def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True self.frame_count = 0 self.last_time = time.time() def run(self): """监控相机帧状态的主循环""" while self.running: try: if self.cam_operation and self.cam_operation.is_grabbing: # 获取帧统计信息 frame_info = self.get_frame_info() if frame_info: fps = frame_info.get('fps', 0) dropped = frame_info.get('dropped', 0) status = f"FPS: {fps:.1f} | 丢帧: {dropped}" self.frame_status.emit(status) else: self.frame_status.emit("取流中...") else: self.frame_status.emit("相机未取流") except Exception as e: self.frame_status.emit(f"监控错误: {str(e)}") # 每500ms检查一次 QThread.msleep(500) def stop(self): """停止监控线程""" self.running = False self.wait(1000) # 等待线程结束 def calculate_fps(self): """计算当前帧率""" current_time = time.time() elapsed = current_time - self.last_time if elapsed > 0: fps = self.frame_count / elapsed self.frame_count = 0 self.last_time = current_time return fps return 0 def get_frame_info(self): """获取帧信息""" try: # 更新帧计数 self.frame_count += 1 # 返回帧信息 return { 'fps': self.calculate_fps(), 'dropped': 0 # 实际应用中需要从相机获取真实丢帧数 } except Exception as e: logging.error(f"获取帧信息失败: {str(e)}") return None # ==================== 优化后的检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05, sensor_data=None): if sensor_data: speed_factor = min(1.0 + sensor_data['speed'] * 0.1, 1.5) env_factor = 1.0 + abs(sensor_data['temperature'] - 25) * 0.01 + abs(sensor_data['humidity'] - 50) * 0.005 adjusted_threshold = threshold * speed_factor * env_factor logging.info(f"根据传感器数据调整阈值: 原始={threshold:.4f}, 调整后={adjusted_threshold:.4f}") else: adjusted_threshold = threshold try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) try: orb = cv2.ORB_create(nfeatures=200) keypoints1, descriptors1 = orb.detectAndCompute(sample_image, None) keypoints2, descriptors2 = orb.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning("无法提取特征描述符,跳过配准") aligned_sample = sample_image else: bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) matches = sorted(matches, key=lambda x: x.distance) if len(matches) > 10: src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info("图像配准成功,使用配准后样本") else: aligned_sample = sample_image logging.warning("无法计算单应性矩阵,使用原始样本") else: aligned_sample = sample_image logging.warning("特征点匹配不足,跳过图像配准") except Exception as e: logging.error(f"图像配准失败: {str(e)}") aligned_sample = sample_image try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None try: from skimage.metrics import structural_similarity as compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True, data_range=255 ) except ImportError: from skimage.measure import compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) except Exception as e: logging.error(f"SSIM计算失败: {str(e)}") abs_diff = cv2.absdiff(aligned_sample, test_image_gray) ssim_diff = abs_diff.astype(np.float32) / 255.0 ssim_score = 1.0 - np.mean(ssim_diff) ssim_diff = (1 - ssim_diff) * 255 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) _, thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels is_qualified = diff_ratio <= adjusted_threshold marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: y, x = prop.centroid cv2.putText(marked_image, f"Defect", (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) return is_qualified, diff_ratio, marked_image # ==================== 传感器控制的质量检测流程 ==================== def sensor_controlled_check(): global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller logging.info("质量检测启动") sensor_data = None if sensor_controller and sensor_controller.connected: sensor_data = sensor_controller.read_data() if not sensor_data: QMessageBox.warning(mainWindow, "传感器警告", "无法读取传感器数据,将使用默认参数", QMessageBox.Ok) else: logging.info("未连接传感器,使用默认参数检测") check_print_with_sensor(sensor_data) def check_print_with_sensor(sensor_data=None): global isGrabbing, obj_cam_operation, current_sample_path, detection_history logging.info("检测印花质量按钮按下") if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return if not obj_cam_operation: QMessageBox.warning(mainWindow, "错误", "相机未正确初始化!", QMessageBox.Ok) return if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: test_image = obj_cam_operation.get_current_frame() progress.setValue(30) if test_image is None: QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return diff_threshold = mainWindow.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(50) is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, test_image, threshold=diff_threshold, sensor_data=sensor_data ) progress.setValue(70) if is_qualified is None: QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n差异占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'sensor_data': sensor_data if sensor_data else {} } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) finally: progress.close() def update_diff_display(diff_ratio, is_qualified): mainWindow.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") if is_qualified: mainWindow.lblDiffStatus.setText("状态: 合格") mainWindow.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.lblDiffStatus.setText("状态: 不合格") mainWindow.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") def update_diff_threshold(value): mainWindow.lblDiffValue.setText(f"{value}%") def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有可用帧 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() format_mapping = {".bmp": "bmp", ".png": "png", ".jpg": "jpg", ".jpeg": "jpg"} save_format = format_mapping.get(file_extension) if not save_format: QMessageBox.warning(mainWindow, "错误", "不支持的文件格式!", QMessageBox.Ok) return directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return try: ret = obj_cam_operation.save_image(file_path, save_format) if ret != MV_OK: strError = f"保存样本图像失败: {hex(ret)}" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) except Exception as e: QMessageBox.critical(mainWindow, "异常错误", f"保存图像时发生错误: {str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: img_data = np.fromfile(current_sample_path, dtype=np.uint8) sample_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if sample_img is None: raise Exception("无法加载图像") cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def update_sample_display(): global current_sample_path if current_sample_path: mainWindow.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") mainWindow.lblSamplePath.setToolTip(current_sample_path) mainWindow.bnPreviewSample.setEnabled(True) else: mainWindow.lblSamplePath.setText("当前样本: 未设置样本") mainWindow.bnPreviewSample.setEnabled(False) def update_history_display(): global detection_history mainWindow.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" mainWindow.cbHistory.addItem(f"[极客{timestamp}] {status} - 差异: {ratio}") def TxtWrapBy(start_str, end, all): start = all.find(start_str) if start >= 0: start += len(start_str) end = all.find(end, start) if end >= 0: return all[start:end].strip() def ToHexStr(num): if not isinstance(num, int): try: num = int(num) except: return f"<非整数:{type(num)}>" chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'} hexStr = "" if num < 0: num = num + 2 ** 32 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return "0x" + hexStr def decoding_char(c_ubyte_value): c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p) try: decode_str = c_char_p_value.value.decode('gbk') except UnicodeDecodeError: decode_str = str(c_char_p_value.value) return decode_str def enum_devices(): global deviceList, obj_cam_operation # 使用正确的常量方式 n_layer_type = ( MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE ) # 正确创建设备列表 deviceList = MV_CC_DEVICE_INFO_LIST() # 调用枚举函数 ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != 0: strError = "Enum devices fail! ret = :" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, "Info", "Find no device", QMessageBox.Ok) return ret print(f"Find {deviceList.nDeviceNum} devices!") # 处理设备信息 devList = [] for i in range(0, deviceList.nDeviceNum): # 获取设备信息指针 device_info = deviceList.pDeviceInfo[i] # 转换为正确的结构体类型 mvcc_dev_info = cast(device_info, POINTER(MV_CC_DEVICE_INFO)).contents # 根据设备类型提取信息 - 关键修复:使用正确的结构体名称 if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE: # 处理GigE设备信息 - 使用正确的stGigEInfo try: # 尝试访问新版SDK的结构体 st_gige_info = mvcc_dev_info.SpecialInfo.stGigEInfo except AttributeError: try: # 尝试访问旧版SDK的结构体 st_gige_info = mvcc_dev_info.SpecialInfo.stGEInfo except AttributeError: # 如果两种结构体都不存在,使用默认值 devList.append(f"[{i}]GigE: Unknown Device") continue user_defined_name = decoding_char(st_gige_info.chUserDefinedName) model_name = decoding_char(st_gige_info.chModelName) nip1 = ((st_gige_info.nCurrentIp & 0xff000000) >> 24) nip2 = ((st_gige_info.nCurrentIp & 0x00ff0000) >> 16) nip3 = ((st_gige_info.nCurrentIp & 0x0000ff00) >> 8) nip4 = (st_gige_info.nCurrentIp & 0x000000ff) devList.append(f"[{i}]GigE: {user_defined_name} {model_name}({nip1}.{nip2}.{nip3}.{nip4})") elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: # 处理USB设备信息 try: st_usb_info = mvcc_dev_info.SpecialInfo.stUsb3VInfo except AttributeError: try: st_usb_info = mvcc_dev_info.SpecialInfo.stUsbInfo except AttributeError: devList.append(f"[{i}]USB: Unknown Device") continue user_defined_name = decoding_char(st_usb_info.chUserDefinedName) model_name = decoding_char(st_usb_info.chModelName) strSerialNumber = "" for per in st_usb_info.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) devList.append(f"[{i}]USB: {user_defined_name} {model_name}({strSerialNumber})") else: # 处理其他类型设备 devList.append(f"[{i}]Unknown Device Type: {mvcc_dev_info.nTLayerType}") # 更新UI mainWindow.ComboDevices.clear() mainWindow.ComboDevices.addItems(devList) mainWindow.ComboDevices.setCurrentIndex(0) # 更新状态栏 mainWindow.statusBar().showMessage(f"找到 {deviceList.nDeviceNum} 个设备", 3000) return MV_OK # ===== 关键改进:相机操作函数 ===== def open_device(): global deviceList, nSelCamIndex, obj_cam_operation, isOpen, frame_monitor_thread, mainWindow if isOpen: QMessageBox.warning(mainWindow, "Error", '相机已打开!', QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = mainWindow.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, "Error", '请选择相机!', QMessageBox.Ok) return MV_E_CALLORDER # 创建相机控制对象 cam = MvCamera() # 初始化相机操作对象 - 确保传入有效的相机对象 obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = "打开设备失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 创建并启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(mainWindow.statusBar().showMessage) frame_monitor_thread.start() def start_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def stop_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return # 关键改进:添加连接状态检查 if not hasattr(obj_cam_operation, 'connected') or not obj_cam_operation.connected: QMessageBox.warning(mainWindow, "Error", "相机未连接", QMessageBox.Ok) return ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = "停止取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() def close_device(): global isOpen, isGrabbing, obj_cam_operation, frame_monitor_thread if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) if isOpen and obj_cam_operation: # 关键改进:确保相机对象存在 if hasattr(obj_cam_operation, 'cam') and obj_cam_operation.cam: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() def set_continue_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = "设置连续模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(True) mainWindow.radioTriggerMode.setChecked(False) mainWindow.bnSoftwareTrigger.setEnabled(False) def set_software_trigger_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = "设置触发模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(False) mainWindow.radioTriggerMode.setChecked(True) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing) def trigger_once(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.trigger_once() if ret != 0: strError = "软触发失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 尝试捕获当前帧 frame = obj_cam_operation.capture_frame() if frame is None: QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 确保图像有效 if frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0: QMessageBox.warning(mainWindow, "无效图像", "捕获的图像无效,请检查相机设置!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return # 确保文件扩展名正确 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() # 创建目录(如果不存在) directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return # 保存图像 try: # 使用OpenCV保存图像 if not cv2.imwrite(file_path, frame): raise Exception("OpenCV保存失败") # 更新状态 current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) # 显示成功消息 QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) # 可选:自动预览样本 preview_sample() except Exception as e: logging.error(f"保存图像失败: {str(e)}") QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 直接使用OpenCV加载图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: raise Exception("无法加载图像") # 显示图像 cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def start_grabbing(): global obj_cam_operation, isGrabbing ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def is_float(str): try: float(str) return True except ValueError: return False def get_param(): try: ret = obj_cam_operation.get_parameters() if ret != MV_OK: strError = "获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: mainWindow.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time)) mainWindow.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain)) mainWindow.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate)) except Exception as e: error_msg = f"获取参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def set_param(): frame_rate = mainWindow.edtFrameRate.text() exposure = mainWindow.edtExposureTime.text() gain = mainWindow.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = "设置参数失败: 参数必须是有效的浮点数" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) return MV_E_PARAMETER try: ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = "设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def enable_controls(): global isGrabbing, isOpen mainWindow.groupGrab.setEnabled(isOpen) mainWindow.paramgroup.setEnabled(isOpen) mainWindow.bnOpen.setEnabled(not isOpen) mainWindow.bnClose.setEnabled(isOpen) mainWindow.bnStart.setEnabled(isOpen and (not isGrabbing)) mainWindow.bnStop.setEnabled(isOpen and isGrabbing) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing and mainWindow.radioTriggerMode.isChecked()) mainWindow.bnSaveImage.setEnabled(isOpen and isGrabbing) mainWindow.bnCheckPrint.setEnabled(isOpen and isGrabbing) mainWindow.bnSaveSample.setEnabled(isOpen and isGrabbing) mainWindow.bnPreviewSample.setEnabled(bool(current_sample_path)) def update_sensor_display(data): if not data: return text = (f"张力: {data['tension']:.2f}N | " f"速度: {data['speed']:.2f}m/s | " f"温度: {data['temperature']:.1f}°C | " f"湿度: {data['humidity']:.1f}%") mainWindow.lblSensorData.setText(text) def connect_sensor(): global sensor_monitor_thread, sensor_controller sensor_type = mainWindow.cbSensorType.currentText() if sensor_controller is None: sensor_controller = SensorController() if sensor_type == "串口": config = { 'type': 'serial', 'port': mainWindow.cbComPort.currentText(), 'baudrate': int(mainWindow.cbBaudrate.currentText()), 'timeout': 1.0 } else: config = { 'type': 'ethernet', 'ip': mainWindow.edtIP.text(), 'port': int(mainWindow.edtPort.text()), 'timeout': 1.0 } if sensor_controller.connect(config): mainWindow.bnConnectSensor.setEnabled(False) mainWindow.bnDisconnectSensor.setEnabled(True) sensor_monitor_thread = SensorMonitorThread(sensor_controller) sensor_monitor_thread.data_updated.connect(update_sensor_display) sensor_monitor_thread.start() def disconnect_sensor(): global sensor_monitor_thread if sensor_controller: sensor_controller.disconnect() mainWindow.bnConnectSensor.setEnabled(True) mainWindow.bnDisconnectSensor.setEnabled(False) if sensor_monitor_thread and sensor_monitor_thread.isRunning(): sensor_monitor_thread.stop() sensor_monitor_thread.wait(2000) sensor_monitor_thread = None mainWindow.lblSensorData.setText("传感器数据: 未连接") def update_sensor_ui(index): mainWindow.serialGroup.setVisible(index == 0) mainWindow.ethernetGroup.setVisible(index == 1) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("布料印花检测系统") self.resize(1200, 800) central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 设备枚举区域 device_layout = QHBoxLayout() self.ComboDevices = QComboBox() self.bnEnum = QPushButton("枚举设备") self.bnOpen = QPushButton("打开设备") self.bnClose = QPushButton("关闭设备") device_layout.addWidget(self.ComboDevices) device_layout.addWidget(self.bnEnum) device_layout.addWidget(self.bnOpen) device_layout.addWidget(self.bnClose) main_layout.addLayout(device_layout) # 取流控制组 self.groupGrab = QGroupBox("取流控制") grab_layout = QHBoxLayout(self.groupGrab) self.bnStart = QPushButton("开始取流") self.bnStop = QPushButton("停止取流") self.radioContinueMode = QRadioButton("连续模式") self.radioTriggerMode = QRadioButton("触发模式") self.bnSoftwareTrigger = QPushButton("软触发") grab_layout.addWidget(self.bnStart) grab_layout.addWidget(self.bnStop) grab_layout.addWidget(self.radioContinueMode) grab_layout.addWidget(self.radioTriggerMode) grab_layout.addWidget(self.bnSoftwareTrigger) main_layout.addWidget(self.groupGrab) # 参数设置组 self.paramgroup = QGroupBox("相机参数") param_layout = QGridLayout(self.paramgroup) self.edtExposureTime = QLineEdit() self.edtGain = QLineEdit() self.edtFrameRate = QLineEdit() self.bnGetParam = QPushButton("获取参数") self.bnSetParam = QPushButton("设置参数") self.bnSaveImage = QPushButton("保存图像") param_layout.addWidget(QLabel("曝光时间:"), 0, 0) param_layout.addWidget(self.edtExposureTime, 0, 1) param_layout.addWidget(self.bnGetParam, 0, 2) param_layout.addWidget(QLabel("增益:"), 1, 0) param_layout.addWidget(self.edtGain, 1, 1) param_layout.addWidget(self.bnSetParam, 1, 2) param_layout.addWidget(QLabel("帧率:"), 2, 0) param_layout.addWidget(self.edtFrameRate, 2, 1) param_layout.addWidget(self.bnSaveImage, 2, 2) main_layout.addWidget(self.paramgroup) # 图像显示区域 self.widgetDisplay = QLabel() self.widgetDisplay.setMinimumSize(640, 480) self.widgetDisplay.setStyleSheet("background-color: black;") self.widgetDisplay.setAlignment(Qt.AlignCenter) self.widgetDisplay.setText("相机预览区域") main_layout.addWidget(self.widgetDisplay, 1) # 状态栏 #self.statusBar = QStatusBar() #self.setStatusBar(self.statusBar) # 创建自定义UI组件 self.setup_custom_ui() def setup_custom_ui(self): # 工具栏 toolbar = self.addToolBar("检测工具") self.bnCheckPrint = QPushButton("检测印花质量") self.bnSaveSample = QPushButton("保存标准样本") self.bnPreviewSample = QPushButton("预览样本") self.cbHistory = QComboBox() self.cbHistory.setMinimumWidth(300) toolbar.addWidget(self.bnCheckPrint) toolbar.addWidget(self.bnSaveSample) toolbar.addWidget(self.bnPreviewSample) toolbar.addWidget(QLabel("历史记录:")) toolbar.addWidget(self.cbHistory) # 状态栏样本路径 self.lblSamplePath = QLabel("当前样本: 未设置样本") self.statusBar().addPermanentWidget(self.lblSamplePath) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 差异度调整组 diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) self.lblDiffThreshold = QLabel("差异度阈值 (0-100%):") self.sliderDiffThreshold = QSlider(Qt.Horizontal) self.sliderDiffThreshold.setRange(0, 100) self.sliderDiffThreshold.setValue(5) self.lblDiffValue = QLabel("5%") self.lblCurrentDiff = QLabel("当前差异度: -") self.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") self.lblDiffStatus = QLabel("状态: 未检测") self.lblDiffStatus.setStyleSheet("font-size: 12px;") diff_layout.addWidget(self.lblDiffThreshold) diff_layout.addWidget(self.sliderDiffThreshold) diff_layout.addWidget(self.lblDiffValue) diff_layout.addWidget(self.lblCurrentDiff) diff_layout.addWidget(self.lblDiffStatus) right_layout.addWidget(diff_group) # 传感器控制面板 sensor_panel = QGroupBox("传感器控制") sensor_layout = QVBoxLayout(sensor_panel) sensor_type_layout = QHBoxLayout() self.lblSensorType = QLabel("传感器类型:") self.cbSensorType = QComboBox() self.cbSensorType.addItems(["串口", "以太网"]) sensor_type_layout.addWidget(self.lblSensorType) sensor_type_layout.addWidget(self.cbSensorType) sensor_layout.addLayout(sensor_type_layout) # 串口参数 self.serialGroup = QGroupBox("串口参数") serial_layout = QVBoxLayout(self.serialGroup) self.lblComPort = QLabel("端口:") self.cbComPort = QComboBox() if platform.system() == 'Windows': ports = [f"COM{i}" for i in range(1, 21)] else: ports = [f"/dev/ttyS{i}" for i in range(0, 4)] + [f"/dev/ttyUSB{i}" for i in range(0, 4)] self.cbComPort.addItems(ports) self.lblBaudrate = QLabel("波特率:") self.cbBaudrate = QComboBox() self.cbBaudrate.addItems(["96000", "19200", "38400", "57600", "115200"]) self.cbBaudrate.setCurrentText("115200") serial_layout.addWidget(self.lblComPort) serial_layout.addWidget(self.cbComPort) serial_layout.addWidget(self.lblBaudrate) serial_layout.addWidget(self.cbBaudrate) sensor_layout.addWidget(self.serialGroup) # 以太网参数 self.ethernetGroup = QGroupBox("以太网参数") ethernet_layout = QVBoxLayout(self.ethernetGroup) self.lblIP = QLabel("IP地址:") self.edtIP = QLineEdit("192.168.1.100") self.lblPort = QLabel("端口:") self.edtPort = QLineEdit("502") ethernet_layout.addWidget(self.lblIP) ethernet_layout.addWidget(self.edtIP) ethernet_layout.addWidget(self.lblPort) ethernet_layout.addWidget(self.edtPort) sensor_layout.addWidget(self.ethernetGroup) # 连接按钮 self.bnConnectSensor = QPushButton("连接传感器") self.bnDisconnectSensor = QPushButton("断开传感器") self.bnDisconnectSensor.setEnabled(False) sensor_layout.addWidget(self.bnConnectSensor) sensor_layout.addWidget(self.bnDisconnectSensor) # 延迟设置 delay_layout = QHBoxLayout() self.lblDelay = QLabel("触发延迟(秒):") self.spinDelay = QSpinBox() self.spinDelay.setRange(0, 60) self.spinDelay.setValue(0) self.spinDelay.setToolTip("传感器检测到布料后延迟拍摄的时间") delay_layout.addWidget(self.lblDelay) delay_layout.addWidget(self.spinDelay) sensor_layout.addLayout(delay_layout) # 传感器数据 self.lblSensorData = QLabel("传感器数据: 未连接") self.lblSensorData.setStyleSheet("font-size: 10pt;") sensor_layout.addWidget(self.lblSensorData) right_layout.addWidget(sensor_panel) right_layout.addStretch(1) # 停靠窗口 dock = QDockWidget("检测控制面板", self) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.addDockWidget(Qt.RightDockWidgetArea, dock) def closeEvent(self, event): logging.info("主窗口关闭,执行清理...") close_device() disconnect_sensor() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) mainWindow = MainWindow() # 信号连接 mainWindow.cbSensorType.currentIndexChanged.connect(update_sensor_ui) update_sensor_ui(0) mainWindow.bnConnectSensor.clicked.connect(connect_sensor) mainWindow.bnDisconnectSensor.clicked.connect(disconnect_sensor) mainWindow.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) mainWindow.bnCheckPrint.clicked.connect(sensor_controlled_check) mainWindow.bnSaveSample.clicked.connect(save_sample_image) mainWindow.bnPreviewSample.clicked.connect(preview_sample) mainWindow.bnEnum.clicked.connect(enum_devices) mainWindow.bnOpen.clicked.connect(open_device) mainWindow.bnClose.clicked.connect(close_device) mainWindow.bnStart.clicked.connect(start_grabbing) mainWindow.bnStop.clicked.connect(stop_grabbing) mainWindow.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.bnGetParam.clicked.connect(get_param) mainWindow.bnSetParam.clicked.connect(set_param) mainWindow.bnSaveImage.clicked.connect(save_sample_image) main() mainWindow.show() app.exec_() close_device() disconnect_sensor() sys.exit() 我想把这个程序打包成exe文件我该怎么做

# -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QButtonGroup, QCheckBox,QSpinBox) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\BasicDemo") import ctypes from datetime import datetime from MvCameraControl_class import * from MvErrorDefine_const import * from CameraParams_header import * from PyUICBasicDemo import Ui_MainWindow import logging import platform import serial import socket import time from scipy import ndimage import skimage.measure from skimage.metrics import structural_similarity as compare_ssim import subprocess # 配置日志系统 logging.basicConfig( level=logging.DEBUG, # 设置为DEBUG级别获取更多信息 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动") # 全局变量 current_sample_path = "" # 当前使用的样本路径 detection_history = [] # 检测历史记录 isGrabbing = False # 相机取流状态 isOpen = False # 相机打开状态 obj_cam_operation = None # 相机操作对象 frame_monitor_thread = None # 帧监控线程 sensor_monitor_thread = None # 传感器监控线程 sensor_controller = None # 传感器控制器 # ==================== 传感器通讯模块 ==================== # 在 SensorController 类中添加新方法 class SensorController: # ... 现有代码 ... def wait_for_material(self, delay_seconds=0): """ 等待布料到达(模拟实现) :param delay_seconds: 延迟秒数 """ if not self.connected: logging.warning("未连接传感器,跳过等待") return False logging.info(f"等待布料到达,延迟 {delay_seconds} 秒") # 实际应用中应根据传感器信号判断布料是否到位 # 这里使用简单的时间延迟模拟 start_time = time.time() while time.time() - start_time < delay_seconds: QThread.msleep(100) # 避免阻塞UI # 检查是否被取消 if not self.running: return False logging.info("布料已到位,准备拍摄") return True # 在 SensorMonitorThread 类中添加新方法 class SensorMonitorThread(QThread): # ... 现有代码 ... def wait_for_material(self, delay_seconds): """ 等待布料到达 """ return self.sensor_controller.wait_for_material(delay_seconds) # 修改 sensor_controlled_check 函数,添加延迟拍摄功能 def sensor_controlled_check(): """传感器控制的质量检测主流程""" # ... 前面的代码 ... # 新增:获取延迟时间设置 delay_seconds = mainWindow.ui.spinDelay.value() # 新增:等待布料到位 if sensor_controller and sensor_controller.connected and delay_seconds > 0: # 显示等待对话框 progress = QProgressDialog("等待布料到位...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(0) # 启动等待线程 def wait_thread(): start_time = time.time() while time.time() - start_time < delay_seconds: elapsed = time.time() - start_time progress.setValue(int((elapsed / delay_seconds) * 100)) QThread.msleep(100) if progress.wasCanceled(): return False return True # 使用线程避免阻塞UI from threading import Thread wait_result = [True] # 使用列表传递结果 thread = Thread(target=lambda: wait_result.append(wait_thread())) thread.start() # 等待线程完成 while thread.is_alive(): QApplication.processEvents() progress.close() if not wait_result[0]: logging.info("用户取消了等待") return # 执行图像捕获和检测 check_print_with_sensor(sensor_data) # ==================== 优化后的检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05, sensor_data=None): """ 优化版布料印花检测算法,增加图像配准和特征匹配 :param sample_image_path: 合格样本图像路径 :param test_image: 测试图像 (numpy数组) :param threshold: 差异阈值 :param sensor_data: 传感器数据字典 :return: 是否合格,差异值,标记图像 """ # 根据传感器数据动态调整阈值 if sensor_data: # 速度越高,允许的差异阈值越大 speed_factor = min(1.0 + sensor_data['speed'] * 0.1, 1.5) # 温度/湿度影响 env_factor = 1.0 + abs(sensor_data['temperature'] - 25) * 0.01 + abs(sensor_data['humidity'] - 50) * 0.005 adjusted_threshold = threshold * speed_factor * env_factor logging.info(f"根据传感器数据调整阈值: 原始={threshold:.4f}, 调整后={adjusted_threshold:.4f}") else: adjusted_threshold = threshold try: # 读取样本图像 sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None # 确保测试图像是灰度图 if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() # 1. 图像预处理 sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) # 2. 图像配准(解决位置偏移问题) try: # 使用ORB特征匹配进行图像配准 orb = cv2.ORB_create(nfeatures=200) keypoints1, descriptors1 = orb.detectAndCompute(sample_image, None) keypoints2, descriptors2 = orb.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning("无法提取特征描述符,跳过配准") aligned_sample = sample_image else: # 使用BFMatcher进行特征匹配 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) matches = sorted(matches, key=lambda x: x.distance) if len(matches) > 10: # 提取匹配点的坐标 src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) # 计算单应性矩阵 H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: # 应用变换 aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info("图像配准成功,使用配准后样本") else: aligned_sample = sample_image logging.warning("无法计算单应性矩阵,使用原始样本") else: aligned_sample = sample_image logging.warning("特征点匹配不足,跳过图像配准") except Exception as e: logging.error(f"图像配准失败: {str(e)}") aligned_sample = sample_image # 3. 确保图像大小一致 try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None # 4. 计算结构相似性(SSIM)和差异 ssim_score, ssim_diff = skimage.measure.compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) ssim_diff = (1 - ssim_diff) * 255 # 转换为0-255范围 # 5. 计算绝对差异 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) # 6. 组合差异(SSIM差异对结构变化敏感,绝对差异对亮度变化敏感) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) # 7. 二值化差异 _, thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) # 8. 形态学操作去除噪声 kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) # 9. 计算差异比例 diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels # 10. 判断是否合格 is_qualified = diff_ratio <= adjusted_threshold # 11. 创建标记图像 marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] # 红色标记缺陷 # 12. 标记大面积缺陷区域 labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: # 只标记大于50像素的区域 y, x = prop.centroid cv2.putText(marked_image, f"Defect", (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) return is_qualified, diff_ratio, marked_image # ==================== 传感器控制的质量检测流程 ==================== # 修改 sensor_controlled_check 函数 def sensor_controlled_check(): """传感器控制的质量检测主流程""" global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller logging.info("质量检测启动") # 1. 检查传感器连接状态 sensor_data = None if sensor_controller and sensor_controller.connected: # 读取传感器数据 sensor_data = sensor_controller.read_data() if not sensor_data: QMessageBox.warning(mainWindow, "传感器警告", "无法读取传感器数据,将使用默认参数", QMessageBox.Ok) else: # 根据传感器数据调整生产参数 adjust_production_parameters(sensor_data) else: logging.info("未连接传感器,使用默认参数检测") # 2. 执行图像捕获和检测 check_print_with_sensor(sensor_data) # 布料印花检测函数(使用优化算法) def check_print_with_sensor(sensor_data=None): """ 使用优化算法检测布料印花是否合格 """ global isGrabbing, obj_cam_operation, current_sample_path, detection_history logging.info("检测印花质量按钮按下") # 1. 检查相机状态 if not isGrabbing: logging.warning("相机未取流") QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 2. 检查相机操作对象 if not obj_cam_operation: logging.error("相机操作对象未初始化") QMessageBox.warning(mainWindow, "错误", "相机未正确初始化!", QMessageBox.Ok) return # 3. 检查样本路径 if not current_sample_path or not os.path.exists(current_sample_path): logging.warning(f"无效样本路径: {current_sample_path}") QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return # 使用进度对话框防止UI阻塞 progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: # 4. 获取当前帧 logging.info("尝试获取当前帧") test_image = obj_cam_operation.get_current_frame() progress.setValue(30) if test_image is None: logging.warning("获取当前帧失败") QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return # 5. 获取差异度阈值 diff_threshold = mainWindow.ui.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(50) # 6. 执行检测 is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, test_image, threshold=diff_threshold, sensor_data=sensor_data ) progress.setValue(70) # 检查返回结果是否有效 if is_qualified is None: logging.error("检测函数返回无效结果") QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) # 7. 更新UI update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n差异占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() else: logging.warning("标记图像为空") # 8. 记录检测结果 detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'sensor_data': sensor_data if sensor_data else {} } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) finally: progress.close() # 更新检测结果显示 def update_diff_display(diff_ratio, is_qualified): """ 更新差异度显示控件 """ # 更新当前差异度显示 mainWindow.ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") # 根据合格状态设置颜色 if is_qualified: mainWindow.ui.lblDiffStatus.setText("状态: 合格") mainWindow.ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.ui.lblDiffStatus.setText("状态: 不合格") mainWindow.ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") # 更新差异度阈值显示 def update_diff_threshold(value): """ 当滑块值改变时更新阈值显示 """ mainWindow.ui.lblDiffValue.setText(f"{value}%") # 保存标准样本函数 def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有有效图像 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 读取上次使用的路径 settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) # 创建默认文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" # 弹出文件保存对话框 file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: logging.info("用户取消了图像保存操作") return # 用户取消保存 # 处理文件扩展名 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: # 根据选择的过滤器添加扩展名 if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: # 默认使用BMP格式 file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() # 根据扩展名设置保存格式 format_mapping = { ".bmp": "bmp", ".png": "png", ".jpg": "jpg", ".jpeg": "jpg" } save_format = format_mapping.get(file_extension) if not save_format: QMessageBox.warning(mainWindow, "错误", "不支持的文件格式!", QMessageBox.Ok) return # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) logging.info(f"创建目录: {directory}") except OSError as e: error_msg = f"无法创建目录 {directory}: {str(e)}" QMessageBox.critical(mainWindow, "目录创建错误", error_msg, QMessageBox.Ok) return # 保存当前帧作为标准样本 try: ret = obj_cam_operation.save_image(file_path, save_format) if ret != MV_OK: strError = f"保存样本图像失败: {hex(ret)}" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: success_msg = f"标准样本已保存至:\n{file_path}" QMessageBox.information(mainWindow, "成功", success_msg, QMessageBox.Ok) # 更新当前样本路径 current_sample_path = file_path update_sample_display() # 保存当前目录 settings.setValue("last_save_dir", os.path.dirname(file_path)) except Exception as e: error_msg = f"保存图像时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "异常错误", error_msg, QMessageBox.Ok) logging.exception("保存样本图像时发生异常") # 预览当前样本 def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 使用安全方法读取图像 img_data = np.fromfile(current_sample_path, dtype=np.uint8) sample_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if sample_img is None: raise Exception("无法加载图像") cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) # 更新样本路径显示 def update_sample_display(): global current_sample_path if current_sample_path: mainWindow.ui.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") mainWindow.ui.lblSamplePath.setToolTip(current_sample_path) mainWindow.ui.bnPreviewSample.setEnabled(True) else: mainWindow.ui.lblSamplePath.setText("当前样本: 未设置样本") mainWindow.ui.bnPreviewSample.setEnabled(False) # 更新历史记录显示 def update_history_display(): global detection_history mainWindow.ui.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): # 显示最近10条记录 timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" mainWindow.ui.cbHistory.addItem(f"[{timestamp}] {status} - 差异: {ratio}") # 获取选取设备信息的索引,通过[]之间的字符去解析 def TxtWrapBy(start_str, end, all): start = all.find(start_str) if start >= 0: start += len(start_str) end = all.find(end, start) if end >= 0: return all[start:end].strip() # 将返回的错误码转换为十六进制显示 def ToHexStr(num): """将错误码转换为十六进制字符串""" # 处理非整数输入 if not isinstance(num, int): try: # 尝试转换为整数 num = int(num) except: # 无法转换时返回类型信息 return f"<非整数:{type(num)}>" chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'} hexStr = "" # 处理负数 if num < 0: num = num + 2 ** 32 # 转换为十六进制 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return "0x" + hexStr # 绑定下拉列表至设备信息索引 def xFunc(event): global nSelCamIndex nSelCamIndex = TxtWrapBy("[", "]", mainWindow.ui.ComboDevices.get()) # Decoding Characters def decoding_char(c_ubyte_value): c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p) try: decode_str = c_char_p_value.value.decode('gbk') # Chinese characters except UnicodeDecodeError: decode_str = str(c_char_p_value.value) return decode_str # ch:枚举相机 | en:enum devices def enum_devices(): global deviceList global obj_cam_operation deviceList = MV_CC_DEVICE_INFO_LIST() n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE) ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != 0: strError = "Enum devices fail! ret = :" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, "Info", "Find no device", QMessageBox.Ok) return ret print("Find %d devices!" % deviceList.nDeviceNum) devList = [] for i in range(0, deviceList.nDeviceNum): mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE or mvcc_dev_info.nTLayerType == MV_GENTL_GIGE_DEVICE: print("\ngige device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24) nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16) nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8) nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff) print("current ip: %d.%d.%d.%d " % (nip1, nip2, nip3, nip4)) devList.append( "[" + str(i) + "]GigE: " + user_defined_name + " " + model_name + "(" + str(nip1) + "." + str( nip2) + "." + str(nip3) + "." + str(nip4) + ")") elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: print("\nu3v device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: " + strSerialNumber) devList.append("[" + str(i) + "]USB: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") elif mvcc_dev_info.nTLayerType == MV_GENTL_CAMERALINK_DEVICE: print("\nCML device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: " + strSerialNumber) devList.append("[" + str(i) + "]CML: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") elif mvcc_dev_info.nTLayerType == MV_GENTL_CXP_DEVICE: print("\nCXP device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stCXPInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stCXPInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: "+strSerialNumber) devList.append("[" + str(i) + "]CXP: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") elif mvcc_dev_info.nTLayerType == MV_GENTL_XOF_DEVICE: print("\nXoF device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stXoFInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stXoFInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stXoFInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: " + strSerialNumber) devList.append("[" + str(i) + "]XoF: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") mainWindow.ui.ComboDevices.clear() mainWindow.ui.ComboDevices.addItems(devList) mainWindow.ui.ComboDevices.setCurrentIndex(0) class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True def run(self): while self.running: if self.cam_operation: status = self.cam_operation.get_frame_status() frame_text = "有帧" if status.get('current_frame', False) else "无帧" self.frame_status.emit(f"帧状态: {frame_text}") QThread.msleep(500) def stop(self): self.running = False # ch:打开相机 | en:open device def open_device(): global deviceList global nSelCamIndex global obj_cam_operation global isOpen global frame_monitor_thread global mainWindow if isOpen: QMessageBox.warning(mainWindow, "Error", 'Camera is Running!', QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = mainWindow.ui.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, "Error", 'Please select a camera!', QMessageBox.Ok) return MV_E_CALLORDER # 创建 MvCamera 实例 cam = MvCamera() # 修复:使用 cam 而不是 obj_cam_operation obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = "Open device failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(mainWindow.ui.statusBar.showMessage) frame_monitor_thread.start() # ch:开始取流 | en:Start grab image def start_grabbing(): global obj_cam_operation global isGrabbing ret = obj_cam_operation.start_grabbing(mainWindow.ui.widgetDisplay.winId()) if ret != 0: strError = "Start grabbing failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # ch:停止取流 | en:Stop grab image def stop_grabbing(): global obj_cam_operation global isGrabbing ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = "Stop grabbing failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() # ch:关闭设备 | Close device def close_device(): global isOpen global isGrabbing global obj_cam_operation global frame_monitor_thread # 停止帧监控线程 if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) if isOpen: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() # ch:设置触发模式 | en:set trigger mode def set_continue_mode(): ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = "Set continue mode failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.ui.radioContinueMode.setChecked(True) mainWindow.ui.radioTriggerMode.setChecked(False) mainWindow.ui.bnSoftwareTrigger.setEnabled(False) # ch:设置软触发模式 | en:set software trigger mode def set_software_trigger_mode(): ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = "Set trigger mode failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.ui.radioContinueMode.setChecked(False) mainWindow.ui.radioTriggerMode.setChecked(True) mainWindow.ui.bnSoftwareTrigger.setEnabled(isGrabbing) # ch:设置触发命令 | en:set trigger software def trigger_once(): ret = obj_cam_operation.trigger_once() if ret != 0: strError = "TriggerSoftware failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) # 保存图像对话框 def save_image_dialog(): """ 打开保存图像对话框并保存当前帧 """ global isGrabbing, obj_cam_operation # 检查相机状态 if not isGrabbing: QMessageBox.warning(mainWindow, "相机未就绪", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有有效图像 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 读取上次使用的路径 settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) # 创建默认文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"capture_{timestamp}" # 弹出文件保存对话框 file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存图像", os.path.join(last_dir, default_filename), # 初始路径 "BMP 图像 (*.bmp);;JPEG 图像 (*.jpg);;PNG 图像 (*.png);;TIFF 图像 (*.tiff);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) # 用户取消操作 if not file_path: logging.info("用户取消了图像保存操作") return # 处理文件扩展名 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: # 根据选择的过滤器添加扩展名 if "BMP" in selected_filter: file_path += ".bmp" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" elif "PNG" in selected_filter: file_path += ".png" elif "TIFF" in selected_filter: file_path += ".tiff" else: # 默认使用BMP格式 file_path += ".bmp" # 确定保存格式 format_mapping = { ".bmp": "bmp", ".jpg": "jpg", ".jpeg": "jpg", ".png": "png", ".tiff": "tiff", ".tif": "tiff" } file_extension = os.path.splitext(file_path)[1].lower() save_format = format_mapping.get(file_extension, "bmp") # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录错误", f"无法创建目录:\n{str(e)}", QMessageBox.Ok) return # 保存图像 try: ret = obj_cam_operation.save_image(file_path, save_format) if ret == MV_OK: QMessageBox.information(mainWindow, "保存成功", f"图像已保存至:\n{file_path}", QMessageBox.Ok) logging.info(f"图像保存成功: {file_path}") # 保存当前目录 settings.setValue("last_save_dir", os.path.dirname(file_path)) else: error_msg = f"保存失败! 错误代码: {hex(ret)}" QMessageBox.warning(mainWindow, "保存失败", error_msg, QMessageBox.Ok) logging.error(f"图像保存失败: {file_path}, 错误代码: {hex(ret)}") except Exception as e: QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) logging.exception(f"保存图像时发生异常: {file_path}") def is_float(str): try: float(str) return True except ValueError: return False # ch: 获取参数 | en:get param def get_param(): try: # 调用方法获取参数 ret = obj_cam_operation.get_parameters() # 记录调用结果(调试用) logging.debug(f"get_param() 返回: {ret} (类型: {type(ret)})") # 处理错误码 if ret != MV_OK: strError = "获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: # 成功获取参数后更新UI mainWindow.ui.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time)) mainWindow.ui.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain)) mainWindow.ui.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate)) # 记录成功信息 logging.info("成功获取相机参数") except Exception as e: # 处理所有异常 error_msg = f"获取参数时发生错误: {str(e)}" logging.error(error_msg) QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) # ch: 设置参数 | en:set param def set_param(): frame_rate = mainWindow.ui.edtFrameRate.text() exposure = mainWindow.ui.edtExposureTime.text() gain = mainWindow.ui.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = "设置参数失败: 参数必须是有效的浮点数" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) return MV_E_PARAMETER try: # 使用正确的参数顺序和关键字 ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = "设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: logging.info("参数设置成功") return MV_OK except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}" logging.error(error_msg) QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) return MV_E_STATE # ch: 设置控件状态 | en:set enable status def enable_controls(): global isGrabbing global isOpen # 先设置group的状态,再单独设置各控件状态 mainWindow.ui.groupGrab.setEnabled(isOpen) mainWindow.ui.groupParam.setEnabled(isOpen) mainWindow.ui.bnOpen.setEnabled(not isOpen) mainWindow.ui.bnClose.setEnabled(isOpen) mainWindow.ui.bnStart.setEnabled(isOpen and (not isGrabbing)) mainWindow.ui.bnStop.setEnabled(isOpen and isGrabbing) mainWindow.ui.bnSoftwareTrigger.setEnabled(isGrabbing and mainWindow.ui.radioTriggerMode.isChecked()) mainWindow.ui.bnSaveImage.setEnabled(isOpen and isGrabbing) mainWindow.ui #添加检测按钮控制 mainWindow.ui.bnCheckPrint.setEnabled(isOpen and isGrabbing) mainWindow.ui.bnSaveSample.setEnabled(isOpen and isGrabbing) mainWindow.ui.bnPreviewSample.setEnabled(bool(current_sample_path)) class MainWindow(QMainWindow): def __init__(self): super().__init__() # 创建UI实例 self.ui = Ui_MainWindow() self.ui.setupUi(self) def closeEvent(self, event): """重写关闭事件,执行清理操作""" logging.info("主窗口关闭,执行清理...") # 关闭设备 close_device() # 断开传感器 disconnect_sensor() event.accept() if __name__ == "__main__": # 初始化UI app = QApplication(sys.argv) # 创建主窗口实例 mainWindow = MainWindow() # 扩大主窗口尺寸 mainWindow.resize(1200, 800) # 宽度1200,高度800 # 创建工具栏 toolbar = mainWindow.addToolBar("检测工具") # 添加检测按钮 mainWindow.ui.bnCheckPrint = QPushButton("检测印花质量") toolbar.addWidget(mainWindow.ui.bnCheckPrint) # 添加保存样本按钮 mainWindow.ui.bnSaveSample = QPushButton("保存标准样本") toolbar.addWidget(mainWindow.ui.bnSaveSample) # 添加预览样本按钮 mainWindow.ui.bnPreviewSample = QPushButton("预览样本") toolbar.addWidget(mainWindow.ui.bnPreviewSample) # 添加历史记录下拉框 mainWindow.ui.cbHistory = QComboBox() mainWindow.ui.cbHistory.setMinimumWidth(300) toolbar.addWidget(QLabel("历史记录:")) toolbar.addWidget(mainWindow.ui.cbHistory) # 添加当前样本显示标签 mainWindow.ui.lblSamplePath = QLabel("当前样本: 未设置样本") status_bar = mainWindow.statusBar() status_bar.addPermanentWidget(mainWindow.ui.lblSamplePath) # === 新增差异度调整控件 === # 创建右侧面板容器 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 创建差异度调整组 diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) # 差异度阈值控制 mainWindow.ui.lblDiffThreshold = QLabel("差异度阈值 (0-100%):") mainWindow.ui.sliderDiffThreshold = QSlider(Qt.Horizontal) mainWindow.ui.sliderDiffThreshold.setRange(0, 100) # 0-100% mainWindow.ui.sliderDiffThreshold.setValue(5) # 默认5% mainWindow.ui.lblDiffValue = QLabel("5%") # 当前差异度显示 mainWindow.ui.lblCurrentDiff = QLabel("当前差异度: -") mainWindow.ui.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") # 差异度状态指示器 mainWindow.ui.lblDiffStatus = QLabel("状态: 未检测") mainWindow.ui.lblDiffStatus.setStyleSheet("font-size: 12px;") # 布局控件 diff_layout.addWidget(mainWindow.ui.lblDiffThreshold) diff_layout.addWidget(mainWindow.ui.sliderDiffThreshold) diff_layout.addWidget(mainWindow.ui.lblDiffValue) diff_layout.addWidget(mainWindow.ui.lblCurrentDiff) diff_layout.addWidget(mainWindow.ui.lblDiffStatus) # 添加差异度组到右侧布局 right_layout.addWidget(diff_group) # === 新增传感器控制面板 === sensor_panel = QGroupBox("传感器控制") sensor_layout = QVBoxLayout(sensor_panel) # 传感器类型选择 sensor_type_layout = QHBoxLayout() mainWindow.ui.lblSensorType = QLabel("传感器类型:") mainWindow.ui.cbSensorType = QComboBox() mainWindow.ui.cbSensorType.addItems(["串口", "以太网"]) sensor_type_layout.addWidget(mainWindow.ui.lblSensorType) sensor_type_layout.addWidget(mainWindow.ui.cbSensorType) # 串口参数 mainWindow.ui.serialGroup = QGroupBox("串口参数") serial_layout = QVBoxLayout(mainWindow.ui.serialGroup) mainWindow.ui.lblComPort = QLabel("端口:") mainWindow.ui.cbComPort = QComboBox() # 获取可用串口 (Windows) if platform.system() == 'Windows': ports = [f"COM{i}" for i in range(1, 21)] else: ports = [f"/dev/ttyS{i}" for i in range(0, 4)] + [f"/dev/ttyUSB{i}" for i in range(0, 4)] mainWindow.ui.cbComPort.addItems(ports) mainWindow.ui.lblBaudrate = QLabel("波特率:") mainWindow.ui.cbBaudrate = QComboBox() mainWindow.ui.cbBaudrate.addItems(["9600", "19200", "38400", "57600", "115200"]) mainWindow.ui.cbBaudrate.setCurrentText("115200") serial_layout.addWidget(mainWindow.ui.lblComPort) serial_layout.addWidget(mainWindow.ui.cbComPort) serial_layout.addWidget(mainWindow.ui.lblBaudrate) serial_layout.addWidget(mainWindow.ui.cbBaudrate) # 以太网参数 mainWindow.ui.ethernetGroup = QGroupBox("以太网参数") ethernet_layout = QVBoxLayout(mainWindow.ui.ethernetGroup) mainWindow.ui.lblIP = QLabel("IP地址:") mainWindow.ui.edtIP = QLineEdit("192.168.1.100") mainWindow.ui.lblPort = QLabel("端口:") mainWindow.ui.edtPort = QLineEdit("502") ethernet_layout.addWidget(mainWindow.ui.lblIP) ethernet_layout.addWidget(mainWindow.ui.edtIP) ethernet_layout.addWidget(mainWindow.ui.lblPort) ethernet_layout.addWidget(mainWindow.ui.edtPort) # 连接/断开按钮 mainWindow.ui.bnConnectSensor = QPushButton("连接传感器") mainWindow.ui.bnDisconnectSensor = QPushButton("断开传感器") mainWindow.ui.bnDisconnectSensor.setEnabled(False) # 传感器数据显示 mainWindow.ui.lblSensorData = QLabel("传感器数据: 未连接") mainWindow.ui.lblSensorData.setStyleSheet("font-size: 10pt;") # 添加到布局 sensor_layout.addLayout(sensor_type_layout) sensor_layout.addWidget(mainWindow.ui.serialGroup) sensor_layout.addWidget(mainWindow.ui.ethernetGroup) sensor_layout.addWidget(mainWindow.ui.bnConnectSensor) sensor_layout.addWidget(mainWindow.ui.bnDisconnectSensor) sensor_layout.addWidget(mainWindow.ui.lblSensorData) # 添加到右侧面板 right_layout.addWidget(sensor_panel) # 添加拉伸项使控件靠上 right_layout.addStretch(1) # 创建停靠窗口 dock = QDockWidget("检测控制面板", mainWindow) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) mainWindow.addDockWidget(Qt.RightDockWidgetArea, dock) # === 差异度调整功能实现 === # 更新差异度阈值显示 def update_diff_threshold(value): mainWindow.ui.lblDiffValue.setText(f"{value}%") # 连接滑块信号 mainWindow.ui.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) # 更新检测结果显示 def update_diff_display(diff_ratio, is_qualified): # 更新当前差异度显示 mainWindow.ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") # 根据合格状态设置颜色 if is_qualified: mainWindow.ui.lblDiffStatus.setText("状态: 合格") mainWindow.ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.ui.lblDiffStatus.setText("状态: 不合格") mainWindow.ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") # 绑定按钮事件 mainWindow.ui.bnCheckPrint.clicked.connect(sensor_controlled_check) mainWindow.ui.bnSaveSample.clicked.connect(save_sample_image) mainWindow.ui.bnPreviewSample.clicked.connect(preview_sample) # 传感器类型切换 def update_sensor_ui(index): mainWindow.ui.serialGroup.setVisible(index == 0) mainWindow.ui.ethernetGroup.setVisible(index == 1) mainWindow.ui.cbSensorType.currentIndexChanged.connect(update_sensor_ui) update_sensor_ui(0) # 初始显示串口 # 传感器连接 def connect_sensor(): global sensor_monitor_thread, sensor_controller sensor_type = mainWindow.ui.cbSensorType.currentText() if sensor_controller is None: sensor_controller = SensorController() if sensor_type == "串口": config = { 'type': 'serial', 'port': mainWindow.ui.cbComPort.currentText(), 'baudrate': int(mainWindow.ui.cbBaudrate.currentText()), 'timeout': 1.0 } else: # 以太网 config = { 'type': 'ethernet', 'ip': mainWindow.ui.edtIP.text(), 'port': int(mainWindow.ui.edtPort.text()), 'timeout': 1.0 } if sensor_controller.connect(config): mainWindow.ui.bnConnectSensor.setEnabled(False) mainWindow.ui.bnDisconnectSensor.setEnabled(True) # 启动传感器数据监控线程 sensor_monitor_thread = SensorMonitorThread(sensor_controller) sensor_monitor_thread.data_updated.connect(update_sensor_display) sensor_monitor_thread.start() # 传感器断开 def disconnect_sensor(): global sensor_monitor_thread if sensor_controller: sensor_controller.disconnect() mainWindow.ui.bnConnectSensor.setEnabled(True) mainWindow.ui.bnDisconnectSensor.setEnabled(False) if sensor_monitor_thread and sensor_monitor_thread.isRunning(): sensor_monitor_thread.stop() sensor_monitor_thread.wait(2000) sensor_monitor_thread = None mainWindow.ui.lblSensorData.setText("传感器数据: 未连接") mainWindow.ui.bnConnectSensor.clicked.connect(connect_sensor) mainWindow.ui.bnDisconnectSensor.clicked.connect(disconnect_sensor) # === 新增延迟拍摄设置 === delay_layout = QHBoxLayout() mainWindow.ui.lblDelay = QLabel("触发延迟(秒):") mainWindow.ui.spinDelay = QSpinBox() mainWindow.ui.spinDelay.setRange(0, 60) # 0-60秒 mainWindow.ui.spinDelay.setValue(0) # 默认无延迟 mainWindow.ui.spinDelay.setToolTip("传感器检测到布料后延迟拍摄的时间") delay_layout.addWidget(mainWindow.ui.lblDelay) delay_layout.addWidget(mainWindow.ui.spinDelay) # 添加到传感器布局中 sensor_layout.addLayout(delay_layout) def update_sensor_display(data): text = (f"张力: {data['tension']:.2f}N | " f"速度: {data['speed']:.2f}m/s | " f"温度: {data['temperature']:.1f}°C | " f"湿度: {data['humidity']:.1f}%") mainWindow.ui.lblSensorData.setText(text) # 绑定其他按钮事件 mainWindow.ui.bnEnum.clicked.connect(enum_devices) mainWindow.ui.bnOpen.clicked.connect(open_device) mainWindow.ui.bnClose.clicked.connect(close_device) mainWindow.ui.bnStart.clicked.connect(start_grabbing) mainWindow.ui.bnStop.clicked.connect(stop_grabbing) mainWindow.ui.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.ui.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.ui.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.ui.bnGetParam.clicked.connect(get_param) mainWindow.ui.bnSetParam.clicked.connect(set_param) # 修改保存图像按钮连接 mainWindow.ui.bnSaveImage.clicked.connect(save_image_dialog) # 显示主窗口 mainWindow.show() # 执行应用 app.exec_() # 关闭设备 close_device() # 断开传感器 disconnect_sensor() sys.exit() 将你刚刚提出的解决方案放入这个代码里

这个程序运行后我点了调试运行之后显示特征点都一样为什么并没有自动触发检测而且我这个以后肯定是要它自动匹配好,自动质量检测的 # -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np import math import time import logging import threading from collections import deque from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBox, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QGridLayout, QSpinBox, QCheckBox, QDialog, QDialogButtonBox, QDoubleSpinBox, QProgressBar, ) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal, QTimer, QMetaObject, pyqtSlot,Q_ARG from PyQt5.QtGui import QImage, QPixmap from CamOperation_class import CameraOperation from MvCameraControl_class import * import ctypes from ctypes import cast, POINTER from datetime import datetime import skimage import platform from CameraParams_header import ( MV_GIGE_DEVICE, MV_USB_DEVICE, MV_GENTL_CAMERALINK_DEVICE, MV_GENTL_CXP_DEVICE, MV_GENTL_XOF_DEVICE ) # ===== 全局配置 ===== # 模板匹配参数 MATCH_THRESHOLD = 0.75 # 降低匹配置信度阈值以提高灵敏度 MIN_MATCH_COUNT = 10 # 最小匹配特征点数量 MIN_FRAME_INTERVAL = 0.1 # 最小检测间隔(秒) # ===== 全局变量 ===== current_sample_path = "" detection_history = [] isGrabbing = False isOpen = False obj_cam_operation = None frame_monitor_thread = None template_matcher_thread = None MV_OK = 0 MV_E_CALLORDER = -2147483647 # ==================== 优化后的质量检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05): # 不再使用传感器数据调整阈值 adjusted_threshold = threshold try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) try: # 使用更鲁棒的SIFT特征检测器 sift = cv2.SIFT_create() keypoints1, descriptors1 = sift.detectAndCompute(sample_image, None) keypoints2, descriptors2 = sift.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning("无法提取特征描述符,跳过配准") aligned_sample = sample_image else: # 使用FLANN匹配器提高匹配精度 FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(descriptors1, descriptors2, k=2) # 应用Lowe's比率测试筛选优质匹配 good_matches = [] for m, n in matches: if m.distance < 0.7 * n.distance: good_matches.append(m) if len(good_matches) > MIN_MATCH_COUNT: src_pts = np.float32([keypoints1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info("图像配准成功,使用配准后样本") else: aligned_sample = sample_image logging.warning("无法计算单应性矩阵,使用原始样本") else: aligned_sample = sample_image logging.warning(f"特征点匹配不足({len(good_matches)}/{MIN_MATCH_COUNT}),跳过图像配准") except Exception as e: logging.error(f"图像配准失败: {str(e)}") aligned_sample = sample_image try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None try: from skimage.metrics import structural_similarity as compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True, data_range=255 ) except ImportError: from skimage.measure import compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) except Exception as e: logging.error(f"SSIM计算失败: {str(e)}") abs_diff = cv2.absdiff(aligned_sample, test_image_gray) ssim_diff = abs_diff.astype(np.float32) / 255.0 ssim_score = 1.0 - np.mean(ssim_diff) ssim_diff = (1 - ssim_diff) * 255 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) _, thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) threshold极光 = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels is_qualified = diff_ratio <= adjusted_threshold marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] # 放大缺陷标记 scale_factor = 2.0 # 放大2倍 marked_image = cv2.resize(marked_image, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_LINEAR) labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: y, x = prop.centroid # 根据放大比例调整坐标 x_scaled = int(x * scale_factor) y_scaled = int(y * scale_factor) cv2.putText(marked_image, f"Defect", (x_scaled, y_scaled), cv2.FONT_HERSHEY_SIMPLEX, 0.5 * scale_factor, (0, 255, 255), int(scale_factor)) return is_qualified, diff_ratio, marked_image # ==================== 视觉触发的质量检测流程 ==================== def vision_controlled_check(capture_image=None, match_score=0.0): """修改为接受图像帧和匹配分数""" global current_sample_path, detection_history logging.info("视觉触发质量检测启动") # 如果没有提供图像,使用当前帧 if capture_image is None: frame = obj_cam_operation.get_current_frame() else: frame = capture_image if frame is None: QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: diff_threshold = mainWindow.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(30) is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, frame, threshold=diff_threshold ) progress.setValue(70) if is_qualified is None: QMessageBox.critical(mainWindow, "检测错误", "检测失败,请极光日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n差异占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: # 创建可调整大小的窗口 cv2.namedWindow("缺陷标记结果", cv2.WINDOW_NORMAL) cv2.resizeWindow("缺陷标记结果", 800, 600) # 初始大小 cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'trigger_type': 'vision' if capture_image else 'manual' } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) finally: progress.close() # ==================== 相机操作函数 ==================== def open_device(): global deviceList, nSelCamIndex, obj_cam_operation, isOpen, frame_monitor_thread, mainWindow if isOpen: QMessageBox.warning(mainWindow, "Error", '相机已打开!', QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = mainWindow.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, "Error", '请选择相机!', QMessageBox.Ok) return MV_E_CALLORDER # 创建相机控制对象 cam = MvCamera() # 初始化相机操作对象 obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = "打开设备失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 创建并启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(mainWindow.statusBar().showMessage) frame_monitor_thread.start() def start_grabbing(): global obj_cam_operation, isGrabbing, template_matcher_thread ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) # 如果启用了自动检测,启动检测线程 if mainWindow.chkContinuousMatch.isChecked(): toggle_template_matching(True) def stop_grabbing(): global obj_cam_operation, isGrabbing, template_matcher_thread ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = "停止取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() # 停止模板匹配线程 if template_matcher_thread and template_matcher_thread.isRunning(): template_matcher_thread.stop() def close_device(): global isOpen, isGrabbing, obj_cam_operation, frame_monitor_thread, template_matcher_thread if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) # 停止模板匹配线程 if template_matcher_thread and template_matcher_thread.isRunning(): template_matcher_thread.stop() template_matcher_thread.wait(2000) template_matcher_thread = None if isOpen and obj_cam_operation: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() # ==================== 连续帧匹配检测器 ==================== class ContinuousFrameMatcher(QThread): frame_processed = pyqtSignal(np.ndarray, float, bool) # 处理后的帧, 匹配分数, 是否匹配 match_score_updated = pyqtSignal(float) # 匹配分数更新信号 match_success = pyqtSignal(np.ndarray, float) # 匹配成功信号 (帧, 匹配分数) trigger_activated = pyqtSignal(bool) # 新增信号:触发状态变化 def __init__(self, cam_operation, parent=None): super().__init__(parent) self.cam_operation = cam_operation self.running = True self.sample_template = None self.min_match_count = MIN_MATCH_COUNT self.match_threshold = MATCH_THRESHOLD self.sample_kp = None self.sample_des = None self.current_match_score = 0.0 self.last_match_time = 0 self.frame_counter = 0 self.consecutive_fail_count = 0 self.last_trigger_time = 0 # 上次触发时间 self.cool_down = 0.2 # 冷却时间(秒) self.trigger_threshold = 0.5 # 默认触发阈值 # 特征检测器 - 使用SIFT self.sift = cv2.SIFT_create() # 特征匹配器 - 使用FLANN提高匹配精度 FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) self.flann = cv2.FlannBasedMatcher(index_params, search_params) # 性能监控 self.processing_times = deque(maxlen=100) self.frame_rates = deque(maxlen=100) # 黑白相机优化 self.clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8)) def calculate_match_score(self, good_matches): """更合理的匹配分数计算方法""" if not good_matches: return 0.0 # 使用匹配质量(距离的倒数)计算分数 total_quality = 0.0 for match in good_matches: # 避免除零错误 distance = max(match.distance, 1e-5) total_quality += 1.0 / distance # 归一化处理 max_quality = len(good_matches) * (1.0 / 0.01) # 假设最小距离0.01 match_score = min(1.0, max(0.0, total_quality / max_quality)) return match_score def preprocess_image(self, image): """更保守的图像预处理""" if len(image.shape) == 2: # 已经是灰度图,直接返回 return image # 仅转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 可选:轻度直方图均衡化 gray = cv2.equalizeHist(gray) return gray def set_sample(self, sample_img): """设置标准样本并提取特征""" # 保存样本图像 self.sample_img = sample_img # 预处理增强特征 processed_sample = self.preprocess_image(sample_img) # 提取样本特征点 self.sample_kp, self.sample_des = self.sift.detectAndCompute(processed_sample, None) if self.sample_des is None or len(self.sample_kp) < 100: # 增加最小特征点要求 logging.warning(f"样本特征点不足({len(self.sample_kp)}个),需要至少100个") return False logging.info(f"样本特征提取成功: {len(self.sample_kp)}个关键点") return True def set_threshold(self, threshold): """更新匹配阈值""" self.match_threshold = max(0.0, min(1.0, threshold)) logging.info(f"更新匹配阈值: {self.match_threshold:.2f}") def set_trigger_threshold(self, threshold): """更新触发阈值""" self.trigger_threshold = max(0.0, min(1.0, threshold)) logging.info(f"更新触发阈值: {self.trigger_threshold:.2f}") def process_frame(self, frame): """处理帧:特征提取、匹配和可视化""" is_matched = False match_score = 0.0 processed_frame = frame.copy() # 检查是否已设置样本 if self.sample_kp is None or self.sample_des is None: return processed_frame, match_score, is_matched # 预处理当前帧 processed_frame = self.preprocess_image(frame) # 转换为灰度图像用于特征提取 gray_frame = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2GRAY) try: # 提取当前帧的特征点 kp, des = self.sift.detectAndCompute(gray_frame, None) # 即使特征点不足也继续处理 if des is None or len(kp) < 5: # 更新匹配分数为0 self.current_match_score = 0.0 self.match_score_updated.emit(0.0) return processed_frame, 0.0, False # 匹配特征点 matches = self.flann.knnMatch(self.sample_des, des, k=2) # 应用Lowe's比率测试 good_matches = [] for m, n in matches: if m.distance < 0.7 * n.distance: good_matches.append(m) # 使用更科学的匹配分数计算方法 match_score = self.calculate_match_score(good_matches) # 添加调试日志 logging.debug(f"匹配结果: 样本特征点={len(self.sample_kp)}, " f"当前帧特征点={len(kp)}, " f"良好匹配={len(good_matches)}, " f"匹配分数={match_score:.4f}") # 判断是否匹配成功(用于UI显示) if len(good_matches) >= self.min_match_count and match_score >= self.match_threshold: is_matched = True # 在图像上绘制匹配结果 if len(gray_frame.shape) == 2: processed_frame = cv2.cvtColor(gray_frame, cv2.COLOR_GRAY2BGR) # 绘制匹配点 processed_frame = cv2.drawMatches( self.sample_img, self.s极光, processed_frame, kp, good_matches, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS ) # 在图像上显示匹配分数 cv2.putText(processed_frame, f"Match Score: {match_score:.2f}", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) # 更新当前匹配分数 self.current_match_score = match_score self.match_score_updated.emit(match_score) # 检查是否达到触发条件(50%以上)并且超过冷却时间 trigger_active = False current_time = time.time() if match_score >= self.trigger_threshold and (current_time - self.last_trigger_time) > self.cool_down: self.last_trigger_time = current_time trigger_active = True logging.info(f"匹配分数达到触发阈值! 分数: {match_score:.2f}, 触发质量检测") # 发出匹配成功信号 (传递当前帧) self.match_success.emit(frame.copy(), match_score) # 发送触发状态信号 self.trigger_activated.emit(trigger_active) except Exception as e: logging.error(f"帧处理错误: {str(e)}") return processed_frame, match_score, is_matched def run(self): """主处理循环 - 连续处理每一帧""" logging.info("连续帧匹配线程启动") self.last_match_time = time.time() self.consecutive_fail_count = 0 while self.running: start_time = time.time() # 检查相机状态 if not self.cam_operation or not self.cam_operation.is_grabbing: if self.consecutive_fail_count % 10 == 0: logging.debug("相机未取流,等待...") time.sleep(0.1) self.consecutive_fail_count += 1 continue # 获取当前帧 frame = self.cam_operation.get_current_frame() if frame is None: self.consecutive_fail_count += 1 if self.consecutive_fail_count % 10 == 0: logging.warning(f"连续{self.consecutive_fail_count}次获取帧失败") time.sleep(0.05) continue self.consecutive_fail_count = 0 try: # 处理帧 processed_frame, match_score, is_matched = self.process_frame(frame) # 发送处理结果 self.frame_processed.emit(processed_frame, match_score, is_matched) except Exception as e: logging.error(f"帧处理错误: {str(e)}") # 控制处理频率 processing_time = time.time() - start_time sleep_time = max(0.01, MIN_FRAME_INTERVAL - processing_time) time.sleep(sleep_time) logging.info("连续帧匹配线程退出") # ==================== 模板匹配控制函数 ==================== def toggle_template_matching(state): global template_matcher_thread, current_sample_path logging.debug(f"切换连续匹配状态: {state}") if state == Qt.Checked and isGrabbing: # 确保已设置样本 if not current_sample_path: logging.warning("尝试启动连续匹配但未设置样本") QMessageBox.warning(mainWindow, "错误", "请先设置标准样本", QMessageBox.Ok) mainWindow.chkContinuousMatch.setChecked(False) return if template_matcher_thread is None: logging.info("创建新的连续帧匹配线程") template_matcher_thread = ContinuousFrameMatcher(obj_cam_operation) template_matcher_thread.frame_processed.connect(update_frame_display) template_matcher_thread.match_score_updated.connect(update_match_score_display) template_matcher_thread.trigger_activated.connect( lambda active: mainWindow.update_trigger_indicator(active) ) # 正确连接匹配成功信号到质量检测函数 template_matcher_thread.match_success.connect( lambda frame, score: vision_controlled_check(frame, score) ) # 加载样本图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: logging.error("无法加载标准样本图像") QMessageBox.warning(mainWindow, "错误", "无法加载标准样本图像", QMessageBox.Ok) mainWindow.chkContinuousMatch.setChecked(False) return if not template_matcher_thread.set_sample(sample_img): logging.warning("标准样本特征不足") QMessageBox.warning(mainWindow, "错误", "标准样本特征不足", QMessageBox.Ok) mainWindow.chkContinuousMatch.setChecked(False) return if not template_matcher_thread.isRunning(): logging.info("启动连续帧匹配线程") template_matcher_thread.start() elif template_matcher_thread and template_matcher_thread.isRunning(): logging.info("停止连续帧匹配线程") template_matcher_thread.stop() # 重置匹配分数显示 update_match_score_display(0.0) # 重置帧显示 if obj_cam_operation and obj_cam_operation.is_frame_available(): frame = obj_cam_operation.get_current_frame() if frame is not None: display_frame = frame.copy() # 添加状态信息 cv2.putText(display_frame, "Continuous Matching Disabled", (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) update_frame_display(display_frame, 0.0, False) # 添加类型转换函数 def numpy_to_qimage(np_array): """将 numpy 数组转换为 QImage""" if np_array is None: return QImage() height, width, channel = np_array.shape bytes_per_line = 3 * width # 确保数据是连续的 if not np_array.flags['C_CONTIGUOUS']: np_array = np.ascontiguousarray(np_array) # 转换 BGR 到 RGB rgb_image = cv2.cvtColor(np_array, cv2.COLOR_BGR2RGB) # 创建 QImage qimg = QImage( rgb_image.data, width, height, bytes_per_line, QImage.Format_RGB888 ) # 复制数据以避免内存问题 return qimg.copy() # 修改 update_frame_display 函数 def update_frame_display(frame, match_score, is_matched): """更新主显示窗口(线程安全)""" # 确保在GUI线程中执行 if QThread.currentThread() != QApplication.instance().thread(): # 转换为 QImage 再传递 qimg = numpy_to_qimage(frame) QMetaObject.invokeMethod( mainWindow, "updateDisplay", Qt.QueuedConnection, Q_ARG(QImage, qimg), Q_ARG(float, match_score), Q_ARG(bool, is_matched) ) return # 如果已经在主线程,直接调用主窗口的更新方法 mainWindow.updateDisplay(frame, match_score, is_matched) def update_match_score_display(score): """更新匹配分数显示""" # 将分数转换为百分比显示 score_percent = score * 100 mainWindow.lblMatchScoreValue.setText(f"{score_percent:.1f}%") # 根据分数设置颜色 if score > 0.8: # 高于80%显示绿色 color = "green" elif score > 0.6: # 60%-80%显示黄色 color = "orange" else: # 低于60%显示红色 color = "red" mainWindow.lblMatchScoreValue.setStyleSheet(f"color: {color}; font-weight: bold;") def update_diff_display(diff_ratio, is_qualified): mainWindow.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") if is_qualified: mainWindow.lblDiffStatus.setText("状态: 合格") mainWindow.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.lblDiffStatus.setText("状态: 不合格") mainWindow.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") def update_diff_threshold(value): mainWindow.lblDiffValue.setText(f"{value}%") def update_sample_display(): global current_sample_path if current_sample_path: mainWindow.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") mainWindow.lblSamplePath.setToolTip(current_sample_path) mainWindow.bnPreviewSample.setEnabled(True) else: mainWindow.lblSamplePath.setText("当前样本: 未设置样本") mainWindow.bnPreviewSample.setEnabled(False) def update_history_display(): global detection_history mainWindow.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" trigger = "视觉" if result['trigger_type'] == 'vision' else "手动" mainWindow.cbHistory.addItem(f"[{trigger} {timestamp}] {status} - 差异: {ratio}") def update_match_threshold(value): """更新匹配阈值显示并应用到匹配器""" global template_matcher_thread # 更新UI显示 if mainWindow: mainWindow.lblThresholdValue.setText(f"{value}%") # 如果匹配线程存在,更新其匹配阈值 if template_matcher_thread: # 转换为0-1范围的浮点数 threshold = value / 100.0 template_matcher_thread.set_threshold(threshold) logging.debug(f"更新匹配阈值: {threshold:.2f}") # 新增函数:保存当前帧 def save_current_frame(): if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return frame = obj_cam_operation.get_current_frame() if frame is None: QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"capture_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存当前帧", os.path.join(last_dir, default_filename), "PNG Files (*.png);;BMP Files (*.bmp);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return # 确保文件扩展名正确 if not os.path.splitext(file_path)[1]: if "PNG" in selected_filter: file_path += ".png" elif "BMP" in selected_filter: file_path += ".bmp" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" # 保存图像 try: if cv2.imwrite(file_path, frame): QMessageBox.information(mainWindow, "成功", f"当前帧已保存至:\n{file_path}", QMessageBox.Ok) else: raise Exception("OpenCV保存失败") except Exception as e: QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) # ==================== 主窗口类 ==================== class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("布料印花检测系统 - 连续匹配版") self.resize(1200, 800) central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 设备枚举区域 device_layout = QHBoxLayout() self.ComboDevices = QComboBox() self.bnEnum = QPushButton("枚举设备") self.bnOpen = QPushButton("打开设备") self.bnClose = QPushButton("关闭设备") device_layout.addWidget(self.ComboDevices) device_layout.addWidget(self.bnEnum) device_layout.addWidget(self.bnOpen) device_layout.addWidget(self.bnClose) main_layout.addLayout(device_layout) # 取流控制组 self.groupGrab = QGroupBox("取流控制") grab_layout = QHBoxLayout(self.groupGrab) self.bnStart = QPushButton("开始取流") self.bnStop = QPushButton("停止取流") self.radioContinueMode = QRadioButton("连续模式") self.radioTriggerMode = QRadioButton("触发模式") self.bnSoftwareTrigger = QPushButton("软触发") grab_layout.addWidget(self.bnStart) grab_layout.addWidget(self.bnStop) grab_layout.addWidget(self.radioContinueMode) grab_layout.addWidget(self.radioTriggerMode) grab_layout.addWidget(self.bnSoftwareTrigger) main_layout.addWidget(self.groupGrab) # 参数设置组 self.paramgroup = QGroupBox("相机参数") param_layout = QGridLayout(self.paramgroup) self.edtExposureTime = QLineEdit() self.edtGain = QLineEdit() self.edtFrameRate = QLineEdit() self.bnGetParam = QPushButton("获取参数") self.bnSetParam = QPushButton("设置参数") self.bnSaveImage = QPushButton("保存图像") param_layout.addWidget(QLabel("曝光时间:"), 0, 0) param_layout.addWidget(self.edtExposureTime, 0, 1) param_layout.addWidget(self.bnGetParam, 0, 2) param_layout.addWidget(QLabel("增益:"), 1, 0) param_layout.addWidget(self.edtGain, 1, 1) param_layout.addWidget(self.bnSetParam, 1, 2) param_layout.addWidget(QLabel("帧率:"), 2, 0) param_layout.addWidget(self.edtFrameRate, 2, 1) param_layout.addWidget(self.bnSaveImage, 2, 2) main_layout.addWidget(self.paramgroup) # 图像显示区域 self.widgetDisplay = QLabel() self.widgetDisplay.setMinimumSize(640, 480) self.widgetDisplay.setStyleSheet("background-color: black;") self.widgetDisplay.setAlignment(Qt.AlignCenter) self.widgetDisplay.setText("相机预览区域") main_layout.addWidget(self.widgetDisplay, 1) # 创建自定义UI组件 self.setup_custom_ui() # 添加阈值自适应定时器 self.threshold_timer = QTimer() self.threshold_timer.timeout.connect(self.auto_adjust_threshold) self.threshold_timer.start(2000) # 每2秒调整一次 def auto_adjust_threshold(self): """根据环境亮度自动调整匹配阈值""" if not obj_cam_operation or not isGrabbing: return # 获取当前帧 frame = obj_cam_operation.get_current_frame() if frame is None: return # 处理不同通道数的图像 if len(frame.shape) == 2 or frame.shape[2] == 1: # 已经是灰度图 gray = frame elif frame.shape[2] == 3: # 三通道彩色图 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) elif frame.shape[2] == 4: # 四通道图(带alpha) gray = cv2.cvtColor(frame, cv2.COLOR_BGRA2GRAY) else: # 其他通道数,无法处理 logging.warning(f"无法处理的图像格式: {frame.shape}") return # 计算平均亮度 try: brightness = np.mean(gray) except Exception as e: logging.error(f"计算亮度失败: {str(e)}") return # 根据亮度动态调整阈值 (亮度低时降低阈值要求) if brightness < 50: # 暗环境 new_threshold = 40 # 40% elif brightness > 200: # 亮环境 new_threshold = 65 # 65% else: # 正常环境 new_threshold = 55 # 55% # 更新UI self.sliderThreshold.setValue(new_threshold) self.lblThresholdValue.setText(f"{new_threshold}%") # 更新匹配器阈值 update_match_threshold(new_threshold) # 状态栏显示调整信息 self.statusBar().showMessage(f"亮度: {brightness:.1f}, 自动调整阈值至: {new_threshold}%", 3000) def update_trigger_indicator(self, active): """更新触发指示灯状态""" if active: self.triggerIndicator.setStyleSheet("background-color: green; border-radius: 10px;") else: self.triggerIndicator.setStyleSheet("background-color: gray; border-radius: 10px;") def update_trigger_threshold(self, value): """更新触发阈值显示并应用到匹配器""" self.lblTriggerValue.setText(f"{value}%") threshold = value / 100.0 if template_matcher_thread: template_matcher_thread.set_trigger_threshold(threshold) def setup_custom_ui(self): # 工具栏 toolbar = self.addToolBar("检测工具") self.bnCheckPrint = QPushButton("手动检测") toolbar.addWidget(self.bnCheckPrint) # 添加分隔标签 toolbar.addWidget(QLabel("图像保存:")) self.bnSaveCurrentFrame = QPushButton("保存当前帧") toolbar.addWidget(self.bnSaveCurrentFrame) self.bnSaveSample = QPushButton("保存标准样本") toolbar.addWidget(self.bnSaveSample) self.bnPreviewSample = QPushButton("预览样本") toolbar.addWidget(self.bnPreviewSample) # 添加触发指示灯 self.triggerIndicator = QLabel() self.triggerIndicator.setFixedSize(20, 20) self.triggerIndicator.setStyleSheet("background-color: gray; border-radius: 10px;") toolbar.addWidget(QLabel("触发状态:")) toolbar.addWidget(self.triggerIndicator) # 历史记录 toolbar.addWidget(QLabel("历史记录:")) self.cbHistory = QComboBox() self.cbHistory.setMinimumWidth(300) toolbar.addWidget(self.cbHistory) # 状态栏样本路径 self.lblSamplePath = QLabel("当前样本: 未设置样本") self.statusBar().addPermanentWidget(self.lblSamplePath) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 差异度调整组 diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) self.lblDiffThreshold = QLabel("差异度阈值 (0-100%):") self.sliderDiffThreshold = QSlider(Qt.Horizontal) self.sliderDiffThreshold.setRange(0, 100) self.sliderDiffThreshold.setValue(5) self.lblDiffValue = QLabel("5%") self.lblCurrentDiff = QLabel("当前差异度: -") self.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") self.lblDiffStatus = QLabel("状态: 未检测") self.lblDiffStatus.setStyleSheet("font-size: 12px;") diff_layout.addWidget(self.lblDiffThreshold) diff_layout.addWidget(self.sliderDiffThreshold) diff_layout.addWidget(self.lblDiffValue) diff_layout.addWidget(self.lblCurrentDiff) diff_layout.addWidget(self.lblDiffStatus) right_layout.addWidget(diff_group) # ===== 连续匹配面板 ===== match_group = QGroupBox("连续帧匹配") match_layout = QVBoxLayout(match_group) # 样本设置 sample_layout = QHBoxLayout() self.bnSetSample = QPushButton("设置标准样本") self.bnPreviewSample = QPushButton("预览样本") self.lblSampleStatus = QLabel("状态: 未设置样本") sample_layout.addWidget(self.bnSetSample) sample_layout.addWidget(self.bnPreviewSample) sample_layout.addWidget(self.lblSampleStatus) match_layout.addLayout(sample_layout) # 匹配参数 param_layout = QHBoxLayout() self.lblMatchThreshold = QLabel("匹配阈值:") self.sliderThreshold = QSlider(Qt.Horizontal) self.sliderThreshold.setRange(50, 100) self.sliderThreshold.setValue(75) # 降低默认阈值 self.lblThresholdValue = QLabel("75%") param_layout.addWidget(self.lblMatchThreshold) param_layout.addWidget(self.sliderThreshold) param_layout.addWidget(self.lblThresholdValue) match_layout.addLayout(param_layout) # 触发阈值调整 trigger_threshold_layout = QHBoxLayout() self.lblTriggerThreshold = QLabel("触发阈值(%):") self.sliderTriggerThreshold = QSlider(Qt.Horizontal) self.sliderTriggerThreshold.setRange(0, 100) self.sliderTriggerThreshold.setValue(50) # 默认50% self.lblTriggerValue = QLabel("50%") trigger_threshold_layout.addWidget(self.lblTriggerThreshold) trigger_threshold_layout.addWidget(self.sliderTriggerThreshold) trigger_threshold_layout.addWidget(self.lblTriggerValue) match_layout.addLayout(trigger_threshold_layout) # 匹配分数显示 match_score_layout = QHBoxLayout() self.lblMatchScore = QLabel("实时匹配分数:") self.lblMatchScoreValue = QLabel("0.0%") self.lblMatchScoreValue.setStyleSheet("font-weight: bold;") match_score_layout.addWidget(self.lblMatchScore) match_score_layout.addWidget(self.lblMatchScoreValue) match_layout.addLayout(match_score_layout) # 连续匹配开关 self.chkContinuousMatch = QCheckBox("启用连续帧匹配") self.chkContinuousMatch.setChecked(False) match_layout.addWidget(self.chkContinuousMatch) right_layout.addWidget(match_group) right_layout.addStretch(1) # 添加调试按钮 self.bnDebug = QPushButton("调试匹配") toolbar.addWidget(self.bnDebug) # 连接信号 self.bnDebug.clicked.connect(self.debug_matching) # 停靠窗口 dock = QDockWidget("检测控制面板", self) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.addDockWidget(Qt.RightDockWidgetArea, dock) @pyqtSlot(QImage, float, bool) def updateDisplay(self, qimg, match_score, is_matched): """线程安全的显示更新方法(只接收 QImage)""" if qimg.isNull(): return # 创建QPixmap并缩放 pixmap = QPixmap.fromImage(qimg) scaled_pixmap = pixmap.scaled( self.widgetDisplay.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) # 更新显示 self.widgetDisplay.setPixmap(scaled_pixmap) self.widgetDisplay.setAlignment(Qt.AlignCenter) def closeEvent(self, event): logging.info("主窗口关闭,执行清理...") close_device() event.accept() def debug_matching(self): """保存当前帧和匹配结果用于调试""" global isGrabbing, current_sample_path, obj_cam_operation # 检查必要状态 if not isGrabbing: QMessageBox.warning(self, "错误", "请先开始相机取流!") return if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(self, "错误", "请先设置有效的标准样本图像!") return try: # 获取当前帧 frame = obj_cam_operation.get_current_frame() if frame is None: QMessageBox.warning(self, "无有效图像", "未获取到有效帧") return # 创建调试目录 debug_dir = "debug_match" os.makedirs(debug_dir, exist_ok=True) # 保存当前帧 current_time = datetime.now().strftime("%Y%m%d_%H%M%S") frame_path = os.path.join(debug_dir, f"current_frame_{current_time}.png") cv2.imwrite(frame_path, frame) # 保存样本图像 sample_img = cv2.imread(current_sample_path) sample_path = os.path.join(debug_dir, f"sample_{os.path.basename(current_sample_path)}") cv2.imwrite(sample_path, sample_img) # 安全预处理:检查图像通道数 def safe_preprocess(img): """安全的图像预处理函数""" # 处理不同通道数的图像 if len(img.shape) == 2 or img.shape[2] == 1: # 已经是灰度图 return img elif img.shape[2] == 3: # 三通道彩色图 return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) elif img.shape[2] == 4: # 四通道图(带alpha) return cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY) else: # 其他通道数,返回原图 logging.warning(f"无法处理的图像格式: {img.shape}") return img # 应用安全预处理 if template_matcher_thread: processed_frame = template_matcher_thread.preprocess_image(frame) processed_sample = template_matcher_thread.preprocess_image(sample_img) else: processed_frame = safe_preprocess(frame) processed_sample = safe_preprocess(sample_img) # 提取特征点 sift = cv2.SIFT_create() kp1, des1 = sift.detectAndCompute(processed_sample, None) kp2, des2 = sift.detectAndCompute(processed_frame, None) # 绘制特征点 - 处理单通道图像 def draw_keypoints_safe(img, keypoints): """安全的特征点绘制函数""" if len(img.shape) == 2: # 单通道转三通道用于彩色绘制 return cv2.drawKeypoints( cv2.cvtColor(img, cv2.COLOR_GRAY2BGR), keypoints, None, color=(0, 255, 0) ) else: return cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0)) sample_with_kp = draw_keypoints_safe(processed_sample, kp1) frame_with_kp = draw_keypoints_safe(processed_frame, kp2) # 保存带特征点的图像 cv2.imwrite(os.path.join(debug_dir, "sample_keypoints.png"), sample_with_kp) cv2.imwrite(os.path.join(debug_dir, "frame_keypoints.png"), frame_with_kp) # 进行匹配(仅当有足够的特征点时) if des1 is not None and des2 is not None and len(des1) > 0 and len(des2) > 0: FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) matches = flann.knnMatch(des1, des2, k=2) good_matches = [] for m, n in matches: if m.distance < 0.7 * n.distance: good_matches.append(m) # 绘制匹配结果 match_img = cv2.drawMatches( sample_img, kp1, frame, kp2, good_matches, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS ) # 保存匹配结果 cv2.imwrite(os.path.join(debug_dir, "matches.png"), match_img) QMessageBox.information(self, "调试完成", f"调试文件已保存到: {debug_dir}\n" f"样本特征点: {len(kp1)}\n" f"当前帧特征点: {len(kp2)}\n" f"良好匹配数: {len(good_matches)}") else: QMessageBox.warning(self, "特征不足", f"无法进行匹配:\n" f"样本特征点: {len(kp1) if kp1 else 0}\n" f"当前帧特征点: {len(kp2) if kp2 else 0}") except Exception as e: logging.exception("调试匹配失败") QMessageBox.critical(self, "调试错误", f"调试过程中发生错误:\n{str(e)}") # ===== 辅助函数 ===== def ToHexStr(num): if not isinstance(num, int): try: num = int(num) except: return f"<非整数:{type(num)}>" chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'} hexStr = "" if num < 0: num = num + 2 ** 32 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return "0x" + hexStr def enum_devices(): global deviceList, obj_cam_operation n_layer_type = ( MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE ) # 创建设备列表 deviceList = MV_CC_DEVICE_INFO_LIST() # 枚举设备 ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != MV_OK: error_msg = f"枚举设备失败! 错误码: 0x{ret:x}" logging.error(error_msg) QMessageBox.warning(mainWindow, "错误", error_msg, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, "提示", "未找到任何设备", QMessageBox.Ok) return MV_OK logging.info(f"找到 {deviceList.nDeviceNum} 个设备") # 处理设备信息 devList = [] for i in range(deviceList.nDeviceNum): # 获取设备信息 mvcc_dev_info = ctypes.cast( deviceList.pDeviceInfo[i], ctypes.POINTER(MV_CC_DEVICE_INFO) ).contents # 根据设备类型提取信息 if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE: st_gige_info = mvcc_dev_info.SpecialInfo.stGigEInfo ip_addr = ( f"{(st_gige_info.nCurrentIp >> 24) & 0xFF}." f"{(st_gige_info.nCurrentIp >> 16) & 0xFF}." f"{(st_gige_info.nCurrentIp >> 8) & 0xFF}." f"{st_gige_info.nCurrentIp & 0xFF}" ) # 修复:将c_ubyte_Array_16转换为字节串再解码 user_defined_bytes = bytes(st_gige_info.chUserDefinedName) dev_name = f"GigE: {user_defined_bytes.decode('gbk', 'ignore')}" devList.append(f"[{i}] {dev_name} ({ip_addr})") elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: st_usb_info = mvcc_dev_info.SpecialInfo.stUsb3VInfo serial = bytes(st_usb_info.chSerialNumber).decode('ascii', 'ignore').rstrip('\x00') # 修复:同样处理用户自定义名称 user_defined_bytes = bytes(st_usb_info.chUserDefinedName) dev_name = f"USB: {user_defined_bytes.decode('gbk', 'ignore')}" devList.append(f"[{i}] {dev_name} (SN: {serial})") else: devList.append(f"[{i}] 未知设备类型: {mvcc_dev_info.nTLayerType}") # 更新UI mainWindow.ComboDevices.clear() mainWindow.ComboDevices.addItems(devList) if devList: mainWindow.ComboDevices.setCurrentIndex(0) mainWindow.statusBar().showMessage(f"找到 {deviceList.nDeviceNum} 个设备", 300) return MV_OK def set_continue_mode(): ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = "设置连续模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(True) mainWindow.radioTriggerMode.setChecked(False) mainWindow.bnSoftwareTrigger.setEnabled(False) def set_software_trigger_mode(): ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = "设置触发模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(False) mainWindow.radioTriggerMode.setChecked(True) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing) def trigger_once(): ret = obj_cam_operation.trigger_once() if ret != 0: strError = "软触发失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 尝试捕获当前帧 frame = obj_cam_operation.capture_frame() if frame is None: QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 确保图像有效 if frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0: QMessageBox.warning(mainWindow, "无效图像", "捕获的图像无效,请检查相机设置!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return # 确保文件扩展名正确 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() # 创建目录(如果不存在) directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return # 保存图像 try: # 使用OpenCV保存图像 if not cv2.imwrite(file_path, frame): raise Exception("OpenCV保存失败") # 更新状态 current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) # 显示成功消息 QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) # 更新样本状态 mainWindow.lblSampleStatus.setText("状态: 样本已设置") mainWindow.lblSampleStatus.setStyleSheet("color: green;") except Exception as e: logging.error(f"保存图像失败: {str(e)}") QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 直接使用OpenCV加载图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: raise Exception("无法加载图像") # 显示图像 cv2.namedWindow("标准样本预览", cv2.WINDOW_NORMAL) cv2.resizeWindow("标准样本预览", 800, 600) cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def is_float(str): try: float(str) return True except ValueError: return False def get_param(): try: ret = obj_cam_operation.get_parameters() if ret != MV_OK: strError = "获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: mainWindow.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time)) mainWindow.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain)) mainWindow.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate)) except Exception as e: error_msg = f"获取参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def set_param(): frame_rate = mainWindow.edtFrameRate.text() exposure = mainWindow.edtExposureTime.text() gain = mainWindow.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = "设置参数失败: 参数必须是有效的浮点数" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) return MV_E_PARAMETER try: ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = "设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def enable_controls(): global isGrabbing, isOpen mainWindow.groupGrab.setEnabled(isOpen) mainWindow.paramgroup.setEnabled(isOpen) mainWindow.bnOpen.setEnabled(not isOpen) mainWindow.bnClose.setEnabled(isOpen) mainWindow.bnStart.setEnabled(isOpen and (not isGrabbing)) mainWindow.bnStop.setEnabled(isOpen and isGrabbing) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing and mainWindow.radioTriggerMode.isChecked()) mainWindow.bnSaveImage.setEnabled(isOpen and isGrabbing) mainWindow.bnCheckPrint.setEnabled(isOpen and isGrabbing) mainWindow.bnSaveSample.setEnabled(isOpen and isGrabbing) mainWindow.bnPreviewSample.setEnabled(bool(current_sample_path)) mainWindow.bnSaveCurrentFrame.setEnabled(isOpen and isGrabbing) # 连续匹配控制 mainWindow.chkContinuousMatch.setEnabled(bool(current_sample_path) and isGrabbing) # ===== 相机帧监控线程 ===== class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) # 用于发送状态消息的信号 def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True self.frame_count = 0 self.last_time = time.time() def run(self): """监控相机帧状态的主循环""" while self.running: try: if self.cam_operation and self.cam_operation.is_grabbing: # 获取帧统计信息 frame_info = self.get_frame_info() if frame_info: fps = frame_info.get('fps', 0) dropped = frame_info.get('dropped', 0) status = f"FPS: {fps:.1f} | 丢帧: {dropped}" self.frame_status.emit(status) else: self.frame_status.emit("取流中...") else: self.frame_status.emit("相机未取流") except Exception as e: self.frame_status.emit(f"监控错误: {str(e)}") # 每500ms检查一次 QThread.msleep(500) def stop(self): """停止监控线程""" self.running = False self.wait(1000) # 等待线程结束 def calculate_fps(self): """计算当前帧率""" current_time = time.time() elapsed = current_time - self.last_time if elapsed > 0: fps = self.frame_count / elapsed self.frame_count = 0 self.last_time = current_time return fps return 0 def get_frame_info(self): """获取帧信息""" try: # 更新帧计数 self.frame_count += 1 # 返回帧信息 return { 'fps': self.calculate_fps(), 'dropped': 0 # 实际应用中需要从相机获取真实丢帧数 } except Exception as e: logging.error(f"获取帧信息失败: {str(e)}") return None # ===== 主程序入口 ===== if __name__ == "__main__": # 配置日志系统 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_continuous.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统(连续匹配版)启动") app = QApplication(sys.argv) mainWindow = MainWindow() # 信号连接 mainWindow.sliderThreshold.valueChanged.connect(update_match_threshold) mainWindow.sliderTriggerThreshold.valueChanged.connect( mainWindow.update_trigger_threshold ) # 其他信号连接 mainWindow.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) mainWindow.bnCheckPrint.clicked.connect(lambda: vision_controlled_check(None)) mainWindow.bnSaveSample.clicked.connect(save_sample_image) mainWindow.bnPreviewSample.clicked.connect(preview_sample) mainWindow.bnEnum.clicked.connect(enum_devices) mainWindow.bnOpen.clicked.connect(open_device) mainWindow.bnClose.clicked.connect(close_device) mainWindow.bnStart.clicked.connect(start_grabbing) mainWindow.bnStop.clicked.connect(stop_grabbing) mainWindow.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.bnGetParam.clicked.connect(get_param) mainWindow.bnSetParam.clicked.connect(set_param) mainWindow.bnSaveImage.clicked.connect(save_current_frame) mainWindow.bnSaveCurrentFrame.clicked.connect(save_current_frame) # 连续匹配信号连接 mainWindow.sliderThreshold.valueChanged.connect(update_match_score_display) mainWindow.chkContinuousMatch.stateChanged.connect(toggle_template_matching) mainWindow.show() app.exec_() close_device() sys.exit()

大家在看

recommend-type

libssl-1_1-x64.zip

如题,放到软件安装目录bin文件夹下即可。 在anaconda更新包时遇到了这个问题,找了了X64位可以用的,之后成功更新
recommend-type

IEC 61400-25风力发电标准-英文版

IEC61400风电国际标准,分为5个部分,内容包括总则、模型、通信协议、风机构成与控制等。
recommend-type

基于GFFT的LFSR序列生成多项式估计方法

针对线性反馈移位寄存器(LFSR)序列生成多项式的估计问题,提出了一种基于LFSR序列有限域傅里叶变换(GFFT)的估计方法。首先证明了LFSR序列GFFT的非零点与LFSR序列生成多项式的零点之间的对应关系,进而利用该性质实现 LFSR 序列生成多项式的快速估计,并给出了算法在误码环境下的改进方法。仿真实验验证了算法的有效性,并对算法的计算复杂度进行了理论分析。和已有算法相比较,本文提出的算法具有更高的计算效率。
recommend-type

IFIX 4.5 MB1 驱动

 MB1:有两个版本,6.x 和 7.x 版,通过串行口连接所有支持 Modbus 串行通讯协议的设备。主要有 Modicon PLC 和其它控制仪表如:Honeywell UDC,UMC,Moore 的控制仪表等。支持 Mobus RTU 和 ASCII 两种通讯协议。当使用其与其它兼容设备通讯时,注意数据类型会与 Modicon 的数据类型有所差别。7.x 版本支持电话拨号连接和无线电连接。
recommend-type

buliding\horse\pig\rabbit\table\wolf等各种点云数据集pcd文件

这里面包含了很多数据集,有buliding\horse\pig\rabbit\table\wolf等点云pcd文件,感兴趣的可以下载。

最新推荐

recommend-type

【Java使用配置文件连接mysql】

mysql使用druid配置连接mysql
recommend-type

Java课程设计画随机圆(1).pdf

Java课程设计画随机圆(1).pdf
recommend-type

python 一个用py3写的数据库自动备份管理系统.zip

python 一个用py3写的数据库自动备份管理系统
recommend-type

电子商务的应用论文(1).docx

电子商务的应用论文(1).docx
recommend-type

“计算机软件著作权”登记【可编辑范本】(1).doc

“计算机软件著作权”登记【可编辑范本】(1).doc
recommend-type

获取本机IP地址的程序源码分析

从给定文件信息中我们可以提取出的关键知识点是“取本机IP”的实现方法以及与之相关的编程技术和源代码。在当今的信息技术领域中,获取本机IP地址是一项基本技能,广泛应用于网络通信类的软件开发中,下面将详细介绍这一知识点。 首先,获取本机IP地址通常需要依赖于编程语言和操作系统的API。不同的操作系统提供了不同的方法来获取IP地址。在Windows操作系统中,可以通过调用Windows API中的GetAdaptersInfo()或GetAdaptersAddresses()函数来获取网络适配器信息,进而得到IP地址。在类Unix操作系统中,可以通过读取/proc/net或是使用系统命令ifconfig、ip等来获取网络接口信息。 在程序设计过程中,获取本机IP地址的源程序通常会用到网络编程的知识,比如套接字编程(Socket Programming)。网络编程允许程序之间进行通信,套接字则是在网络通信过程中用于发送和接收数据的接口。在许多高级语言中,如Python、Java、C#等,都提供了内置的网络库和类来简化网络编程的工作。 在网络通信类中,IP地址是区分不同网络节点的重要标识,它是由IP协议规定的,用于在网络中唯一标识一个网络接口。IP地址可以是IPv4,也可以是较新的IPv6。IPv4地址由32位二进制数表示,通常分为四部分,每部分由8位构成,并以点分隔,如192.168.1.1。IPv6地址则由128位二进制数表示,其表示方法与IPv4有所不同,以冒号分隔的8组16进制数表示,如2001:0db8:85a3:0000:0000:8a2e:0370:7334。 当编写源代码以获取本机IP地址时,通常涉及到以下几个步骤: 1. 选择合适的编程语言和相关库。 2. 根据目标操作系统的API或系统命令获取网络接口信息。 3. 分析网络接口信息,提取出IP地址。 4. 将提取的IP地址转换成适合程序内部使用的格式。 5. 在程序中提供相应功能,如显示IP地址或用于网络通信。 例如,在Python中,可以使用内置的socket库来获取本机IP地址。一个简单的示例代码如下: ```python import socket # 获取主机名 hostname = socket.gethostname() # 获取本机IP local_ip = socket.gethostbyname(hostname) print("本机IP地址是:", local_ip) ``` 在实际应用中,获取本机IP地址通常是为了实现网络通信功能,例如建立客户端与服务器的连接,或者是在开发涉及到IP地址的其他功能时使用。 关于文件名称“getIP”,这是一个自解释的文件名,明显表示该文件或程序的作用是获取本机的IP地址。从标签“控件 源码 网络通信类 资源”中我们可以看出,这个文件很可能是一个可以嵌入其他软件中的代码片段,用以实现网络通信功能的一部分,具有较高的灵活性和重用性。 综上所述,获取本机IP地址是一个涉及到操作系统API、网络编程以及特定编程语言知识的技能。它不仅需要对编程语言提供的库函数有深入的理解,还要对操作系统的网络配置和IP协议有足够的认识。通过阅读和分析相关的源代码,如“getIP”,可以加深对网络编程实现细节的理解,并能够在实际开发中快速地应用这一技术。
recommend-type

【权威指南】:Win11笔记本上RTX4080驱动的安装与调试秘籍

# 1. Win11笔记本与RTX4080显卡介绍 随着技术的不断发展,笔记本电脑已不再是仅仅满足办公需求的移动设备,而是逐渐成为可以胜任复杂图形处理和游戏任务的强大工具。微软推出的Windows 11操作系统,以其全新的用户界面和改进的系统性能,为用户提供了更为流畅的操作体验。而在硬件方面,NVIDIA的RTX 4080显卡以其卓越的图形处理能力和AI支持,为笔记本电脑带来了前所未有的性能提升。本章节将带您初步了解Windows 11笔记本与NVIDIA RTX 4080显卡的基础信息,为进一步深入探讨它们的优化和调试技巧奠定基础。 ## 1.1 Win11笔记本的优势 Windows
recommend-type

windows环境举例

<think>好的,用户现在想在Windows环境下创建C函数库,需要具体的方法和示例代码。我需要结合之前的回答和用户提供的引用内容来给出合适的指导。 首先回顾之前的回答,用户已经了解了在Linux下创建静态库和动态库的方法,现在需要切换到Windows环境。根据引用[2],Windows下的C标准库和动态链接库的处理与Linux不同,比如使用dlfcn.h在Linux,而Windows可能需要其他方式。另外,引用[1]提到了在Windows下配置gcc环境(MinGW-w64),这可能是一个关键点,因为用户可能需要使用MinGW来编译库。 用户提供的引用[3]提到了使用MSVC编译器,这
recommend-type

QQ自动发送/回复系统源代码开放

根据提供的文件信息,我们可以了解到以下几点关键的知识点: ### 标题:“qqhelp” 1. **项目类型**: 标题“qqhelp”暗示这是一个与QQ相关的帮助工具或项目。QQ是中国流行的即时通讯软件,因此这个标题表明项目可能提供了对QQ客户端功能的辅助或扩展。 2. **用途**: “help”表明此项目的主要目的是提供帮助或解决问题。由于它提到了QQ,并且涉及“autosend/reply”功能,我们可以推测该项目可能用于自动化发送消息回复,或提供某种形式的自动回复机制。 ### 描述:“I put it to my web, but nobody sendmessage to got the source, now I public it. it supply qq,ticq autosend/reply ,full sourcecode use it as you like” 1. **发布情况**: 描述提到该项目原先被放置在某人的网站上,并且没有收到请求源代码的消息。这可能意味着项目不够知名或者需求不高。现在作者决定公开发布,这可能是因为希望项目能够被更多人了解和使用,或是出于开源共享的精神。 2. **功能特性**: 提到的“autosend/reply”表明该项目能够实现自动发送和回复消息。这种功能对于需要进行批量或定时消息沟通的应用场景非常有用,例如客户服务、自动化的营销通知等。 3. **代码可用性**: 作者指出提供了“full sourcecode”,意味着源代码完全开放,用户可以自由使用,无论是查看、学习还是修改,用户都有很大的灵活性。这对于希望学习编程或者有特定需求的开发者来说是一个很大的优势。 ### 标签:“综合系统类” 1. **项目分类**: 标签“综合系统类”表明这个项目可能是一个多功能的集成系统,它可能不仅限于QQ相关的功能,还可能包含了其他类型的综合服务或特性。 2. **技术范畴**: 这个标签可能表明该项目的技术实现比较全面,可能涉及到了多个技术栈或者系统集成的知识点,例如消息处理、网络编程、自动化处理等。 ### 压缩包子文件的文件名称列表: 1. **Unit1.dfm**: 这是一个Delphi或Object Pascal语言的窗体定义文件,用于定义应用程序中的用户界面布局。DFM文件通常用于存储组件的属性和位置信息,使得开发者可以快速地进行用户界面的设计和调整。 2. **qqhelp.dpr**: DPR是Delphi项目文件的扩展名,包含了Delphi项目的核心设置,如程序入口、使用的单元(Units)等。这个文件是编译和构建Delphi项目的起点,它能够帮助开发者了解项目的组织结构和编译指令。 3. **Unit1.pas**: PAS是Delphi或Object Pascal语言的源代码文件。这个文件可能包含了与QQ帮助工具相关的核心逻辑代码,例如处理自动发送和回复消息的算法等。 4. **readme.txt**: 这是一个常见的文本文件,包含项目的基本说明和使用指导,帮助用户了解如何获取、安装、运行和定制该项目。README文件通常是用户与项目首次交互时首先阅读的文件,因此它对于一个开源项目的用户友好度有着重要影响。 通过以上分析,我们可以看出“qqhelp”项目是一个针对QQ通讯工具的自动化消息发送与回复的辅助工具。项目包含完整的源代码,用户可以根据自己的需要进行查看、修改和使用。它可能包含Delphi语言编写的窗体界面和后端逻辑代码,具有一定的综合系统特性。项目作者出于某种原因将其开源,希望能够得到更广泛的使用和反馈。
recommend-type

【7步打造Win11深度学习利器】:Tensorflow-GPU与RTX4080终极优化指南

# 1. 深度学习与Windows 11的融合 在人工智能时代,深度学习已渗透到生活的方方面面,而Windows 11作为最新一代的操作系统,为深度学习提供了一个高效的工作平台。本章节将探讨深度学习技术如何与Windows 11系统融合,以及这一