基于 U-Net 的图像分割

news2024/10/5 21:18:58

点击下方卡片,关注“小白玩转Python”公众号

ed0861c98cdeb401967b09f826166af7.png

图像分割是一种将图像划分为不同区域或对象的过程。它通常在像素级别进行,通过将图像中具有相似特征的区域分组或定义对象的边界来完成。这是一种识别和解析图像中不同对象或特征的方法。

假设一位医学专业人士正在检查脑部扫描图像,试图找到潜在的癌性病变。这就是图像分割发挥作用的地方。分割过程用于识别图像中的不同组织和结构,在区分癌细胞和其他正常组织方面发挥着重要作用。

例如,在下面的脑部扫描图像中,分割已经识别出癌性肿瘤并以不同的颜色显示。

e618ce160df954ddc5de8698b8df1cee.png

尽管 U-Net 专注于生物医学图像,但其灵活的架构允许它有效地用于其他类型的图像数据。

a960a58e1dc6e4eea0aaf750f16dd072.png

U-Net 的命名是因为它的结构类似于字母 U,如图所示。我们在输出端得到分割后的输入图像。U-Net 的架构是独特的,因为它由收缩路径和扩展路径组成。

收缩路径(编码器)从输入图像中提取属性图,而扩展路径(解码器)将这些属性转换回更高分辨率的形式。跳跃连接允许低级和高级属性结合,从而实现更好的分割性能。

今天,我们将使用 U-Net 架构对 Kvasir 数据集中的图像进行分割。该数据集是在挪威 Vestre Viken 健康基金会(VV)使用内窥镜设备收集的。数据集已被经验丰富的内窥镜医生标记。您可以在此处访问数据集。我们首先导入所需的库。

import os
import cv2
import random
import numpy as np


import matplotlib.pyplot as plt
%matplotlib inline


from sklearn.model_selection import train_test_split
from skimage.io import imread, imshow
from skimage.transform import resize
from skimage.color import rgb2gray




import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import backend as K
from tensorflow.keras.models import Model, load_model, save_model
from tensorflow.keras.optimizers import Adam, Adamax
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras.layers import Input, Activation, BatchNormalization, Dropout, Lambda, Conv2D, Conv2DTranspose, MaxPooling2D, concatenate


# Ignore Warnings
import warnings
warnings.filterwarnings("ignore")

然后我们为 U-Net 输入图像定义特定大小的变量。

IMG_CHANNELS = 3
IMG_WIDTH = 256
IMG_HEIGHT = 256

由于我们在 kaggle 上运行我们的笔记本,我们以以下方式访问我们的数据并获取它们的 id,即图像的名称。

images_path = "/kaggle/input/kvasir-dataset-for-classification-and-segmentation/kvasir-seg/Kvasir-SEG/images"
mask_path = "/kaggle/input/kvasir-dataset-for-classification-and-segmentation/kvasir-seg/Kvasir-SEG/masks"


img_ids = next(os.walk(images_path))[2]
mask_ids = next(os.walk(mask_path))[2]

对于变量 img_ids 和 mask_ids,您应该得到以下输出:

7848711e45c2f15ffa64a167da851b02.png

X = np.zeros((len(img_ids), 256, 256, 3), dtype=np.uint8)
y = np.zeros((len(mask_ids), 256, 256, 1), dtype=np.bool_)

以这些零开头的数据结构是数据预处理步骤的一部分。实际的图像和标签数据随后将填充这些结构,并用于模型训练。这个预处理步骤是清洁、组织和准备数据集以训练模型的过程的一部分。

  • 输入图像(X):创建一个数组来存储输入图像。在这个数组中,每张图像的大小为 256x256 像素,并且有 3 个颜色通道(RGB)。然而,最初这些图像的内容未指定,它们都填充了零。

  • 目标标签(y):目标标签代表模型应该学习的正确的输出。这个数组也包含具有 256x256 像素大小的掩码,但只有一个颜色通道(黑白)。最初,这些掩码也填充了零。

