QCheckBox控件扩大点击范围。

本文介绍了如何解决QCheckBox控件点击范围有限的问题,通过直接重写控件的点击事件,使得在QCheckBox的整个区域内点击都能改变选中状态,这种方法被认为比设置样式表更有效。

QCheckBox控件扩大点击范围。

QCheckBox使用时发现只能点击文本或者中间的小白框才能改变选中状态,想要点击控件的范围内都能改变选中状态。

 

方法。直接重写控件的点击事件。

 

 

这个方法感觉比设置样式表好用。

# -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np import json import time import logging import platform from datetime import datetime from scipy import ndimage from scipy.spatial import distance # PyQt5 导入 from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QCheckBox # 添加 QCheckBox ) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap # 可能需要这些用于图像显示 # 海康 SDK 导入 sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\BasicDemo") from MvCameraControl_class import * from MvErrorDefine_const import * from CameraParams_header import * # 自定义模块导入 from CamOperation_class import CameraOperation from PyUICBasicDemo import Ui_MainWindow # 如果使用 UI 文件生成 # 图像处理库导入 try: from skimage.feature import local_binary_pattern from skimage import exposure except ImportError: # 提供替代方案或错误处理 pass # 配置日志系统 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 = [] # 检测历史记录 sample_features = {} # 存储样本特征数据 config_file = "detection_config.json" # 配置文件路径 # 加载配置文件 def load_config(): global config_file if os.path.exists(config_file): try: with open(config_file, 'r') as f: return json.load(f) except Exception as e: logging.error(f"加载配置文件失败: {str(e)}") return {} return {} # 保存配置文件 def save_config(config): global config_file try: with open(config_file, 'w') as f: json.dump(config, f, indent=4) logging.info("配置文件已保存") except Exception as e: logging.error(f"保存配置文件失败: {str(e)}") # 初始化配置 default_config = { "threshold": 0.05, "min_defect_area": 50, "max_defect_area": 5000, "texture_analysis": True, "color_sensitivity": 0.8, "edge_detection": True, "adaptive_threshold": True, "multi_scale_levels": 3, "feature_extraction": "histogram", # histogram, lbp, sift, orb "defect_classification": True } app_config = {**default_config, **load_config()} # 帧监控线程 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 # ======================== 增强版布料印花检测算法 ======================== def extract_features(image): """ 提取图像的多尺度特征 :param image: 输入图像 (灰度或彩色) :return: 特征字典 """ features = {} # 确保图像是灰度图 if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image # 1. 直方图特征 hist = cv2.calcHist([gray], [0], None, [256], [0, 256]) hist = cv2.normalize(hist, hist).flatten() features['histogram'] = hist.tolist() # 2. LBP纹理特征 (局部二值模式) radius = 3 n_points = 8 * radius lbp = local_binary_pattern(gray, n_points, radius, method='uniform') lbp_hist, _ = np.histogram(lbp.ravel(), bins=np.arange(0, n_points + 3), range=(0, n_points + 2)) lbp_hist = lbp_hist.astype("float") lbp_hist /= (lbp_hist.sum() + 1e-7) # 归一化 features['lbp'] = lbp_hist.tolist() # 3. 颜色特征 (如果是彩色图像) if len(image.shape) == 3: # 在HSV空间中计算颜色直方图 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) h_hist = cv2.calcHist([hsv], [0], None, [180], [0, 180]) s_hist = cv2.calcHist([hsv], [1], None, [256], [0, 256]) v_hist = cv2.calcHist([hsv], [2], None, [256], [0, 256]) h_hist = cv2.normalize(h_hist, h_hist).flatten() s_hist = cv2.normalize(s_hist, s_hist).flatten() v_hist = cv2.normalize(v_hist, v_hist).flatten() features['h_hist'] = h_hist.tolist() features['s_hist'] = s_hist.tolist() features['v_hist'] = v_hist.tolist() # 4. 边缘特征 edges = cv2.Canny(gray, 100, 200) edge_density = np.sum(edges > 0) / (edges.shape[0] * edges.shape[1]) features['edge_density'] = edge_density # 5. 多尺度分析 scales = [1.0, 0.5, 0.25] scale_features = [] for scale in scales: scaled = cv2.resize(gray, None, fx=scale, fy=scale) # 计算小波变换 (近似) coeffs = cv2.dct(np.float32(scaled)/255.0) coeffs_flat = coeffs[:10, :10].flatten() scale_features.extend(coeffs_flat.tolist()) features['multi_scale'] = scale_features return features def compute_feature_distance(features1, features2): """ 计算两个特征集之间的距离 :param features1: 特征集1 :param features2: 特征集2 :return: 距离分数 (0-1, 0表示完全相同) """ # 直方图距离 (卡方距离) hist_dist = distance.chisquare(features1['histogram'], features2['histogram'])[0] / 1000 # LBP距离 (巴氏距离) lbp_dist = distance.bhattacharyya(features1['lbp'], features2['lbp']) # 颜色距离 (如果存在) color_dist = 0 if 'h_hist' in features1 and 'h_hist' in features2: h_dist = distance.chisquare(features1['h_hist'], features2['h_hist'])[0] / 1000 s_dist = distance.chisquare(features1['s_hist'], features2['s_hist'])[0] / 1000 v_dist = distance.chisquare(features1['v_hist'], features2['v_hist'])[0] / 1000 color_dist = (h_dist + s_dist + v_dist) / 3 # 边缘密度差异 edge_dist = abs(features1['edge_density'] - features2['edge_density']) # 多尺度特征差异 (欧氏距离) scale_dist = distance.euclidean(features1['multi_scale'], features2['multi_scale']) / 100 # 加权组合距离 weights = { 'hist': 0.3, 'lbp': 0.3, 'color': 0.2, 'edge': 0.1, 'scale': 0.1 } total_dist = ( weights['hist'] * hist_dist + weights['lbp'] * lbp_dist + weights['color'] * color_dist + weights['edge'] * edge_dist + weights['scale'] * scale_dist ) # 归一化到0-1范围 total_dist = min(1.0, max(0.0, total_dist)) return total_dist def adaptive_threshold_diff(sample_gray, test_gray): """ 自适应阈值差异检测 :param sample_gray: 样本灰度图像 :param test_gray: 测试灰度图像 :return: 二值差异图像 """ # 计算绝对差异 diff = cv2.absdiff(sample_gray, test_gray) # 自适应阈值 block_size = max(11, min(sample_gray.shape[0] // 20, 51)) adaptive_thresh = cv2.adaptiveThreshold( diff, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, block_size, 5 ) # 形态学操作增强缺陷区域 kernel = np.ones((3, 3), np.uint8) enhanced = cv2.morphologyEx(adaptive_thresh, cv2.MORPH_CLOSE, kernel) enhanced = cv2.morphologyEx(enhanced, cv2.MORPH_OPEN, kernel) return enhanced def detect_defects(sample_gray, test_gray, diff_map): """ 检测并分类缺陷 :param sample_gray: 样本灰度图像 :param test_gray: 测试灰度图像 :param diff_map: 差异图 :return: 缺陷列表, 标记图像 """ # 查找连通区域 num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(diff_map, connectivity=8) defects = [] marked_image = cv2.cvtColor(test_gray, cv2.COLOR_GRAY2BGR) # 最小和最大缺陷面积 min_area = app_config.get("min_defect_area", 50) max_area = app_config.get("max_defect_area", 5000) for i in range(1, num_labels): # 跳过背景 area = stats[i, cv2.CC_STAT_AREA] # 过滤太小或太大的区域 if area < min_area or area > max_area: continue # 获取缺陷区域 x, y, w, h = stats[i, cv2.CC_STAT_LEFT], stats[i, cv2.CC_STAT_TOP], stats[i, cv2.CC_STAT_WIDTH], stats[i, cv2.CC_STAT_HEIGHT] defect_roi = diff_map[y:y+h, x:x+w] # 计算缺陷特征 sample_roi = sample_gray[y:y+h, x:x+w] test_roi = test_gray[y:y+h, x:x+w] # 缺陷类型分类 defect_type = classify_defect(sample_roi, test_roi, defect_roi) # 保存缺陷信息 defects.append({ "type": defect_type, "area": area, "location": (x, y, w, h), "centroid": (int(centroids[i][0]), int(centroids[i][1])) }) # 在标记图像上绘制 color = (0, 0, 255) # 默认红色 if defect_type == "color": color = (0, 165, 255) # 橙色 elif defect_type == "texture": color = (0, 255, 255) # 黄色 elif defect_type == "missing": color = (255, 0, 0) # 蓝色 elif defect_type == "stain": color = (0, 255, 0) # 绿色 cv2.rectangle(marked_image, (x, y), (x+w, y+h), color, 2) cv2.putText(marked_image, f"{defect_type}", (x, y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) return defects, marked_image def classify_defect(sample_roi, test_roi, defect_mask): """ 分类缺陷类型 :param sample_roi: 样本区域 :param test_roi: 测试区域 :param defect_mask: 缺陷掩码 :return: 缺陷类型 (color, texture, missing, stain) """ # 计算颜色差异 color_diff = np.mean(np.abs(sample_roi.astype(np.float32) - test_roi.astype(np.float32))) # 计算纹理差异 (使用LBP) radius = 2 n_points = 8 * radius lbp_sample = local_binary_pattern(sample_roi, n_points, radius, method='uniform') lbp_test = local_binary_pattern(test_roi, n_points, radius, method='uniform') hist_sample, _ = np.histogram(lbp_sample.ravel(), bins=np.arange(0, n_points + 3), range=(0, n_points + 2)) hist_test, _ = np.histogram(lbp_test.ravel(), bins=np.arange(0, n_points + 3), range=(0, n_points + 2)) hist_sample = hist_sample.astype("float") hist_test = hist_test.astype("float") hist_sample /= (hist_sample.sum() + 1e-7) hist_test /= (hist_test.sum() + 1e-7) texture_diff = distance.chisquare(hist_sample, hist_test)[0] # 计算边缘密度差异 edges_sample = cv2.Canny(sample_roi, 50, 150) edges_test = cv2.Canny(test_roi, 50, 150) edge_density_sample = np.sum(edges_sample > 0) / edges_sample.size edge_density_test = np.sum(edges_test > 0) / edges_test.size edge_diff = abs(edge_density_sample - edge_density_test) # 根据特征分类 if color_diff > 30 and texture_diff < 10: return "color" # 颜色缺陷 elif texture_diff > 15 and color_diff < 20: return "texture" # 纹理缺陷 elif edge_diff > 0.1 and np.mean(test_roi[defect_mask > 0]) < np.mean(sample_roi[defect_mask > 0]) - 20: return "missing" # 缺失图案 else: return "stain" # 污渍 def check_print_quality(sample_image_path, test_image, threshold=None): """ 增强版布料印花检测算法 :param sample_image_path: 合格样本图像路径 :param test_image: 内存中的测试图像 (numpy数组) :param threshold: 差异阈值 :return: 是否合格, 差异值, 标记图像, 缺陷列表 """ global sample_features, app_config # 使用配置中的阈值 if threshold is None: threshold = app_config.get("threshold", 0.05) # 读取样本图像 try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_COLOR) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None, None # 确保测试图像是彩色 if len(test_image.shape) == 2: # 如果是灰度图像 test_image = cv2.cvtColor(test_image, cv2.COLOR_GRAY2BGR) # 确保两个图像大小一致 try: test_image = cv2.resize(test_image, (sample_image.shape[1], sample_image.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None, None # 特征提取和比较 if sample_image_path not in sample_features: logging.info(f"提取样本特征: {sample_image_path}") sample_features[sample_image_path] = extract_features(sample_image) test_features = extract_features(test_image) feature_diff = compute_feature_distance(sample_features[sample_image_path], test_features) # 转换为灰度图像进行像素级比较 sample_gray = cv2.cvtColor(sample_image, cv2.COLOR_BGR2GRAY) test_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) # 自适应阈值差异检测 diff_map = adaptive_threshold_diff(sample_gray, test_gray) # 计算差异比例 diff_pixels = np.count_nonzero(diff_map) total_pixels = sample_gray.size pixel_diff_ratio = diff_pixels / total_pixels # 综合差异分数 combined_diff = 0.7 * feature_diff + 0.3 * pixel_diff_ratio # 检测和分类缺陷 defects, marked_image = detect_defects(sample_gray, test_gray, diff_map) # 判断是否合格 is_qualified = combined_diff <= threshold and len(defects) == 0 return is_qualified, combined_diff, marked_image, defects # ======================== UI更新函数 ======================== def update_diff_display(diff_ratio, is_qualified, defects): """ 更新差异度显示控件 """ # 更新当前差异度显示 ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") # 显示缺陷数量 defect_count = len(defects) ui.lblDefectCount.setText(f"缺陷数量: {defect_count}") # 根据合格状态设置颜色 if is_qualified: ui.lblDiffStatus.setText("状态: 合格") ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") ui.lblDefectCount.setStyleSheet("color: green;") else: ui.lblDiffStatus.setText("状态: 不合格") ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") ui.lblDefectCount.setStyleSheet("color: red;") def update_diff_threshold(value): """ 当滑块值改变时更新阈值显示 """ global app_config app_config["threshold"] = value / 100.0 ui.lblDiffValue.setText(f"{value}%") save_config(app_config) def update_min_defect_area(value): """ 更新最小缺陷面积阈值 """ global app_config app_config["min_defect_area"] = value ui.lblMinAreaValue.setText(f"{value} px²") save_config(app_config) def update_max_defect_area(value): """ 更新最大缺陷面积阈值 """ global app_config app_config["max_defect_area"] = value ui.lblMaxAreaValue.setText(f"{value} px²") save_config(app_config) def toggle_texture_analysis(state): """ 切换纹理分析功能 """ global app_config app_config["texture_analysis"] = (state == Qt.Checked) save_config(app_config) def toggle_edge_detection(state): """ 切换边缘检测功能 """ global app_config app_config["edge_detection"] = (state == Qt.Checked) save_config(app_config) def toggle_adaptive_threshold(state): """ 切换自适应阈值功能 """ global app_config app_config["adaptive_threshold"] = (state == Qt.Checked) save_config(app_config) def update_color_sensitivity(value): """ 更新颜色敏感度 """ global app_config app_config["color_sensitivity"] = value / 100.0 ui.lblColorSensitivityValue.setText(f"{value}%") save_config(app_config) # 布料印花检测功能(增强版) def check_print(): global isGrabbing, obj_cam_operation, current_sample_path, detection_history, app_config 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 = app_config.get("threshold", 0.05) logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(50) # 6. 执行检测 is_qualified, diff_ratio, marked_image, defects = check_print_quality( current_sample_path, test_image, threshold=diff_threshold ) progress.setValue(70) # 检查返回结果是否有效 if is_qualified is None: logging.error("检测函数返回无效结果") QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}, 缺陷数={len(defects)}") progress.setValue(90) # 7. 更新UI update_diff_display(diff_ratio, is_qualified, defects) # 显示详细结果 result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n" result_text += f"综合差异度: {diff_ratio*100:.2f}%\n" result_text += f"缺陷数量: {len(defects)}\n" result_text += f"阈值: {diff_threshold*100:.2f}%" if not is_qualified and defects: result_text += "\n\n缺陷详情:" for i, defect in enumerate(defects[:3]): # 最多显示3个主要缺陷 result_text += f"\n{i+1}. {defect['type']} (大小: {defect['area']}像素)" 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, 'defects': defects, 'threshold': diff_threshold } 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 save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path, sample_features 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() # 提取并保存样本特征 sample_img = cv2.imread(file_path) if sample_img is not None: sample_features[file_path] = extract_features(sample_img) logging.info(f"样本特征已提取并保存: {file_path}") # 保存当前目录 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("无法加载图像") # 显示特征信息 if current_sample_path in sample_features: features = sample_features[current_sample_path] edge_density = features.get('edge_density', 0) feature_info = f"边缘密度: {edge_density:.4f}\n特征点: {len(features.get('multi_scale', []))}" cv2.putText(sample_img, feature_info, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) 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}%" defects = len(result.get('defects', [])) ui.cbHistory.addItem(f"[{timestamp}] {status} - 差异: {ratio} - 缺陷: {defects}") # ... [其余原有函数保持不变,如设备枚举、相机操作等] ... if __name__ == "__main__": # ... [原有初始化代码] ... # 初始化UI app = QApplication(sys.argv) mainWindow = QMainWindow() ui = Ui_MainWindow() ui.setupUi(mainWindow) # 扩大主窗口尺寸 mainWindow.resize(1400, 900) # 更宽的界面 # 创建工具栏 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(350) 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(int(app_config.get("threshold", 0.05) * 100)) ui.lblDiffValue = QLabel(f"{ui.sliderDiffThreshold.value()}%") # 当前差异度显示 ui.lblCurrentDiff = QLabel("当前差异度: -") ui.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") # 缺陷数量显示 ui.lblDefectCount = QLabel("缺陷数量: -") ui.lblDefectCount.setStyleSheet("font-size: 14px;") # 差异度状态指示器 ui.lblDiffStatus = QLabel("状态: 未检测") ui.lblDiffStatus.setStyleSheet("font-size: 12px;") # 添加分隔线 diff_layout.addWidget(QLabel(" ")) diff_layout.addWidget(QLabel("缺陷检测参数")) # 最小缺陷面积 ui.lblMinArea = QLabel("最小缺陷面积:") ui.sliderMinArea = QSlider(Qt.Horizontal) ui.sliderMinArea.setRange(10, 500) ui.sliderMinArea.setValue(app_config.get("min_defect_area", 50)) ui.lblMinAreaValue = QLabel(f"{ui.sliderMinArea.value()} px²") # 最大缺陷面积 ui.lblMaxArea = QLabel("最大缺陷面积:") ui.sliderMaxArea = QSlider(Qt.Horizontal) ui.sliderMaxArea.setRange(1000, 10000) ui.sliderMaxArea.setValue(app_config.get("max_defect_area", 5000)) ui.lblMaxAreaValue = QLabel(f"{ui.sliderMaxArea.value()} px²") # 颜色敏感度 ui.lblColorSensitivity = QLabel("颜色敏感度:") ui.sliderColorSensitivity = QSlider(Qt.Horizontal) ui.sliderColorSensitivity.setRange(0, 100) ui.sliderColorSensitivity.setValue(int(app_config.get("color_sensitivity", 0.8) * 100)) ui.lblColorSensitivityValue = QLabel(f"{ui.sliderColorSensitivity.value()}%") # 添加分隔线 diff_layout.addWidget(QLabel(" ")) diff_layout.addWidget(QLabel("高级分析选项")) # 纹理分析开关 ui.chkTextureAnalysis = QCheckBox("启用纹理分析") ui.chkTextureAnalysis.setChecked(app_config.get("texture_analysis", True)) # 边缘检测开关 ui.chkEdgeDetection = QCheckBox("启用边缘检测") ui.chkEdgeDetection.setChecked(app_config.get("edge_detection", True)) # 自适应阈值开关 ui.chkAdaptiveThreshold = QCheckBox("使用自适应阈值") ui.chkAdaptiveThreshold.setChecked(app_config.get("adaptive_threshold", True)) # 布局控件 diff_layout.addWidget(ui.lblDiffThreshold) diff_layout.addWidget(ui.sliderDiffThreshold) diff_layout.addWidget(ui.lblDiffValue) diff_layout.addWidget(ui.lblCurrentDiff) diff_layout.addWidget(ui.lblDefectCount) diff_layout.addWidget(ui.lblDiffStatus) diff_layout.addWidget(ui.lblMinArea) diff_layout.addWidget(ui.sliderMinArea) diff_layout.addWidget(ui.lblMinAreaValue) diff_layout.addWidget(ui.lblMaxArea) diff_layout.addWidget(ui.sliderMaxArea) diff_layout.addWidget(ui.lblMaxAreaValue) diff_layout.addWidget(ui.lblColorSensitivity) diff_layout.addWidget(ui.sliderColorSensitivity) diff_layout.addWidget(ui.lblColorSensitivityValue) diff_layout.addWidget(ui.chkTextureAnalysis) diff_layout.addWidget(ui.chkEdgeDetection) diff_layout.addWidget(ui.chkAdaptiveThreshold) # 添加拉伸项使控件靠上 diff_layout.addStretch(1) # 创建停靠窗口 dock = QDockWidget("检测参数面板", mainWindow) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) mainWindow.addDockWidget(Qt.RightDockWidgetArea, dock) # === 连接信号 === # 差异度阈值滑块 ui.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) # 最小缺陷面积滑块 ui.sliderMinArea.valueChanged.connect(update_min_defect_area) # 最大缺陷面积滑块 ui.sliderMaxArea.valueChanged.connect(update_max_defect_area) # 颜色敏感度滑块 ui.sliderColorSensitivity.valueChanged.connect(update_color_sensitivity) # 分析选项复选框 ui.chkTextureAnalysis.stateChanged.connect(toggle_texture_analysis) ui.chkEdgeDetection.stateChanged.connect(toggle_edge_detection) ui.chkAdaptiveThreshold.stateChanged.connect(toggle_adaptive_threshold) # 绑定按钮事件 ui.bnCheckPrint.clicked.connect(check_print) ui.bnSaveSample.clicked.connect(save_sample_image) ui.bnPreviewSample.clicked.connect(preview_sample) # ... [其余原有绑定代码] ... # 显示主窗口 mainWindow.show() # 执行应用 app.exec_() # 关闭设备 close_device() # ch:反初始化SDK | en: finalize SDK MvCamera.MV_CC_Finalize() sys.exit() 这个程序运行后出现了查找不到打不开相机同时原有的差异度检查还有新增的功能都看不到了
07-09
# -*- 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) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\BasicDemo") from MvCameraControl_class import * from MvErrorDefine_const import * from CameraParams_header import * from PyUICBasicDemo import Ui_MainWindow import ctypes from datetime import datetime import logging import platform # 配置日志系统 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 = [] # 检测历史记录 # 帧监控线程 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 # 布料印花检测函数(修复版) def check_print_quality(sample_image_path, test_image, threshold=0.05): """ 检测布料印花是否合格,直接使用内存中的测试图像 :param sample_image_path: 合格样本图像路径 :param test_image: 内存中的测试图像 (numpy数组) :param threshold: 差异阈值 :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 except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None # 确保测试图像是灰度图 if len(test_image.shape) == 3: # 如果是彩色图像 test_image = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) # 确保两个图像大小一致 try: test_image = cv2.resize(test_image, (sample_image.shape[1], sample_image.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None # 计算差异 diff = cv2.absdiff(sample_image, test_image) # 二值化差异 _, thresholded = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY) # 计算差异比例 diff_pixels = np.count_nonzero(thresholded) total_pixels = sample_image.size diff_ratio = diff_pixels / total_pixels # 判断是否合格 is_qualified = diff_ratio <= threshold # 创建标记图像(红色标记差异区域) marked_image = cv2.cvtColor(test_image, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] # 红色标记 return is_qualified, diff_ratio, marked_image # 更新检测结果显示 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 check_print(): 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 = check_print_quality( current_sample_path, test_image, threshold=diff_threshold ) 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 } 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 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 # ch:初始化SDK | en: initialize SDK MvCamera.MV_CC_Initialize() global deviceList deviceList = MV_CC_DEVICE_INFO_LIST() global cam cam = MvCamera() global nSelCamIndex nSelCamIndex = 0 global obj_cam_operation obj_cam_operation = 0 global isOpen isOpen = False global isGrabbing isGrabbing = False global isCalibMode # 是否是标定模式(获取原始图像) isCalibMode = True global frame_monitor_thread # 绑定下拉列表至设备信息索引 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.chUser极DefinedName) 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)) if __name__ == "__main__": # ch:初始化SDK | en: initialize SDK MvCamera.MV_CC_Initialize() deviceList = MV_CC_DEVICE_INFO_LIST() cam = MvCamera() nSelCamIndex = 0 obj_cam_operation = 0 isOpen = False isGrabbing = False isCalibMode = True # 是否是标定模式(获取原始图像) frame_monitor_thread = None # 初始化UI app = QApplication(sys.argv) mainWindow = QMainWindow() ui = 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) # 添加拉伸项使控件靠上 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(check_print) ui.bnSaveSample.clicked.connect(save_sample_image) ui.bnPreviewSample.clicked.connect(preview_sample) # 绑定其他按钮事件 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() # ch:反初始化SDK | en: finalize SDK MvCamera.MV_CC_Finalize() sys.exit() 在这个代码的基础上增加一个可以让传感器传入信号启动质量检测的功能,再优化一下这个代码中的检测算法
最新发布
07-09
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值