活动介绍

AttributeError: module 'scipy.signal' has no attribute 'gaussian_filter'

时间: 2024-05-14 15:11:17 浏览: 470
AttributeError: module 'scipy.signal' has no attribute 'gaussian_filter' 是一个 Python 错误,通常是因为 Scipy 的版本较低或者未正确安装所致。 scipy.signal.gaussian_filter 是 Scipy 中用于图像处理的一个函数,它可以对图像进行高斯滤波,从而实现模糊、去噪等效果。如果你的 Scipy 版本较低或者未正确安装,就可能会出现 AttributeError: module 'scipy.signal' has no attribute 'gaussian_filter' 这个错误。 解决方法通常有两种:一种是升级 Scipy 至最新版,另一种是重新安装 Scipy。 以下是回答你问题的相关问题:
相关问题

AttributeError: module 'scipy.signal' has no attribute 'gaussian'

AttributeError: 'module' object has no attribute 'function_name' 是Python中常见的错误提示,它表示你在尝试访问某个模块(这里是`scipy.signal`)里的属性(如`gaussian`),但是该模块实际上并没有这个属性。`scipy.signal.gaussian`在这个版本的`scipy`包里可能是不存在的,或者你拼写有误,或者是导入模块的方式不正确。 解决这个问题的步骤通常包括: 1. 检查函数名是否正确拼写:确认`gaussian`是不是应该为`gaussian()`或者其他的函数形式。 2. 确认已安装的scipy版本:查看你当前安装的scipy库是否有提供这个功能。你可以通过运行 `import scipy; print(scipy.__version__)` 来查看版本。 3. 更新依赖:如果发现确实旧版scipy缺少此功能,可以尝试更新到最新版scipy,通过pip命令 `pip install -U scipy` 进行升级。 4. 查阅文档:检查Scipy信号处理模块的官方文档,确认`gaussian`函数是否存在及其正确的使用方式。

报错AttributeError: module 'scipy.signal' has no attribute 'gaussian'如何解决

这个报错的意思是在使用scipy.signal中的gaussian函数时,发现该模块中没有这个函数。这可能是由于您的scipy版本过低或者没有安装scipy导致的。您可以尝试升级您的scipy版本或者安装scipy。 升级scipy的方法如下: ``` pip install --upgrade scipy ``` 安装scipy的方法如下: ``` pip install scipy ``` 如果您使用的是conda环境,可以使用以下命令进行升级或安装: ``` conda update scipy conda install scipy ```
阅读全文

相关推荐

# -*- 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() 将你刚刚提出的解决方案放入这个代码里

