深度学习实战-基于U-Net视网膜血管图像分割模型 ‍♂️ 个人主页艾派森的个人主页✍作者简介Python学习者 希望大家多多支持我们一起进步如果文章对你有帮助的话欢迎评论 点赞 收藏 加关注目录1.项目背景2.数据集介绍3.技术工具4.实验过程4.1导入数据4.2数据可视化4.3构建模型4.4训练模型4.5模型评估4.6模型预测5.总结源代码1.项目背景在现代眼科学的数字化诊断进程中视网膜血管的形态学特征——如血管的粗细、分支角度、扭曲程度以及动静脉比值——是筛查糖尿病视网膜病变、高血压性视网膜病变以及青光眼等致盲性疾病的关键临床指标。然而眼底影像通常伴随着光照不均、视盘干扰以及病理渗出物等复杂的背景噪声传统的临床评估高度依赖资深医师的手工勾勒这不仅耗时耗力且在处理大批量防盲筛查任务时存在较强的主观偏差。实现视网膜血管的自动化、高精度分割已成为构建智能化眼科诊断系统、实现早发现与早治疗的技术瓶颈。本项目依托于经典且强大的U-Net语义分割架构致力于在像素层面攻克视网膜微细血管提取的难题。U-Net 独特的“对称式”结构通过编码器捕获眼底影像的全局病理语义并利用关键的跳跃连接Skip Connections将浅层的高清纹理细节直接补偿给解码器从而在大幅下采样的同时极大地降低了毛细血管等微小目标的特征流失。本实战不仅涵盖了从 Kaggle 专业医学数据集的自动化加载、基于albumentations的强力数据增强到深度卷积网络的逐层搭建与训练监控的全过程更通过多维度的 IoU 性能评估与推理可视化验证了深度学习在处理极度不平衡医疗影像任务中的优越性。这不仅是一场关于算法精度演进的实操更是为未来构建低成本、标准化的眼病自动筛查范式提供了切实可行的工程路径。2.数据集介绍本实验数据集来源于Kaggle该数据集是推进医学图像分析领域发展和提升视网膜血管疾病诊断水平的宝贵资源。本数据集包含大量视网膜眼底图像并经过精心标注用于血管分割。准确的血管分割是眼科领域的一项关键任务有助于早期发现和治疗各种视网膜疾病例如糖尿病视网膜病变和黄斑变性。主要特点图像尺寸数据集中的图像尺寸各异从 XXX 像素到 XXX 像素不等模拟了真实世界中视网膜图像的多样性。标注每张图像都提供了相应的像素级标注采用二值掩码格式。血管像素标记为 1背景像素标记为 0。病理变异该数据集涵盖了一系列视网膜疾病包括不同的血管宽度、分支模式和异常情况使其适用于评估分割模型的鲁棒性。应用案例医学图像分析、计算机视觉和人工智能领域的研究人员和从业人员会发现该数据集在以下几个应用场景中具有极高的价值算法开发利用该数据集训练和测试创新的分割算法借助精确的标注信息获得准确可靠的结果。疾病检测创建能够辅助早期检测视网膜病变的模型从而有助于及时进行医疗干预。教育该数据集可用于教育目的帮助学生和专业人士理解视网膜血管结构的复杂性。评估指标性能评估主要包括将分割结果与真实标注进行比较以衡量分割精度。常用的指标例如交并比 (IoU)、Dice 系数和像素级准确率可用于量化模型的性能。3.技术工具Python版本:3.9代码编辑器jupyter notebook4.实验过程4.1导入数据在视网膜血管分割这类对边缘极其敏感的任务中数据预处理与增强策略直接决定了模型对细小微血管的捕捉上限。本阶段我们首先集成了 TensorFlow/Keras 深度学习框架与专业的影像增强库albumentations后者在处理医学影像的几何变换如翻转、旋转、随机缩放时能够确保图像与掩码Mask的同步性这对于像素对齐至关重要。我们设计的load_data流水线不仅实现了眼底原图与血管金标准Gold Standard的自动化路径配对还通过load_image函数完成了尺寸归一化与通道校准。针对医疗样本通常存在的稀缺性问题我们在加载过程中嵌入了实时增强机制通过模拟不同的拍摄角度与尺度变化强迫网络学习血管的拓扑结构而非位置死记为后续 U-Net 网络解析复杂的视网膜神经网络奠定了坚实的工程基石。# --- 1. 导入核心工程库涵盖数据分析、模型构建与性能度量 --- import pandas as pd import numpy as np import matplotlib.pyplot as plt # 采用深色绘图背景更清晰地观察血管细微分支 plt.style.use(dark_background) import os import math # 数据处理与进度监控 from sklearn.model_selection import train_test_split import cv2 from tqdm import tqdm # 深度学习框架组件 import tensorflow as tf from tensorflow.keras import backend as K import keras from keras.models import Model from keras.layers import Input,Conv2D,MaxPool2D,Conv2DTranspose,Resizing,Concatenate,Activation from keras.optimizers import Adam from keras.callbacks import EarlyStopping,ModelCheckpoint import albumentations as A from albumentations.core.composition import OneOf from tensorflow.keras.metrics import * import warnings warnings.filterwarnings(ignore) # --- 2. 定义增强策略针对眼底图像特征进行几何变换 --- augmentation A.Compose([ A.HorizontalFlip(p0.5), # 随机水平翻转模拟左右眼镜像 A.Rotate(limit30, p0.3), # 随机小角度旋转模拟拍摄倾斜 A.RandomScale(scale_limit0.2, p0.3), # 随机缩放应对不同视野大小 A.Resize(512, 512) # 统一重采样至 512x512 ]) # --- 3. 定义单张影像加载与归一化逻辑 --- def load_image(path, size, maskFalse): 加载影像并进行通道预处理与灰度归一化 image cv2.imread(path) image cv2.resize(image, (size, size)) if mask: # 掩码采用灰度读取代表血管的真值分布 image cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: # 原始眼底图转换为 RGB 格式 image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 像素值归一化至 [0, 1] 空间加速梯度下降收敛 image image / 255.0 return image # --- 4. 构建自动化数据加载流水线 --- def load_data(root_path, size, augmentFalse): 遍历目录结构实现原图与掩码的精准匹配及批处理加载 images, masks [], [] image_paths, mask_paths [], [] # 递归检索文件夹自动区分 Image 与 Mask 路径 for folder in os.listdir(root_path): folder_path os.path.join(root_path, folder) if not os.path.isdir(folder_path): continue is_mask folder.lower() mask for img in os.listdir(folder_path): image_path os.path.join(folder_path, img) if is_mask: mask_paths.append(image_path) else: image_paths.append(image_path) # 路径排序确保每一个眼底样本都对应其正确的血管标签 image_paths.sort() mask_paths.sort() # 利用 tqdm 监控大规模数据读取进度 for img_path, mask_path in tqdm(zip(image_paths, mask_paths), totallen(image_paths), desc加载并执行实时增强): img load_image(img_path, size, maskFalse) mask load_image(mask_path, size, maskTrue) # 训练集执行数据增强提升模型的泛化边界 if augment: augmented augmentation(imageimg, maskmask) img augmented[image] mask augmented[mask] images.append(img) masks.append(mask) return np.array(images), np.array(masks) # --- 5. 执行数据加载完成训练集与测试集的初始化 --- X, y load_data(/kaggle/input/retina-blood-vessel/Data/train, 512, augmentTrue) X_test, y_test load_data(/kaggle/input/retina-blood-vessel/Data/test, 512)4.2数据可视化为了确保训练集中的每一张眼底图都精准对应其血管分布标签我们编写了show_images_and_masks函数。该函数采用了多行多列的矩阵布局将原始 RGB 影像与灰度掩码成对排列。在深色背景的衬托下我们可以看到血管在 Mask 中呈现为明亮的白色细线而背景则被完全抑制为黑色。通过观察随机抽取的 12 组样本可以确认数据增强后的影像依然保持了极佳的对齐性且血管分支的细节在 512 像素的分辨率下得到了充分保留。这种“所见即所得”的核验方式能够有效排除因路径错位导致的训练失效为模型在微小血管上的定位精度提供了第一道质量防线。# --- 1. 定义多样本配对可视化函数 --- def show_images_and_masks(images, masks, num_samples8): 以矩阵排列方式同时展示原始眼底图与对应的血管分割掩码 # 确保请求的样本数不超过数据集实际规模 num_samples min(num_samples, len(images), len(masks)) # 配置绘图布局每行显示 4 对共 8 列 cols 2 pairs_per_row 4 rows math.ceil(num_samples / pairs_per_row) # 创建画布设置适合大批量样本展示的尺寸 fig, axes plt.subplots(rows, pairs_per_row * cols, figsize(pairs_per_row * 5, rows * 3.5)) # 处理单行显示的特殊情况保持 axes 索引的一致性 if rows 1: axes np.expand_dims(axes, 0) fig.suptitle(视网膜血管分割原始影像与金标准对比, fontsize18, colorwhite) for i in range(num_samples): # 提取当前样本对 image images[i] mask masks[i] # 计算当前子图在矩阵中的行列索引 row i // pairs_per_row col_pair (i % pairs_per_row) * 2 # 绘制原始眼底照片 ax_img axes[row, col_pair] ax_img.imshow(image) ax_img.set_title(原始影像 (Image), fontsize12) ax_img.axis(off) # 绘制对应的血管掩码 ax_mask axes[row, col_pair 1] cmap gray if len(mask.shape) 2 else None ax_mask.imshow(mask, cmapcmap) ax_mask.set_title(血管掩码 (Mask), fontsize12) ax_mask.axis(off) # 清理多余的空白子图槽位 total_slots rows * pairs_per_row for i in range(num_samples, total_slots): row i // pairs_per_row col_pair (i % pairs_per_row) * 2 axes[row, col_pair].axis(off) axes[row, col_pair 1].axis(off) # 优化布局防止标题重叠 plt.tight_layout() plt.subplots_adjust(top0.88) plt.show() # --- 2. 随机抽取 12 组样本进行视觉合规性核验 --- show_images_and_masks(X, y, num_samples12)4.3构建模型在正式构建网络前我们首先对掩码数据执行了np.expand_dims操作为其增加通道维度以匹配 Keras 的张量输入规范。核心架构函数unet_model严格遵循了收缩路径Encoder与扩张路径Decoder的平衡。编码器模块通过连续的 3 x 3 卷积与 ReLU 激活提取深层病理特征而解码器模块则引入了关键的跳跃连接Skip Connections——利用Concatenate将编码阶段保留的原始高清细节直接“喂”给上采样层。这种设计有效缓解了深层网络在下采样过程中的信息流失确保模型在最终输出阶段能够精准定位每一处血管分叉。最后通过一个 1 x 1 卷积层配合 Sigmoid 激活函数我们将多维特征压缩为单通道的概率图实现了从复杂影像到二值化掩码的像素级映射。# --- 1. 维度对齐为掩码数据增加通道维度 (H, W) - (H, W, 1) --- y np.expand_dims(y, -1) y_test np.expand_dims(y_test, -1) # --- 2. 定义编码器模块收缩路径提取深层抽象特征 --- def encoder_block(inputs, num_filters): 包含双层卷积与最大池化用于捕捉图像的语义信息 x Conv2D(num_filters, 3, paddingsame)(inputs) x Activation(relu)(x) x Conv2D(num_filters, 3, paddingsame)(x) x Activation(relu)(x) # 空间维度减半通道数翻倍深度提取病灶特征 x MaxPool2D(pool_size(2, 2))(x) return x # --- 3. 定义解码器模块扩张路径恢复空间分辨率 --- def decoder_block(inputs, skip_features, num_filters): 通过转置卷积与跳跃连接将深层语义与浅层细节融合 # 上采样将特征图尺寸放大 2 倍 x Conv2DTranspose(num_filters, (2, 2), strides2, paddingsame)(inputs) # 鲁棒性处理确保跳跃连接的特征图尺寸与上采样后的张量完全对齐 skip_features Resizing(x.shape[1], x.shape[2])(skip_features) # 核心步骤特征拼接弥补下采样丢失的空间坐标信息 x Concatenate()([x, skip_features]) # 拼接后再次卷积平滑特征过渡 x Conv2D(num_filters, 3, paddingsame)(x) x Activation(relu)(x) x Conv2D(num_filters, 3, paddingsame)(x) x Activation(relu)(x) return x # --- 4. 封装 U-Net 完整模型 --- def unet_model(input_shape(256, 256, 3), num_classes1): inputs tf.keras.layers.Input(shapeinput_shape) # --- 编码器阶段 (Contracting Path) --- s1 encoder_block(inputs, 64) s2 encoder_block(s1, 128) s3 encoder_block(s2, 256) s4 encoder_block(s3, 512) # --- 瓶颈层 (Bottleneck) --- # 位于 U 型底部的最深层捕捉全局视野下的最抽象特征 b1 Conv2D(1024, 3, paddingsame)(s4) b1 Activation(relu)(b1) b1 Conv2D(1024, 3, paddingsame)(b1) b1 Activation(relu)(b1) # --- 解码器阶段 (Expansive Path) --- d1 decoder_block(b1, s4, 512) d2 decoder_block(d1, s3, 256) d3 decoder_block(d2, s2, 128) d4 decoder_block(d3, s1, 64) # --- 输出层 --- # 采用 Sigmoid 激活输出每个像素属于血管的概率值 [0, 1] outputs Conv2D(num_classes, 1, paddingsame, activationsigmoid)(d4) model Model(inputsinputs, outputsoutputs, nameU-Net_Retina) return model # --- 5. 实例化并审查模型参数 --- # 针对视网膜影像我们采用 512x512 的高分辨率输入以保留微血管 model unet_model(input_shape(512, 512, 3), num_classes1) model.summary()通过model.summary()的输出可以观察到整个网络呈现出完美的对称结构。特别是解码器部分的参数量非常庞大这正是因为它需要不断整合来自编码器的跳跃连接数据。在医疗影像分割中这种“慢速下降、精准上升”的策略是应对视网膜血管这类极端细长目标的良药。随着模型架构的就绪接下来的任务是为其注入“灵魂”——定义专门针对极度不平衡数据的损失函数与评价指标。4.4训练模型在模型编译环节我们采用了Adam优化器并配合Binary Cross-entropy损失函数。考虑到血管分割任务的特殊性我们特别引入了MeanIoU作为监控指标它能比单纯的准确率更客观地反映预测区域与真值区域的重叠程度。在回调函数Callbacks的设计上我们构建了两道防线ModelCheckpoint负责实时扫描并锁定验证集表现最优的时刻将其固化为.keras模型文件而EarlyStopping则充当了“性能守望者”如果模型在连续 10 个 Epoch 内无法刷新最佳战绩则会自动触发停机并回滚至最优状态。这种“小批次、多轮次”的训练策略配合 0.1 的验证集比例使得U-Net能够在有限的眼底样本中稳健地提取血管纹理。# --- 1. 模型编译配置优化器、损失函数与语义分割评价指标 --- model.compile( optimizeradam, # 采用自适应矩估计优化器确保收敛平稳 lossbinary_crossentropy, # 针对二分类掩码的像素级交叉熵损失 # MeanIoU 能更真实地反映血管区域的分割质量 metrics[accuracy, MeanIoU(num_classes2)] ) # --- 2. 配置智能监控回调函数 --- # 自动保存验证集表现最优秀的模型权重 checkpoint_cb ModelCheckpoint( best_model.keras, save_best_onlyTrue, # 仅保留历史表现最好的权重 monitorval_loss, # 以验证集损失作为评判标准 modemin, # 目标是最小化损失值注原代码中max需根据monitor调整 verbose1 ) # 动态早停防止过度拟合节约计算资源 early_stopping_cb EarlyStopping( monitorval_loss, patience10, # 容忍 10 轮无性能提升 modemin, restore_best_weightsTrue, # 训练结束后自动加载表现最好的权重 verbose1 ) # --- 3. 启动模型迭代流程 --- # 在 512x512 高分辨率下Batch Size 设为 8 以平衡显存压力与梯度更新频率 history model.fit( X, y, validation_split0.1, # 划出 10% 的训练数据用于在线验证 epochs100, # 设置最大迭代上限 batch_size8, callbacks[checkpoint_cb, early_stopping_cb], verbose1 )4.5模型评估评估的第一步是审视训练全周期的历史轨迹。我们利用matplotlib构建了三联排的可视化图表分别监控 Loss损失、Accuracy准确率以及 Mean IoU平均交并比。在血管分割中Mean IoU 是含金量最高的指标它反映了模型预测的血管区域与医生手工标注区域的重叠程度。理想的状态是训练曲线与验证曲线保持高度同步下降或上升且在触发早停机制前达到平稳的平台期。如果发现验证集 IoU 在后期出现剧烈波动通常预示着模型在尝试过度拟合某些非典型的眼底渗出物或视盘边缘而我们通过回调函数锁定的“巅峰权重”正是为了规避这一风险。# --- 1. 绘制三位一体的训练监控曲线 --- fig, ax plt.subplots(1, 3, figsize(15, 4)) # 绘制 Loss 曲线观察模型对像素分类误差的整体收敛情况 ax[0].plot(history.epoch, history.history[loss], label训练损失 (Train)) ax[0].plot(history.epoch, history.history[val_loss], label验证损失 (Val)) ax[0].set_title(损失函数 (Loss)) ax[0].legend() # 绘制 Accuracy 曲线监控整体像素层面的分类正确率 ax[1].plot(history.epoch, history.history[accuracy], label训练准确率 (Train)) ax[1].plot(history.epoch, history.history[val_accuracy], label验证准确率 (Val)) ax[1].set_title(准确率 (Accuracy)) ax[1].legend() # 绘制 Mean IoU 曲线这是衡量血管重叠精度的核心硬指标 # 注意Key 名需与模型编译时定义的指标名称一致 ax[2].plot(history.epoch, history.history[mean_io_u], label训练 IoU (Train)) ax[2].plot(history.epoch, history.history[val_mean_io_u], label验证 IoU (Val)) ax[2].set_title(平均交并比 (Mean IoU)) ax[2].legend() fig.suptitle(模型训练全周期指标演进图, fontsize16) plt.tight_layout() plt.show()results model.evaluate(X_test, y_test, verbose-1) for name, value in zip([Test Loss,Test Accuracy,Test Mean IOU], results): print(f{name}: {value:.4f})4.6模型预测评估模型在实际应用中的表现需要将模型预测的概率图进行二值化处理。我们设定了0.5作为判定界限概率高于此值的像素被标记为血管白色低于此值的则判定为背景黑色。通过display_predictions函数我们将原始眼底图、专家标注的真值Ground Truth以及模型生成的预测图Predicted Mask进行三位一体的横向对比。这种直观的可视化能让我们立刻发现模型在视盘边缘或黄斑区是否存在误判以及 U-Net 的跳跃连接是否成功保留了那些末梢微血管的连通性。# --- 1. 执行全测试集推理 --- # 获取模型对测试集 X_test 的原始概率预测图 y_pred model.predict(X_test) # --- 2. 阈值化处理将 [0, 1] 的连续概率转化为二值掩码 --- # 设定 0.5 为分类界限保留高置信度的血管像素 y_pred_thresholded (y_pred 0.5).astype(np.uint8) # --- 3. 定义三视图对比函数 --- def display_predictions(X_data, y_true, y_pred, index0): 并排显示原始眼底图、医生标注真值与模型预测结果 image X_data[index] true_mask y_true[index].squeeze() # 移除通道维度用于显示 pred_mask y_pred[index].squeeze() fig, axes plt.subplots(1, 3, figsize(15, 5)) # 绘制原始彩色眼底影像 axes[0].imshow(image) axes[0].set_title(原始眼底影像 (Original), fontsize12) axes[0].axis(off) # 绘制专家标注的血管金标准 axes[1].imshow(true_mask, cmapgray) axes[1].set_title(专家标注真值 (Ground Truth), fontsize12) axes[1].axis(off) # 绘制 U-Net 预测生成的血管掩码 axes[2].imshow(pred_mask, cmapgray) axes[2].set_title(模型预测结果 (Predicted), fontsize12) axes[2].axis(off) plt.tight_layout() plt.show() # --- 4. 随机抽取测试样本进行视觉验证 --- # 查看索引为 0 的样本检查血管末梢的还原度 display_predictions(X_test, y_test, y_pred_thresholded, index0)5.总结本实验依托于 Kaggle 提供的专业视网膜眼底影像数据集通过构建对称式的U-Net深度卷积网络成功实现了对复杂血管拓扑结构的像素级自动化分割。实验结果表明模型在测试集上达到了0.8770的像素准确率并在最具挑战性的平均交并比Mean IoU指标上取得了0.4734的成绩。虽然视网膜血管极度稀疏且形态纤细给分割任务带来了巨大的数据不平衡压力但 U-Net 凭借其特有的跳跃连接机制在抑制背景噪声的同时有效地还原了微细血管的连通性。这种精准的数字化剥离能力不仅验证了深度学习在处理高精度医疗影像任务中的优越性更为糖尿病视网膜病变、黄斑变性等致盲性眼疾的早期辅助诊断提供了关键的技术支撑展示了 AI 在提升临床筛查效率与诊断一致性方面的巨大潜力。源代码# Common import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.style.use(dark_background) import os import math # Data from sklearn.model_selection import train_test_split import cv2 from tqdm import tqdm # Model import tensorflow as tf from tensorflow.keras import backend as K import keras from keras.models import Model from keras.layers import Input,Conv2D,MaxPool2D,Conv2DTranspose,Resizing,Concatenate,Activation from keras.optimizers import Adam from keras.callbacks import EarlyStopping,ModelCheckpoint import albumentations as A from albumentations.core.composition import OneOf # Metrics from tensorflow.keras.metrics import * # Ignore import warnings warnings.filterwarnings(ignore) augmentation A.Compose([ A.HorizontalFlip(p0.5), A.Rotate(limit30, p0.3), A.RandomScale(scale_limit0.2, p0.3), A.Resize(512, 512) ]) def load_image(path, size, maskFalse): image cv2.imread(path) image cv2.resize(image, (size, size)) if mask: image cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #image image[..., np.newaxis] # add channel dimension for consistency else: image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image image / 255.0 return image def load_data(root_path, size, augmentFalse): images [] masks [] image_paths [] mask_paths [] for folder in os.listdir(root_path): folder_path os.path.join(root_path, folder) if not os.path.isdir(folder_path): continue is_mask folder.lower() mask for img in os.listdir(folder_path): image_path os.path.join(folder_path, img) if is_mask: mask_paths.append(image_path) else: image_paths.append(image_path) # Sort paths to match images with masks image_paths.sort() mask_paths.sort() for img_path, mask_path in tqdm(zip(image_paths, mask_paths), totallen(image_paths), descLoading with Augmentation): img load_image(img_path, size, maskFalse) mask load_image(mask_path, size, maskTrue) if augment: augmented augmentation(imageimg, maskmask) img augmented[image] mask augmented[mask] images.append(img) masks.append(mask) return np.array(images), np.array(masks) X,y load_data(/kaggle/input/retina-blood-vessel/Data/train,512,augmentTrue) X_test,y_test load_data(/kaggle/input/retina-blood-vessel/Data/test,512) def show_images_and_masks(images, masks, num_samples8): num_samples min(num_samples, len(images), len(masks)) cols 2 pairs_per_row 4 rows math.ceil(num_samples / pairs_per_row) fig, axes plt.subplots(rows, pairs_per_row * cols, figsize(pairs_per_row * 5, rows * 3.5)) if rows 1: axes np.expand_dims(axes, 0) fig.suptitle(Retina Blood Vessel Segmentation: Images and Masks, fontsize18, colorwhite) for i in range(num_samples): image images[i] mask masks[i] row i // pairs_per_row col_pair (i % pairs_per_row) * 2 ax_img axes[row, col_pair] ax_mask axes[row, col_pair 1] ax_img.imshow(image) ax_img.set_title(Image, fontsize12) ax_img.axis(off) cmap gray if len(mask.shape) 2 else None ax_mask.imshow(mask, cmapcmap) ax_mask.set_title(Mask, fontsize12) ax_mask.axis(off) total_slots rows * pairs_per_row for i in range(num_samples, total_slots): row i // pairs_per_row col_pair (i % pairs_per_row) * 2 axes[row, col_pair].axis(off) axes[row, col_pair 1].axis(off) plt.tight_layout() plt.subplots_adjust(top0.88) plt.show() show_images_and_masks(X, y, num_samples12) y np.expand_dims(y, -1) y_test np.expand_dims(y_test, -1) def encoder_block(inputs, num_filters): x Conv2D(num_filters, 3, paddingsame)(inputs) x Activation(relu)(x) x Conv2D(num_filters, 3, paddingsame)(x) x Activation(relu)(x) x MaxPool2D(pool_size(2, 2))(x) return x def decoder_block(inputs, skip_features, num_filters): x Conv2DTranspose(num_filters, (2, 2), strides2, paddingsame)(inputs) skip_features Resizing(x.shape[1], x.shape[2])(skip_features) x Concatenate()([x, skip_features]) x Conv2D(num_filters, 3, paddingsame)(x) x Activation(relu)(x) x Conv2D(num_filters, 3, paddingsame)(x) x Activation(relu)(x) return x def unet_model(input_shape(256, 256, 3), num_classes1): inputs tf.keras.layers.Input(shapeinput_shape) # Contracting Path (Encoder) s1 encoder_block(inputs, 64) s2 encoder_block(s1, 128) s3 encoder_block(s2, 256) s4 encoder_block(s3, 512) # Bottleneck b1 Conv2D(1024, 3, paddingsame)(s4) b1 Activation(relu)(b1) b1 Conv2D(1024, 3, paddingsame)(b1) b1 Activation(relu)(b1) # Expansive Path (Decoder) d1 decoder_block(b1, s4, 512) d2 decoder_block(d1, s3, 256) d3 decoder_block(d2, s2, 128) d4 decoder_block(d3, s1, 64) outputs Conv2D(num_classes, 1, paddingsame, activationsigmoid)(d4) model Model(inputsinputs, outputsoutputs, nameU-Net) return model model unet_model(input_shape(512, 512, 3), num_classes1) model.summary() # Compile model model.compile( optimizeradam, lossbinary_crossentropy, metrics[accuracy, MeanIoU(num_classes2)] ) # Callbacks checkpoint_cb ModelCheckpoint( best_model.keras, save_best_onlyTrue, monitorval_loss, modemax, verbose1 ) early_stopping_cb EarlyStopping( monitorval_loss, patience10, modemax, restore_best_weightsTrue, verbose1 ) # Train model history model.fit( X, y, validation_split.1, epochs100, batch_size8, callbacks[checkpoint_cb, early_stopping_cb] ) fig, ax plt.subplots(1, 3, figsize(15, 4)) # Plot Loss ax[0].plot(history.epoch, history.history[loss], labelTrain loss) ax[0].plot(history.epoch, history.history[val_loss], labelValidation loss) ax[0].set_title(Loss) ax[0].legend() # Plot Accuracy ax[1].plot(history.epoch, history.history[accuracy], labelTrain accuracy) ax[1].plot(history.epoch, history.history[val_accuracy], labelValidation accuracy) ax[1].set_title(Accuracy) ax[1].legend() # Plot Mean IoU ax[2].plot(history.epoch, history.history[mean_io_u], labelTrain Mean IoU) ax[2].plot(history.epoch, history.history[val_mean_io_u], labelValidation Mean IoU) ax[2].set_title(Mean IoU) ax[2].legend() fig.suptitle(Loss, Accuracy, and Mean IoU, fontsize16) plt.tight_layout() plt.show() results model.evaluate(X_test, y_test, verbose-1) for name, value in zip([Test Loss,Test Accuracy,Test Mean IOU], results): print(f{name}: {value:.4f}) # Predict masks y_pred model.predict(X_test) # Threshold to get binary masks y_pred_thresholded (y_pred 0.5).astype(np.uint8) def display_predictions(X_data, y_true, y_pred, index0): Display original image, ground truth mask, and predicted mask. image X_data[index] true_mask y_true[index].squeeze() pred_mask y_pred[index].squeeze() fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(image) axes[0].set_title(Original Image) axes[0].axis(off) axes[1].imshow(true_mask, cmapgray) axes[1].set_title(Ground Truth Mask) axes[1].axis(off) axes[2].imshow(pred_mask, cmapgray) axes[2].set_title(Predicted Mask) axes[2].axis(off) plt.tight_layout() plt.show() display_predictions(X_test, y_test, y_pred, index0)资料获取更多粉丝福利关注下方公众号获取