ITK 形态学处理(Morph process)

本文介绍了ITK框架中如何使用NeighborhoodIterators和NeighborhoodOperators实现数学形态学滤波器,包括二值图像的腐蚀和膨胀操作。通过具体的代码示例展示了如何定义和应用构造成员,如BinaryBallStructuringElement,来执行数学形态学操作。

定义:数字图像处理中的形态学处理是指将数字形态学作为工具从图像中提取对于表达和描绘区域形状有用处的图像分量,比如边界、骨架以及凸壳,还包括用于预处理或后处理的形态学过滤、细化和修剪等。图像形态学处理中我们感兴趣的主要是二值图像。

具体定义参考博客http://blog.youkuaiyun.com/poem_qianmo/article/details/23710721

ITK中使用NeighborhoodIteratorsitk::NeighborhoodOperators来实现数学形态学滤波器。研发平台中包含有两种图像形态学算法:对二值图像操作的滤波器和对灰度尺图像操作的滤波器。

itk::BinaryErodeImageFilter  腐蚀操作

itk::BinaryDilateImageFilter  膨胀操作

ITK中的数学形态学操作通过对每个输入像素的邻域应用一个操作来实现。规则和邻域的结合被称为构造成员。

下面包含了构造一个简单地使用数学形态学滤波器需要的头文件:
#include "itkBinaryErodeImageFilter.h"
#include "itkBinaryDilateImageFilter.h"
#include "itkBinaryBallStructuringElement.h"
接下来的代码定义了输入、输出像素类型以及和它们相关的图像类型:
const unsigned int Dimension = 2;
typedef unsigned char InputPixelType;
typedef unsigned char OutputPixelType;
typedef itk::Image< InputPixelType, Dimension > InputImageType;
typedef itk::Image< OutputPixelType, Dimension > OutputImageType;

构造成员是作为一个NeighborhoodOperator 来实现的。事实上,默认的构造成员是
itk::BinaryBallStructuringElement 类。这个类是使用像素类型和输入图像维来进行实例化的:
typedef itk::BinaryBallStructuringElement<
InputPixelType,
Dimension > StructuringElementType;
然后使用构造成员类型和输入、输出图像类型一起来对滤波器类型进行实例化:
typedef itk::BinaryErodeImageFilter<
InputImageType,
OutputImageType,
StructuringElementType > ErodeFilterType;
typedef itk::BinaryDilateImageFilter<
InputImageType,
OutputImageType,
StructuringElementType > DilateFilterType;
现在通过调用 New( )方式来创建这个滤波器并将结果指向一个itk::SmartPointer
ErodeFilterType::Pointer binaryErode = ErodeFilterType::New( );
DilateFilterType::Pointer binaryDilate = DilateFilterType::New( );

构造成员不是一个引用记数类。因此它是作为一个C++堆栈而不是使用 New( )SmartPointers来创建的。使用SetRadius( )方式来定义和构造成员相关的邻域半径并调用CreateStructuringElement( )方式以便于初始化操作符。如下面所阐述的那样,使用SetKernel( )方式将构造成员的结果传递给数学形态学滤波器。

StructuringElementType structuringElement;
structuringElement.SetRadius( 1 ); // 3x3 structuring element
structuringElement.CreateStructuringElement( );

130
binaryErode->SetKernel( structuringElement );
binaryDilate->SetKernel( structuringElement );









