Python中使用OpenCV读取灰度图像时遇到的错误:module ‘cv2‘ has no attribute ‘CV_LOAD_IMAGE_GRAYSCA...

1151 篇文章 ¥299.90 ¥399.90
在使用OpenCV的Python接口读取灰度图像时,遇到'cv2'模块没有'CV_LOAD_IMAGE_GRAYSCALE'属性的错误。该问题源于此属性在新版本的OpenCV中已被弃用。解决方案是使用cv2.IMREAD_GRAYSCALE替代,例如:`img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)`。这段代码将正确读取并显示灰度图像。" 114461770,10539233,Java JpcapDumper: 抓包与分析工具,"['网络编程', 'Java开发', '数据捕获', '协议解析']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Python中使用OpenCV读取灰度图像时遇到的错误:module ‘cv2’ has no attribute ‘CV_LOAD_IMAGE_GRAYSCALE’。

OpenCV是一款广泛应用于计算机视觉领域的开源计算机视觉库,它可以实现图像处理、分析、识别等功能。而在使用OpenCV读取灰度图像时,可能会出现上述错误。

这个错误发生的原因是因为cv2模块中CV_LOAD_IMAGE_GRAYSCALE已经被弃用。在最新版的OpenCV中(3.x版本以上),可以直接使用cv2.IMREAD_GRAYSCALE来代替。

以下是一个Python代码示例,演示如何使用cv2.IMREAD_GRAYSCALE读取灰度图像:

import cv2

img = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('Gray Image', img)
cv2.waitKey()
cv2.destroyAllWindows()

上述代码中,首先通过cv2.imread函数读取名为“image.jpg”的图像文件,并将其以灰度图像的形式存储在img变量中。然后,使用cv2.imshow函数显示灰度图像,cv2.waitKey函数等待用户按下任意键,最后使用cv2.destroyAllWindows函数关闭所有打开的窗口。

总之,如果你在使用OpenCV时遇到了“module ‘cv2’ has no attribute ‘CV_LOAD_IMAGE_GRAYSCALE’”这个错误,可以尝试使用cv2.IMREAD_GRAYSCALE代替,它可以更好地实现灰度图像的读取。

# -*- 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_signals() self.load_settings() def setup_ui(self): # ... 其他UI代码 ... # 创建工具栏 toolbar = self.addToolBar("主工具栏") save_action = toolbar.addAction("保存图像") save_action.triggered.connect(self.save_image) # 正确连接信号 settings_action = toolbar.addAction("设置") settings_action.triggered.connect(self.open_settings) # 添加缺失的 save_image 方法 def save_image(self): """保存当前显示的图像到文件""" if self.current_frame is None: QMessageBox.warning(self, "保存失败", "没有可保存的图像", QMessageBox.Ok) return # 获取默认保存路径 default_path = self.settings.get('save_path', os.path.expanduser("~/Pictures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = os.path.join(default_path, f"capture_{timestamp}.png") # 弹出保存对话框 file_path, _ = QFileDialog.getSaveFileName( self, "保存图像", default_filename, "PNG图像 (*.png);;JPEG图像 (*.jpg *.jpeg);;BMP图像 (*.bmp);;所有文件 (*)" ) if not file_path: # 用户取消 return # 更新保存路径设置 self.settings['save_path'] = os.path.dirname(file_path) try: # 根据文件扩展名确定保存格式 ext = os.path.splitext(file_path)[1].lower() # 保存图像 if ext in ['.png', '.jpg', '.jpeg', '.bmp']: success = cv2.imwrite(file_path, self.current_frame) else: # 添加默认扩展名 file_path += '.png' success = cv2.imwrite(file_path, self.current_frame) if success: self.statusBar().showMessage(f"图像已保存: {file_path}", 5000) logging.info(f"图像保存成功: {file_path}") else: QMessageBox.critical(self, "保存失败", f"无法保存图像到: {file_path}\n" "可能原因:\n" "1. 路径无效或没有写入权限\n" "2. 磁盘空间不足\n" "3. 文件被其他程序占用", QMessageBox.Ok) except Exception as e: logging.error(f"保存图像出错: {str(e)}") QMessageBox.critical(self, "保存失败", f"保存图像发生错误:\n{str(e)}\n\n" "请检查:\n" "1. 文件路径是否有效\n" "2. 磁盘是否有足够空间\n" "3. 文件是否被其他程序锁定", QMessageBox.Ok) # 添加 open_settings 方法 def open_settings(self): """打开设置对话框(示例实现)""" QMessageBox.information(self, "设置", "设置功能将在后续版本中实现", QMessageBox.Ok) # 确保在图像回调中更新 current_frame def image_callback(self, pData, pFrameInfo, pUser): """海康SDK要求的图像数据回调函数""" try: # ... 图像处理代码 ... # 更新当前帧 self.current_frame = image.copy() # 更新UI显示 self.display_image(image) except Exception as e: logging.error(f"图像回调处理错误: {str(e)}") def display_image(self, image): """在QLabel上显示图像""" try: # 将OpenCV图像转换为Qt图像 height, width, channel = image.shape bytes_per_line = 3 * width q_img = QImage(image.data, width, height, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(q_img.rgbSwapped()) # BGR转RGB # 缩放图像以适应标签大小 scaled_pixmap = pixmap.scaled( self.image_label.width(), self.image_label.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_label.setPixmap(scaled_pixmap) except Exception as e: logging.error(f"显示图像错误: {str(e)}") # 其他方法保持不变... # ====================== 相机操作方法 ====================== 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: "设备已被其他程序占用 - 关闭其他相机应用程序", } # 特殊处理常见错误 if error_code == 0x80000004: # MV_E_NO_DEVICE_FOUND return ( "设备未找到 (错误: 0x80000004)\n\n" "可能原因:\n" "1. 相机未上电或未连接\n" "2. 网络配置错误(IP不在同一子网)\n" "3. 防火墙阻止了通信\n" "4. 驱动程序未正确安装\n\n" "解决方案:\n" "1. 检查相机电源和连接线\n" "2. 使用海康官方MVS软件测试连接\n" "3. 禁用防火墙或添加例外规则" ) if error_code == 0x80000005: # MV_E_ACCESS_DENIED return ( "设备已被占用 (错误: 0x80000005)\n\n" "可能原因:\n" "1. 相机已被其他应用程序打开\n" "2. 相机被系统进程占用\n\n" "解决方案:\n" "1. 关闭其他使用相机的程序\n" "2. 重启相机\n" "3. 重启计算机" ) return error_map.get(error_code, f"未知错误 (代码: 0x{error_code:08X})") 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 1460, in <module> main_window = MainWindow() File "d:\海康\MVS\Development\Samples\Python\MvImport\2.py", line 450, in __init__ self.setup_variables() AttributeError: 'MainWindow' object has no attribute 'setup_variables'
07-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值