现在我们将创建我们的输入图像以提供给我们的模型。

for n,img in enumerate(os.listdir(images_path)):
    file_path = os.path.join(images_path, img)
    image = imread(file_path)
    image = resize(image, (256, 256), mode="constant", preserve_range=True)
    X[n] = image


for n,mask in enumerate(os.listdir(mask_path)):
    file_path = os.path.join(mask_path, mask)
    mask = imread(file_path)
    mask = rgb2gray(mask)
    mask = np.expand_dims(resize(mask, (256, 256), mode="constant", preserve_range=True), axis=-1)
    y[n] = mask

这个脚本从给定目录(images_path)获取图像文件。然后它将每张图像调整到给定的大小(256x256 像素)并保存在 numpy 数组(X)中。掩码也执行相同的过程。掩码也被调整到相同的大小并保存在 numpy 数组(y)中。结果,数组 X 包含处理后的图像,而数组 y 包含处理后的掩码。让我们看一个示例图像及其掩码:

561aa79890ce5358b4e4a92c88ddc686.png

使用 train_test_split 函数,我们自动化了分割和拆分用于训练模型和评估其性能的数据集的过程。这允许模型使用训练数据进行训练,然后使用拆分的测试数据评估其性能。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)


print(f"X_train.shape: {X_train.shape}\nY_train.shape: {y_train.shape}")

我们正在编写我们的 U-Net 模型架构。

input = tf.keras.layers.Input((256, 256, 3))


# reduce image config
s = tf.keras.layers.Lambda(lambda x: x / 256)(input)


# block 1
c1 = tf.keras.layers.Conv2D(16, (3, 3), activation="relu", kernel_initializer='he_normal', padding='same')(s)
c1 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c1)
c1 = tf.keras.layers.Dropout(0.1)(c1)
c1 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c1)
p1 = tf.keras.layers.MaxPooling2D((2, 2))(c1)


# block 2
c2 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p1)
c2 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c2)
c2 = tf.keras.layers.Dropout(0.1)(c2)
c2 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c2)
p2 = tf.keras.layers.MaxPooling2D((2, 2))(c2)


# block 3
c3 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p2)
c3 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c3)
c3  = tf.keras.layers.Dropout(0.2)(c3)
c3 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c3)
p3 = tf.keras.layers.MaxPooling2D((2, 2))(c3)


# block 4
c4 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p3)
c4 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c4)
c4 = tf.keras.layers.Dropout(0.2)(c4)
c4 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c4)
p4 = tf.keras.layers.MaxPooling2D((2, 2))(c4)


# block 5
c5 = tf.keras.layers.Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p4)
c5 = tf.keras.layers.Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c5)
c5 = tf.keras.layers.Dropout(0.3)(c5)
c5 = tf.keras.layers.Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c5)
p5 = tf.keras.layers.MaxPooling2D((2, 2))(c5)


# block 6
c6 = tf.keras.layers.Conv2D(512, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(p5)
c6 = tf.keras.layers.Conv2D(512, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c6)
c6 = tf.keras.layers.Dropout(0.3)(c6)
c6 = tf.keras.layers.Conv2D(512, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c6)


# block 7 - back 1
u7 = tf.keras.layers.Conv2DTranspose(256, (2, 2), strides=(2, 2), padding='same')(c6)
u7 = tf.keras.layers.concatenate([u7, c5])
c7 = tf.keras.layers.Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u7)
c7 = tf.keras.layers.Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c7)
c7 = tf.keras.layers.Dropout(0.3)(c7)
c7 = tf.keras.layers.Conv2D(256, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c7)


# block 8 - back 2
u8 = tf.keras.layers.Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(c7)
u8 = tf.keras.layers.concatenate([u8, c4])
c8 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u8)
c8 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c8)
c8 = tf.keras.layers.Dropout(0.2)(c8)
c8 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c8)