C:\Anaconda\python.exe C:\Users\汤伟娟\PycharmProjects\pythonProject\地形等高线图.py C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 30446 (\N{CJK UNIFIED IDEOGRAPH-76EE}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 26631 (\N{CJK UNIFIED IDEOGRAPH-6807}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 28857 (\N{CJK UNIFIED IDEOGRAPH-70B9}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 21608 (\N{CJK UNIFIED IDEOGRAPH-5468}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 22260 (\N{CJK UNIFIED IDEOGRAPH-56F4}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 22320 (\N{CJK UNIFIED IDEOGRAPH-5730}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 24418 (\N{CJK UNIFIED IDEOGRAPH-5F62}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 31561 (\N{CJK UNIFIED IDEOGRAPH-7B49}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 39640 (\N{CJK UNIFIED IDEOGRAPH-9AD8}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 32447 (\N{CJK UNIFIED IDEOGRAPH-7EBF}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 22270 (\N{CJK UNIFIED IDEOGRAPH-56FE}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 38388 (\N{CJK UNIFIED IDEOGRAPH-95F4}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 38548 (\N{CJK UNIFIED IDEOGRAPH-9694}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 65306 (\N{FULLWIDTH COLON}) missing from current font. super()._update_title_position(renderer) C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py:527: UserWarning: Glyph 31859 (\N{CJK UNIFIED IDEOGRAPH-7C73}) missing from current font. super()._update_title_position(renderer) Traceback (most recent call last): File "C:\Users\汤伟娟\PycharmProjects\pythonProject\地形等高线图.py", line 204, in <module> plot_simple_contour( File "C:\Users\汤伟娟\PycharmProjects\pythonProject\地形等高线图.py", line 190, in plot_simple_contour plt.tight_layout() File "C:\Anaconda\Lib\site-packages\matplotlib\pyplot.py", line 2349, in tight_layout return gcf().tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Anaconda\Lib\site-packages\matplotlib\figure.py", line 3544, in tight_layout engine.execute(self) File "C:\Anaconda\Lib\site-packages\matplotlib\layout_engine.py", line 184, in execute kwargs = get_tight_layout_figure( ^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Anaconda\Lib\site-packages\matplotlib\_tight_layout.py", line 266, in get_tight_layout_figure kwargs = _auto_adjust_subplotpars(fig, renderer, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Anaconda\Lib\site-packages\matplotlib\_tight_layout.py", line 82, in _auto_adjust_subplotpars bb += [martist._get_tightbbox_for_layout_only(ax, renderer)] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Anaconda\Lib\site-packages\matplotlib\artist.py", line 1415, in _get_tightbbox_for_layout_only return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True}) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py", line 498, in get_tightbbox return super().get_tightbbox(renderer, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Anaconda\Lib\site-packages\matplotlib\axes\_base.py", line 4388, in get_tightbbox self._update_title_position(renderer) File "C:\Anaconda\Lib\site-packages\cartopy\mpl\geoaxes.py", line 543, in _update_title_position gl._draw_gridliner(renderer=renderer) File "C:\Anaconda\Lib\site-packages\cartopy\mpl\gridliner.py", line 789, in _draw_gridliner formatter.set_locs(line_ticks) ^^^^^^^^^^^^^^^^^^ AttributeError: 'function' object has no attribute 'set_locs' 进程已结束,退出代码为 1

最新推荐

recommend-type

langchain4j-anthropic-spring-boot-starter-0.31.0.jar中文文档.zip

1、压缩文件中包含: 中文文档、jar包下载地址、Maven依赖、Gradle依赖、源代码下载地址。 2、使用方法: 解压最外层zip,再解压其中的zip包,双击 【index.html】 文件,即可用浏览器打开、进行查看。 3、特殊说明: (1)本文档为人性化翻译,精心制作,请放心使用; (2)只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; (3)不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 4、温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件。 5、本文件关键字: jar中文文档.zip,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册。
recommend-type

TMS320F28335电机控制程序详解:BLDC、PMSM无感有感及异步VF源代码与开发资料

TMS320F28335这款高性能数字信号处理器(DSP)在电机控制领域的应用,涵盖了BLDC(无刷直流电机)、PMSM(永磁同步电机)的无感有感控制以及异步VF(变频调速)程序。文章不仅解释了各类型的电机控制原理,还提供了完整的开发资料,包括源代码、原理图和说明文档,帮助读者深入了解其工作原理和编程技巧。 适合人群:从事电机控制系统开发的技术人员,尤其是对TMS320F28335感兴趣的工程师。 使用场景及目标:适用于需要掌握TMS320F28335在不同电机控制应用场景下具体实现方法的专业人士,旨在提高他们对该微控制器的理解和实际操作能力。 其他说明:文中提供的开发资料为读者提供了从硬件到软件的全面支持,有助于加速项目开发进程并提升系统性能。
recommend-type

基于爬山搜索法的风力发电MPPT控制Simulink仿真:定步长与变步长算法性能对比 - 爬山搜索法 最新版

基于爬山搜索法的风力发电最大功率点追踪(MPPT)控制的Simulink仿真模型,重点比较了定步长和变步长算法在不同风速条件下的表现。文中展示了两种算法的具体实现方法及其优缺点。定步长算法虽然结构简单、计算量小,但在风速突变时响应较慢,存在明显的稳态振荡。相比之下,变步长算法能够根据功率变化动态调整步长,表现出更快的响应速度和更高的精度,尤其在风速突变时优势明显。实验数据显示,变步长算法在风速从8m/s突增至10m/s的情况下,仅用0.3秒即可稳定,功率波动范围仅为±15W,而定步长算法则需要0.8秒,功率波动达到±35W。 适合人群:从事风力发电研究的技术人员、对MPPT控制感兴趣的工程技术人员以及相关专业的高校师生。 使用场景及目标:适用于风力发电系统的设计与优化,特别是需要提高系统响应速度和精度的场合。目标是在不同风速条件下,选择合适的MPPT算法以最大化风能利用率。 其他说明:文章还讨论了定步长算法在风速平稳情况下的优势,提出了根据不同应用场景灵活选择或组合使用这两种算法的建议。
recommend-type

基于MatlabSimulink的风电场调频策略研究:虚拟惯性、超速减载与下垂控制的协调优化

内容概要:本文详细探讨了在Matlab/Simulink环境下,针对风电场调频的研究,尤其是双馈风机调频策略的应用及其与其他调频策略的协调工作。文中介绍了三种主要的调频策略——虚拟惯性、超速减载和下垂控制,并基于三机九节点系统进行了改进,模拟了四组风电机组的协同调频过程。研究指出,虚拟惯性的应用虽然可以提供短期频率支持,但也可能导致频率二次跌落的问题。因此,需要通过超速减载和下垂控制来进行补偿,以维持电网的稳定。此外,文章还展示了在变风速条件下,风电机组对电网频率支撑能力的显著提升,尤其是在高比例风电并网渗透的情况下,频率最低点提高了50%,验证了调频策略的有效性。 适合人群:从事电力系统、风电场运营管理和调频技术研发的专业人士,以及对风电调频感兴趣的科研人员和技术爱好者。 使用场景及目标:适用于希望深入理解风电场调频机制及其优化方法的人群。目标是掌握不同调频策略的工作原理及其协调工作的关键点,提高风电场的运行效率和稳定性。 其他说明:本文通过具体的案例研究和仿真数据,展示了调频策略的实际效果,强调了合理运用调频策略对于风电场稳定运行的重要意义。同时,也为未来的风电调频技术创新提供了理论依据和实践经验。
recommend-type

三菱QL系列PLC在3C-FPC组装机中的定位与伺服控制及触摸屏应用解析

三菱Q系列和L系列PLC在3C-FPC组装机中的具体应用,涵盖硬件架构、软件编程以及实际操作技巧。主要内容包括:使用QX42数字输入模块和QY42P晶体管输出模块进行高效信号处理;采用JE系列伺服控制系统实现高精度四轴联动;利用SFC(顺序功能图)和梯形图编程方法构建稳定可靠的控制系统;通过触摸屏实现多用户管理和权限控制;并分享了一些实用的调试和维护技巧,如流水线节拍控制和工程师模式进入方法。最终,该系统的设备综合效率(OEE)达到了92%以上。 适合人群:从事自动化控制领域的工程师和技术人员,尤其是对三菱PLC有初步了解并希望深入了解其高级应用的人群。 使用场景及目标:适用于需要高精度、高效能的工业生产设备控制场合,旨在帮助用户掌握三菱PLC及其相关组件的应用技能,提高生产效率和产品质量。 其他说明:文中提供了详细的编程实例和操作指南,有助于读者更好地理解和实践。同时提醒使用者在调试过程中应注意伺服刚性参数调整,避免不必要的机械损伤。
recommend-type

Visual C++.NET编程技术实战指南

根据提供的文件信息,可以生成以下知识点: ### Visual C++.NET编程技术体验 #### 第2章 定制窗口 - **设置窗口风格**:介绍了如何通过编程自定义窗口的外观和行为。包括改变窗口的标题栏、边框样式、大小和位置等。这通常涉及到Windows API中的`SetWindowLong`和`SetClassLong`函数。 - **创建六边形窗口**:展示了如何创建一个具有特殊形状边界的窗口,这类窗口不遵循标准的矩形形状。它需要使用`SetWindowRgn`函数设置窗口的区域。 - **创建异形窗口**:扩展了定制窗口的内容,提供了创建非标准形状窗口的方法。这可能需要创建一个不规则的窗口区域,并将其应用到窗口上。 #### 第3章 菜单和控制条高级应用 - **菜单编程**:讲解了如何创建和修改菜单项,处理用户与菜单的交互事件,以及动态地添加或删除菜单项。 - **工具栏编程**:阐述了如何使用工具栏,包括如何创建工具栏按钮、分配事件处理函数,并实现工具栏按钮的响应逻辑。 - **状态栏编程**:介绍了状态栏的创建、添加不同类型的指示器(如文本、进度条等)以及状态信息的显示更新。 - **为工具栏添加皮肤**:展示了如何为工具栏提供更加丰富的视觉效果,通常涉及到第三方的控件库或是自定义的绘图代码。 #### 第5章 系统编程 - **操作注册表**:解释了Windows注册表的结构和如何通过程序对其进行读写操作,这对于配置软件和管理软件设置非常关键。 - **系统托盘编程**:讲解了如何在系统托盘区域创建图标,并实现最小化到托盘、从托盘恢复窗口的功能。 - **鼠标钩子程序**:介绍了钩子(Hook)技术,特别是鼠标钩子,如何拦截和处理系统中的鼠标事件。 - **文件分割器**:提供了如何将文件分割成多个部分,并且能够重新组合文件的技术示例。 #### 第6章 多文档/多视图编程 - **单文档多视**:展示了如何在同一个文档中创建多个视图,这在文档编辑软件中非常常见。 #### 第7章 对话框高级应用 - **实现无模式对话框**:介绍了无模式对话框的概念及其应用场景,以及如何实现和管理无模式对话框。 - **使用模式属性表及向导属性表**:讲解了属性表的创建和使用方法,以及如何通过向导性质的对话框引导用户完成多步骤的任务。 - **鼠标敏感文字**:提供了如何实现点击文字触发特定事件的功能,这在阅读器和编辑器应用中很有用。 #### 第8章 GDI+图形编程 - **图像浏览器**:通过图像浏览器示例,展示了GDI+在图像处理和展示中的应用,包括图像的加载、显示以及基本的图像操作。 #### 第9章 多线程编程 - **使用全局变量通信**:介绍了在多线程环境下使用全局变量进行线程间通信的方法和注意事项。 - **使用Windows消息通信**:讲解了通过消息队列在不同线程间传递信息的技术,包括发送消息和处理消息。 - **使用CriticalSection对象**:阐述了如何使用临界区(CriticalSection)对象防止多个线程同时访问同一资源。 - **使用Mutex对象**:介绍了互斥锁(Mutex)的使用,用以同步线程对共享资源的访问,保证资源的安全。 - **使用Semaphore对象**:解释了信号量(Semaphore)对象的使用,它允许一个资源由指定数量的线程同时访问。 #### 第10章 DLL编程 - **创建和使用Win32 DLL**:介绍了如何创建和链接Win32动态链接库(DLL),以及如何在其他程序中使用这些DLL。 - **创建和使用MFC DLL**:详细说明了如何创建和使用基于MFC的动态链接库,适用于需要使用MFC类库的场景。 #### 第11章 ATL编程 - **简单的非属性化ATL项目**:讲解了ATL(Active Template Library)的基础使用方法,创建一个不使用属性化组件的简单项目。 - **使用ATL开发COM组件**:详细阐述了使用ATL开发COM组件的步骤,包括创建接口、实现类以及注册组件。 #### 第12章 STL编程 - **list编程**:介绍了STL(标准模板库)中的list容器的使用,讲解了如何使用list实现复杂数据结构的管理。 #### 第13章 网络编程 - **网上聊天应用程序**:提供了实现基本聊天功能的示例代码,包括客户端和服务器的通信逻辑。 - **简单的网页浏览器**:演示了如何创建一个简单的Web浏览器程序,涉及到网络通信和HTML解析。 - **ISAPI服务器扩展编程**:介绍了如何开发ISAPI(Internet Server API)服务器扩展来扩展IIS(Internet Information Services)的功能。 #### 第14章 数据库编程 - **ODBC数据库编程**:解释了ODBC(开放数据库互联)的概念,并提供了使用ODBC API进行数据库访问的示例。 - **ADO编程**:介绍了ADO(ActiveX Data Objects)技术,讲解了如何使用ADO进行数据库的增删改查等操作。 #### 第15章 HTML帮助系统编程 - **创建HTML帮助文件**:讲解了如何创建HTML帮助文件(.chm),这是微软帮助系统的一种形式。 - **区分上下文帮助**:提供了在应用程序中实现上下文敏感帮助的技术示例。 #### 第16章 安装和部署项目 - **用安装程序向导创建安装项目**:介绍了如何使用Visual Studio中的安装和部署项目向导,快速创建安装程序。 - **部署应用程序**:阐述了部署应用程序到目标系统的过程,以及如何确保应用程序的正确安装和运行。 以上内容涵盖了Visual C++.NET编程技术体验一书中涉及的各个技术点,并对每个章节中的示例做了详细的说明。这些知识点对于理解和掌握Visual C++.NET编程非常重要,并能够帮助读者在实际项目开发中应用这些技术。
recommend-type

HarmonyOS内核深度探秘:优化自由行旅游系统的策略

# 1. HarmonyOS内核概述与旅游系统需求分析 ## 1.1 HarmonyOS内核概述 HarmonyOS(鸿蒙操作系统)是由华为开发的一个分布式操作系统,旨在提供跨多种设备的无缝体验。其内核设计理念强调高效、安全和模块化,以微内核架构和分布式能力
recommend-type

tkinter模块所有控件

<think>好的,我现在需要回答用户关于Tkinter模块中所有控件列表的问题。首先,我需要回忆Tkinter的基础知识,确保自己了解各个控件的名称和用途。用户希望得到所有支持的控件列表,所以必须全面覆盖,不能遗漏。 首先,我应该从Tkinter的标准控件开始。常见的控件包括Label、Button、Entry这些基础部件。然后是Frame,用于布局,还有Canvas用于绘图。接下来是Checkbutton、Radiobutton,这些属于选择类控件。Listbox和Scrollbar通常一起使用,处理滚动内容。还有Scale(滑块)、Spinbox、Menu、Menubutton这些可能
recommend-type

局域网五子棋游戏:娱乐与聊天的完美结合

标题“网络五子棋”和描述“适合于局域网之间娱乐和聊天!”以及标签“五子棋 网络”所涉及的知识点主要围绕着五子棋游戏的网络版本及其在局域网中的应用。以下是详细的知识点: 1. 五子棋游戏概述: 五子棋是一种两人对弈的纯策略型棋类游戏,又称为连珠、五子连线等。游戏的目标是在一个15x15的棋盘上,通过先后放置黑白棋子,使得任意一方先形成连续五个同色棋子的一方获胜。五子棋的规则简单,但策略丰富,适合各年龄段的玩家。 2. 网络五子棋的意义: 网络五子棋是指可以在互联网或局域网中连接进行对弈的五子棋游戏版本。通过网络版本,玩家不必在同一地点即可进行游戏,突破了空间限制,满足了现代人们快节奏生活的需求,同时也为玩家们提供了与不同对手切磋交流的机会。 3. 局域网通信原理: 局域网(Local Area Network,LAN)是一种覆盖较小范围如家庭、学校、实验室或单一建筑内的计算机网络。它通过有线或无线的方式连接网络内的设备,允许用户共享资源如打印机和文件,以及进行游戏和通信。局域网内的计算机之间可以通过网络协议进行通信。 4. 网络五子棋的工作方式: 在局域网中玩五子棋,通常需要一个客户端程序(如五子棋.exe)和一个服务器程序。客户端负责显示游戏界面、接受用户输入、发送落子请求给服务器,而服务器负责维护游戏状态、处理玩家的游戏逻辑和落子请求。当一方玩家落子时,客户端将该信息发送到服务器,服务器确认无误后将更新后的棋盘状态传回给所有客户端,更新显示。 5. 五子棋.exe程序: 五子棋.exe是一个可执行程序,它使得用户可以在个人计算机上安装并运行五子棋游戏。该程序可能包含了游戏的图形界面、人工智能算法(如果支持单机对战AI的话)、网络通信模块以及游戏规则的实现。 6. put.wav文件: put.wav是一个声音文件,很可能用于在游戏进行时提供声音反馈,比如落子声。在网络环境中,声音文件可能被用于提升玩家的游戏体验,尤其是在局域网多人游戏场景中。当玩家落子时,系统会播放.wav文件中的声音,为游戏增添互动性和趣味性。 7. 网络五子棋的技术要求: 为了确保多人在线游戏的顺利进行,网络五子棋需要具备一些基本的技术要求,包括但不限于稳定的网络连接、高效的数据传输协议(如TCP/IP)、以及安全的数据加密措施(如果需要的话)。此外,还需要有一个良好的用户界面设计来提供直观和舒适的用户体验。 8. 社交与娱乐: 网络五子棋除了是一个娱乐游戏外,它还具有社交功能。玩家可以通过游戏内的聊天系统进行交流,分享经验和策略,甚至通过网络寻找新的朋友。这使得网络五子棋不仅是一个个人娱乐工具,同时也是一种社交活动。 总结来说,网络五子棋结合了五子棋游戏的传统魅力和现代网络技术,使得不同地区的玩家能够在局域网内进行娱乐和聊天,既丰富了人们的娱乐生活,又加强了人际交流。而实现这一切的基础在于客户端程序的设计、服务器端的稳定运行、局域网的高效通信,以及音效文件增强的游戏体验。
recommend-type

自由行旅游新篇章:HarmonyOS技术融合与系统架构深度解析

# 1. HarmonyOS技术概述 ## 1.1 HarmonyOS的起源与发展 HarmonyOS(鸿蒙操作系统)由华为公司开发,旨在构建全场景分布式OS,以应对不同设备间的互联问题。自从2019年首次发布以来,HarmonyOS迅速成长,并迅速应用于智能手机、平板、智能穿戴、车载设备等多种平台。该系