draw_shapes.h

本文介绍了Windows应用程序中消息处理函数WndProc和About的声明方式,并展示了如何通过预处理器指令#define来定义资源标识符,如IDM_EXIT、IDM_TEST和IDM_ABOUT等。

  name="google_ads_frame" marginwidth="0" marginheight="0" src="http://pagead2.googlesyndication.com/pagead/ads?client=ca-pub-5572165936844014&dt=1194442938015&lmt=1194190197&format=336x280_as&output=html&correlator=1194442937843&url=file%3A%2F%2F%2FC%3A%2FDocuments%2520and%2520Settings%2Flhh1%2F%E6%A1%8C%E9%9D%A2%2FCLanguage.htm&color_bg=FFFFFF&color_text=000000&color_link=000000&color_url=FFFFFF&color_border=FFFFFF&ad_type=text&ga_vid=583001034.1194442938&ga_sid=1194442938&ga_hid=1942779085&flash=9&u_h=768&u_w=1024&u_ah=740&u_aw=1024&u_cd=32&u_tz=480&u_java=true" frameborder="0" width="336" scrolling="no" height="280" allowtransparency="allowtransparency"> #define IDM_EXIT           100
#define IDM_TEST           200
#define IDM_ABOUT          301

LRESULT CALLBACK WndProc  (HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About    (HWND, UINT, WPARAM, LPARAM);

import time import os import sys from media.sensor import * from media.display import * from media.media import * from time import ticks_ms, sleep_ms import gc import math # 全局配置 DISPLAY_MODE = "LCD" # "VIRT"或"LCD" PICTURE_WIDTH = 640 PICTURE_HEIGHT = 480 DEBUG_MODE = False # 调试模式开关 # 测量参数 CALIBRATED_DISTANCE = 150.0 # 标定距离 (cm) CALIBRATED_A4_HEIGHT = 0.0 # 标定时的A4像素高度 A4_ACTUAL_HEIGHT = 29.7 # A4纸实际高度 (cm) A4_ACTUAL_WIDTH = 21.0 # A4纸实际宽度 (cm) BLACK_BORDER_ACTUAL = 2.0 # 黑框实际宽度 (cm) # 状态变量 measurement_count = 0 selected_id = 1 max_power = 0.0 power_sum = 0.0 power_count = 0 start_time = 0 is_measuring = False rotation_angle = 0 # 测量结果 D = 0.0 # 距离 (cm) x = 0.0 # 图形尺寸 (cm) Is = 0.0 # 电流 (A) P = 0.0 # 功耗 (W) Pmax = 0.0 # 最大功耗 (W) # 目标物类型 TARGET_TYPES = { "circle": 0, "triangle": 1, "square": 2, "multi_squares": 3, "numbered_squares": 4, "rotated_square": 5 } current_target = "" def init_sensor(): """初始化摄像头传感器""" sensor = Sensor(id=2) sensor.reset() sensor.set_framesize(width=PICTURE_WIDTH, height=PICTURE_HEIGHT, chn=CAM_CHN_ID_0) sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_0) return sensor def init_display(): """初始化显示器""" if DISPLAY_MODE == "VIRT": DISPLAY_WIDTH = ALIGN_UP(1920, 16) DISPLAY_HEIGHT = 1080 Display.init(Display.VIRT, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, fps=30) elif DISPLAY_MODE == "LCD": DISPLAY_WIDTH = 640 DISPLAY_HEIGHT = 480 Display.init(Display.ST7701, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True) def calibrate_camera(sensor): """摄像头标定函数""" global CALIBRATED_A4_HEIGHT print("开始摄像头标定...") img = sensor.snapshot(chn=CAM_CHN_ID_0) rects = img.find_rects(thresholds=50) if rects: outer_rect = max(rects, key=lambda r: r.w() * r.h()) CALIBRATED_A4_HEIGHT = outer_rect.h() print(f"标定完成! A4像素高度: {CALIBRATED_A4_HEIGHT}") else: print("标定失败! 未检测到A4纸") def calculate_distance(a4_height_px): """计算目标物距离""" if CALIBRATED_A4_HEIGHT == 0: return 0.0 # 使用相似三角形原理计算距离 return (A4_ACTUAL_HEIGHT * CALIBRATED_DISTANCE * CALIBRATED_A4_HEIGHT) / (a4_height_px * CALIBRATED_A4_HEIGHT) def detect_a4_paper(gray_img): """检测A4纸位置""" rects = gray_img.find_rects(thresholds=50) if rects: return max(rects, key=lambda r: r.w() * r.h()) return None def detect_shapes(roi_img): """检测几何图形""" # 1. 尝试检测圆形 circles = roi_img.find_circles(threshold=500, r_min=8, r_max=600) if circles: largest_circle = max(circles, key=lambda c: c.r()) return "circle", largest_circle.r() * 2 # 返回直径 # 2. 尝试检测矩形/正方形 rects = roi_img.find_rects(thresholds=200) if rects: # 过滤掉过小的矩形 valid_rects = [r for r in rects if r.w() > 20 and r.h() > 20] if valid_rects: # 如果是基本目标物,取最大的矩形 if current_target in ["circle", "triangle", "square"]: largest_rect = max(valid_rects, key=lambda r: r.w() * r.h()) return "square", min(largest_rect.w(), largest_rect.h()) # 取较小边作为边长 # 如果是多正方形目标物 elif current_target == "multi_squares": min_area_rect = min(valid_rects, key=lambda r: r.w() * r.h()) return "square", min(min_area_rect.w(), min_area_rect.h()) # 如果是带编号的正方形 elif current_target == "numbered_squares": # 在实际应用中应添加OCR数字识别 # 这里简化为返回第一个矩形 first_rect = valid_rects[0] return "square", min(first_rect.w(), first_rect.h()) # 3. 尝试检测三角形 triangles = [] for rect in rects: if 30 < abs(rect.rotation()) < 60 or 120 < abs(rect.rotation()) < 150: triangles.append(rect) if triangles: largest_triangle = max(triangles, key=lambda t: t.w() * t.h()) return "triangle", (largest_triangle.w() + largest_triangle.h()) / 2 return "unknown", 0 def measure_target(sensor): """执行测量任务""" global D, x, Is, P, Pmax, is_measuring, start_time is_measuring = True start_time = ticks_ms() # 捕获图像 img = sensor.snapshot(chn=CAM_CHN_ID_0) gray_img = img.to_grayscale(copy=True) # 检测A4纸 a4_rect = detect_a4_paper(gray_img) if not a4_rect: print("未检测到目标物!") is_measuring = False return # 计算距离 a4_height_px = a4_rect.h() D = calculate_distance(a4_height_px) pixel_size = A4_ACTUAL_HEIGHT / a4_height_px # cm/像素 # 提取ROI区域 (A4纸内部) roi = img.crop(a4_rect.x(), a4_rect.y(), a4_rect.w(), a4_rect.h()) roi_gray = roi.to_grayscale(copy=True) # 检测几何图形 shape_type, shape_size_px = detect_shapes(roi_gray) # 计算实际尺寸 x = shape_size_px * pixel_size # 模拟电流测量 (实际应用中应读取ADC) Is = 0.25 + (measurement_count % 10) * 0.01 P = 5.0 * Is # P = U*I # 更新功耗统计 if P > Pmax: Pmax = P # 绘制检测结果 img.draw_rectangle(a4_rect.rect(), color=(0, 255, 0), thickness=2) img.draw_string_advanced (a4_rect.x(), a4_rect.y() - 20, f"{current_target}", x_scal=1, y_scal=1, color=(255, 0, 0)) is_measuring = False print(f"测量完成! D: {D:.1f}cm, x: {x:.1f}cm, I: {Is:.2f}A, P: {P:.2f}W") def draw_info(img): """在图像上绘制测量信息""" # 绘制测量结果 img.draw_string_advanced (10, 10, f"D: {D:.1f} cm", x_scal=1, y_scal=1, color=(255, 0, 0)) img.draw_string_advanced (10, 40, f"x: {x:.1f} cm", x_scal=1, y_scal=1, color=(0, 255, 0)) img.draw_string_advanced (10, 70, f"I: {Is:.2f} A", x_scal=1, y_scal=1, color=(0, 0, 255)) img.draw_string_advanced (10, 100, f"P: {P:.2f} W", x_scal=1, y_scal=1, color=(255, 255, 0)) img.draw_string_advanced (10, 130, f"Pmax: {Pmax:.2f} W", x_scal=1, y_scal=1, color=(255, 0, 255)) # 绘制状态信息 if is_measuring: elapsed = (ticks_ms() - start_time) // 1000 img.draw_string_advanced (10, 160, f"Measuring... {elapsed}s", color=(255, 255, 255)) # 绘制目标物信息 img.draw_string_advanced (10, 190, f"Target: {current_target}", x_scal=1, y_scal=1, color=(255, 255, 255)) if current_target == "numbered_squares": img.draw_string_advanced (10, 220, f"Selected ID: {selected_id}", x_scal=1, y_scal=1, color=(255, 255, 255)) def handle_buttons(): """处理按钮输入 (模拟)""" global measurement_count, selected_id, current_target # 在实际应用中应读取GPIO # 这里模拟按钮按下 measurement_count += 1 # 基本目标物序列 basic_targets = ["circle", "triangle", "square"] # 发挥目标物序列 advanced_targets = ["multi_squares", "numbered_squares", "rotated_square"] # 选择目标物类型 if measurement_count <= 3: current_target = basic_targets[(measurement_count - 1) % 3] elif measurement_count <= 6: current_target = advanced_targets[(measurement_count - 4) % 3] else: current_target = "square" # 默认 # 编号选择 if current_target == "numbered_squares": selected_id = (selected_id % 9) + 1 # 1-9循环 print(f"目标物: {current_target}, 编号: {selected_id}") def main(): global measurement_count, Pmax, start_time try: # 初始化系统 init_display() sensor = init_sensor() MediaManager.init() sensor.run() clock = time.clock() # 摄像头标定 calibrate_camera(sensor) print("系统初始化完成,等待测量指令...") while True: os.exitpoint() clock.tick() # 处理按钮输入 (模拟) if not is_measuring and (ticks_ms() - start_time) > 3000: handle_buttons() start_time = ticks_ms() # 捕获图像 img = sensor.snapshot(chn=CAM_CHN_ID_0) # 绘制测量信息 draw_info(img) # 显示图像 img.compressed_for_ide() Display.show_image(img) # 打印帧率 if DEBUG_MODE: print(f"FPS: {clock.fps():.1f}") # 内存管理 if measurement_count % 10 == 0: gc.collect() except KeyboardInterrupt: print("程序已停止") except Exception as e: print(f"发生错误: {str(e)}") finally: if 'sensor' in locals(): sensor.stop() Display.deinit() MediaManager.deinit() os.exitpoint(os.EXITPOINT_ENABLE_SLEEP) sleep_ms(100) if __name__ == "__main__": main() 根据以上代码告诉我哪里还有这个问题Not enough positional arguments! 并修改
08-03
import sys import cv2 import numpy as np from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QDialog, QLabel, QPushButton, QFileDialog, QTextEdit, QTabWidget, QMessageBox, QProgressBar, QSlider, QComboBox, QGroupBox, QGridLayout, QToolBar, QStatusBar, QDockWidget, QSplitter, QScrollArea, QMenu, QSpinBox, QDoubleSpinBox, QCheckBox, QRadioButton, QButtonGroup # 添加缺失的导入 ) from PySide6.QtGui import ( QAction, QPixmap, QImage, QPainter, QPen, QColor, QIcon, QKeySequence, QTransform, QCursor ) from PySide6.QtCore import Qt, QThread, Signal, QPoint, QSize, QRect import matplotlib matplotlib.use('Agg') # 使用Agg后端,不显示图形窗口 import matplotlib.pyplot as plt from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure import os import math from scipy import ndimage try: from skimage.feature import graycomatrix, graycoprops except ImportError: from skimage.feature import greycomatrix as graycomatrix, greycoprops as graycoprops plt.rcParams["font.family"] = ["SimHei"] # 仅保留 SimHei(黑体) plt.rcParams["axes.unicode_minus"] = False class ImageProcessingThread(QThread): """图像处理的工作线程,避免界面卡顿""" finished = Signal(object) def __init__(self, function, *args): super().__init__() self.function = function self.args = args def run(self): result = self.function(*self.args) self.finished.emit(result) class ImageViewer(QWidget): """图像显示组件,支持缩放和平移""" def __init__(self, parent=None): super().__init__(parent) self.image = QImage() self.scale_factor = 1.0 self.dragging = False self.last_pos = QPoint() self.setMouseTracking(True) layout = QVBoxLayout(self) self.label = QLabel(self) self.label.setAlignment(Qt.AlignCenter) self.label.setMinimumSize(1, 1) # 允许缩小 layout.addWidget(self.label) def set_image(self, image): self.image = image self.update_pixmap() def update_pixmap(self): if not self.image.isNull(): scaled_pixmap = QPixmap.fromImage(self.image).scaled( self.image.width() * self.scale_factor, self.image.height() * self.scale_factor, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.label.setPixmap(scaled_pixmap) def wheelEvent(self, event): """鼠标滚轮缩放""" delta = event.angleDelta().y() if delta > 0: self.scale_factor *= 1.1 else: self.scale_factor *= 0.9 self.update_pixmap() def mousePressEvent(self, event): """鼠标按下开始拖动""" if event.button() == Qt.LeftButton: self.dragging = True self.last_pos = event.position().toPoint() # 修改后 def mouseMoveEvent(self, event): """鼠标拖动图像""" if self.dragging: delta = event.pos() - self.last_pos scroll_bar = self.parent().horizontalScrollBar() scroll_bar.setValue(scroll_bar.value() - delta.x()) scroll_bar = self.parent().verticalScrollBar() scroll_bar.setValue(scroll_bar.value() - delta.y()) self.last_pos = event.pos() def mouseReleaseEvent(self, event): """鼠标释放结束拖动""" if event.button() == Qt.LeftButton: self.dragging = False def resizeEvent(self, event): """窗口大小变化时更新图像显示""" self.update_pixmap() super().resizeEvent(event) class HistogramWidget(QWidget): """直方图显示组件""" def __init__(self, parent=None): super().__init__(parent) self.figure = Figure(figsize=(5, 3), dpi=100) self.canvas = FigureCanvas(self.figure) layout = QVBoxLayout(self) layout.addWidget(self.canvas) self.axes = self.figure.add_subplot(111) def update_histogram(self, image): """更新直方图显示""" self.axes.clear() if image.ndim == 3: # 彩色图像 colors = ('b', 'g', 'r') for i, color in enumerate(colors): hist = cv2.calcHist([image], [i], None, [256], [0, 256]) self.axes.plot(hist, color=color) else: # 灰度图像 hist = cv2.calcHist([image], [0], None, [256], [0, 256]) self.axes.plot(hist, color='black') self.axes.set_xlim([0, 256]) self.axes.set_title('图像直方图') self.axes.set_xlabel('像素值') self.axes.set_ylabel('像素数量') self.figure.tight_layout() self.canvas.draw() class GLCMWidget(QWidget): """灰度共生矩阵(GLCM)特征显示组件""" def __init__(self, parent=None): super().__init__(parent) layout = QVBoxLayout(self) self.text_edit = QTextEdit() self.text_edit.setReadOnly(True) layout.addWidget(self.text_edit) def update_glcm(self, image): """更新GLCM特征显示""" if image.ndim == 3: image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 量化到16级灰度以减少计算量 image_quantized = (image // 16).astype(np.uint8) # 计算GLCM矩阵 (距离=1, 角度=0, 45, 90, 135度) glcm = graycomatrix(image_quantized, distances=[1], angles=[0, np.pi / 4, np.pi / 2, 3 * np.pi / 4], levels=16, symmetric=True, normed=True) # 计算各种特征 contrast = graycoprops(glcm, 'contrast')[0] dissimilarity = graycoprops(glcm, 'dissimilarity')[0] homogeneity = graycoprops(glcm, 'homogeneity')[0] energy = graycoprops(glcm, 'energy')[0] correlation = graycoprops(glcm, 'correlation')[0] asm = graycoprops(glcm, 'ASM')[0] # 显示结果 result = "灰度共生矩阵(GLCM)特征:\n\n" result += f"对比度: {contrast}\n" result += f"相异性: {dissimilarity}\n" result += f"同质性: {homogeneity}\n" result += f"能量: {energy}\n" result += f"相关性: {correlation}\n" result += f"角二阶矩: {asm}\n" self.text_edit.setText(result) class ProcessingDialog(QDialog): def __init__(self, title, parent=None): super().__init__(parent) self.setWindowTitle(title) self.layout = QGridLayout(self) self.setLayout(self.layout) self.row = 0 def add_slider(self, label_text, min_val, max_val, default_val, step=1, callback=None): """添加滑块控件""" label = QLabel(label_text) slider = QSlider(Qt.Horizontal) slider.setMinimum(min_val) slider.setMaximum(max_val) slider.setValue(default_val) slider.setSingleStep(step) spinbox = QSpinBox() # 修正拼写错误 spinbox.setMinimum(min_val) spinbox.setMaximum(max_val) spinbox.setValue(default_val) spinbox.setSingleStep(step) # 同步滑块和数值框 slider.valueChanged.connect(spinbox.setValue) spinbox.valueChanged.connect(slider.setValue) if callback: slider.valueChanged.connect(callback) self.layout.addWidget(label, self.row, 0) self.layout.addWidget(slider, self.row, 1) self.layout.addWidget(spinbox, self.row, 2) self.row += 1 return slider, spinbox def add_double_slider(self, label_text, min_val, max_val, default_val, step=0.1, decimals=1, callback=None): """添加浮点数滑块控件""" label = QLabel(label_text) slider = QSlider(Qt.Horizontal) slider.setMinimum(int(min_val * 10)) slider.setMaximum(int(max_val * 10)) slider.setValue(int(default_val * 10)) spinbox = QDoubleSpinBox() spinbox.setMinimum(min_val) spinbox.setMaximum(max_val) spinbox.setValue(default_val) spinbox.setSingleStep(step) spinbox.setDecimals(decimals) # 同步滑块和数值框 def update_slider(value): slider.setValue(int(value * 10)) def update_spinbox(value): spinbox.setValue(value / 10) slider.valueChanged.connect(update_spinbox) spinbox.valueChanged.connect(update_slider) if callback: slider.valueChanged.connect(lambda: callback(spinbox.value())) self.layout.addWidget(label, self.row, 0) self.layout.addWidget(slider, self.row, 1) self.layout.addWidget(spinbox, self.row, 2) self.row += 1 return slider, spinbox def add_combo_box(self, label_text, items, default_index=0, callback=None): """添加下拉选择框""" label = QLabel(label_text) combo_box = QComboBox() combo_box.addItems(items) combo_box.setCurrentIndex(default_index) if callback: combo_box.currentIndexChanged.connect(callback) self.layout.addWidget(label, self.row, 0) self.layout.addWidget(combo_box, self.row, 1, 1, 2) self.row += 1 return combo_box def add_checkbox(self, label_text, default_state=False, callback=None): """添加复选框""" checkbox = QCheckBox(label_text) checkbox.setChecked(default_state) if callback: checkbox.stateChanged.connect(callback) self.layout.addWidget(checkbox, self.row, 0, 1, 3) self.row += 1 return checkbox def add_radio_buttons(self, label_text, options, default_index=0, callback=None): """添加单选按钮组""" label = QLabel(label_text) button_group = QButtonGroup(self) layout = QHBoxLayout() for i, option in enumerate(options): radio = QRadioButton(option) if i == default_index: radio.setChecked(True) button_group.addButton(radio, i) layout.addWidget(radio) if callback: button_group.buttonClicked.connect(callback) self.layout.addWidget(label, self.row, 0) self.layout.addLayout(layout, self.row, 1, 1, 2) self.row += 1 return button_group def add_button_box(self): """添加确认和取消按钮""" button_layout = QHBoxLayout() ok_button = QPushButton("确定") cancel_button = QPushButton("取消") ok_button.clicked.connect(self.accept) cancel_button.clicked.connect(self.reject) button_layout.addStretch() button_layout.addWidget(ok_button) button_layout.addWidget(cancel_button) self.layout.addLayout(button_layout, self.row, 0, 1, 3) self.row += 1 return ok_button, cancel_button class MainWindow(QMainWindow): """主窗口类""" def __init__(self): super().__init__() self.setWindowTitle("数字图像处理系统") self.setGeometry(100, 100, 1200, 800) # 初始化变量 self.original_image = None # 原始图像 self.processed_image = None # 处理后的图像 self.current_image = None # 当前显示的图像 self.history = [] # 操作历史 self.history_index = -1 # 当前历史位置 # 创建中心部件 self.central_widget = QWidget() self.setCentralWidget(self.central_widget) # 创建主布局 self.main_layout = QHBoxLayout(self.central_widget) # 创建左侧面板 self.left_panel = QVBoxLayout() # 创建工具栏 self.create_toolbar() # 创建图像显示区域 self.create_image_viewer() # 创建右侧面板 self.right_panel = QVBoxLayout() # 创建处理历史标签页 self.create_history_tabs() # 添加分割器 self.splitter = QSplitter(Qt.Horizontal) left_widget = QWidget() left_widget.setLayout(self.left_panel) right_widget = QWidget() right_widget.setLayout(self.right_panel) self.splitter.addWidget(left_widget) self.splitter.addWidget(right_widget) self.splitter.setSizes([800, 400]) # 初始大小 self.main_layout.addWidget(self.splitter) # 创建菜单 self.create_menu() # 状态栏 self.statusBar().showMessage("就绪") def create_menu(self): """创建菜单栏""" # 文件菜单 file_menu = self.menuBar().addMenu("文件") open_action = QAction("打开", self) open_action.setShortcut(QKeySequence.Open) open_action.triggered.connect(self.open_image) file_menu.addAction(open_action) save_action = QAction("保存", self) save_action.setShortcut(QKeySequence.Save) save_action.triggered.connect(self.save_image) file_menu.addAction(save_action) save_as_action = QAction("另存为", self) save_as_action.setShortcut(QKeySequence.SaveAs) save_as_action.setShortcut("Ctrl+Shift+S") save_as_action.triggered.connect(self.save_image_as) file_menu.addAction(save_as_action) file_menu.addSeparator() exit_action = QAction("退出", self) exit_action.setShortcut(QKeySequence.Quit) exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) # 编辑菜单 edit_menu = self.menuBar().addMenu("编辑") undo_action = QAction("撤销", self) undo_action.setShortcut(QKeySequence.Undo) undo_action.triggered.connect(self.undo) edit_menu.addAction(undo_action) redo_action = QAction("重做", self) redo_action.setShortcut(QKeySequence.Redo) redo_action.triggered.connect(self.redo) edit_menu.addAction(redo_action) # 处理菜单 process_menu = self.menuBar().addMenu("图像处理") # 图像转换子菜单 convert_menu = QMenu("图像转换", self) rgb_to_gray_action = QAction("RGB转灰度", self) rgb_to_gray_action.triggered.connect(self.rgb_to_gray) convert_menu.addAction(rgb_to_gray_action) resize_action = QAction("调整分辨率", self) resize_action.triggered.connect(self.resize_image) convert_menu.addAction(resize_action) process_menu.addMenu(convert_menu) # 图像增强子菜单 enhance_menu = QMenu("图像增强", self) histogram_equalization_action = QAction("直方图均衡化", self) histogram_equalization_action.triggered.connect(self.histogram_equalization) enhance_menu.addAction(histogram_equalization_action) log_transform_action = QAction("对数变换", self) log_transform_action.triggered.connect(self.log_transform) enhance_menu.addAction(log_transform_action) power_law_action = QAction("幂律变换", self) power_law_action.triggered.connect(self.power_law_transform) enhance_menu.addAction(power_law_action) blur_menu = QMenu("平滑滤波", self) mean_blur_action = QAction("均值滤波", self) mean_blur_action.triggered.connect(lambda: self.spatial_filtering("均值滤波")) blur_menu.addAction(mean_blur_action) gaussian_blur_action = QAction("高斯滤波", self) gaussian_blur_action.triggered.connect(lambda: self.spatial_filtering("高斯滤波")) blur_menu.addAction(gaussian_blur_action) median_blur_action = QAction("中值滤波", self) median_blur_action.triggered.connect(lambda: self.spatial_filtering("中值滤波")) blur_menu.addAction(median_blur_action) enhance_menu.addMenu(blur_menu) sharpen_menu = QMenu("锐化滤波", self) sobel_action = QAction("Sobel算子", self) sobel_action.triggered.connect(lambda: self.spatial_filtering("Sobel算子")) sharpen_menu.addAction(sobel_action) prewitt_action = QAction("Prewitt算子", self) prewitt_action.triggered.connect(lambda: self.spatial_filtering("Prewitt算子")) sharpen_menu.addAction(prewitt_action) laplacian_action = QAction("Laplacian算子", self) laplacian_action.triggered.connect(lambda: self.spatial_filtering("Laplacian算子")) sharpen_menu.addAction(laplacian_action) enhance_menu.addMenu(sharpen_menu) process_menu.addMenu(enhance_menu) # 图像复原子菜单 restore_menu = QMenu("图像复原", self) motion_deblur_action = QAction("运动模糊复原", self) motion_deblur_action.triggered.connect(self.motion_deblur) restore_menu.addAction(motion_deblur_action) gaussian_noise_removal_action = QAction("高斯噪声去除", self) gaussian_noise_removal_action.triggered.connect(lambda: self.noise_removal("高斯噪声")) restore_menu.addAction(gaussian_noise_removal_action) salt_pepper_noise_removal_action = QAction("椒盐噪声去除", self) salt_pepper_noise_removal_action.triggered.connect(lambda: self.noise_removal("椒盐噪声")) restore_menu.addAction(salt_pepper_noise_removal_action) process_menu.addMenu(restore_menu) # 几何变换子菜单 geometric_menu = QMenu("几何变换", self) translate_action = QAction("平移", self) translate_action.triggered.connect(self.translate_image) geometric_menu.addAction(translate_action) rotate_action = QAction("旋转", self) rotate_action.triggered.connect(self.rotate_image) geometric_menu.addAction(rotate_action) scale_action = QAction("缩放", self) scale_action.triggered.connect(self.scale_image) geometric_menu.addAction(scale_action) flip_action = QAction("镜像", self) flip_action.triggered.connect(self.flip_image) geometric_menu.addAction(flip_action) process_menu.addMenu(geometric_menu) # 形态学处理子菜单 morphology_menu = QMenu("形态学处理", self) erosion_action = QAction("腐蚀", self) erosion_action.triggered.connect(lambda: self.morphological_operation("腐蚀")) morphology_menu.addAction(erosion_action) dilation_action = QAction("膨胀", self) dilation_action.triggered.connect(lambda: self.morphological_operation("膨胀")) morphology_menu.addAction(dilation_action) opening_action = QAction("开运算", self) opening_action.triggered.connect(lambda: self.morphological_operation("开运算")) morphology_menu.addAction(opening_action) closing_action = QAction("闭运算", self) closing_action.triggered.connect(lambda: self.morphological_operation("闭运算")) morphology_menu.addAction(closing_action) edge_extraction_action = QAction("边界提取", self) edge_extraction_action.triggered.connect(self.edge_extraction) morphology_menu.addAction(edge_extraction_action) process_menu.addMenu(morphology_menu) # 图像分割子菜单 segmentation_menu = QMenu("图像分割", self) threshold_action = QAction("阈值分割", self) threshold_action.triggered.connect(self.threshold_segmentation) segmentation_menu.addAction(threshold_action) adaptive_threshold_action = QAction("自适应阈值分割", self) adaptive_threshold_action.triggered.connect(self.adaptive_threshold_segmentation) segmentation_menu.addAction(adaptive_threshold_action) watershed_action = QAction("分水岭分割", self) watershed_action.triggered.connect(self.watershed_segmentation) segmentation_menu.addAction(watershed_action) process_menu.addMenu(segmentation_menu) # 图像描述子菜单 description_menu = QMenu("图像描述", self) hu_moments_action = QAction("计算Hu不变矩", self) hu_moments_action.triggered.connect(self.calculate_hu_moments) description_menu.addAction(hu_moments_action) glcm_action = QAction("计算灰度共生矩阵", self) glcm_action.triggered.connect(self.calculate_glcm) description_menu.addAction(glcm_action) process_menu.addMenu(description_menu) # 视图菜单 view_menu = self.menuBar().addMenu("视图") zoom_in_action = QAction("放大", self) zoom_in_action.setShortcut("Ctrl++") zoom_in_action.triggered.connect(self.zoom_in) view_menu.addAction(zoom_in_action) zoom_out_action = QAction("缩小", self) zoom_out_action.setShortcut("Ctrl+-") zoom_out_action.triggered.connect(self.zoom_out) view_menu.addAction(zoom_out_action) fit_to_window_action = QAction("适应窗口", self) fit_to_window_action.setShortcut("Ctrl+F") fit_to_window_action.triggered.connect(self.fit_to_window) view_menu.addAction(fit_to_window_action) # 帮助菜单 help_menu = self.menuBar().addMenu("帮助") about_action = QAction("关于", self) about_action.triggered.connect(self.about) help_menu.addAction(about_action) help_action = QAction("帮助", self) help_action.triggered.connect(self.show_help) help_menu.addAction(help_action) def create_toolbar(self): """创建工具栏""" toolbar = QToolBar("工具栏") self.addToolBar(toolbar) # 文件操作 open_action = QAction(QIcon.fromTheme("document-open"), "打开", self) open_action.triggered.connect(self.open_image) toolbar.addAction(open_action) save_action = QAction(QIcon.fromTheme("document-save"), "保存", self) save_action.triggered.connect(self.save_image) toolbar.addAction(save_action) toolbar.addSeparator() # 编辑操作 undo_action = QAction(QIcon.fromTheme("edit-undo"), "撤销", self) undo_action.triggered.connect(self.undo) toolbar.addAction(undo_action) redo_action = QAction(QIcon.fromTheme("edit-redo"), "重做", self) redo_action.triggered.connect(self.redo) toolbar.addAction(redo_action) toolbar.addSeparator() # 视图操作 zoom_in_action = QAction(QIcon.fromTheme("zoom-in"), "放大", self) zoom_in_action.triggered.connect(self.zoom_in) toolbar.addAction(zoom_in_action) zoom_out_action = QAction(QIcon.fromTheme("zoom-out"), "缩小", self) zoom_out_action.triggered.connect(self.zoom_out) toolbar.addAction(zoom_out_action) fit_to_window_action = QAction(QIcon.fromTheme("zoom-fit-best"), "适应窗口", self) fit_to_window_action.triggered.connect(self.fit_to_window) toolbar.addAction(fit_to_window_action) toolbar.addSeparator() # 图像比较 compare_action = QAction(QIcon.fromTheme("view-compare"), "比较原图", self) compare_action.triggered.connect(self.compare_with_original) toolbar.addAction(compare_action) def create_image_viewer(self): """创建图像显示区域""" # 创建滚动区域 self.scroll_area = QScrollArea() self.scroll_area.setWidgetResizable(True) # 创建图像查看器 self.image_viewer = ImageViewer() self.scroll_area.setWidget(self.image_viewer) # 添加到左侧面板 self.left_panel.addWidget(self.scroll_area) # 创建直方图显示区域 self.histogram_widget = HistogramWidget() self.left_panel.addWidget(self.histogram_widget) def create_history_tabs(self): """创建历史标签页""" self.history_tabs = QTabWidget() # 原始图像标签页 self.original_tab = QWidget() self.original_tab_layout = QVBoxLayout(self.original_tab) self.original_viewer = ImageViewer() self.original_tab_layout.addWidget(self.original_viewer) self.history_tabs.addTab(self.original_tab, "原始图像") # 处理后图像标签页 self.processed_tab = QWidget() self.processed_tab_layout = QVBoxLayout(self.processed_tab) self.processed_viewer = ImageViewer() self.processed_tab_layout.addWidget(self.processed_viewer) self.history_tabs.addTab(self.processed_tab, "处理后图像") # 描述信息标签页 self.info_tab = QWidget() self.info_tab_layout = QVBoxLayout(self.info_tab) self.info_text = QTextEdit() self.info_text.setReadOnly(True) self.info_tab_layout.addWidget(self.info_text) self.history_tabs.addTab(self.info_tab, "图像信息") # GLCM特征标签页 self.glcm_tab = QWidget() self.glcm_tab_layout = QVBoxLayout(self.glcm_tab) self.glcm_widget = GLCMWidget() self.glcm_tab_layout.addWidget(self.glcm_widget) self.history_tabs.addTab(self.glcm_tab, "GLCM特征") # 添加到右侧面板 self.right_panel.addWidget(self.history_tabs) def open_image(self): """打开图像文件""" file_path, _ = QFileDialog.getOpenFileName( self, "打开图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp *.gif *.tiff)" ) if file_path: self.statusBar().showMessage(f"正在加载图像: {file_path}") # 在单独的线程中加载图像,避免界面卡顿 thread = ImageProcessingThread(self._load_image, file_path) thread.finished.connect(self._on_image_loaded) thread.start() def _load_image(self, file_path): """在线程中加载图像""" image = cv2.imread(file_path) if image is None: return None, file_path return image, file_path def _on_image_loaded(self, result): """图像加载完成后的回调函数""" image, file_path = result if image is None: QMessageBox.critical(self, "错误", f"无法加载图像: {file_path}") self.statusBar().showMessage("加载图像失败") return self.original_image = image self.processed_image = image.copy() self.current_image = image.copy() # 显示图像 self.display_image(self.current_image) # 更新原始图像查看器 self.original_viewer.set_image(self.cv_to_qimage(self.original_image)) # 更新图像信息 self.update_image_info() # 清空历史 self.history = [self.original_image.copy()] self.history_index = 0 self.statusBar().showMessage(f"已加载图像: {os.path.basename(file_path)}") def save_image(self): """保存当前图像""" if self.processed_image is None: QMessageBox.warning(self, "警告", "没有可保存的图像") return if not hasattr(self, 'current_file_path'): self.save_image_as() else: try: cv2.imwrite(self.current_file_path, self.processed_image) self.statusBar().showMessage(f"已保存图像: {os.path.basename(self.current_file_path)}") except Exception as e: QMessageBox.critical(self, "错误", f"保存图像失败: {str(e)}") self.statusBar().showMessage("保存图像失败") def save_image_as(self): """另存为图像""" if self.processed_image is None: QMessageBox.warning(self, "警告", "没有可保存的图像") return file_path, _ = QFileDialog.getSaveFileName( self, "保存图像", "", "PNG (*.png);;JPEG (*.jpg);;BMP (*.bmp);;TIFF (*.tiff)" ) if file_path: try: # 确保保存的是RGB格式 if len(self.processed_image.shape) == 3: image_to_save = cv2.cvtColor(self.processed_image, cv2.COLOR_BGR2RGB) else: image_to_save = self.processed_image cv2.imwrite(file_path, image_to_save) self.current_file_path = file_path self.statusBar().showMessage(f"已保存图像: {os.path.basename(file_path)}") except Exception as e: QMessageBox.critical(self, "错误", f"保存图像失败: {str(e)}") self.statusBar().showMessage("保存图像失败") def display_image(self, image): """显示图像""" if image is None: return qimage = self.cv_to_qimage(image) self.image_viewer.set_image(qimage) # 更新直方图 self.histogram_widget.update_histogram(image) # 更新处理后图像查看器 self.processed_viewer.set_image(qimage) def cv_to_qimage(self, cv_image): """将OpenCV图像转换为Qt图像""" if len(cv_image.shape) == 3: # 彩色图像 height, width, channel = cv_image.shape bytes_per_line = 3 * width qimage = QImage(cv_image.data, width, height, bytes_per_line, QImage.Format_BGR888) else: # 灰度图像 height, width = cv_image.shape bytes_per_line = width qimage = QImage(cv_image.data, width, height, bytes_per_line, QImage.Format_Grayscale8) return qimage def update_image_info(self): """更新图像信息""" if self.original_image is None: return info = "图像信息:\n\n" info += f"尺寸: {self.original_image.shape[1]} x {self.original_image.shape[0]} 像素\n" if len(self.original_image.shape) == 3: info += f"通道数: {self.original_image.shape[2]}\n" info += "类型: 彩色图像\n" else: info += "通道数: 1\n" info += "类型: 灰度图像\n" info += f"数据类型: {self.original_image.dtype}" self.info_text.setText(info) def add_to_history(self, processed_image, operation_name): """添加操作到历史记录""" # 如果当前不在历史的末尾,删除后面的所有历史 if self.history_index < len(self.history) - 1: self.history = self.history[:self.history_index + 1] # 添加新的历史记录 self.history.append(processed_image.copy()) self.history_index += 1 # 更新处理后图像标签页 self.processed_viewer.set_image(self.cv_to_qimage(processed_image)) # 更新历史标签页标题 self.history_tabs.setTabText(1, f"处理后图像 ({operation_name})") def undo(self): """撤销操作""" if self.history_index > 0: self.history_index -= 1 self.processed_image = self.history[self.history_index].copy() self.display_image(self.processed_image) self.statusBar().showMessage("已撤销操作") def redo(self): """重做操作""" if self.history_index < len(self.history) - 1: self.history_index += 1 self.processed_image = self.history[self.history_index].copy() self.display_image(self.processed_image) self.statusBar().showMessage("已重做操作") def zoom_in(self): """放大图像""" self.image_viewer.scale_factor *= 1.1 self.image_viewer.update_pixmap() def zoom_out(self): """缩小图像""" self.image_viewer.scale_factor *= 0.9 self.image_viewer.update_pixmap() def fit_to_window(self): """适应窗口显示""" if self.current_image is None: return # 计算适应窗口的缩放因子 scroll_area_width = self.scroll_area.width() - 20 # 减去边框 scroll_area_height = self.scroll_area.height() - 20 image_width = self.current_image.shape[1] image_height = self.current_image.shape[0] scale_x = scroll_area_width / image_width scale_y = scroll_area_height / image_height self.image_viewer.scale_factor = min(scale_x, scale_y) self.image_viewer.update_pixmap() def compare_with_original(self): """比较处理后的图像与原始图像""" if self.original_image is None or self.processed_image is None: return # 创建一个新窗口进行比较 compare_window = QMainWindow() compare_window.setWindowTitle("图像比较") compare_window.resize(1000, 500) # 创建分割器 splitter = QSplitter(Qt.Horizontal) # 左侧显示原始图像 left_widget = QWidget() left_layout = QVBoxLayout(left_widget) left_label = QLabel("原始图像") left_label.setAlignment(Qt.AlignCenter) left_viewer = ImageViewer() left_viewer.set_image(self.cv_to_qimage(self.original_image)) left_layout.addWidget(left_label) left_layout.addWidget(left_viewer) # 右侧显示处理后的图像 right_widget = QWidget() right_layout = QVBoxLayout(right_widget) right_label = QLabel("处理后图像") right_label.setAlignment(Qt.AlignCenter) right_viewer = ImageViewer() right_viewer.set_image(self.cv_to_qimage(self.processed_image)) right_layout.addWidget(right_label) right_layout.addWidget(right_viewer) # 添加到分割器 splitter.addWidget(left_widget) splitter.addWidget(right_widget) splitter.setSizes([500, 500]) compare_window.setCentralWidget(splitter) compare_window.show() def about(self): """显示关于对话框""" QMessageBox.about(self, "关于数字图像处理系统", "数字图像处理系统\n\n" "基于OpenCV和PySide6开发\n" "支持图像转换、增强、复原、几何变换、形态学处理、分割和描述等功能\n\n" "版本: 1.0.0" ) def show_help(self): """显示帮助对话框""" help_text = ( "数字图像处理系统帮助文档\n\n" "1. 文件操作:\n" " - 打开: 从文件系统加载图像\n" " - 保存: 保存当前处理的图像\n" " - 另存为: 以新文件名保存图像\n\n" "2. 编辑操作:\n" " - 撤销: 撤销上一步操作\n" " - 重做: 恢复撤销的操作\n\n" "3. 图像处理:\n" " - 图像转换: 支持模式转换和分辨率调整\n" " - 图像增强: 包括直方图均衡化、滤波等\n" " - 图像复原: 处理运动模糊和噪声\n" " - 几何变换: 平移、旋转、缩放和镜像\n" " - 形态学处理: 腐蚀、膨胀、开/闭运算等\n" " - 图像分割: 阈值分割和区域分割\n" " - 图像描述: 计算不变矩和灰度共生矩阵\n\n" "4. 视图操作:\n" " - 放大/缩小: 调整图像显示大小\n" " - 适应窗口: 自动调整图像大小以适应窗口\n\n" "5. 比较功能:\n" " - 比较原图: 同时显示原始图像和处理后图像进行对比" ) QMessageBox.information(self, "帮助", help_text) # 图像处理功能实现 def rgb_to_gray(self): """RGB转灰度""" if self.processed_image is None: return if len(self.processed_image.shape) == 2: QMessageBox.warning(self, "警告", "当前图像已经是灰度图像") return thread = ImageProcessingThread(cv2.cvtColor, self.processed_image, cv2.COLOR_BGR2GRAY) thread.finished.connect(lambda result: self._process_finished(result, "RGB转灰度")) thread.start() def resize_image(self): """调整图像分辨率""" if self.processed_image is None: return dialog = ProcessingDialog("调整分辨率") # 获取当前图像尺寸 current_width = self.processed_image.shape[1] current_height = self.processed_image.shape[0] # 添加宽度和高度输入框 width_spinbox = QSpinBox() width_spinbox.setRange(1, 10000) width_spinbox.setValue(current_width) height_spinbox = QSpinBox() height_spinbox.setRange(1, 10000) height_spinbox.setValue(current_height) # 保持比例复选框 keep_ratio_checkbox = QCheckBox("保持比例") keep_ratio_checkbox.setChecked(True) # 添加到对话框 dialog.layout.addWidget(QLabel("宽度:"), 0, 0) dialog.layout.addWidget(width_spinbox, 0, 1) dialog.layout.addWidget(QLabel("像素"), 0, 2) dialog.layout.addWidget(QLabel("高度:"), 1, 0) dialog.layout.addWidget(height_spinbox, 1, 1) dialog.layout.addWidget(QLabel("像素"), 1, 2) dialog.layout.addWidget(keep_ratio_checkbox, 2, 0, 1, 3) # 添加按钮 ok_button, cancel_button = dialog.add_button_box() # 保持比例功能 ratio = current_width / current_height def update_height(): if keep_ratio_checkbox.isChecked(): height_spinbox.setValue(int(width_spinbox.value() / ratio)) def update_width(): if keep_ratio_checkbox.isChecked(): width_spinbox.setValue(int(height_spinbox.value() * ratio)) width_spinbox.valueChanged.connect(update_height) height_spinbox.valueChanged.connect(update_width) if dialog.exec_(): new_width = width_spinbox.value() new_height = height_spinbox.value() # 添加插值方法选择 interpolation_dialog = ProcessingDialog("选择插值方法") methods = ["最近邻", "双线性", "双三次", "Lanczos"] method_combo = interpolation_dialog.add_combo_box("插值方法", methods) if interpolation_dialog.exec_(): method_index = method_combo.currentIndex() interpolation_methods = [ cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_LANCZOS4 ] interpolation = interpolation_methods[method_index] thread = ImageProcessingThread(cv2.resize, self.processed_image, (new_width, new_height), interpolation=interpolation) thread.finished.connect(lambda result: self._process_finished(result, "调整分辨率")) thread.start() def histogram_equalization(self): """直方图均衡化""" if self.processed_image is None: return if len(self.processed_image.shape) == 3: # 彩色图像需要先转换到YUV空间 def equalize_color(image): yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV) yuv[:, :, 0] = cv2.equalizeHist(yuv[:, :, 0]) return cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR) thread = ImageProcessingThread(equalize_color, self.processed_image) else: # 灰度图像直接均衡化 thread = ImageProcessingThread(cv2.equalizeHist, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, "直方图均衡化")) thread.start() def log_transform(self): """对数变换""" if self.processed_image is None: return dialog = ProcessingDialog("对数变换") c_slider, c_spinbox = dialog.add_slider("常数C", 1, 100, 25) if dialog.exec_(): c = c_spinbox.value() def log_transform(image): # 确保图像是浮点类型 image_float = image.astype(np.float32) / 255.0 # 应用对数变换 result = c * np.log(1 + image_float) # 归一化到[0, 1] result = cv2.normalize(result, None, 0, 1, cv2.NORM_MINMAX) # 转回uint8 return (result * 255).astype(np.uint8) thread = ImageProcessingThread(log_transform, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, "对数变换")) thread.start() def power_law_transform(self): """幂律变换""" if self.processed_image is None: return dialog = ProcessingDialog("幂律变换") gamma_slider, gamma_spinbox = dialog.add_double_slider("伽马值", 0.1, 5.0, 1.0, 0.1, 1) if dialog.exec_(): gamma = gamma_spinbox.value() def power_law_transform(image): # 确保图像是浮点类型 image_float = image.astype(np.float32) / 255.0 # 应用幂律变换 result = np.power(image_float, gamma) # 归一化到[0, 1] result = cv2.normalize(result, None, 0, 1, cv2.NORM_MINMAX) # 转回uint8 return (result * 255).astype(np.uint8) thread = ImageProcessingThread(power_law_transform, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, f"幂律变换 (γ={gamma})")) thread.start() def spatial_filtering(self, filter_type): """空域滤波""" if self.processed_image is None: return dialog = ProcessingDialog(f"{filter_type}参数设置") if filter_type in ["均值滤波", "高斯滤波"]: kernel_size, _ = dialog.add_slider("核大小", 1, 21, 3, 2) # 奇数 elif filter_type == "中值滤波": kernel_size, _ = dialog.add_slider("孔径大小", 1, 21, 3, 2) # 奇数 elif filter_type in ["Sobel算子", "Prewitt算子"]: direction_combo = dialog.add_combo_box("方向", ["水平", "垂直", "两者"]) elif filter_type == "Laplacian算子": ksize_combo = dialog.add_combo_box("核大小", ["1", "3", "5", "7"], 1) if dialog.exec_(): if filter_type == "均值滤波": ksize = kernel_size.value() def mean_filter(image): return cv2.blur(image, (ksize, ksize)) thread = ImageProcessingThread(mean_filter, self.processed_image) elif filter_type == "高斯滤波": ksize = kernel_size.value() def gaussian_filter(image): return cv2.GaussianBlur(image, (ksize, ksize), 0) thread = ImageProcessingThread(gaussian_filter, self.processed_image) elif filter_type == "中值滤波": ksize = kernel_size.value() def median_filter(image): return cv2.medianBlur(image, ksize) thread = ImageProcessingThread(median_filter, self.processed_image) elif filter_type == "Sobel算子": direction = direction_combo.currentIndex() def sobel_filter(image): if len(image.shape) == 3: image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) if direction == 0: # 水平 sobelx = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3) return cv2.convertScaleAbs(sobelx) elif direction == 1: # 垂直 sobely = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3) return cv2.convertScaleAbs(sobely) else: # 两者 sobelx = cv2.Sobel(image, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(image, cv2.CV_64F, 0, 1, ksize=3) return cv2.addWeighted(cv2.convertScaleAbs(sobelx), 0.5, cv2.convertScaleAbs(sobely), 0.5, 0) thread = ImageProcessingThread(sobel_filter, self.processed_image) elif filter_type == "Prewitt算子": direction = direction_combo.currentIndex() def prewitt_filter(image): if len(image.shape) == 3: image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) kernelx = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=np.float32) kernely = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype=np.float32) if direction == 0: # 水平 prewittx = cv2.filter2D(image, -1, kernelx) return prewittx elif direction == 1: # 垂直 prewitty = cv2.filter2D(image, -1, kernely) return prewitty else: # 两者 prewittx = cv2.filter2D(image, -1, kernelx) prewitty = cv2.filter2D(image, -1, kernely) return cv2.addWeighted(prewittx, 0.5, prewitty, 0.5, 0) thread = ImageProcessingThread(prewitt_filter, self.processed_image) elif filter_type == "Laplacian算子": ksize = [1, 3, 5, 7][ksize_combo.currentIndex()] def laplacian_filter(image): if len(image.shape) == 3: image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) return cv2.Laplacian(image, cv2.CV_64F, ksize=ksize) thread = ImageProcessingThread(laplacian_filter, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, filter_type)) thread.start() def motion_deblur(self): """运动模糊复原""" if self.processed_image is None: return dialog = ProcessingDialog("运动模糊复原参数设置") length_slider, length_spinbox = dialog.add_slider("运动长度", 1, 100, 15) angle_slider, angle_spinbox = dialog.add_slider("运动角度", 0, 360, 0) gamma_slider, gamma_spinbox = dialog.add_double_slider("噪声功率谱比", 0.01, 10.0, 0.1, 0.01, 2) if dialog.exec_(): length = length_spinbox.value() angle = angle_spinbox.value() gamma = gamma_spinbox.value() def deblur(image): # 确保图像是灰度图 if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() # 创建运动模糊核 kernel = np.zeros((length, length), dtype=np.float32) kernel[int((length - 1) / 2), :] = np.ones(length, dtype=np.float32) # 旋转核 M = cv2.getRotationMatrix2D((length / 2, length / 2), angle, 1.0) kernel = cv2.warpAffine(kernel, M, (length, length)) # 归一化 kernel /= length # 傅里叶变换 fft = np.fft.fft2(gray) fft_kernel = np.fft.fft2(kernel, s=gray.shape) # 维纳滤波 H_conj = np.conj(fft_kernel) H_squared = np.abs(fft_kernel) ** 2 G = (H_conj / (H_squared + gamma)) * fft # 逆傅里叶变换 deblurred = np.fft.ifft2(G) deblurred = np.abs(deblurred) # 归一化到0-255 deblurred = cv2.normalize(deblurred, None, 0, 255, cv2.NORM_MINMAX) deblurred = deblurred.ast(np.uint8) return deblurred thread = ImageProcessingThread(deblur, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, "运动模糊复原")) thread.start() def noise_removal(self, noise_type): """噪声去除""" if self.processed_image is None: return dialog = ProcessingDialog(f"{noise_type}去除") if noise_type == "高斯噪声": kernel_size, _ = dialog.add_slider("核大小", 1, 21, 3, 2) # 奇数 elif noise_type == "椒盐噪声": kernel_size, _ = dialog.add_slider("核大小", 1, 21, 3, 2) # 奇数 if dialog.exec_(): ksize = kernel_size.value() if noise_type == "高斯噪声": def remove_gaussian_noise(image): return cv2.GaussianBlur(image, (ksize, ksize), 0) thread = ImageProcessingThread(remove_gaussian_noise, self.processed_image) elif noise_type == "椒盐噪声": def remove_salt_pepper_noise(image): return cv2.medianBlur(image, ksize) thread = ImageProcessingThread(remove_salt_pepper_noise, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, f"{noise_type}去除")) thread.start() def translate_image(self): """平移图像""" if self.processed_image is None: return dialog = ProcessingDialog("平移图像") tx_slider, tx_spinbox = dialog.add_slider("水平偏移", -500, 500, 0) ty_slider, ty_spinbox = dialog.add_slider("垂直偏移", -500, 500, 0) if dialog.exec_(): tx = tx_spinbox.value() ty = ty_spinbox.value() def translate(image): M = np.float32([[1, 0, tx], [0, 1, ty]]) return cv2.warpAffine(image, M, (image.shape[1], image.shape[0])) thread = ImageProcessingThread(translate, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, f"平移 (tx={tx}, ty={ty})")) thread.start() def rotate_image(self): """旋转图像""" if self.processed_image is None: return dialog = ProcessingDialog("旋转图像") angle_slider, angle_spinbox = dialog.add_slider("旋转角度", -180, 180, 0) scale_slider, scale_spinbox = dialog.add_double_slider("缩放比例", 0.1, 5.0, 1.0, 0.1, 1) if dialog.exec_(): angle = angle_spinbox.value() scale = scale_spinbox.value() def rotate(image): center = (image.shape[1] // 2, image.shape[0] // 2) M = cv2.getRotationMatrix2D(center, angle, scale) return cv2.warpAffine(image, M, (image.shape[1], image.shape[0])) thread = ImageProcessingThread(rotate, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, f"旋转 ({angle}°, 缩放{scale}x)")) thread.start() def scale_image(self): """缩放图像""" if self.processed_image is None: return dialog = ProcessingDialog("缩放图像") scale_slider, scale_spinbox = dialog.add_double_slider("缩放比例", 0.1, 5.0, 1.0, 0.1, 1) if dialog.exec_(): scale = scale_spinbox.value() def scale_image(image): new_width = int(image.shape[1] * scale) new_height = int(image.shape[0] * scale) return cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_LINEAR) thread = ImageProcessingThread(scale_image, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, f"缩放 ({scale}x)")) thread.start() def flip_image(self): """镜像图像""" if self.processed_image is None: return dialog = ProcessingDialog("镜像图像") flip_type = dialog.add_combo_box("镜像类型", ["水平", "垂直", "水平和垂直"]) if dialog.exec_(): flip_code = flip_type.currentIndex() def flip(image): return cv2.flip(image, flip_code) flip_types = ["水平", "垂直", "水平和垂直"] thread = ImageProcessingThread(flip, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, f"镜像 ({flip_types[flip_code]})")) thread.start() def morphological_operation(self, operation_type): """形态学操作""" if self.processed_image is None: return dialog = ProcessingDialog(f"{operation_type}参数设置") kernel_size, _ = dialog.add_slider("核大小", 1, 21, 3) kernel_shape = dialog.add_combo_box("核形状", ["矩形", "椭圆", "十字形"]) if dialog.exec_(): ksize = kernel_size.value() shape_index = kernel_shape.currentIndex() kernel_shapes = [ cv2.MORPH_RECT, cv2.MORPH_ELLIPSE, cv2.MORPH_CROSS ] kernel = cv2.getStructuringElement(kernel_shapes[shape_index], (ksize, ksize)) if operation_type == "腐蚀": def erode(image): return cv2.erode(image, kernel) thread = ImageProcessingThread(erode, self.processed_image) elif operation_type == "膨胀": def dilate(image): return cv2.dilate(image, kernel) thread = ImageProcessingThread(dilate, self.processed_image) elif operation_type == "开运算": def opening(image): return cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel) thread = ImageProcessingThread(opening, self.processed_image) elif operation_type == "闭运算": def closing(image): return cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel) thread = ImageProcessingThread(closing, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, operation_type)) thread.start() def edge_extraction(self): """边界提取""" if self.processed_image is None: return dialog = ProcessingDialog("边界提取参数设置") kernel_size, _ = dialog.add_slider("核大小", 1, 21, 3) kernel_shape = dialog.add_combo_box("核形状", ["矩形", "椭圆", "十字形"]) if dialog.exec_(): ksize = kernel_size.value() shape_index = kernel_shape.currentIndex() kernel_shapes = [ cv2.MORPH_RECT, cv2.MORPH_ELLIPSE, cv2.MORPH_CROSS ] kernel = cv2.getStructuringElement(kernel_shapes[shape_index], (ksize, ksize)) def extract_edge(image): if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() # 膨胀 dilated = cv2.dilate(gray, kernel) # 边界提取 return dilated - gray thread = ImageProcessingThread(extract_edge, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, "边界提取")) thread.start() def threshold_segmentation(self): """阈值分割""" if self.processed_image is None: return dialog = ProcessingDialog("阈值分割参数设置") threshold_slider, threshold_spinbox = dialog.add_slider("阈值", 0, 255, 127) max_value_slider, max_value_spinbox = dialog.add_slider("最大值", 0, 255, 255) threshold_type = dialog.add_combo_box("阈值类型", [ "二进制阈值", "反二进制阈值", "截断阈值", "零阈值", "反零阈值", "Otsu算法" ]) if dialog.exec_(): threshold = threshold_spinbox.value() max_value = max_value_spinbox.value() type_index = threshold_type.currentIndex() threshold_types = [ cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV, cv2.THRESH_TRUNC, cv2.THRESH_TOZERO, cv2.THRESH_TOZERO_INV, cv2.THRESH_BINARY + cv2.THRESH_OTSU ] def threshold_segment(image): if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() if type_index == 5: # Otsu算法,忽略手动设置的阈值 _, thresh = cv2.threshold(gray, 0, max_value, threshold_types[type_index]) else: _, thresh = cv2.threshold(gray, threshold, max_value, threshold_types[type_index]) return thresh type_names = ["二进制阈值", "反二进制阈值", "截断阈值", "零阈值", "反零阈值", "Otsu算法"] thread = ImageProcessingThread(threshold_segment, self.processed_image) thread.finished.connect( lambda result: self._process_finished(result, f"阈值分割 ({type_names[type_index]})")) thread.start() def adaptive_threshold_segmentation(self): """自适应阈值分割""" if self.processed_image is None: return dialog = ProcessingDialog("自适应阈值分割参数设置") max_value_slider, max_value_spinbox = dialog.add_slider("最大值", 0, 255, 255) method_combo = dialog.add_combo_box("自适应方法", ["均值", "高斯"]) type_combo = dialog.add_combo_box("阈值类型", ["二进制阈值", "反二进制阈值"]) block_size_slider, block_size_spinbox = dialog.add_slider("块大小", 3, 101, 11, 2) # 奇数 c_slider, c_spinbox = dialog.add_double_slider("常数C", -10, 10, 2, 0.1, 1) if dialog.exec_(): max_value = max_value_spinbox.value() method_index = method_combo.currentIndex() type_index = type_combo.currentIndex() block_size = block_size_spinbox.value() c = c_spinbox.value() adaptive_methods = [ cv2.ADAPTIVE_THRESH_MEAN_C, cv2.ADAPTIVE_THRESH_GAUSSIAN_C ] threshold_types = [ cv2.THRESH_BINARY, cv2.THRESH_BINARY_INV ] def adaptive_threshold(image): if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() return cv2.adaptiveThreshold( gray, max_value, adaptive_methods[method_index], threshold_types[type_index], block_size, c ) method_names = ["均值", "高斯"] type_names = ["二进制阈值", "反二进制阈值"] thread = ImageProcessingThread(adaptive_threshold, self.processed_image) thread.finished.connect(lambda result: self._process_finished( result, f"自适应阈值分割 ({method_names[method_index]}, {type_names[type_index]})" )) thread.start() def watershed_segmentation(self): """分水岭分割""" if self.processed_image is None: return if len(self.processed_image.shape) != 3: QMessageBox.warning(self, "警告", "分水岭分割需要彩色图像") return dialog = ProcessingDialog("分水岭分割参数设置") threshold_slider, threshold_spinbox = dialog.add_slider("阈值", 0, 255, 100) morph_size_slider, morph_size_spinbox = dialog.add_slider("形态学操作核大小", 1, 21, 3) if dialog.exec_(): threshold = threshold_spinbox.value() morph_size = morph_size_spinbox.value() def watershed(image): # 转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 阈值处理 ret, thresh = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 噪声去除 kernel = np.ones((morph_size, morph_size), np.uint8) opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 确定背景区域 sure_bg = cv2.dilate(opening, kernel, iterations=3) # 确定前景区域 dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5) ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0) # 找到未知区域 sure_fg = np.uint8(sure_fg) unknown = cv2.subtract(sure_bg, sure_fg) # 标记标签 ret, markers = cv2.connectedComponents(sure_fg) # 为所有标签加1,确保背景不是0而是1 markers = markers + 1 # 将未知区域标记为0 markers[unknown == 255] = 0 # 应用分水岭算法 markers = cv2.watershed(image, markers) image[markers == -1] = [0, 0, 255] # 标记边界为红色 return image thread = ImageProcessingThread(watershed, self.processed_image) thread.finished.connect(lambda result: self._process_finished(result, "分水岭分割")) thread.start() def calculate_hu_moments(self): """计算Hu不变矩""" if self.processed_image is None: return def calculate_moments(image): if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() # 计算矩 moments = cv2.moments(gray) # 计算Hu不变矩 hu_moments = cv2.HuMoments(moments) # 对数变换,方便显示 for i in range(7): hu_moments[i] = -1 * np.copysign(1.0, hu_moments[i]) * np.log10(np.abs(hu_moments[i])) return hu_moments thread = ImageProcessingThread(calculate_moments, self.processed_image) thread.finished.connect(self._on_hu_moments_calculated) thread.start() def _on_hu_moments_calculated(self, hu_moments): """Hu不变矩计算完成后的回调""" result = "Hu不变矩:\n\n" for i, moment in enumerate(hu_moments): result += f"H{i + 1}: {moment[0]:.8f}\n" self.info_text.setText(result) self.history_tabs.setCurrentWidget(self.info_tab) self.statusBar().showMessage("Hu不变矩计算完成") def calculate_glcm(self): """计算灰度共生矩阵""" if self.processed_image is None: return thread = ImageProcessingThread(self._calculate_glcm_thread, self.processed_image) thread.finished.connect(self._on_glcm_calculated) thread.start() def _calculate_glcm_thread(self, image): """在线程中计算GLCM""" if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image.copy() # 量化到16级灰度以减少计算量 gray_quantized = (gray // 16).astype(np.uint8) # 计算GLCM矩阵 (距离=1, 角度=0, 45, 90, 135度) glcm = graycomatrix(gray_quantized, distances=[1], angles=[0, np.pi / 4, np.pi / 2, 3 * np.pi / 4], levels=16, symmetric=True, normed=True) return glcm def _on_glcm_calculated(self, glcm): """GLCM计算完成后的回调""" self.glcm_widget.update_glcm(self.processed_image) self.history_tabs.setCurrentWidget(self.glcm_tab) self.statusBar().showMessage("灰度共生矩阵计算完成") def _process_finished(self, result, operation_name): """图像处理完成后的回调""" self.processed_image = result self.display_image(result) self.add_to_history(result, operation_name) self.statusBar().showMessage(f"{operation_name}完成") if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())为什么运行后打开图片D:\python\python.exe D:\PythonProject\.venv\2.py 插入图片后闪退后显示 进程已结束,退出代码为 -1073740791 (0xC0000409)
06-09
import sensor, image, time sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(time=1000) # 定义ROI ROI = (80, 60, 160, 120) # 颜色阈值,根据实际目标调整 GRAY_THRESHOLD = (100, 255) # 灰度阈值 def detect_shapes_with_blobs(img): shapes = [] # 在ROI内查找所有灰度值在阈值内的色块 blobs = img.find_blobs([GRAY_THRESHOLD], roi=ROI, merge=True, margin=10) for blob in blobs: # 过滤小区域 if blob.area() < 500: continue # 获取blob的外接矩形 rect = blob.rect() # 计算矩形度 rect_degree = blob.area() / (rect[2] * rect[3]) # 计算圆形度 circularity = 4 * 3.1416 * blob.area() / (blob.perimeter()**2) if blob.perimeter() > 0 else 0 # 根据特征判断形状 if circularity > 0.8: shapes.append(("circle", blob)) elif len(blob.min_rect()) == 4 and rect_degree > 0.85: # 注意:min_rect返回四个角点,但这里我们使用外接矩形的特征 shapes.append(("rectangle", blob)) elif len(blob.min_rect()) == 3: # 注意:min_rect对于三角形也会返回4个点?所以这种方法不直接支持三角形 # 对于三角形,可能需要其他方法 shapes.append(("triangle", blob)) else: # 其他情况 pass return shapes while True: img = sensor.snapshot() # 绘制ROI区域 img.draw_rectangle(ROI, color=(255,0,0)) # 检测形状 shapes = detect_shapes_with_blobs(img) # 绘制结果 for shape_type, blob in shapes: if shape_type == "circle": # 使用最小外接圆 img.draw_circle(blob.enclosing_circle(), color=(0,255,0)) else: # 绘制外接矩形 img.draw_rectangle(blob.rect(), color=(0,255,0)) # 在中心点标注形状 img.draw_string(blob.cx(), blob.cy(), shape_type, color=(0,255,0))
08-01
import numpy as np import matplotlib.pyplot as plt from scipy.sparse import diags from scipy.sparse.linalg import spsolve import matplotlib.colors as colors from matplotlib.widgets import Slider, Button from matplotlib.patches import Rectangle import time class NMOSIonDiffusionSimulator: def __init__(self): # 物理参数设置 self.D0 = 5e-15 # 基础扩散系数 (m&sup2;/s) self.activation_energy = 1.2 # 激活能量 (eV) self.dose = 1e20 # 注入剂量 (ions/cm&sup2;) self.Rp = 100e-9 # 投影射程 (100 nm) self.ΔRp = 30e-9 # 投影射程偏差 (30 nm) self.substrate_temp = 1000 # 基底温度 (K) # 数值模拟参数 self.depth = 500e-9 # 模拟深度 (500 nm) self.dz = 1e-9 # 空间步长 (1 nm) self.dt = 0.1 # 时间步长 (0.1 s) self.simulation_time = 3600 # 总模拟时间 (3600 s = 1小时) # 计算网格 self.z = np.arange(0, self.depth, self.dz) self.nz = len(self.z) self.nt = int(self.simulation_time / self.dt) # 初始化离子浓度(高斯分布) self.C = self.dose * np.exp(-(self.z - self.Rp) ** 2 / (2 * self.ΔRp ** 2)) # 结果存储 self.time_points = [] self.concentration_profiles = [] # 热力学参数 self.k_boltzmann = 8.617333e-5 # 玻尔兹曼常数 (eV/K) # 计算温度依赖的扩散系数 self.D = self.D0 * np.exp(-self.activation_energy / (self.k_boltzmann * self.substrate_temp)) print(f"计算扩散系数: {self.D:.2e} m&sup2;/s") # 设置扩散矩阵 self.setup_diffusion_matrix() def setup_diffusion_matrix(self): """设置用于求解扩散方程的系数矩阵(隐式格式)""" r = self.D * self.dt / self.dz ** 2 # 创建三对角矩阵 diagonals = [ -r * np.ones(self.nz - 1), # 下对角线 (1 + 2 * r) * np.ones(self.nz), # 主对角线 -r * np.ones(self.nz - 1) # 上对角线 ] offsets = [-1, 0, 1] # 创建稀疏矩阵 self.A = diags(diagonals, offsets, shape=(self.nz, self.nz), format='csc') # 边界条件(表面无扩散) self.A[0, 0] = 1 self.A[0, 1] = 0 self.A[-1, -1] = 1 self.A[-1, -2] = 0 def solve_diffusion(self): """求解扩散方程""" self.time_points = [] self.concentration_profiles = [self.C.copy()] current_C = self.C.copy() start_time = time.time() for t in range(self.nt): # 创建右侧向量 b = current_C.copy() b[0] = 0 # 表面边界条件:浓度固定为0 b[-1] = 0 # 底部边界条件:浓度固定为0 # 求解线性系统 new_C = spsolve(self.A, b) # 更新浓度 current_C = new_C # 每10秒保存一次结果 if t % 100 == 0: self.time_points.append(t * self.dt) self.concentration_profiles.append(new_C.copy()) end_time = time.time() print(f"扩散模拟完成! 耗时: {end_time - start_time:.2f} 秒") print(f"存储了 {len(self.concentration_profiles)} 个时间点的浓度分布") def draw_nmos_structure(self, ax): """绘制NMOS器件结构示意图""" # 清除原有内容 ax.clear() # 绘制衬底 substrate = plt.Rectangle((0, 0), 100, 40, facecolor='sandybrown', alpha=0.6) ax.add_patch(substrate) # 绘制栅氧化层 gate_oxide = plt.Rectangle((30, 40), 40, 5, facecolor='gray', alpha=0.7) ax.add_patch(gate_oxide) # 绘制多晶硅栅极 gate = plt.Rectangle((35, 45), 30, 10, facecolor='silver', alpha=0.8) ax.add_patch(gate) # 绘制源极 source = plt.Rectangle((10, 40), 15, 15, facecolor='blue', alpha=0.5) ax.add_patch(source) # 绘制漏极 drain = plt.Rectangle((75, 40), 15, 15, facecolor='blue', alpha=0.5) ax.add_patch(drain) # 添加文本标签 ax.text(15, 47, "Source", fontsize=10, ha='center') ax.text(85, 47, "Drain", fontsize=10, ha='center') ax.text(50, 50, "Gate", fontsize=10, ha='center') ax.text(50, 20, "Substrate", fontsize=10, ha='center') # 设置坐标范围 ax.set_xlim(0, 100) ax.set_ylim(0, 60) ax.set_aspect('equal') ax.axis('off') # 隐藏坐标轴 # 添加标题 ax.set_title('NMOS Device Structure', fontsize=12) def interactive_visualization(self): """创建交互式可视化界面""" # 创建更大的图形,包含三个子图 fig = plt.figure(figsize=(16, 10)) gs = fig.add_gridspec(2, 2, width_ratios=[2, 1], height_ratios=[1, 1]) # 创建三个轴 ax1 = fig.add_subplot(gs[0, 0]) # 浓度深度曲线 ax2 = fig.add_subplot(gs[1, 0]) # 时间热图 ax3 = fig.add_subplot(gs[:, 1]) # NMOS器件图 plt.subplots_adjust(bottom=0.15, left=0.1, right=0.95, top=0.95, hspace=0.3, wspace=0.2) # 绘制初始器件结构 self.draw_nmos_structure(ax3) # 在器件图中添加扩散效果可视化 diffusion_line = ax3.axvline(x=50, ymin=0.67, ymax=1.0, color='red', linewidth=3, alpha=0.7, visible=False) diffusion_text = ax3.text(50, 55, '', fontsize=10, ha='center', bbox=dict(facecolor='white', alpha=0.7)) # 主图设置 ax1.set_title('Ion Concentration Profile', fontsize=14) ax1.set_xlabel('Depth from Surface (nm)', fontsize=12) ax1.set_ylabel('Ion Concentration (ions/cm³)', fontsize=12) ax1.grid(True) # 转换为nm单位 z_nm = self.z * 1e9 # 绘制初始浓度分布 line, = ax1.plot(z_nm, self.concentration_profiles[0], 'b-', lw=2) # 添加浓度标签 text = ax1.text(0.05, 0.95, '', transform=ax1.transAxes, bbox=dict(facecolor='white', alpha=0.8)) # 添加深度标记 depth_line = ax1.axvline(x=0, color='r', linestyle='--', alpha=0) depth_text = ax1.text(0.05, 0.85, '', transform=ax1.transAxes, bbox=dict(facecolor='white', alpha=0.8)) # 创建时间滑块 ax_time = plt.axes([0.25, 0.05, 0.5, 0.03]) time_slider = Slider( ax_time, 'Time (s)', 0, self.simulation_time, valinit=0, valstep=self.dt * 100 ) # 在下方创建热图 time_arr = np.array(self.time_points) conc_arr = np.array(self.concentration_profiles).T # 对浓度取对数以更好显示 log_conc = np.log10(conc_arr + 1e10) # 加上小值避免log(0) # 创建更全面的热图显示 fig = plt.figure(figsize=(16, 12)) # 增加图形大小 gs = fig.add_gridspec(3, 2, width_ratios=[2, 1], height_ratios=[1, 1, 0.2]) # 创建四个轴 (增加一个颜色条专用轴) ax1 = fig.add_subplot(gs[0, 0]) # 浓度深度曲线 ax2 = fig.add_subplot(gs[1, 0]) # 时间热图 cax = fig.add_subplot(gs[2, 0]) # 颜色条专用轴 ax3 = fig.add_subplot(gs[:, 1]) # NMOS器件图 # 创建热图 # 改进2: 使用更有表现力的颜色映射和归一化 norm = colors.LogNorm(vmin=1e15, vmax=conc_arr.max()) # 使用对数归一化 im = ax2.imshow(conc_arr, aspect='auto', origin='lower', extent=[0, self.simulation_time, 0, self.depth * 1e9], cmap='viridis', norm=norm) # 使用Viridis颜色映射 # 添加更全面的颜色条 cbar = fig.colorbar(im, cax=cax, orientation='horizontal') cbar.set_label('Ion Concentration (ions/cm³)', fontsize=10) cax.xaxis.set_ticks_position('top') cax.xaxis.set_label_position('top') # 添加等值线增强可读性 X, Y = np.meshgrid(time_arr, self.z * 1e9) levels = np.logspace(np.log10(1e16), np.log10(conc_arr.max()), 10) ax2.contour(X, Y, conc_arr, levels=levels, colors='white', alpha=0.3, linewidths=0.5) # 添加时间标记线 time_line = ax2.axvline(x=0, color='cyan', linewidth=2, alpha=0.7) time_text = ax2.text(0.05, 0.95, '', transform=ax2.transAxes, bbox=dict(facecolor='white', alpha=0.8)) # ❓要删吗 # ax2.set_title('Concentration Evolution Over Time', fontsize=14) # ax2.set_xlabel('Time (s)', fontsize=12) # ax2.set_ylabel('Depth (nm)', fontsize=12) # 添加颜色条 # cbar = fig.colorbar(im, ax=ax2) # cbar.set_label('log10(Concentration) (ions/cm³)', fontsize=10) # 添加时间标记线 # time_line = ax2.axvline(x=0, color='cyan', linewidth=2, alpha=0.7) # time_text = ax2.text(0.05, 0.95, '', transform=ax2.transAxes, # bbox=dict(facecolor='white', alpha=0.8)) # 添加半导体结构示意图 # gate_oxide = Rectangle((0.7, 0.05), 0.2, 0.1, transform=fig.transFigure, # facecolor='gray', alpha=0.5) # source = Rectangle((0.6, 0.05), 0.1, 0.1, transform=fig.transFigure, # facecolor='blue', alpha=0.3) # drain = Rectangle((0.9, 0.05), 0.1, 0.1, transform=fig.transFigure, # facecolor='blue', alpha=0.3) # substrate = Rectangle((0.6, 0), 0.4, 0.05, transform=fig.transFigure, # facecolor='sandybrown', alpha=0.5) # # fig.patches.extend([gate_oxide, source, drain, substrate]) # # # 添加文本标签 # fig.text(0.75, 0.13, 'Gate Oxide', ha='center') # fig.text(0.65, 0.13, 'Source', ha='center') # fig.text(0.95, 0.13, 'Drain', ha='center') # fig.text(0.8, 0.025, 'Substrate', ha='center') # 浓度分布更新函数 def update(val): time_val = time_slider.val idx = min(int(time_val / (self.dt * 100)), len(self.concentration_profiles) - 1) # 更新浓度曲线 line.set_ydata(self.concentration_profiles[idx]) text.set_text( f'Time: {time_val:.1f} s\nMax Concentration: {self.concentration_profiles[idx].max():.2e} ions/cm³') # 更新热图时间线 time_line.set_xdata([time_val, time_val]) time_text.set_text(f"Time: {time_val:.1f} s") # 更新器件图中的扩散效果 - 显示扩散深度 # 计算最大浓度的深度位置 max_conc_idx = np.argmax(self.concentration_profiles[idx]) max_conc_depth = z_nm[max_conc_idx] # 计算扩散深度(浓度降至最大浓度1%的深度) threshold = self.concentration_profiles[idx].max() * 0.01 above_threshold = np.where(self.concentration_profiles[idx] > threshold)[0] if above_threshold.size > 0: diffusion_depth = z_nm[above_threshold[-1]] else: diffusion_depth = 0 # 更新扩散线位置和文本 # 计算在器件图中的扩散深度(像素高度) # 500nm对应40像素(衬底高度) h_pixels = (diffusion_depth * 40) / 500 # 清除之前的扩散区域 for patch in ax3.patches: if patch.get_facecolor() == (1., 0., 0., 0.3): patch.remove() # 创建渐变扩散效果(多个矩形叠加) if diffusion_depth > 0: # 创建5个渐变矩形 steps = 5 for i in range(steps): step_depth = h_pixels * (i + 1) / steps alpha = 0.3 * (1 - i / steps) # 顶部透明度高,底部透明度低 diffusion_area = Rectangle( (30, 40 - step_depth), 40, step_depth, facecolor='red', alpha=alpha ) ax3.add_patch(diffusion_area) diffusion_text.set_text(f'Diffusion Depth: {diffusion_depth:.1f} nm') fig.canvas.draw_idle() time_slider.on_changed(update) # 点击事件处理 def onclick(event): if event.inaxes == ax1: depth_nm = event.xdata depth_m = depth_nm * 1e-9 # 找到时间点 time_idx = min(int(time_slider.val / (self.dt * 100)), len(self.concentration_profiles) - 1) # 找到深度对应的浓度 z_idx = int(depth_nm) # 1nm分辨率 if 0 <= z_idx < len(self.concentration_profiles[time_idx]): conc = self.concentration_profiles[time_idx][z_idx] # 更新深度线和文本 depth_line.set_xdata([depth_nm, depth_nm]) depth_line.set_alpha(1) depth_text.set_text(f'Depth: {depth_nm:.1f} nm\nConcentration: {conc:.2e} ions/cm³') # 更新热图时间线 time_line.set_xdata([time_slider.val, time_slider.val]) fig.canvas.draw_idle() fig.canvas.mpl_connect('button_press_event', onclick) # 添加重置按钮 resetax = plt.axes([0.8, 0.01, 0.1, 0.04]) button = Button(resetax, 'Reset', color='lightgoldenrodyellow', hovercolor='0.975') def reset(event): time_slider.reset() # 重置器件图中的扩散效果 diffusion_line.set_visible(False) diffusion_text.set_text('') # 清除扩散区域 for patch in ax3.patches: if patch.get_facecolor() == (1., 0., 0., 0.3): patch.remove() self.draw_nmos_structure(ax3) # 重绘器件结构 fig.canvas.draw_idle() button.on_clicked(reset) # 添加截图按钮(模拟电镜观察) screenshotax = plt.axes([0.65, 0.01, 0.1, 0.04]) screenshot_btn = Button(screenshotax, 'Take SEM Image', color='lightblue', hovercolor='0.975') def take_sem_image(event): """模拟电镜下观察离子浓度""" # 获取当前时间点的浓度分布 time_val = time_slider.val idx = min(int(time_val / (self.dt * 100)), len(self.concentration_profiles) - 1) # 创建新的电镜观察图 sem_fig, sem_ax = plt.subplots(figsize=(10, 6)) # 计算扩散深度 threshold = self.concentration_profiles[idx].max() * 0.01 diffusion_depth = z_nm[np.where(self.concentration_profiles[idx] > threshold)[0][-1]] # 显示离子分布 sem_ax.plot(z_nm, self.concentration_profiles[idx], 'b-', lw=2) sem_ax.axvline(x=diffusion_depth, color='r', linestyle='--', label=f'Diffusion Depth: {diffusion_depth:.1f} nm') sem_ax.set_title(f'SEM Observation at {time_val:.1f} s', fontsize=14) sem_ax.set_xlabel('Depth from Surface (nm)', fontsize=12) sem_ax.set_ylabel('Ion Concentration (ions/cm³)', fontsize=12) sem_ax.grid(True) sem_ax.legend() # 添加NMOS器件示意图 sem_ax.text(0.95, 0.95, 'NMOS Structure', transform=sem_ax.transAxes, ha='right', va='top', fontsize=12, bbox=dict(facecolor='white', alpha=0.7)) plt.tight_layout() plt.show() screenshot_btn.on_clicked(take_sem_image) plt.show() def run(self): """运行完整模拟和可视化""" self.solve_diffusion() self.interactive_visualization() # 运行模拟 if __name__ == "__main__": simulator = NMOSIonDiffusionSimulator() simulator.run() 以上代码为什么报错: Traceback (most recent call last): File "D:\Users\x00708\SRAM_Classification-242终极版\test_code\ion.py", line 411, in <module> simulator.run() File "D:\Users\x00708\SRAM_Classification-242终极版\test_code\ion.py", line 405, in run self.interactive_visualization() File "D:\Users\x00708\SRAM_Classification-242终极版\test_code\ion.py", line 225, in interactive_visualization ax2.contour(X, Y, conc_arr, levels=levels, File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\__init__.py", line 1524, in inner return func( ^^^^^ File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\axes\_axes.py", line 6779, in contour contours = mcontour.QuadContourSet(self, *args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\contour.py", line 701, in __init__ kwargs = self._process_args(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\contour.py", line 1319, in _process_args x, y, z = self._contour_args(args, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\contour.py", line 1359, in _contour_args x, y, z = self._check_xyz(x, y, z_orig, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\Users\x00708\SRAM_Classification\.venv\Lib\site-packages\matplotlib\contour.py", line 1406, in _check_xyz raise TypeError( TypeError: Shapes of x (500, 360) and z (500, 361) do not match
最新发布
11-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值