# block 9 - back 3
u9 = tf.keras.layers.Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(c8)
u9 = tf.keras.layers.concatenate([u9, c3])
c9 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u9)
c9 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c9)
c9 = tf.keras.layers.Dropout(0.2)(c9)
c9 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c9)


# block 10 - back 4
u10 = tf.keras.layers.Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(c9)
u10 = tf.keras.layers.concatenate([u10, c2])
c10 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u10)
c10 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c10)
c10 = tf.keras.layers.Dropout(0.1)(c10)
c10 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c10)


# block 11 - back 5
u11 = tf.keras.layers.Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same')(c10)
u11 = tf.keras.layers.concatenate([u11, c1])
c11 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(u11)
c11 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c11)
c11 = tf.keras.layers.Dropout(0.1)(c11)
c11 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal', padding='same')(c11)


outputs = tf.keras.layers.Conv2D(1, (1, 1), activation='sigmoid')(c11)


model = tf.keras.Model(inputs=input, outputs=outputs, name='U-NET')


model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy','iou_coef'])

Adam 优化器是一种在训练期间更新权重的优化算法。Adam 使用自适应矩估计,这在处理大型数据集和复杂模型时特别有效。

我们使用 binary_crossentropy 作为损失函数。binary_crossentropy 是一种常用于二元分类问题的损失函数。这个损失函数通过计算两个类别之间的差异,并通过比较模型的输出与实际标签来计算误差,优化了模型的学习过程。在这里我们执行像素级分割。我们检查图像中的每个像素是否属于一个类别。在我们的问题中,有两个类别:背景和对象。因此,我们更喜欢使用 binary_crossentropy 损失函数。

在我们的评估指标中,我们看到 iou_coef 指标。这个指标帮助我们评估我们在分割问题中的成功程度。iou_coef 也被称为交集比并集(IoU)系数,衡量模型预测的分割结果与实际分割结果的匹配程度。这个指标越高,模型的分割性能就越好。因此,IoU 系数是确定模型执行分割的准确性的重要指标。

9c27cc43b031e7afd52dc76e2fa0ea3b.jpeg

现在我们在代码中包括 iou_score:

def iou_coef(y_true, y_pred, smooth=100):
    intersection = K.sum(y_true * y_pred)
    sum = K.sum(y_true + y_pred)
    iou = (intersection + smooth) / (sum - intersection + smooth)
    return iou

我们开始训练。

model.fit(X_train, y_train, validation_split=0.1, batch_size=8, epochs=100)

作为模型的结果,我们看到训练阶段的评估指标:

Epoch 100/100
76/76 [==========================] - 5s 60ms/step - loss: 0.0473 - accuracy: 0.9813 - iou_coef: 0.8491 -val_loss: 0.3569 - val_accuracy: 0.9017 - val_iou_coef: 0.5388
<keras.callbacks.History at 0x7e9c514b6290>

iou_coef 的值在 0 和 1 之间,越接近 1,模型的性能越好。0.8496 的 IOU 值意味着模型的预测与实际分割掩码很好地重叠。这意味着模型的预测相当准确地识别了真实图像中的对象。现在,我们进入模型将在测试数据上进行预测的阶段:

ind = random.randint(0, len(X_test))
img = X_test[ind]
predictions = model.predict(np.expand_dims(img, axis=0), verbose=0)
plt.figure(figsize=(15, 12))
plt.subplot(1, 3, 1)
plt.title("original image")
plt.imshow(np.squeeze(img))
plt.subplot(1, 3, 2)
plt.title("predicted mask")
plt.imshow(np.squeeze(predictions))
plt.subplot(1, 3, 3)
plt.imshow(np.squeeze(img))
# plt.imshow(msk,alpha=0.5)
plt.imshow(np.squeeze(predictions), alpha=0.5)
plt.show()

让我们看看我们的结果:

f6fdb08dff6a70c66d61aaf80a7a74bf.png

完整代码地址:https://www.kaggle.com/code/melikegkdemir/kvasir-segmentation-using-u-net

·  END  ·

HAPPY LIFE

c0b8ca2bdb8c97c0f6539f7b17835939.png

本文仅供学习交流使用,如有侵权请联系作者删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1830370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

气象数据NC、grb2解析成矢量json、CMIS、MICPS及图片应用到webgis

一、基础概念 气象数据通常以多种格式存储和交换&#xff0c;以适应不同的应用需求和处理工具。以下是一些常见的气象数据格式及其转换方法的概述&#xff1a; 常见气象数据格式 1. NetCDF&#xff08;Network Common Data Form&#xff09;&#xff1a;一种自描述、自包含的…

SD卡、MicroSD卡与SD NAND的全面对比分析

在当今多样化的存储设备市场中&#xff0c;SD卡、MicroSD卡和SD NAND因其不同的特性和应用场景而广受欢迎。这三种存储解决方案各有千秋&#xff0c;为消费者提供了丰富的选择。以下是MK米客方德SD卡、MicroSD卡和SD NAND全面的对比分析&#xff1a;

springboot“漫画之家”系统 LW+PPT+源码

3 系统分析 链接&#xff1a;https://pan.baidu.com/s/1ihILTui-XEFdC15mcOB0vA?pwdewry 提取码&#xff1a;ewry 3.1系统可行性分析 3.1.1经济可行性 由于本系统是作为毕业设计系统&#xff0c;且系统本身存在一些技术层面的缺陷&#xff0c;并不能直接用于商业用途&#xf…

英伟达发布开源模型Nemotron-4 340B

&#x1f680; 英伟达发布开源模型Nemotron-4 340B 摘要&#xff1a;英伟达最新发布的开源模型Nemotron-4 340B&#xff0c;可能彻底改变大语言模型&#xff08;LLM&#xff09;训练方式。该模型支持多种自然语言和编程语言&#xff0c;使用9万亿个token训练&#xff0c;高达9…

python数据分析-心脏瓣膜手术风险分析与预测

一、研究背景和意义 人的心脏有四个瓣膜&#xff0c;主动脉银、二尖、肺动脉和三尖源 不管是那一个膜发生了病变&#xff0c;都会导致心脏内的血流受到影响&#xff0c;这就是通常所说的心脏期膜病&#xff0c;很多是需要通过手术的方式进行改善的。随着人口老龄化的加剧,&…

Aeron:Multi-Destination-Cast

Multi-Destination-Cast&#xff08;MDC&#xff09;是一种功能&#xff0c;允许 Aeron 从单个 Publication 同时向多个目的地传送数据。Multiple-Destination-Cast是 Aeron 的一项高级功能&#xff0c;本指南将介绍如何开发一个简单示例的基本知识。 一、MDC Publications 注&…

机器学习中的监督学习介绍

In this post well go into the concept of supervised learning, the requirements for machines to learn, and the process of learning and enhancing prediction accuracy. 在这篇文章中&#xff0c;我们将深入探讨监督学习的概念、机器学习的要求以及学习和提高预测准确…

Proxmox VE (PVE) 教学 (3) | 在 Proxmox VE 中安装与配置 OpenWrt

