# -*- 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)
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.feature import ORB, match_descriptors
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 # 传感器控制器
# ==================== 传感器通讯模块 ====================
class SensorController:
def __init__(self):
self.sensor_type = None # 'serial' 或 'ethernet'
self.serial_conn = None
self.socket_conn = None
self.sensor_data = {
'tension': 0.0, # 布料张力 (N)
'speed': 0.0, # 布料速度 (m/s)
'temperature': 25.0, # 环境温度 (°C)
'humidity': 50.0 # 环境湿度 (%)
}
self.connected = False
def connect(self, config):
"""连接传感器"""
try:
if config['type'] == 'serial':
self.sensor_type = 'serial'
self.serial_conn = serial.Serial(
port=config['port'],
baudrate=config['baudrate'],
timeout=config['timeout']
)
logging.info(f"串口传感器已连接: {config['port']}")
elif config['type'] == 'ethernet':
self.sensor_type = 'ethernet'
self.socket_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket_conn.connect((config['ip'], config['port']))
self.socket_conn.settimeout(config['timeout'])
logging.info(f"以太网传感器已连接: {config['ip']}:{config['port']}")
self.connected = True
return True
except Exception as e:
logging.error(f"传感器连接失败: {str(e)}")
self.connected = False
return False
def disconnect(self):
"""断开传感器连接"""
if self.serial_conn and self.serial_conn.is_open:
self.serial_conn.close()
if self.socket_conn:
try:
self.socket_conn.shutdown(socket.SHUT_RDWR)
self.socket_conn.close()
except:
pass
self.connected = False
logging.info("传感器已断开")
def read_data(self):
"""从传感器读取数据(模拟实现)"""
if not self.connected:
return None
# 实际应用中应替换为真实传感器协议
if self.sensor_type == 'serial' and self.serial_conn:
try:
# 模拟串口数据读取
self.sensor_data = {
'tension': np.random.uniform(5.0, 20.0),
'speed': np.random.uniform(0.5, 2.5),
'temperature': 25.0 + np.random.uniform(-2, 2),
'humidity': 50.0 + np.random.uniform(-10, 10)
}
return self.sensor_data
except serial.SerialException as e:
logging.error(f"串口读取失败: {str(e)}")
self.disconnect()
return None
elif self.sensor_type == 'ethernet' and self.socket_conn:
try:
# 模拟以太网数据读取
self.sensor_data = {
'tension': np.random.uniform(5.0, 20.0),
'speed': np.random.uniform(0.5, 2.5),
'temperature': 25.0 + np.random.uniform(-2, 2),
'humidity': 50.0 + np.random.uniform(-10, 10)
}
return self.sensor_data
except (socket.timeout, socket.error) as e:
logging.error(f"网络传感器读取失败: {str(e)}")
self.disconnect()
return None
return None
def send_command(self, command):
"""向传感器发送控制命令"""
if not self.connected:
return False
try:
if self.sensor_type == 'serial' and self.serial_conn:
# 实际应用中应根据传感器协议构造命令
self.serial_conn.write(command.encode())
return True
elif self.sensor_type == 'ethernet' and self.socket_conn:
self.socket_conn.send(command.encode())
return True
return False
except Exception as e:
logging.error(f"发送传感器命令失败: {str(e)}")
return False
# 帧监控线程
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
# 传感器数据监控线程
class SensorMonitorThread(QThread):
data_updated = pyqtSignal(dict)
def __init__(self, sensor_controller):
super().__init__()
self.sensor_controller = sensor_controller
self.running = True
def run(self):
while self.running:
if self.sensor_controller and self.sensor_controller.connected:
data = self.sensor_controller.read_data()
if data:
self.data_updated.emit(data)
QThread.msleep(1000) # 每秒更新一次
def stop极(self):
self.running = False
# ==================== 优化后的检测算法 ====================
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
# ==================== 传感器控制的质量检测流程 ====================
def sensor_controlled_check():
"""传感器控制的质量检测主流程"""
global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller
logging.info("传感器控制的质量检测启动")
# 1. 检查传感器连接
if not sensor_controller or not sensor_controller.connected:
QMessageBox.warning(mainWindow, "传感器错误", "传感器未连接,请先连接传感器!", QMessageBox.Ok)
return
# 2. 读取传感器数据
sensor_data = sensor_controller.read_data()
if not sensor_data:
QMessageBox.warning(mainWindow, "传感器错误", "无法读取传感器数据!", QMessageBox.Ok)
return
# 3. 根据传感器数据调整生产参数
adjust_production_parameters(sensor_data)
# 4. 执行图像捕获和检测
check_print_with_sensor(sensor_data)
def adjust_production_parameters(sensor_data):
"""根据传感器数据调整生产参数"""
global obj_cam_operation
# 示例:根据张力调整相机参数
tension = sensor_data['tension']
# 张力过大时增加曝光时间
if tension > 15.0 and obj_cam_operation:
logging.info(f"高张力({tension}N)环境,增加曝光时间")
try:
current_exposure = obj_cam_operation.exposure_time
new_exposure = min(current_exposure * 1.2, 100000) # 增加20%,上限100ms
obj_cam_operation.set_exposure(new_exposure)
logging.info(f"曝光时间调整为: {new_exposure}us")
except Exception as e:
logging.error(f"调整曝光失败: {str(e)}")
# 速度过快时降低图像分辨率
speed = sensor_data['speed']
if speed > 2.0 and obj_cam_operation:
logging.info(f"高速度({speed}m/s)环境,降低分辨率")
try:
# 实际应用中应调用相机SDK的分辨率设置
# 这里仅为示例
pass
except Exception as e:
logging.error(f"调整分辨率失败: {str(e)}")
# 发送控制命令到传感器
if tension < 5.0 and sensor_controller:
sensor_controller.send_command("INCREASE_TENSION")
logging.info("发送增加张力命令")
elif tension > 18.0 and sensor_controller:
sensor_controller.send_command("DECREASE_TENSION")
logging.info("发送减少张力命令")
# 布料印花检测函数(使用优化算法)
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 = 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):
"""
更新差异度显示控件
"""
# 更新当前差异度显示
ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%")
# 根据合格状态设置颜色
if is_qualified:
ui.lblDiffStatus.setText("状态: 合格")
ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;")
else:
ui.lblDiffStatus.setText("状态: 不合格")
ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;")
# 更新差异度阈值显示
def update_diff_threshold(value):
"""
当滑块值改变时更新阈值显示
"""
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:
ui.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}")
ui.lblSamplePath.setToolTip(current_sample_path)
ui.bnPreviewSample.setEnabled(True)
else:
ui.lblSamplePath.setText("当前样本: 未设置样本")
ui.bnPreviewSample.setEnabled(False)
# 更新历史记录显示
def update_history_display():
global detection_history
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}%"
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("[", "]", 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) + ")")
ui.ComboDevices.clear()
ui.ComboDevices.addItems(devList)
ui.ComboDevices.setCurrentIndex(0)
# ch:打开相机 | en:open device
def open_device():
global deviceList
global nSelCamIndex
global obj_cam_operation
global isOpen
global frame_monitor_thread
if isOpen:
QMessageBox.warning(mainWindow, "Error", 'Camera is Running!', QMessageBox.Ok)
return MV_E_CALLORDER
nSelCamIndex = ui.ComboDevices.currentIndex()
if nSelCamIndex < 0:
QMessageBox.warning(mainWindow, "Error", 'Please select a camera!', QMessageBox.Ok)
return MV_E_CALLORDER
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(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(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:
ui.radioContinueMode.setChecked(True)
ui.radioTriggerMode.setChecked(False)
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:
ui.radioContinueMode.setChecked(False)
ui.radioTriggerMode.setChecked(True)
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
ui.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time))
ui.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain))
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 = ui.edtFrameRate.text()
exposure = ui.edtExposureTime.text()
gain = 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的状态,再单独设置各控件状态
ui.groupGrab.setEnabled(isOpen)
ui.groupParam.setEnabled(isOpen)
ui.bnOpen.setEnabled(not isOpen)
ui.bnClose.setEnabled(isOpen)
ui.bnStart.setEnabled(isOpen and (not isGrabbing))
ui.bnStop.setEnabled(isOpen and isGrabbing)
ui.bnSoftwareTrigger.setEnabled(isGrabbing and ui.radioTriggerMode.isChecked())
ui.bnSaveImage.setEnabled(isOpen and isGrabbing)
# 添加检测按钮控制
ui.bnCheckPrint.setEnabled(isOpen and isGrabbing)
ui.bnSaveSample.setEnabled(isOpen and isGrabbing)
ui.bnPreviewSample.setEnabled(bool(current_sample_path))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
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()
ui = MainWindow()
ui.setupUi(mainWindow)
# 扩大主窗口尺寸
mainWindow.resize(1200, 800) # 宽度1200,高度800
# 创建工具栏
toolbar = mainWindow.addToolBar("检测工具")
# 添加检测按钮
ui.bnCheckPrint = QPushButton("检测印花质量")
toolbar.addWidget(ui.bnCheckPrint)
# 添加保存样本按钮
ui.bnSaveSample = QPushButton("保存标准样本")
toolbar.addWidget(ui.bnSaveSample)
# 添加预览样本按钮
ui.bnPreviewSample = QPushButton("预览样本")
toolbar.addWidget(ui.bnPreviewSample)
# 添加历史记录下拉框
ui.cbHistory = QComboBox()
ui.cbHistory.setMinimumWidth(300)
toolbar.addWidget(QLabel("历史记录:"))
toolbar.addWidget(ui.cbHistory)
# 添加当前样本显示标签
ui.lblSamplePath = QLabel("当前样本: 未设置样本")
status_bar = mainWindow.statusBar()
status_bar.addPermanentWidget(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)
# 差异度阈值控制
ui.lblDiffThreshold = QLabel("差异度阈值 (0-100%):")
ui.sliderDiffThreshold = QSlider(Qt.Horizontal)
ui.sliderDiffThreshold.setRange(0, 100) # 0-100%
ui.sliderDiffThreshold.setValue(5) # 默认5%
ui.lblDiffValue = QLabel("5%")
# 当前差异度显示
ui.lblCurrentDiff = QLabel("当前差异度: -")
ui.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;")
# 差异度状态指示器
ui.lblDiffStatus = QLabel("状态: 未检测")
ui.lblDiffStatus.setStyleSheet("font-size: 12px;")
# 布局控件
diff_layout.addWidget(ui.lblDiffThreshold)
diff_layout.addWidget(ui.sliderDiffThreshold)
diff_layout.addWidget(ui.lblDiffValue)
diff_layout.addWidget(ui.lblCurrentDiff)
diff_layout.addWidget(ui.lblDiffStatus)
# 添加差异度组到右侧布局
right_layout.addWidget(diff_group)
# === 新增传感器控制面板 ===
sensor_panel = QGroupBox("传感器控制")
sensor_layout = QVBoxLayout(sensor_panel)
# 传感器类型选择
sensor_type_layout = QHBoxLayout()
ui.lblSensorType = QLabel("传感器类型:")
ui.cbSensorType = QComboBox()
ui.cbSensorType.addItems(["串口", "以太网"])
sensor_type_layout.addWidget(ui.lblSensorType)
sensor_type_layout.addWidget(ui.cbSensorType)
# 串口参数
ui.serialGroup = QGroupBox("串口参数")
serial_layout = QVBoxLayout(ui.serialGroup)
ui.lblComPort = QLabel("端口:")
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)]
ui.cbComPort.addItems(ports)
ui.lblBaudrate = QLabel("波特率:")
ui.cbBaudrate = QComboBox()
ui.cbBaudrate.addItems(["9600", "19200", "38400", "57600", "115200"])
ui.cbBaudrate.setCurrentText("115200")
serial_layout.addWidget(ui.lblComPort)
serial_layout.addWidget(ui.cbComPort)
serial_layout.addWidget(ui.lblBaudrate)
serial_layout.addWidget(ui.cbBaudrate)
# 以太网参数
ui.ethernetGroup = QGroupBox("以太网参数")
ethernet_layout = QVBoxLayout(ui.ethernetGroup)
ui.lblIP = QLabel("IP地址:")
ui.edtIP = QLineEdit("192.168.1.100")
ui.lblPort = QLabel("端口:")
ui.edtPort = QLineEdit("502")
ethernet_layout.addWidget(ui.lblIP)
ethernet_layout.addWidget(ui.edtIP)
ethernet_layout.addWidget(ui.lblPort)
ethernet_layout.addWidget(ui.edtPort)
# 连接/断开按钮
ui.bnConnectSensor = QPushButton("连接传感器")
ui.bnDisconnectSensor = QPushButton("断开传感器")
ui.bnDisconnectSensor.setEnabled(False)
# 传感器数据显示
ui.lblSensorData = QLabel("传感器数据: 未连接")
ui.lblSensorData.setStyleSheet("font-size: 10pt;")
# 添加到布局
sensor_layout.addLayout(sensor_type_layout)
sensor_layout.addWidget(ui.serialGroup)
sensor_layout.addWidget(ui.ethernetGroup)
sensor_layout.addWidget(ui.bnConnectSensor)
sensor_layout.addWidget(ui.bnDisconnectSensor)
sensor_layout.addWidget(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):
ui.lblDiffValue.setText(f"{value}%")
# 连接滑块信号
ui.sliderDiffThreshold.valueChanged.connect(update_diff_threshold)
# 更新检测结果显示
def update_diff_display(diff_ratio, is_qualified):
# 更新当前差异度显示
ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%")
# 根据合格状态设置颜色
if is_qualified:
ui.lblDiffStatus.setText("状态: 合格")
ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;")
else:
ui.lblDiffStatus.setText("状态: 不合格")
ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;")
# 绑定按钮事件
ui.bnCheckPrint.clicked.connect(sensor_controlled_check)
ui.bnSaveSample.clicked.connect(save_sample_image)
ui.bnPreviewSample.clicked.connect(preview_sample)
# 传感器类型切换
def update_sensor_ui(index):
ui.serialGroup.setVisible(index == 0)
ui.ethernetGroup.setVisible(index == 1)
ui.cbSensorType.currentIndexChanged.connect(update_sensor_ui)
update_sensor_ui(0) # 初始显示串口
# 传感器连接
def connect_sensor():
global sensor_monitor_thread, sensor_controller
sensor_type = ui.cbSensorType.currentText()
if sensor_controller is None:
sensor_controller = SensorController()
if sensor_type == "串口":
config = {
'type': 'serial',
'port': ui.cbComPort.currentText(),
'baudrate': int(ui.cbBaudrate.currentText()),
'timeout': 1.0
}
else: # 以太网
config = {
'type': 'ethernet',
'ip': ui.edtIP.text(),
'port': int(ui.edtPort.text()),
'timeout': 1.0
}
if sensor_controller.connect(config):
ui.bnConnectSensor.setEnabled(False)
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()
ui.bnConnectSensor.setEnabled(True)
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
ui.lblSensorData.setText("传感器数据: 未连接")
ui.bnConnectSensor.clicked.connect(connect_sensor)
ui.bnDisconnectSensor.clicked.connect(disconnect_sensor)
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}%")
ui.lblSensorData.setText(text)
# 绑定其他按钮事件
ui.bnEnum.clicked.connect(enum_devices)
ui.bnOpen.clicked.connect(open_device)
ui.bnClose.clicked.connect(close_device)
ui.bnStart.clicked.connect(start_grabbing)
ui.bnStop.clicked.connect(stop_grabbing)
ui.bnSoftwareTrigger.clicked.connect(trigger_once)
ui.radioTriggerMode.clicked.connect(set_software_trigger_mode)
ui.radioContinueMode.clicked.connect(set_continue_mode)
ui.bnGetParam.clicked.connect(get_param)
ui.bnSetParam.clicked.connect(set_param)
# 修改保存图像按钮连接
ui.bnSaveImage.clicked.connect(save_image_dialog)
# 显示主窗口
mainWindow.show()
# 执行应用
app.exec_()
# 关闭设备
close_device()
# 断开传感器
disconnect_sensor()
sys.exit()
这个程序出现了下面的问题
Traceback (most recent call last):
File "d:\海康\MVS\Development\Samples\Python\MvImport\three.py", line 1120, in <module>
mainWindow = MainWindow()
File "d:\海康\MVS\Development\Samples\Python\MvImport\three.py", line 1105, in __init__
self.ui = ui.MainWindow()
NameError: name 'ui' is not defined
最新发布