Qt+海康虚拟相机的调试

做机器视觉项目的时候,在没有相机或需要把现场采集的图片在本地跑一下做测试时,可以使用海康的虚拟相机调试。以下是设置步骤:

1.安装好海康MVS软件,在菜单栏->工具选择虚拟相机工具,如下图:

2.打开虚拟相机工具后,根据实际情况添加相机。我这里添加了一个线阵相机。


3.回到软件主页,在左侧双击添加的虚拟相机,会打开相机,然后点击开始采集按钮,会出现图像,如果没有设置图片,会显示默认图片。右侧可修改相机参数。

4.可根据需要设置要显示的图片,在目录C:\Windows\Temp\VirtualCamera\Cameras中,找到以虚拟相机序列号命名的文件夹。将要显示图片添加到相机目录的文件夹中,彩色相机添加到RGB24文件夹中,黑白相机添加到Mono文件夹。

5.最后,可使用项目代码进行调试,可以查找到添加的虚拟相机,采集的图像为设置的图像。


注意事项:

图像尺寸需要和相机分辨率一致。
图像文件的格式需要是bmp

# -*- 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
<think>这个错误提示表明在`MainWindow`类的`__init__`方法中,调用了`self.setup_variables()`方法,但是`MainWindow`类中并没有定义这个方法。从提供的代码中,我们可以看到在`__init__`方法中,调用了几个初始化方法: ```python self.setup_ui() self.setup_variables() self.setup_signals() self.load_settings() ``` 但是,在`MainWindow`类中,我们只看到了`setup_ui`、`setup_signals`(注意:代码中实际是`setup_connections`)和`load_settings`方法,并没有`setup_variables`方法。 因此,我们需要检查: 1. 是否在类中遗漏了`setup_variables`方法的定义? 2. 或者可能是方法名不一致? 在提供的代码中,我们并没有看到`setup_variables`的定义,但是看到了一个`setup_connections`方法。另外,在`__init__`中调用了`setup_signals`,但类中并没有这个方法,而有一个`setup_connections`方法。这可能是笔误。 根据上下文,我推测: - `setup_variables` 可能是用来初始化一些成员变量的。 - `setup_signals` 可能是用来连接信号和槽的,但实际方法名为`setup_connections`。 因此,我们需要做以下调整: 1. 将`__init__`中的`setup_signals`改为`setup_connections`(因为类中定义的是`setup_connections`)。 2. 添加`setup_variables`方法,用于初始化成员变量。 但是,在代码中,我们并没有找到`setup_signals`方法,而是有一个`setup_connections`方法。所以,我们首先将`__init__`中的`setup_signals`改为`setup_connections`。 然后,我们需要添加`setup_variables`方法。在`MainWindow`类中,我们确实需要初始化一些变量,比如相机状态等。 让我们在`MainWindow`类中添加`setup_variables`方法,并在其中初始化必要的变量。同时,将`__init__`中的`setup_signals`改为`setup_connections`。 修改如下: ```python def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # 初始化顺序保持不变 self.setup_ui() self.setup_variables() # 初始化变量 self.setup_connections() # 注意:这里改为setup_connections self.load_settings() ``` 然后定义`setup_variables`方法: ```python def setup_variables(self): """初始化变量""" self.isOpen = False # 相机是否打开 self.isGrabbing = False # 相机是否在取流 self.cam = None # 相机对象 self.current_frame = None # 当前帧 self.settings = None # 用于存储设置 ``` 另外,在`__init__`中,我们调用了`self.load_settings()`,它依赖于`self.settings`,所以我们需要在`setup_variables`中初始化`self.settings`,或者至少在`load_settings`之前确保`self.settings`被初始化。 在`load_settings`方法中,我们使用了`self.settings = QSettings(...)`,所以实际上在`load_settings`中已经初始化了`self.settings`。因此,在`setup_variables`中,我们可以先初始化为`None`。 此外,我们还需要注意,在`setup_ui`方法中,我们创建了一些UI控件,这些控件在后面的方法中会被使用。因此,初始化顺序很重要:`setup_ui` -> `setup_variables` -> `setup_connections` -> `load_settings`。 但是,在提供的代码中,`MainWindow`类并没有完整给出,特别是`setup_ui`方法。我们只看到了一部分(有`setup_ui`方法,但内容不完整)。因此,我们需要确保在`setup_ui`中创建了所有必要的控件,以便在`setup_connections`中连接信号时不会出现属性错误。 另外,在`setup_connections`方法中,我们连接了很多信号,这些信号对应的控件必须在`setup_ui`中已经创建。 由于代码量很大,我们只针对错误进行修改。即添加`setup_variables`方法,并将`setup_signals`改为`setup_connections`。 注意:在提供的代码中,`setup_connections`方法已经存在,所以我们只需要修改调用它的方法名即可。 总结修改步骤: 1. 在`MainWindow`类中添加`setup_variables`方法,初始化必要的成员变量。 2. 将`__init__`中的`self.setup_signals()`改为`self.setup_connections()`。 修改后的代码片段: ```python def setup_variables(self): """初始化变量""" self.isOpen = False # 相机是否打开 self.isGrabbing = False # 相机是否在取流 self.cam = None # 相机对象 self.current_frame = None # 当前帧 self.settings = None # 用于存储设置,将在load_settings中初始化 # 其他需要初始化的变量 ``` 这样,程序启动时就不会出现`AttributeError: 'MainWindow' object has no attribute 'setup_variables'`的错误了。 同时,我们还需要注意,在`setup_ui`方法中,我们创建了工具栏,但是连接了`save_image`方法,而我们在后面定义了`save_image`方法,所以这里没有问题。 但是,在`__init__`中,我们还调用了`self.load_settings()`,它使用了`self.settings`,而我们在`setup_variables`中将其初始化为`None`,然后在`load_settings`中重新赋值,所以也是安全的。 最后,由于代码很长,我们无法在这里完整运行,但至少解决了这个特定的错误。 如果还有其他错误,需要根据错误信息进一步调整。</think>这个错误是因为在`MainWindow`类的`__init__`方法中调用了`self.setup_variables()`方法,但这个方法在类中并没有定义。根据代码上下文,应该是缺少了初始化变量的方法实现。 让我为你添加`setup_variables`方法并修复这个问题: ```python # 在 MainWindow 类中添加 setup_variables 方法 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) ``` 同时,在`__init__`方法中,我们需要将`setup_signals`改为`setup_connections`,因为类中实际定义的是`setup_connections`方法: ```python def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # 初始化顺序保持不变 self.setup_ui() self.setup_variables() # 添加这个方法 self.setup_connections() # 修改为 setup_connections(原为 setup_signals) self.load_settings() ``` 这个错误的原因是: 1. 代码中调用了未定义的`setup_variables`方法 2. 调用了`setup_signals`方法,但实际方法名是`setup_connections` **完整修复后的代码:** ```python 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) # 其他方法保持不变... ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光谷码农

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值