大家好,很长时间没有更新这个系列了。最近正在开发新项目,刚刚想起来我是不是还有一个什么专栏没更新。 本期的网络配置背景同于前两期的描述( 详见https://www.hestudio.net/category/proxmox-ve/ ),这一期只是对网络配置的扩展,也就是安装软路由,实现网络配置的更多功…

计算机网络:1概述

概述 因特网 网络、互连网&#xff08;互联网&#xff09;与因特网的区别与关系 若干节点和链路互连形成网络&#xff0c;若干网络通过路由器互连形成互连网&#xff0c;世界上最大的互连网是互联网&#xff08;因特网Internet&#xff09;。 因特网发展的三个阶段 因特网…

【挑战100天首通《谷粒商城》】-【第一天】【10 番外篇】 解决docker 仓库无法访问 + MobaXterm连接VirtualBox虚拟机

文章目录 课程介绍 1、解决docker 仓库无法访问 2、 MobaXterm连接VirtualBox虚拟机 Stage 1&#xff1a;下载MobaXterm选择适合你的版本 Stage 2&#xff1a;vagrant ssh 连接&#xff0c;开启ssh访问 Stage 2-1&#xff1a;su获取root账号权限,输入密码&#xff08;默认vagra…

C语言笔记第15篇:文件操作

1、为什么使用文件&#xff1f; 如果没有文件&#xff0c;我们写的程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运行程序&#xff0c;是看不到上次程序的数据的&#xff0c;如果要将数据进行持久化…

Java 集合框架:LinkedList 的介绍、使用、原理与源码解析

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 014 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…

【漏洞复现】畅捷通T+ App_Code.ashx 远程命令执行漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

什么是加密算法,有什么不同类型?

加密算法是一种数学函数或程序&#xff0c;它能够将原始的、可读的数据&#xff08;也称为“明文”&#xff09;转换为一种不可读的代码形式&#xff08;称为“密文”&#xff09;。这种转换是通过特定的算法和密钥来实现的&#xff0c;目的是保护数据的机密性和完整性&#xf…

HTML表单深度解析:构建互动的网页界面

表单是HTML中用于收集用户输入信息的重要元素&#xff0c;是网页与用户交互的关键组件。以下是一个典型的HTML表单示例&#xff0c;我们将会详细解析其中的各个元素及属性含义。 <form action"https://xx.xxx.xx/search" target"_self" method"ge…

CDN简介

CDN 的基本概念 CDN&#xff08;Content Delivery Network&#xff09;&#xff0c;即内容分发网络。 CDN是一种分布式网络架构&#xff1a;它由分布在不同地理位置的服务器组成网络&#xff0c;这些服务器协同工作以提供内容服务。 内容分发的核心目标 确保用户能够快速、可…

WordPress管理员后台登录地址修改教程,WordPress admin登录地址文件修改方法

我们使用WordPress时&#xff0c;管理员后台登录默认地址为“域名/wp-login.php”或“域名/wp-admin”&#xff0c;为了安全&#xff0c;一般会把此地址改掉&#xff0c;防止有人恶意来攻击咱的WordPress&#xff0c;今天出个WordPress后台登录地址修改教程&#xff0c;修改之后…

[oeasy]python0021_宝剑镶宝石_爱之石中剑_批量替换_特殊字符_特殊颜色

继续运行 &#x1f94b; 回忆上次内容 上次 运行了 game.py分析了 game.py也大致读懂了 game.py 这个 程序 可以进一步 进行修改吗&#xff1f;&#xff1f; 添加爱心 可以 把这个 ❤ 选中并复制 再粘贴到 虚拟机右侧的 剪贴板 然后 回到 游戏程序 进行修改和粘贴 按方向键h…

2024广州光亚展参展记录

参展总结 智控面板外观设计百家齐放&#xff0c;但始终逃不出几大设计元素的组合&#xff08;各种尺寸的屏、不同规则的按键切分、不同材质的面板材质&#xff09;&#xff1b;互联互通的趋势明显&#xff0c;接入米家、小度、涂鸦、HomeKit平台成为众多厂商的首选&#xff1b;…

JavaFX BorderPane布局

BorderPane布局顶部&#xff0c;底部&#xff0c;左&#xff0c;右或中心区域中的子节点。每个区域只能有一个节点。BorderPane的顶部和底部区域允许可调整大小的节点占用所有可用宽度。 左边界区域和右边界区域占据顶部和底部边界之间的可用垂直空间。 默认情况下&#xff0c…