# -*- 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
# 配置日志系统
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 check_network_configuration():
"""检查网络配置是否适合海康相机"""
global discovered_cameras
# 模拟相机发现 - 在实际应用中应使用海康SDK
discovered_cameras = [
{"ip": "192.168.1.101", "model": "MV-CA016-10GC", "serial": "SN123456"},
{"ip": "192.168.1.102", "model": "MV-CA020-10GC", "serial": "SN789012"}
]
# 在实际应用中,这里应该使用海康SDK的MV_CC_EnumDevices函数
# 例如:
# device_list = MV_CC_DEVICE_INFO_LIST()
# ret = MvCamera.MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, device_list)
logging.info(f"发现 {len(discovered_cameras)} 台网络相机")
return bool(discovered_cameras)
# ====================== 主窗口类 ======================
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setup_ui()
self.setup_variables()
self.setup_connections()
def setup_ui(self):
"""设置用户界面"""
self.setWindowTitle("布料印花检测系统")
self.resize(1400, 900)
# 创建主窗口的中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QHBoxLayout(central_widget)
# 左侧布局(相机控制和图像显示)
left_layout = QVBoxLayout()
# 相机控制组
camera_group = QGroupBox("相机控制")
camera_layout = QGridLayout(camera_group)
# 相机控制按钮
self.bnEnum = QPushButton("枚举设备")
self.ComboDevices = QComboBox()
self.bnOpen = QPushButton("打开设备")
self.bnClose = QPushButton("关闭设备")
self.bnStart = QPushButton("开始取流")
self.bnStop = QPushButton("停止取流")
self.bnSaveImage = QPushButton("保存图像")
# 添加到布局
camera_layout.addWidget(self.bnEnum, 0, 0)
camera_layout.addWidget(self.ComboDevices, 0, 1, 1, 2)
camera_layout.addWidget(self.bnOpen, 1, 0)
camera_layout.addWidget(self.bnClose, 0, 1)
camera_layout.addWidget(self.bnStart, 2, 0)
camera_layout.addWidget(self.bnStop, 2, 1)
camera_layout.addWidget(self.bnSaveImage, 2, 2)
# 参数控制组
param_group = QGroupBox("相机参数")
param_layout = QGridLayout(param_group)
# 参数控件
self.lblExposure = QLabel("曝光时间(μs):")
self.edtExposureTime = QLineEdit("10000")
self.lblGain = QLabel("增益(dB):")
self.edtGain = QLineEdit("0")
self.lblFrameRate = QLabel("帧率(fps):")
self.edtFrameRate = QLineEdit("30")
self.bnGetParam = QPushButton("获取参数")
self.bnSetParam = QPushButton("设置参数")
# 添加到布局
param_layout.addWidget(self.lblExposure, 0, 0)
param_layout.addWidget(self.edtExposureTime, 0, 1)
param_layout.addWidget(self.lblExposure,1,0)
param_layout.addWidget(self.edtGain, 1, 1)
param_layout.addWidget(self.lblFrameRate, 2, 0)
param_layout.addWidget(self.edtFrameRate, 2, 1)
param_layout.addWidget(self.bnGetParam, 3, 0)
param_layout.addWidget(self.bnSetParam, 3, 1)
# 触发模式组
trigger_group = QGroupBox("触发模式")
trigger_layout = QVBoxLayout(trigger_group)
self.radioContinueMode = QRadioButton("连续采集模式")
self.radioTriggerMode = QRadioButton("触发采集模式")
self.bnSoftwareTrigger = QPushButton("软触发")
trigger_layout.addWidget(self.radioContinueMode)
trigger_layout.addWidget(self.radioTriggerMode)
trigger_layout.addWidget(self.bnSoftwareTrigger)
self.radioContinueMode.setChecked(True)
# 图像显示区域
self.lblImageDisplay = QLabel()
self.lblImageDisplay.setAlignment(Qt.AlignCenter)
self.lblImageDisplay.setMinimumSize(640, 480)
self.lblImageDisplay.setStyleSheet("background-color: black;")
# 添加到左侧布局
left_layout.addWidget(camera_group)
left_layout.addWidget(param_group)
left_layout.addWidget(trigger_group)
left_layout.addWidget(self.lblImageDisplay, 1)
# 右侧布局(检测控制)
right_layout = QVBoxLayout()
# 差异度调整组
diff_group = QGroupBox("检测参数")
diff_layout = QGridLayout(diff_group)
# 差异度控件
self.lblDiffThreshold = QLabel("差异度阈值 (%):")
self.sliderDiffThreshold = QSlider(Qt.Horizontal)
self.sliderDiffThreshold.setRange(0, 100)
self.sliderDiffThreshold.setValue(5)
self.lblDiffValue = QLabel("5%")
self.lblDiffValue.setMinimumWidth(50)
# 算法选项
self.cbUseSSIM = QCheckBox("使用SSIM算法(更准确)")
self.cbUseSSIM.setChecked(True)
# 添加到布局
diff_layout.addWidget(self.lblDiffThreshold, 0, 0)
diff_layout.addWidget(self.sliderDiffThreshold, 0, 1)
diff_layout.addWidget(self.lblDiffValue, 0, 2)
diff_layout.addWidget(self.cbUseSSIM, 1, 0, 1, 3)
# 样本管理组
sample_group = QGroupBox("样本管理")
sample_layout = QGridLayout(sample_group)
# 样本控件
self.bnSaveSample = QPushButton("保存标准样本")
self.bnPreviewSample = QPushButton("预览样本")
self.lblSamplePath = QLabel("当前样本: 未设置样本")
self.lblSamplePath.setWordWrap(True)
# 添加到布局
sample_layout.addWidget(self.bnSaveSample, 0, 0)
sample_layout.addWidget(self.bnPreviewSample, 0, 1)
sample_layout.addWidget(self.lblSamplePath, 1, 0, 1, 2)
# 传感器控制组
sensor_group = QGroupBox("传感器控制")
sensor_layout = QGridLayout(sensor_group)
# 传感器控件
self.cbEnableSensor = QCheckBox("启用传感器触发")
self.cbEnableSensor.setChecked(True)
self.lblSensorType = QLabel("传感器类型:")
self.comboSensorType = QComboBox()
self.comboSensorType.addItems(["光电传感器", "接近传感器", "编码器"])
self.lblSensorDelay = QLabel("触发延迟 (秒):")
self.edtSensorDelay = QLineEdit("0.5")
self.bnSetSensorDelay = QPushButton("设置")
self.cbMockSensor = QCheckBox("模拟传感器")
self.spinMockInterval = QSpinBox()
self.spinMockInterval.setRange(1000, 10000)
self.spinMockInterval.setValue(3000)
self.spinMockInterval.setSuffix(" ms")
self.bnStartMock = QPushButton("启动模拟")
self.bnStopMock = QPushButton("停止模拟")
self.bnManualTrigger =
# 添加到布局
sensor_layout.addWidget(self.cbEnableSensor, 0, 0, 1, 2)
sensor_layout.addWidget(self.lblSensorType, 1, 0)
sensor_layout.addWidget(self.comboSensorType, 1, 1)
sensor_layout.addWidget(self.lblSensorDelay, 2, 0)
sensor_layout.addWidget(self.edtSensorDelay, 2, 1)
sensor_layout.addWidget(self.bnSetSensorDelay, 2, 2)
sensor_layout.addWidget(self.cbMockSensor, 3, 0, 1, 2)
sensor_layout.addWidget(QLabel("模拟间隔:"), 4, 0)
sensor_layout.addWidget(self.spinMockInterval, 4, 1)
sensor_layout.addWidget(self.bnStartMock, 5, 0)
sensor_layout.addWidget(self.bnStopMock, 5, 1)
sensor_layout.addWidget(self.bnManualTrigger, 6, 0, 1, 3)
# 检测结果组
result_group = QGroupBox("检测结果")
result_layout = QVBoxLayout(result_group)
# 结果控件
self.lblCurrentDiff = QLabel("当前差异度: -")
self.lblCurrentDiff.setFont(QFont("Arial", 12, QFont.Bold))
self.lblDiffStatus = QLabel("状态: 未检测")
self.lblDiffStatus.setFont(QFont("Arial", 10))
self.bnCheckPrint = QPushButton("执行检测")
self.bnCheckPrint.setFont(QFont("Arial", 12, QFont.Bold))
self.bnCheckPrint.setStyleSheet("background-color: #4CAF50; color: white;")
# 历史记录
self.lblHistory = QLabel("历史记录:")
self.cbHistory = QComboBox()
# 添加到布局
result_layout.addWidget(self.lblCurrentDiff)
result_layout.addWidget(self.lblDiffStatus)
result_layout.addStretch(1)
result_layout.addWidget(self.bnCheckPrint)
result_layout.addStretch(1)
result_layout.addWidget(self.lblHistory)
result_layout.addWidget(self.cbHistory)
# 添加到右侧布局
right_layout.addWidget(diff_group)
right_layout.addWidget(sample_group)
right_layout.addWidget(sensor_group)
right_layout.addWidget(result_group, 1)
# 添加到主布局
main_layout.addLayout(left_layout, 3)
main_layout.addLayout(right_layout, 1)
# 状态栏
self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)
self.lblFrameStatus = QLabel("帧状态: 无帧")
self.statusBar.addPermanentWidget(self.lblFrameStatus)
# 设置样式
self.setStyleSheet("""
QGroupBox {
font-weight: bold;
border: 1px solid gray;
border-radius: 5px;
margin-top: 1ex;
}
QGroupBox::title {
subcontrol-origin: margin;
padding: 0 3px;
}
QLabel {
font-size: 10pt;
}
QPushButton {
font-size: 10pt;
padding: 5px;
}
""")
def setup_variables(self):
"""初始化变量"""
# 相机相关变量
self.isOpen = False
self.isGrabbing = False
self.cam_operation = None
# 传感器线程
self.sensor_thread = SensorThread(virtual_sensor)
self.sensor_thread.sensor_triggered.connect(self.sensor_triggered)
# 图像处理线程
self.image_thread = None
# 加载设置
self.load_settings()
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)
def load_settings(self):
"""加载应用程序设置"""
self.settings = QSettings("ClothInspection", "CameraApp")
# 加载样本路径
sample_path = self.settings.value("current_sample_path", "")
if sample极速响应
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 enum_devices(self):
"""枚举可用设备(模拟)"""
devices = [
"[0]GigE: Camera 1 (192.168.1.101)",
"[1]GigE: Camera 2 (192.168.1.102)",
"[2]USB: Camera 3 (SN:123456)"
]
self.ComboDevices.clear()
self.ComboDevices.addItems(devices)
self.statusBar.showMessage("已枚举到3个设备", 3000)
def open_device(self):
"""打开设备(模拟)"""
if self.isOpen:
QMessageBox.warning(self, "错误", "设备已打开", QMessageBox.Ok)
return
self.isOpen = True
self.enable_controls()
self.statusBar.showMessage("设备已打开", 3000)
def close_device(self):
"""关闭设备(模拟)"""
if not self.isOpen:
return
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
self.isGrabbing = True
self.enable_controls()
# 模拟图像显示
self.display_test_image()
self.statusBar.showMessage("已开始取流", 3000)
def stop_grabbing(self):
"""停止取流(模拟)"""
if not self.isGrabbing:
return
self.isGrabbing = False
self.enable_controls()
self.statusBar.showMessage("已停止取流", 3000)
def set_continue_mode(self):
"""设置连续采集模式"""
if self.isOpen:
self.bnSoftwareTrigger.setEnabled(False)
self.statusBar.showMessage("已设置为连续采集模式", 3000)
def set_software_trigger_mode(self):
"""设置触发采集模式"""
if self.isOpen:
self.bnSoftwareTrigger.setEnabled(self.isGrabbing)
self.statusBar.showMessage("已设置为触发采集模式", 3000)
def trigger_once(self):
"""执行软触发"""
if self.isOpen and self.isGrabbing:
self.statusBar.showMessage("已执行软触发", 3000)
# 模拟图像更新
self.display_test_image()
def get_param(self):
"""获取相机参数(模拟)"""
if not self.isOpen:
QMessageBox.warning(self, "错误", "设备未打开", QMessageBox.Ok)
return
# 设置随机参数值(模拟)
exposure = random.uniform(5000, 20000)
gain = random.uniform(0, 20)
frame_rate = random.uniform(10, 60)
self.edtExposureTime.setText(f"{exposure:.2f}")
self.edtGain.setText(f"{gain:.2f}")
self.edtFrameRate.setText(f"{frame_rate:.2f}")
self.statusBar.showMessage("已获取相机参数", 3000)
def set_param(self):
"""设置相机参数(模拟)"""
if not self.isOpen:
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之间")
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 update_diff_threshold(self, value):
"""更新差异度阈值显示"""
self.lblDiffValue.setText(f"{value}%")
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
self.update_sample_display()
self.save_settings()
self.statusBar.showMessage(f"标准样本已保存: {file_path}", 5000)
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
# 在实际应用中这里会显示样本图像
QMessageBox.information(self, "样本预览", f"预览样本: {os.path.basename(current_sample_path)}", QMessageBox.Ok)
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 display_test_image(self):
"""显示测试图像(模拟)"""
if not self.isGrabbing:
return
# 创建模拟图像
width, height = 640, 480
image = np.zeros((height, width, 3), dtype=np.uint8)
# 添加一些模拟纹理
for i in range(100):
x = random.randint(0, width-1)
y = random.randint(0, height-1)
radius = random.randint(5, 20)
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.circle(image, (x, y), radius, color, -1)
# 转换为QPixmap并显示
h, w, ch = image.shape
bytes_per_line = ch * w
q_img = QImage(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
))
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
# 创建模拟测试图像(实际应用中从相机获取)
test_image = np.zeros((480, 640, 3), dtype=np.uint8)
cv2.putText(test_image, "Test Image", (50, 240),
cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 3)
# 显示进度对话框
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 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:
# 在实际应用中这里会显示真实标记图像
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.lbl极速响应
else:
self.lblDiffStatus.setText("状态: 不合格")
self.lblDiffStatus.setStyleSheet("color: red;")
# ====================== 传感器相关函数 ======================
def sensor_triggered(self):
"""传感器触发时执行检测"""
if not self.cbEnableSensor.isChecked():
return
if not self.isGrabbing:
logging.warning("传感器触发时相机未就绪")
return
# 在实际系统中,这里会确保布料移动到正确位置
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 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)
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_())
检查一下这个程序