UNET架构综合指南 | 图像分割的掌握
UNET架构综合指南 | 图像分割的掌握
介绍
在令人兴奋的计算机视觉领域中,图像包含许多秘密和信息,区分和突出显示物品至关重要。图像分割是将图像分割为有意义的区域或对象的过程,在医学图像到自动驾驶和物体识别等各种应用中都是必不可少的。准确而自动的分割长期以来一直是一项具有挑战性的任务,传统方法经常在准确性和效率方面表现不佳。这就是UNET架构的出现,一种革命性的智能方法,它彻底改变了图像分割的方式。凭借其简单的设计和创新的技术,UNET为更准确和稳健的分割结果铺平了道路。无论您是计算机视觉领域的新手还是经验丰富的从业者,希望提高分割能力,本深入的博客文章将揭示UNET的复杂性,并全面了解其架构、组件和用途。
本文是作为Data Science Blogathon的一部分发布的。
理解卷积神经网络
CNN是一种经常在计算机视觉任务中使用的深度学习模型,包括图像分类、物体识别和图像分割。CNN主要用于学习和提取图像中的相关信息,使其在视觉数据分析中非常有用。
CNN的关键组件
- 卷积层:CNN由一系列可学习的过滤器(卷积核)与输入图像或特征图进行卷积。每个过滤器应用逐元素乘法和求和,以产生突出显示输入中特定模式或局部特征的特征图。这些过滤器可以捕捉许多视觉元素,如边缘、角点和纹理。
- 池化层:使用卷积层创建的特征图通过池化层进行下采样。池化层减少了特征图的空间维度,同时保持最重要的信息,降低了后续层的计算复杂度,使模型对输入波动更具抵抗力。最常见的池化操作是最大池化,它在给定邻域内选择最大的值。
- 激活函数:使用激活函数将非线性引入CNN模型。将它们逐元素地应用于卷积或池化层的输出,使网络能够理解复杂的关联关系并做出非线性决策。由于其在解决消失梯度问题方面的简单性和效率,修正线性单元(ReLU)激活函数在CNN中很常见。
- 全连接层:全连接层,也称为密集层,使用提取的特征完成最终的分类或回归操作。它们将一层中的每个神经元连接到下一层的每个神经元,使网络能够学习全局表示,并根据前面层的组合输入进行高级判断。
网络从一系列卷积层开始,以捕捉低级特征,然后是池化层。更深的卷积层学习更高级的特征随着网络的发展。最后,使用一个或多个全连接层进行分类或回归操作。
全连接网络的需求
传统的CNN通常用于图像分类任务,其中将一个标签分配给整个输入图像。另一方面,传统的CNN架构在像语义分割这样的细粒度任务中存在问题,其中必须将图像的每个像素分到不同的类别或区域中。全卷积网络(FCN)在这里发挥作用。
传统CNN在分割任务中的局限性
空间信息的丢失:传统CNN使用池化层逐渐减少特征图的空间维度。虽然这种下采样有助于捕捉高级特征,但会导致空间信息的丢失,使得在像素级别上精确检测和分割对象变得困难。
固定输入尺寸:CNN架构通常被构建为接受特定尺寸的图像。然而,在分割任务中,输入图像的尺寸可能各不相同,这使得使用传统CNN处理可变大小的输入变得具有挑战性。
有限的本地化精度:传统的卷积神经网络通常在最后使用全连接层来提供一个固定大小的输出向量进行分类。因为它们不保留空间信息,所以无法精确地定位图像中的对象或区域。
全卷积网络 (FCNs) 作为语义分割的解决方案
通过仅在卷积层上工作并在整个网络中保持空间信息,全卷积网络 (FCNs) 解决了经典卷积神经网络在分割任务中的限制。FCNs旨在进行像素级预测,将输入图像中的每个像素分配一个标签或类别。通过上采样特征图,FCNs可以构建一个密集的分割地图,提供像素级别的预测。反卷积(也称为上采样层)用于替换卷积神经网络设计后的完全连接层。反卷积通过增加特征图的空间分辨率,使其与输入图像大小相同。
在上采样过程中,FCNs通常使用跳跃连接,绕过特定层,直接将低层特征图与高层特征图相连。这些跳跃连接有助于保留细节和上下文信息,提高分割区域的本地化精度。FCNs在各种分割应用中非常有效,包括医学图像分割、场景解析和实例分割。通过利用FCNs进行语义分割,它现在可以处理各种大小的输入图像、提供像素级别的预测和在整个网络中保持空间信息。
图像分割
图像分割是计算机视觉中的基本过程,它将图像分成许多有意义且独立的部分或分割区域。与图像分类不同,图像分割为每个像素或像素组添加标签,从而将图像分割成语义上重要的部分。图像分割的重要性在于它可以更详细地理解图像的内容。通过将图片分割成多个部分,我们可以提取关于对象边界、形态、大小和空间关系的大量信息。这种精细的分析对于各种计算机视觉任务至关重要,可以实现更好的应用并支持更高级别的视觉数据解读。
理解UNET架构
传统的图像分割技术,如手动注释和像素级分类,存在各种缺点,使其在准确和有效的分割任务中具有浪费和困难。正因为这些限制,出现了更先进的解决方案,如UNET架构。让我们看看以前的方法的缺点以及为什么创建UNET来克服这些问题。
- 手动注释:手动注释需要对图像边界或感兴趣区域进行勾画和标记。虽然这种方法可以产生可靠的分割结果,但它耗时、劳动密集且容易出现人为错误。手动注释对于大型数据集来说不可扩展,并且在复杂的分割任务中保持一致性和注释者间一致性是困难的。
- 像素级分类:另一种常见的方法是像素级分类,即对图像中的每个像素进行独立分类,通常使用决策树、支持向量机 (SVM) 或随机森林等算法。然而,像素级分类难以捕捉全局上下文和周围像素之间的依赖关系,导致过度分割或欠分割问题。它无法考虑空间关系,经常无法提供准确的对象边界。
克服挑战
UNET架构的开发旨在解决这些限制,并克服传统图像分割方法面临的挑战。以下是UNET解决这些问题的方法:
- 端到端学习:UNET采用端到端学习方法,意味着它可以直接从输入-输出对中学习对图像进行分割,而无需用户注释。通过在大型标记数据集上进行训练,UNET可以自动提取关键特征并执行准确的分割,无需劳动密集的手动注释。
- 全卷积架构:UNET基于全卷积架构,这意味着它完全由卷积层构成,不包含任何全连接层。这种架构使UNET能够处理任意大小的输入图像,增加了其对各种分割任务和输入变化的灵活性和适应性。
- U形架构和跳跃连接:该网络的特殊架构包括一个编码路径(收缩路径)和一个解码路径(扩展路径),使其能够收集局部信息和全局上下文。跳跃连接填补了编码和解码路径之间的差距,保持了之前层次的关键信息,从而实现更精确的分割。
- 上下文信息和本地化:UNET使用跳跃连接汇集多层的多尺度特征图,使网络能够吸收上下文信息并捕捉不同抽象级别的细节。这种信息整合提高了定位精度,实现了精确的对象边界和准确的分割结果。
- 数据增强和正则化:UNET利用数据增强和正则化技术来提高其在训练过程中的鲁棒性和泛化能力。数据增强包括向训练图像添加各种变换,如旋转、翻转、缩放和变形,以增加训练数据的多样性。正则化技术,如dropout和批归一化,防止过拟合并提高模型在未知数据上的性能。
UNET 架构概述
UNET 是一个专为图像分割应用而构建的全卷积神经网络(FCN)架构。它是由 Olaf Ronneberger、Philipp Fischer 和 Thomas Brox 在 2015 年首次提出的。UNET 在图像分割的准确性方面经常被使用,并在各种医学成像应用中成为了流行的选择。UNET 将编码路径(也称为收缩路径)与解码路径(扩展路径)相结合。该架构的名称来源于其在图表中呈现的 U 形状。由于这种 U 形状的架构,网络可以同时记录局部特征和全局背景,从而得到精确的分割结果。
UNET 架构的关键组件
- 收缩路径(编码路径):UNET 的收缩路径由卷积层和最大池化操作组成。通过逐渐降低输入图像的空间维度,这种方法捕捉高分辨率、低级特征。
- 扩展路径(解码路径):在 UNET 扩展路径中,使用转置卷积(也称为反卷积或上采样层)对编码路径中的特征图进行上采样。在上采样阶段,特征图的空间分辨率增加,使网络能够重建密集的分割地图。
- 跳跃连接:UNET 使用跳跃连接将编码路径与解码路径中的匹配层连接起来。这些连接使网络能够收集局部和全局数据。通过将先前层的特征图与解码路径中的特征图整合起来,网络保留了关键的空间信息,提高了分割的准确性。
- 连接:连接在 UNET 中常用于实现跳跃连接。在上采样过程中,将编码路径中的特征图与解码路径中的上采样特征图进行连接。这种连接允许网络结合多尺度信息进行适当的分割,利用高级上下文和低级特征。
- 全卷积层:UNET 包含没有全连接层的卷积层。这种卷积结构使得 UNET 能够处理任意尺寸的图像,并在整个网络中保留空间信息,使其灵活适应各种分割任务。
编码路径或收缩路径是 UNET 架构的一个重要组成部分。它负责从输入图像中提取高级信息,同时逐渐缩小空间维度。
卷积层
编码过程从一组卷积层开始。卷积层通过将一组可学习的滤波器应用于输入图像,在多个尺度上提取信息。这些滤波器在局部感受野上运行,使网络能够捕捉空间模式和细微特征。每个卷积层的特征图深度增加,使网络能够学习更复杂的表示。
激活函数
在每个卷积层之后,会逐元素地应用激活函数,如修正线性单元(ReLU),将非线性引入网络。激活函数帮助网络学习输入图像和提取特征之间的非线性相关性。
池化层
池化层用于在卷积层之后减少特征图的空间维度。例如,最大池化等操作将特征图分为不重叠的区域,并仅保留每个区域内的最大值。它通过下采样特征图来减少空间分辨率,使网络能够捕捉更抽象和更高级别的数据。
编码路径的任务是以分层的方式以不同的尺度和抽象级别捕捉特征。编码过程专注于提取全局背景和高级信息,同时降低空间维度。
跳跃连接
UNET 架构的一个显著特点是具有连接适当级别的跳跃连接的能力。这些跳跃连接在编码过程中保持关键数据的重要性。
来自先前层的特征图在编码路径中收集局部细节和细粒度信息。这些特征图与解码路径中的上采样特征图通过跳跃连接进行连接。这使得网络能够融合多尺度数据、低级特征和高级上下文进行分割。
通过保留先前层的空间信息,UNET 能够可靠地定位对象并保留分割结果中的细节。UNET 的跳跃连接有助于解决由于下采样导致的信息丢失问题。跳跃连接允许更好地整合局部和全局信息,提高整体分割性能。
总结一下,UNET编码方法对于捕捉高级特征和降低输入图像的空间维度非常关键。编码路径通过卷积层、激活函数和池化层逐渐提取抽象表示。通过整合局部特征和全局上下文,引入跳跃连接可以保留关键的空间信息,促进可靠的分割结果。
UNET中的解码路径
UNET架构的一个关键组成部分是解码路径,也称为扩展路径。它负责对编码路径的特征图进行上采样,并构建最终的分割掩码。
上采样层(转置卷积)
为了提高特征图的空间分辨率,UNET解码方法包括上采样层,通常使用转置卷积或反卷积来完成。转置卷积本质上是常规卷积的相反操作。它增强了空间维度,而不是减小它们,从而实现了上采样。通过构造稀疏核并将其应用于输入特征图,转置卷积学习对特征图进行上采样。网络在此过程中学会填充当前空间位置之间的空白区域,从而提高特征图的分辨率。
连接
在解码阶段,将前面层的特征图与上采样的特征图进行连接。这种连接使网络能够聚合多尺度信息以进行正确的分割,利用高级上下文和低级特征。除了上采样外,UNET解码路径还包括来自编码路径相应层的跳跃连接。
通过连接跳跃连接的特征图,网络可以恢复和整合在编码过程中丢失的细粒度特征。这使得分割掩码中的对象定位和描绘更加精确。
UNET的解码过程通过逐渐上采样特征图并包含跳跃连接来重建与输入图片的空间分辨率相匹配的密集分割图。
解码路径的功能是恢复在编码路径中丢失的空间信息并改进分割结果。它将编码细节与上采样层获得的高级上下文相结合,提供准确和全面的分割掩码。
通过在解码过程中使用转置卷积,UNET可以提高特征图的空间分辨率,从而将其上采样以匹配原始图像大小。转置卷积通过学习填充空白区域和扩展空间维度的方式,帮助网络生成密集且细粒度的分割掩码。
总之,UNET的解码过程通过增强特征图的空间分辨率,使用上采样层和跳跃连接来重建分割掩码。转置卷积在此阶段至关重要,因为它们允许网络对特征图进行上采样,并构建与原始输入图像相匹配的详细分割掩码。
UNET中的收缩和扩展路径
UNET架构采用“编码器-解码器”结构,其中收缩路径代表编码器,扩展路径代表解码器。这个设计类似于将信息编码为压缩形式,然后解码以重建原始数据。
收缩路径(编码器)
UNET中的编码器是收缩路径。它通过逐渐减小空间维度来提取上下文并压缩输入图像。该方法包括卷积层,然后是池化过程(如最大池化)来对特征图进行下采样。收缩路径负责获取高级特征、学习全局上下文和减小空间分辨率。它专注于压缩和抽象化输入图像,有效地捕捉与分割相关的信息。
扩展路径(解码器)
UNET中的解码器是扩展路径。通过上采样收缩路径的特征图,它恢复空间信息并生成最终的分割图。扩展路径包括上采样层,通常使用转置卷积或反卷积来增加特征图的空间分辨率。扩展路径通过将上采样的特征图与收缩路径的等价特征图进行集成,通过跳跃连接来重建原始的空间维度。这种方法使网络能够恢复细粒度特征并正确定位物体。
UNET设计通过结合收缩和扩展路径来捕捉全局上下文和局部细节。收缩路径将输入图像压缩为紧凑的表示,决定通过扩展路径构建详细的分割图。扩展路径关注将压缩的表示解码为密集和精确的分割图。它重建丢失的空间信息并改进分割结果。这种编码器-解码器结构利用高级上下文和细粒度的空间信息实现精确的分割。
总结来说,UNET的收缩和扩张路径类似于“编码器-解码器”结构。扩张路径是解码器,恢复空间信息并生成最终的分割地图。相反,收缩路径作为编码器,捕获上下文并压缩输入图像。这种架构使UNET能够有效地编码和解码信息,实现准确全面的图像分割。
UNET中的跳跃连接
跳跃连接对于UNET的设计至关重要,因为它们允许信息在收缩(编码)和扩张(解码)路径之间传递。它们对于保持空间信息和提高分割准确性至关重要。
保留空间信息
在编码路径中,一些空间信息可能会在特征图经过诸如最大池化等降采样过程时丢失。这种信息丢失可能会导致定位准确性降低,并且在分割掩码中丢失细粒度的细节。
通过在编码和解码过程中建立相应层之间的直接连接,跳跃连接有助于解决这个问题。跳跃连接保护了在降采样过程中可能丢失的重要空间信息。这些连接使来自编码流的信息避免降采样,并直接传输到解码路径。
多尺度信息融合
跳跃连接允许从许多网络层合并多尺度信息。编码过程的后期层捕获高级上下文和语义信息,而较早的层捕捉局部细节和细粒度信息。UNET可以通过连接编码路径中的这些特征图到解码路径中的相应层来成功合并局部和全局信息。这种多尺度信息的整合可以提高整体的分割准确性。网络可以利用来自编码路径的低级数据来优化解码路径中的分割结果,从而实现更精确的定位和更好的物体边界勾画。
结合高级上下文和低级细节
跳跃连接允许解码路径结合高级上下文和低级细节。跳跃连接中的连接特征图包括解码路径的上采样特征图和编码路径的特征图。
这种组合使网络能够利用解码路径中记录的高级上下文和编码路径中捕捉的细粒度特征。网络可以整合多个尺度的信息,从而实现更精确和详细的分割。
UNET可以通过添加跳跃连接来利用多尺度信息,保留空间细节,并将高级上下文与低级细节合并。结果,分割准确性提高,物体定位改善,分割掩码中的细粒度信息保留。
总之,UNET中的跳跃连接对于保持空间信息、整合多尺度信息和提高分割准确性至关重要。它们在编码和解码路径之间提供直接的信息流,使网络能够收集局部和全局细节,从而实现更精确和详细的图像分割。
UNET中的损失函数
在训练UNET并优化其参数进行图像分割任务时,选择适当的损失函数至关重要。UNET经常使用分割友好的损失函数,如Dice系数或交叉熵损失。
Dice系数损失
Dice系数是一种相似性统计量,用于计算预期分割掩码与真实分割掩码之间的重叠程度。Dice系数损失,或软Dice损失,通过从Dice系数中减去一来计算。当预期和真实掩码很好地对齐时,损失减小,从而获得更高的Dice系数。
Dice系数损失对于不平衡的数据集特别有效,其中背景类具有大量像素。通过惩罚假阳性和假阴性,它促使网络准确地划分前景和背景区域。
交叉熵损失
在图像分割任务中使用交叉熵损失函数。它衡量预测的类别概率与真实标签之间的差异。在图像分割中,将每个像素视为一个独立的分类问题,并逐像素计算交叉熵损失。
交叉熵损失鼓励网络为每个像素分配正确的类别标签高概率。它惩罚与真实情况的偏差,促进准确的分割结果。当前景和背景类平衡或分割任务涉及多个类别时,这种损失函数非常有效。
选择Dice系数损失和交叉熵损失之间的选择取决于分割任务的特定要求和数据集的特征。两种损失函数都有优势,并且可以根据具体需求进行组合或定制。
1: 导入库
import tensorflow as tf
import os
import numpy as np
from tqdm import tqdm
from skimage.io import imread, imshow
from skimage.transform import resize
import matplotlib.pyplot as plt
import random
2: 图像尺寸设置
IMG_WIDTH = 128
IMG_HEIGHT = 128
IMG_CHANNELS = 3
3: 随机性设置
seed = 42
np.random.seed = seed
4: 导入数据集
# 数据从 - https://www.kaggle.com/competitions/data-science-bowl-2018/data 下载
# 导入数据集
TRAIN_PATH = 'stage1_train/'
TEST_PATH = 'stage1_test/'
5: 读取子文件夹中的所有图像
train_ids = next(os.walk(TRAIN_PATH))[1]
test_ids = next(os.walk(TEST_PATH))[1]
6: 训练
X_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), dtype=np.uint8)
Y_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)
7: 调整图像大小
print('调整训练图像和掩膜的大小')
for n, id_ in tqdm(enumerate(train_ids), total=len(train_ids)):
path = TRAIN_PATH + id_
img = imread(path + '/images/' + id_ + '.png')[:,:,:IMG_CHANNELS]
img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)
X_train[n] = img # 用img的值填充空的X_train
mask = np.zeros((IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)
for mask_file in next(os.walk(path + '/masks/'))[2]:
mask_ = imread(path + '/masks/' + mask_file)
mask_ = np.expand_dims(resize(mask_, (IMG_HEIGHT, IMG_WIDTH), mode='constant',
preserve_range=True), axis=-1)
mask = np.maximum(mask, mask_)
Y_train[n] = mask
8: 测试图像
# 测试图像
X_test = np.zeros((len(test_ids), IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS), dtype=np.uint8)
sizes_test = []
print('调整测试图像的大小')
for n, id_ in tqdm(enumerate(test_ids), total=len(test_ids)):
path = TEST_PATH + id_
img = imread(path + '/images/' + id_ + '.png')[:,:,:IMG_CHANNELS]
sizes_test.append([img.shape[0], img.shape[1]])
img = resize(img, (IMG_HEIGHT, IMG_WIDTH), mode='constant', preserve_range=True)
X_test[n] = img
print('完成!')
9: 随机检查图像
image_x = random.randint(0, len(train_ids))
imshow(X_train[image_x])
plt.show()
imshow(np.squeeze(Y_train[image_x]))
plt.show()
10: 构建模型
inputs = tf.keras.layers.Input((IMG_HEIGHT, IMG_WIDTH, IMG_CHANNELS))
s = tf.keras.layers.Lambda(lambda x: x / 255)(inputs)
11: 路径
# 收缩路径
c1 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu',
kernel_initializer='he_normal', padding='same')(s)
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)
c2 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu',
kernel_initializer='he_normal', padding='same')(p1)
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)
c3 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu',
kernel_initializer='he_normal', padding='same')(p2)
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)
c4 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu',
kernel_initializer='he_normal', padding='same')(p3)
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(pool_size=(2, 2))(c4)
c5 = tf.keras.layers.Conv2D(256, (3, 3), activation='relu',
kernel_initializer='he_normal', padding='same')(p4)
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)
12: 扩展路径
u6 = tf.keras.layers.Conv2DTranspose(128, (2, 2), strides=(2, 2), padding='same')(c5)
u6 = tf.keras.layers.concatenate([u6, c4])
c6 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(u6)
c6 = tf.keras.layers.Dropout(0.2)(c6)
c6 = tf.keras.layers.Conv2D(128, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(c6)
u7 = tf.keras.layers.Conv2DTranspose(64, (2, 2), strides=(2, 2), padding='same')(c6)
u7 = tf.keras.layers.concatenate([u7, c3])
c7 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(u7)
c7 = tf.keras.layers.Dropout(0.2)(c7)
c7 = tf.keras.layers.Conv2D(64, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(c7)
u8 = tf.keras.layers.Conv2DTranspose(32, (2, 2), strides=(2, 2), padding='same')(c7)
u8 = tf.keras.layers.concatenate([u8, c2])
c8 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(u8)
c8 = tf.keras.layers.Dropout(0.1)(c8)
c8 = tf.keras.layers.Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(c8)
u9 = tf.keras.layers.Conv2DTranspose(16, (2, 2), strides=(2, 2), padding='same')(c8)
u9 = tf.keras.layers.concatenate([u9, c1], axis=3)
c9 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(u9)
c9 = tf.keras.layers.Dropout(0.1)(c9)
c9 = tf.keras.layers.Conv2D(16, (3, 3), activation='relu', kernel_initializer='he_normal',
padding='same')(c9)
13: 输出
outputs = tf.keras.layers.Conv2D(1, (1, 1), activation='sigmoid')(c9)
14: 概要
model = tf.keras.Model(inputs=[inputs], outputs=[outputs])
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model.summary()
15: 模型检查点
checkpointer = tf.keras.callbacks.ModelCheckpoint('model_for_nuclei.h5',
verbose=1, save_best_only=True)
callbacks = [
tf.keras.callbacks.EarlyStopping(patience=2, monitor='val_loss'),
tf.keras.callbacks.TensorBoard(log_dir='logs')]
results = model.fit(X_train, Y_train, validation_split=0.1, batch_size=16, epochs=25,
callbacks=callbacks)
16: 最后阶段 – 预测
idx = random.randint(0, len(X_train))
preds_train = model.predict(X_train[:int(X_train.shape[0]*0.9)], verbose=1)
preds_val = model.predict(X_train[int(X_train.shape[0]*0.9):], verbose=1)
preds_test = model.predict(X_test, verbose=1)
preds_train_t = (preds_train > 0.5).astype(np.uint8)
preds_val_t = (preds_val > 0.5).astype(np.uint8)
preds_test_t = (preds_test > 0.5).astype(np.uint8)
# 对一些随机训练样本进行合理性检查
ix = random.randint(0, len(preds_train_t))
imshow(X_train[ix])
plt.show()
imshow(np.squeeze(Y_train[ix]))
plt.show()
imshow(np.squeeze(preds_train_t[ix]))
plt.show()
# 对一些随机验证样本进行合理性检查
ix = random.randint(0, len(preds_val_t))
imshow(X_train[int(X_train.shape[0]*0.9):][ix])
plt.show()
imshow(np.squeeze(Y_train[int(Y_train.shape[0]*0.9):][ix]))
plt.show()
imshow(np.squeeze(preds_val_t[ix]))
plt.show()
结论
在这篇全面的博客文章中,我们介绍了用于图像分割的UNET架构。通过解决先前方法的限制,UNET架构在图片分割方面取得了革命性的进展。它的编码和解码路径、跳跃连接以及其他修改,如U-Net ++、Attention U-Net和Dense U-Net,在捕捉上下文、保持空间信息和提高分割准确性方面证明了其高度有效。UNET准确和自动分割的潜力为改善计算机视觉和其他领域提供了新的途径。我们鼓励读者了解更多关于UNET的知识,并尝试在其图片分割项目中实现,以最大限度地发挥其效用。
关键要点
1. 图像分割在计算机视觉任务中至关重要,允许将图像分割成有意义的区域或对象。
2. 传统的图像分割方法,如手动注释和逐像素分类,在效率和准确性方面存在局限。
3. 开发UNET架构以解决这些限制并实现准确的分割结果。
4. 它是一个完全卷积神经网络(FCN),结合编码路径来捕捉高层特征和解码方法来生成分割蒙版。
5. UNET中的跳跃连接保留了空间信息,增强了特征传播,并提高了分割准确性。
6. 在医学成像、卫星图像分析和工业质量控制方面取得了成功的应用,实现了显着的基准和比赛认可。
常见问题
在本文中显示的媒体不归Analytics Vidhya所有,并由作者自行决定使用。