题目:“改进经典算法的肝脏 CT 序列图像自动分割与体积测量” 解读:构建一个能自动处理一个完整CT序列(即一个病例)的流程,从读入所有切片,到逐片或整体分割,再到合成三维体积并计算其大小。 对CT图像的理解(HU值、窗宽窗位等)需要自学科普。 医学图像处理的特殊性: (1)CT图像理解:需理解HU值、窗宽窗位、部分容积效应等概念 (2)解剖结构复杂性:肝脏形状不规则,与相邻器官对比度低 (3)序列图像处理:需要处理整个3D序列,而不仅是单张图像 一、背景 肝癌术前评估需快速获得肝脏体积。本任务以临床门户提供的门静脉期 CT 切片(512×512,HU 值存为 16-bit PNG)为处理对象,要求仅使用经典图像处理技术,实现肝脏区域的自动分割,并基于分割结果计算肝脏体积。CT扫描是一系列连续的切片,形成3D结构。 核心要求:自动分割肝脏区域并计算体积,要求对两种传统算法做出可量化改进,在完全不引入深度学习的前提下,对以下两类传统方法进行原创性改进,使 IoU 相较原始版本平均提升≥1个百分点。 可直接下载、且明确标注为“门静脉期 CT 切片”的公开数据集主链接如下(均含金标准 mask,无需注册或注册后即可下载): 数据集:Medical Decathlon – Task03 Liver 链接:http://medicaldecathlon.com/ 说明:131 例腹部增强 CT,全部为门静脉期,512×512 切片,附带肝脏与肝肿瘤金标准 mask(nii.gz 格式),下载后可用 3D Slicer 或简单脚本批量转 PNG序列。 二、方法改进任务 选项1:重点改进Otsu阈值分割(推荐多数同学选择) 主要任务:完整改进Otsu方法(如多级 Otsu、自适应阈值) -附加要求:实现基础版区域生长算法作为对比方法 - 输出:需展示两种方法的分割结果对比,分析各自优缺点 三、提交要求 a) 改进动机与理论推导(至少一个核心方法的详细推导) b) 完整可运行的代码 c) 参数设置表 d) 分割结果对比图(至少包含:GT、原始方法、改进方法) e) 在5例测试数据上的IoU提升统计与显著性检验。指定使用配对 t 检验,并说明 p<0.05 为显著 评分:强调创新性、实现完整性和分析深度 六、说明 可借助简单脚本或工具(如3D Slicer)转换数据格式 可使用Python + OpenCV / SimpleITK / scikit-image 等库 鼓励查阅文献,理解经典分割方法的基本原理与改进思路。按照要求设计代码并完整输出出来
最新发布
01-09
import os import cv2 import numpy as np import json import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure from matplotlib.patches import Rectangle from pathlib import Path import tkinter as tk from tkinter import filedialog, messagebox, ttk, simpledialog from PIL import Image, ImageTk import threading class VertebraeCropper: def __init__(self): self.root = tk.Tk() self.root.title("腰椎MRI图像裁剪工具 - 专业版") self.root.geometry("1200x800") # 分割线位置(初始化为等分,共7根线将图像分成8份) self.divisions = [i/8 for i in range(1, 8)] # 预设管理 self.presets = {} self.current_preset = "default" self.load_presets() # 分割线设置 self.upper_cut_line = 2 # 默认使用第3条线作为上切割线(索引2) self.lower_cut_line = 5 # 默认使用第6条线作为下切割线(索引5) self.split_line = 3 # 默认使用第4条线作为分组线(索引3) # 创建界面元素 self.create_widgets() def create_widgets(self): # 主框架 main_frame = tk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 标题 title_label = tk.Label(main_frame, text="腰椎MRI图像批量裁剪工具 - 专业版", font=("Arial", 16, "bold")) title_label.pack(pady=10) # 说明文本 desc_text = """ 本工具用于批量处理腰椎MRI图像,将每张图像中的椎体分成两组进行裁剪: - T12-L2 (上3个椎体) - L3-L5 (下3个椎体) 您可以在预览界面中调整分割线位置,确保椎体正确分组。 裁剪后的图像会放大并铺满512×512的画布。 """ desc_label = tk.Label(main_frame, text=desc_text, justify=tk.LEFT, wraplength=1000) desc_label.pack(pady=5) # 控制框架 control_frame = tk.Frame(main_frame) control_frame.pack(pady=10, fill=tk.X) # 预设管理框架 preset_frame = tk.Frame(control_frame) preset_frame.pack(pady=5, fill=tk.X) tk.Label(preset_frame, text="预设:", width=8, anchor='w').pack(side=tk.LEFT) self.preset_var = tk.StringVar(value=self.current_preset) preset_combo = ttk.Combobox(preset_frame, textvariable=self.preset_var, width=15) preset_combo['values'] = list(self.presets.keys()) preset_combo.pack(side=tk.LEFT, padx=5) preset_combo.bind('<<ComboboxSelected>>', self.load_selected_preset) tk.Button(preset_frame, text="保存预设", command=self.save_preset).pack(side=tk.LEFT, padx=5) tk.Button(preset_frame, text="新建预设", command=self.create_new_preset).pack(side=tk.LEFT, padx=5) tk.Button(preset_frame, text="删除预设", command=self.delete_preset).pack(side=tk.LEFT, padx=5) # 输入文件夹选择 input_frame = tk.Frame(control_frame) input_frame.pack(pady=5, fill=tk.X) tk.Label(input_frame, text="输入文件夹:", width=12, anchor='w').pack(side=tk.LEFT) self.input_path = tk.StringVar() tk.Entry(input_frame, textvariable=self.input_path, width=60).pack(side=tk.LEFT, padx=5) tk.Button(input_frame, text="浏览", command=self.select_input_folder).pack(side=tk.LEFT) # 输出文件夹选择 output_frame = tk.Frame(control_frame) output_frame.pack(pady=5, fill=tk.X) tk.Label(output_frame, text="输出文件夹:", width=12, anchor='w').pack(side=tk.LEFT) self.output_path = tk.StringVar() tk.Entry(output_frame, textvariable=self.output_path, width=60).pack(side=tk.LEFT, padx=5) tk.Button(output_frame, text="浏览", command=self.select_output_folder).pack(side=tk.LEFT) # 按钮框架 button_frame = tk.Frame(control_frame) button_frame.pack(pady=10) self.preview_btn = tk.Button(button_frame, text="预览并调整分割", command=self.preview_and_adjust, width=20, height=2) self.preview_btn.pack(side=tk.LEFT, padx=10) self.process_btn = tk.Button(button_frame, text="开始批量处理", command=self.start_processing, width=20, height=2) self.process_btn.pack(side=tk.LEFT, padx=10) # 进度条 progress_frame = tk.Frame(main_frame) progress_frame.pack(pady=10, fill=tk.X) self.progress = ttk.Progressbar(progress_frame, orient=tk.HORIZONTAL, length=800, mode='determinate') self.progress.pack(fill=tk.X) self.progress_label = tk.Label(progress_frame, text="准备就绪") self.progress_label.pack(pady=5) # 状态栏 self.status_var = tk.StringVar() self.status_var.set("准备就绪") status_bar = tk.Label(main_frame, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 当前图像和分割线信息 self.current_image = None self.current_image_path = None def load_presets(self): """加载预设""" try: if os.path.exists("presets.json"): with open("presets.json", "r") as f: data = json.load(f) self.presets = data.get("presets", {"default": [i/8 for i in range(1, 8)]}) # 加载切割线设置 self.upper_cut_line = data.get("upper_cut_line", 2) self.lower_cut_line = data.get("lower_cut_line", 5) self.split_line = data.get("split_line", 3) except: self.presets = {"default": [i/8 for i in range(1, 8)]} def save_presets(self): """保存预设""" data = { "presets": self.presets, "upper_cut_line": self.upper_cut_line, "lower_cut_line": self.lower_cut_line, "split_line": self.split_line } with open("presets.json", "w") as f: json.dump(data, f) def load_selected_preset(self, event=None): """加载选中的预设""" preset_name = self.preset_var.get() if preset_name in self.presets: self.divisions = self.presets[preset_name] self.current_preset = preset_name def save_preset(self): """保存当前预设""" preset_name = self.preset_var.get() self.presets[preset_name] = self.divisions self.save_presets() messagebox.showinfo("成功", f"预设 '{preset_name}' 已保存") def create_new_preset(self): """创建新预设""" preset_name = simpledialog.askstring("新建预设", "请输入预设名称:") if preset_name: if preset_name in self.presets: messagebox.showerror("错误", "预设名称已存在") else: self.presets[preset_name] = self.divisions self.preset_var.set(preset_name) self.current_preset = preset_name self.save_presets() messagebox.showinfo("成功", f"预设 '{preset_name}' 已创建") def delete_preset(self): """删除当前预设""" preset_name = self.preset_var.get() if preset_name == "default": messagebox.showerror("错误", "不能删除默认预设") return if messagebox.askyesno("确认", f"确定要删除预设 '{preset_name}' 吗?"): del self.presets[preset_name] self.preset_var.set("default") self.current_preset = "default" self.divisions = self.presets["default"] self.save_presets() messagebox.showinfo("成功", f"预设 '{preset_name}' 已删除") def select_input_folder(self): folder = filedialog.askdirectory(title="选择包含MRI图像的文件夹") if folder: self.input_path.set(folder) def select_output_folder(self): folder = filedialog.askdirectory(title="选择保存结果的文件夹") if folder: self.output_path.set(folder) def detect_vertebrae_centers(self, image): """检测椎体中心位置""" # 转换为灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 应用高斯模糊减少噪声 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 使用自适应阈值处理 thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 形态学操作去除小噪声 kernel = np.ones((3, 3), np.uint8) thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 查找轮廓 contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 筛选出可能是椎体的轮廓 vertebra_centers = [] for contour in contours: area = cv2.contourArea(contour) if area < 500 or area > 10000: # 过滤太大或太小的区域 continue # 计算轮廓的边界矩形 x, y, w, h = cv2.boundingRect(contour) aspect_ratio = w / h # 根据纵横比筛选 if 0.5 < aspect_ratio < 2.0: # 计算轮廓的中心 M = cv2.moments(contour) if M["m00"] != 0: cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) vertebra_centers.append((cX, cY, w, h)) # 按y坐标排序(从上到下) vertebra_centers.sort(key=lambda center: center[1]) return vertebra_centers def preview_and_adjust(self): """预览图像并允许调整分割线""" input_dir = self.input_path.get() if not input_dir: messagebox.showerror("错误", "请先选择输入文件夹") return # 获取第一个图像文件 image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff') image_files = [f for f in os.listdir(input_dir) if f.lower().endswith(image_extensions)] if not image_files: messagebox.showerror("错误", "输入文件夹中没有找到图像文件") return # 加载第一张图像 self.current_image_path = os.path.join(input_dir, image_files[0]) self.current_image = cv2.imread(self.current_image_path) if self.current_image is None: messagebox.showerror("错误", "无法读取图像文件") return # 调整大小为512x512(如果还不是) if self.current_image.shape[0] != 512 or self.current_image.shape[1] != 512: self.current_image = cv2.resize(self.current_image, (512, 512)) # 创建交互式分割调整界面 self.create_adjustment_ui() def create_adjustment_ui(self): """创建分割调整界面""" # 创建新窗口 self.adjust_window = tk.Toplevel(self.root) self.adjust_window.title("调整分割线") self.adjust_window.geometry("1200x800") # 创建Matplotlib图形 fig = Figure(figsize=(12, 8), dpi=100) self.canvas = FigureCanvasTkAgg(fig, master=self.adjust_window) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 添加控制按钮的框架 button_frame = tk.Frame(self.adjust_window) button_frame.pack(fill=tk.X, pady=5) tk.Button(button_frame, text="应用分割", command=self.confirm_divisions).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="保存设置", command=self.save_settings).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="重置分割", command=self.reset_divisions).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="自动检测", command=self.auto_detect).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="添加分割线", command=self.add_division).pack(side=tk.LEFT, padx=10) tk.Button(button_frame, text="删除分割线", command=self.remove_division).pack(side=tk.LEFT, padx=10) # 添加切割线选择框架 cut_frame = tk.Frame(self.adjust_window) cut_frame.pack(fill=tk.X, pady=5) tk.Label(cut_frame, text="上切割线:", width=10).pack(side=tk.LEFT) self.upper_cut_var = tk.IntVar(value=self.upper_cut_line) upper_spin = tk.Spinbox(cut_frame, from_=0, to=len(self.divisions)-1, textvariable=self.upper_cut_var, width=5) upper_spin.pack(side=tk.LEFT, padx=5) tk.Label(cut_frame, text="下切割线:", width=10).pack(side=tk.LEFT) self.lower_cut_var = tk.IntVar(value=self.lower_cut_line) lower_spin = tk.Spinbox(cut_frame, from_=0, to=len(self.divisions)-1, textvariable=self.lower_cut_var, width=5) lower_spin.pack(side=tk.LEFT, padx=5) tk.Label(cut_frame, text="分组线:", width=10).pack(side=tk.LEFT) self.split_var = tk.IntVar(value=self.split_line) split_spin = tk.Spinbox(cut_frame, from_=0, to=len(self.divisions)-1, textvariable=self.split_var, width=5) split_spin.pack(side=tk.LEFT, padx=5) # 创建子图 ax = fig.add_subplot(111) ax.imshow(cv2.cvtColor(self.current_image, cv2.COLOR_BGR2RGB)) ax.set_title('调整分割线位置 (拖拽红线调整,点击按钮添加/删除分割线)') # 绘制初始分割线 self.division_lines = [] height = self.current_image.shape[0] for i, ratio in enumerate(self.divisions): y_pos = int(height * ratio) color = 'red' if i == self.upper_cut_line: color = 'green' elif i == self.lower_cut_line: color = 'blue' elif i == self.split_line: color = 'yellow' line = ax.axhline(y=y_pos, color=color, linestyle='--', linewidth=2, picker=5) self.division_lines.append(line) # 添加分割线标签 ax.text(10, y_pos + 5, f"线 {i+1}", color=color, bbox=dict(facecolor='white', alpha=0.7)) # 绘制椎体分组标签 ax.text(10, height * 0.25, "T12-L2", color='green', fontsize=14, bbox=dict(facecolor='white', alpha=0.7)) ax.text(10, height * 0.75, "L3-L5", color='blue', fontsize=14, bbox=dict(facecolor='white', alpha=0.7)) # 设置交互功能 self.current_dragging_line = None self.canvas.mpl_connect('pick_event', self.on_pick) self.canvas.mpl_connect('motion_notify_event', self.on_motion) self.canvas.mpl_connect('button_release_event', self.on_release) fig.tight_layout() self.canvas.draw() def on_pick(self, event): """当用户选择一条分割线时""" if event.artist in self.division_lines: self.current_dragging_line = event.artist def on_motion(self, event): """当用户拖动分割线时""" if self.current_dragging_line is not None and event.ydata is not None: # 限制分割线在图像范围内 y_pos = max(0, min(event.ydata, self.current_image.shape[0])) self.current_dragging_line.set_ydata([y_pos, y_pos]) self.canvas.draw() def on_release(self, event): """当用户释放鼠标时""" self.current_dragging_line = None def add_division(self): """添加分割线""" # 在中间位置添加新分割线 height = self.current_image.shape[0] new_y = height // 2 ax = self.canvas.figure.axes[0] line = ax.axhline(y=new_y, color='red', linestyle='--', linewidth=2, picker=5) self.division_lines.append(line) # 添加标签 ax.text(10, new_y + 5, f"线 {len(self.division_lines)}", color='red', bbox=dict(facecolor='white', alpha=0.7)) # 更新分割线选择框的范围 self.update_spinbox_ranges() self.canvas.draw() def remove_division(self): """删除最后一条分割线""" if len(self.division_lines) > 1: # 至少保留一条分割线 line = self.division_lines.pop() line.remove() # 更新分割线选择框的范围 self.update_spinbox_ranges() self.canvas.draw() else: messagebox.showwarning("警告", "至少需要保留一条分割线") def update_spinbox_ranges(self): """更新分割线选择框的范围""" max_line = len(self.division_lines) - 1 for widget in self.adjust_window.winfo_children(): if isinstance(widget, tk.Frame): for child in widget.winfo_children(): if isinstance(child, tk.Spinbox): child.configure(to=max_line) def save_settings(self): """保存当前分割线设置""" # 更新切割线设置 self.upper_cut_line = self.upper_cut_var.get() self.lower_cut_line = self.lower_cut_var.get() self.split_line = self.split_var.get() # 保存到文件 self.save_presets() messagebox.showinfo("成功", "分割线设置已保存") def confirm_divisions(self): """确认当前分割线位置(应用)""" # 更新分割线位置(归一化) height = self.current_image.shape[0] self.divisions = [line.get_ydata()[0] / height for line in self.division_lines] # 更新切割线设置 self.upper_cut_line = self.upper_cut_var.get() self.lower_cut_line = self.lower_cut_var.get() self.split_line = self.split_var.get() # 排序分割线 self.divisions.sort() # 关闭调整窗口 self.adjust_window.destroy() # 显示预览结果 self.show_preview_result() def reset_divisions(self): """重置分割线到初始位置""" height = self.current_image.shape[0] # 重置为等分 self.divisions = [i/8 for i in range(1, 8)] # 清除所有分割线 for line in self.division_lines: line.remove() self.division_lines = [] # 重新绘制分割线 ax = self.canvas.figure.axes[0] for i, ratio in enumerate(self.divisions): y_pos = int(height * ratio) color = 'red' if i == self.upper_cut_line: color = 'green' elif i == self.lower_cut_line: color = 'blue' elif i == self.split_line: color = 'yellow' line = ax.axhline(y=y_pos, color=color, linestyle='--', linewidth=2, picker=5) self.division_lines.append(line) # 添加分割线标签 ax.text(10, y_pos + 5, f"线 {i+1}", color=color, bbox=dict(facecolor='white', alpha=0.7)) self.canvas.draw() def auto_detect(self): """自动检测椎体并设置分割线""" vertebra_centers = self.detect_vertebrae_centers(self.current_image) if len(vertebra_centers) >= 6: # 获取椎体的y坐标 y_positions = [center[1] for center in vertebra_centers[:6]] # 计算分割线位置(在椎体之间) new_divisions = [] for i in range(5): division = (y_positions[i] + y_positions[i+1]) / 2 new_divisions.append(division / self.current_image.shape[0]) # 添加额外的分割线以保持总数 while len(new_divisions) < 7: # 确保有7条分割线 new_divisions.append(new_divisions[-1] + 0.05) # 更新分割线 height = self.current_image.shape[0] # 清除所有分割线 for line in self.division_lines: line.remove() self.division_lines = [] # 重新绘制分割线 ax = self.canvas.figure.axes[0] for i, ratio in enumerate(new_divisions): y_pos = int(height * ratio) color = 'red' if i == self.upper_cut_line: color = 'green' elif i == self.lower_cut_line: color = 'blue' elif i == self.split_line: color = 'yellow' line = ax.axhline(y=y_pos, color=color, linestyle='--', linewidth=2, picker=5) self.division_lines.append(line) # 添加分割线标签 ax.text(10, y_pos + 5, f"线 {i+1}", color=color, bbox=dict(facecolor='white', alpha=0.7)) self.canvas.draw() else: messagebox.showwarning("警告", f"只检测到 {len(vertebra_centers)} 个椎体,无法自动分割") def show_preview_result(self): """显示预览结果""" # 创建新窗口显示裁剪结果 preview_window = tk.Toplevel(self.root) preview_window.title("预览裁剪结果") preview_window.geometry("1200x600") # 裁剪图像 group1, group2 = self.crop_vertebrae_groups(self.current_image) # 显示结果 result_frame = tk.Frame(preview_window) result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 显示T12-L2组 tk.Label(result_frame, text="T12-L2", font=("Arial", 12, "bold")).grid(row=0, column=0, pady=5) group1_img = Image.fromarray(cv2.cvtColor(group1, cv2.COLOR_BGR2RGB)) group1_img = group1_img.resize((450, 450), Image.LANCZOS) group1_tk = ImageTk.PhotoImage(group1_img) group1_label = tk.Label(result_frame, image=group1_tk) group1_label.image = group1_tk group1_label.grid(row=1, column=0, padx=10) # 显示L3-L5组 tk.Label(result_frame, text="L3-L5", font=("Arial", 12, "bold")).grid(row=0, column=1, pady=5) group2_img = Image.fromarray(cv2.cvtColor(group2, cv2.COLOR_BGR2RGB)) group2_img = group2_img.resize((450, 450), Image.LANCZOS) group2_tk = ImageTk.PhotoImage(group2_img) group2_label = tk.Label(result_frame, image=group2_tk) group2_label.image = group2_tk group2_label.grid(row=1, column=1, padx=10) # 确认按钮 confirm_frame = tk.Frame(preview_window) confirm_frame.pack(pady=10) tk.Button(confirm_frame, text="确认并开始批量处理", command=lambda: [preview_window.destroy(), self.start_processing()]).pack() def crop_vertebrae_groups(self, image): """根据当前分割线裁剪椎体组""" height, width = image.shape[:2] # 确保切割线索引有效 upper_idx = min(self.upper_cut_line, len(self.divisions) - 1) lower_idx = min(self.lower_cut_line, len(self.divisions) - 1) split_idx = min(self.split_line, len(self.divisions) - 1) # 确保切割线顺序正确 if upper_idx >= lower_idx: upper_idx, lower_idx = 0, len(self.divisions) - 1 if split_idx <= upper_idx or split_idx >= lower_idx: split_idx = (upper_idx + lower_idx) // 2 # 获取切割线位置 sorted_divisions = sorted(self.divisions) y_upper = int(height * sorted_divisions[upper_idx]) y_lower = int(height * sorted_divisions[lower_idx]) y_split = int(height * sorted_divisions[split_idx]) # 裁剪T12-L2组(上组)- 只保留上切割线和分组线之间的部分 top_group = image[y_upper:y_split, :] # 裁剪L3-L5组(下组)- 只保留分组线和下切割线之间的部分 bottom_group = image[y_split:y_lower, :] # 创建512x512的输出图像,放大并铺满 def create_output_image(region): if region.size == 0: return np.zeros((512, 512, 3), dtype=np.uint8) # 计算缩放比例 - 放大到至少512x512 h, w = region.shape[:2] # 确定缩放比例,使图像至少有一个维度达到512 scale = max(512/h, 512/w) # 调整大小 new_h, new_w = int(h * scale), int(w * scale) resized = cv2.resize(region, (new_w, new_h)) # 创建512x512的画布 canvas = np.zeros((512, 512, 3), dtype=np.uint8) # 计算放置位置(居中) y_offset = (512 - new_h) // 2 x_offset = (512 - new_w) // 2 # 确保不超出边界 y_end = min(512, y_offset + new_h) x_end = min(512, x_offset + new_w) # 将调整大小后的图像放入画布中 canvas[y_offset:y_end, x_offset:x_end] = resized[:y_end-y_offset, :x_end-x_offset] return canvas group1 = create_output_image(top_group) group2 = create_output_image(bottom_group) return group1, group2 def start_processing(self): """开始批量处理图像""" input_dir = self.input_path.get() output_dir = self.output_path.get() if not input_dir or not output_dir: messagebox.showerror("错误", "请先选择输入和输出文件夹") return # 创建输出目录 os.makedirs(output_dir, exist_ok=True) # 获取图像文件 image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.tiff') image_files = [f for f in os.listdir(input_dir) if f.lower().endswith(image_extensions)] if not image_files: messagebox.showerror("错误", "输入文件夹中没有找到图像文件") return # 在后台线程中处理图像 self.processing_thread = threading.Thread( target=self.process_images, args=(input_dir, output_dir, image_files) ) self.processing_thread.daemon = True self.processing_thread.start() def process_images(self, input_dir, output_dir, image_files): """批量处理图像""" # 设置进度条 self.progress['maximum'] = len(image_files) self.progress['value'] = 0 # 处理每个图像 success_count = 0 for i, image_file in enumerate(image_files): self.status_var.set(f"处理中: {image_file}") self.progress_label.config(text=f"处理中: {image_file} ({i+1}/{len(image_files)})") # 更新GUI self.root.update_idletasks() image_path = os.path.join(input_dir, image_file) image = cv2.imread(image_path) if image is not None: # 调整大小为512x512(如果还不是) if image.shape[0] != 512 or image.shape[1] != 512: image = cv2.resize(image, (512, 512)) # 裁剪椎体组 group1, group2 = self.crop_vertebrae_groups(image) # 保存结果 filename = Path(image_file).stem cv2.imwrite(os.path.join(output_dir, f"{filename}_T12-L2.jpg"), group1) cv2.imwrite(os.path.join(output_dir, f"{filename}_L3-L5.jpg"), group2) success_count += 1 self.progress['value'] = i + 1 # 完成处理 self.status_var.set(f"处理完成: 成功处理 {success_count}/{len(image_files)} 张图像") self.progress_label.config(text="处理完成") messagebox.showinfo("完成", f"处理完成: 成功处理 {success_count}/{len(image_files)} 张图像") def run(self): self.root.mainloop() # 运行程序 if __name__ == "__main__": app = VertebraeCropper() app.run()
08-28
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值