问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

图像分割 — 初学者指南

创作时间:
作者:
@小白创作中心

图像分割 — 初学者指南

引用
CSDN
1.
https://blog.csdn.net/weixin_38739735/article/details/138237531

图像分割是一种计算机视觉技术,它为图像中的每个像素分配一个标签,使得具有相同标签的像素具有某些特征。例如,在街景中,所有属于汽车的像素可能被标记为一种颜色,而属于道路的像素可能被标记为另一种颜色。但是,要理解图像分割以及它为什么有用,让我们回到基础知识……

分类器


可爱的小狗

但是如果我们想要知道狗到底在哪里呢?一种方法是在狗周围画一个边界框,这称为目标检测。

可爱的小狗 + 边界框

但是如果你想要在像素级别精确地知道狗在哪里,那么你就需要更好的东西。这就是图像分割发挥作用的地方。

图像分割

街道分割

在上面的街景中,有5个类别:道路(粉色)、车辆(红色)、建筑物(黄色)、自然(绿色)、天空(蓝色)。每个像素被分配为这些类别中的一个。但是有时你想要能够区分不同的汽车或不同的树。为此,有3种主要类型的图像分割,每种提供不同的细节和信息级别。

语义分割 vs. 实例分割 vs. 泛化分割

语义分割 vs. 实例分割 vs. 泛化分割

  • 语义分割根据像素的语义类别对其进行分类。所有的鸟属于同一个类别。
  • 实例分割为不同的实例分配唯一的标签,即使它们是相同的语义类别。每只鸟都属于不同的类别。
  • 泛化分割结合了两者,提供了类别级别和实例级别的标签。每只鸟都有自己的类别,但它们都被识别为“鸟”。

很酷,但我们怎么实际实现图像分割呢?有几种方法,比如阈值处理和聚类,但是当涉及到图像分割时,深度学习(我的最爱)确实是焦点。

U-Net

U-Net 架构最初设计用于医学图像分割,但现已被适用于许多其他用例。

U-Net

U-Net 具有编码器-解码器结构。编码器用于通过卷积和下采样将输入图像压缩为潜在空间表示。解码器用于通过卷积和上采样将潜在表示扩展到分割图像。沿着“U”形的长灰色箭头是跳过连接,它们具有两个主要目的:

  1. 在前向传播期间,它们使解码器能够访问来自编码器的信息。
  2. 在反向传播期间,它们充当梯度“超高速公路”,使解码器的梯度能够流向编码器。

模型的输出具有与输入相同的宽度和高度,但通道数将等于我们正在分割的类别数。

编码

U-Net 架构

定义模型架构相当简单:

from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, UpSampling2D, 
concatenate, Conv2DTranspose

def conv_block(x, n_filters):
    """two convolutions"""
    x = Conv2D(n_filters, (3, 3), padding='same', activation='relu')(x)
    x = Conv2D(n_filters, (3, 3), padding='same', activation='relu')(x)
    return x
    
def encoder_block(x, n_filters):
    """conv block and max pooling"""
    x = conv_block(x, n_filters)
    p = MaxPooling2D((2, 2))(x)
    return x, p # we will need x for the skip connections later

def decoder_block(x, p, n_filters):
    """upsample, skip connection, and conv block"""
    x = Conv2DTranspose(n_filters, (2, 2), strides=(2, 2), padding='same')(x)
    x = concatenate([x, p]) # concatenate = skip connection
    x = conv_block(x, n_filters)
    return x

def unet_model(n_classes, img_height, img_width, img_channels):
    inputs = Input((img_height, img_width, img_channels)) # 512x512x3
    
    # Contraction path, encoder
    c1, p1 = encoder_block(inputs, n_filters=64) # c1=512x512x64 p1=256x256x64
    c2, p2 = encoder_block(p1, n_filters=128) # c2=256x256x128 p2=128x128x128
    c3, p3 = encoder_block(p2, n_filters=256) # c3=128x128x256 p3=64x64x256
    c4, p4 = encoder_block(p3, n_filters=512) # c4=64x64x512 p4=32x32x512
    # Bottleneck
    bridge = conv_block(p5, n_filters=1024) # bridge=32x32x1024
    
    # Expansive path, decoder
    u4 = decoder_block(bridge, p4, n_filters=512) # 64x64x512
    u3 = decoder_block(u4, p3, n_filters=256) # 128x128x256
    u2 = decoder_block(u3, p2, n_filters=128) # 256x256x128
    u1 = decoder_block(u2, p1, n_filters=64) # 512x512x64
    outputs = Conv2D(n_classes, (1, 1), activation='softmax')(u1) # 512x512xn_classes
    # notice the softmax activation in the final layer
    model = Model(inputs=[inputs], outputs=[outputs])
    return model

# example classes: [road, vehicles, buildings, nature, background]
# instantiate model to predict 5 classes
unet_model = multi_unet_model(
    n_classes=5, 
    img_height=IMG_HEIGHT, 
    img_width=IMG_WIDTH, 
    img_channels=3
)
# input: 512x512x3
# output: 512x512x5  

损失函数:分类交叉熵

我们如何优化这个模型?嗯,因为图像分割实际上只是在像素级别上的分类,我们可以使用标准的分类损失函数,即分类交叉熵。

model.compile(
    loss="categorical_crossentropy",
    categorical_crossentropy
)  

我们可以将结果(512x512x5)体积的每个像素解释为长度为 5 的向量。由于最后一层在最后一个维度上使用 softmax 激活,因此每个像素向量包含该像素属于每个类别的概率。

模型输出

在训练模型之前,我们需要一个数据集。数据集应包含(图像,掩码)对,其中图像(x)的形状为(512x512x3),掩码(y)的形状为(512x512x5)。

这是一个示例的地面真实掩码:

每个像素只能属于一个类别,因此它包含一个类别通道中的“1”,并在其他通道中包含一个“0”。您可以将每个像素视为一个独热向量(因为它就是)。一旦准备好你的数据集,你就可以开始训练了:

model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=10,
)  

当然,这段代码不足以运行一个成功的模型。如果你真的想要实现这个,你需要考虑预处理、重新缩放、批处理等。我准备了一个运行图像分割模型的完整代码,可以用于汽车分割(对汽车的不同部分进行分割),链接:https://www.kaggle.com/code/rajpulapakura/car-segmentation-model-dev/notebook#Segmentation-model

最后说明

  • 类别不平衡:在图像分割中经常存在严重的类别不平衡。例如,在平均街景图像中,汽车和建筑物占据了大量像素,但停车标志占用的像素非常少。模型在分割停车标志时数据较少,因此它的性能会较差。为了解决这个问题,您可以使用 Focal 分类交叉熵和类别权重,它们强调少数类别。
  • 其他架构:U-Net 不是唯一的图像分割架构,尽管概念上是最简单的。其他架构包括 SegNet、Mask R-CNN 和 PSPNet。
  • 二进制分割:如果你只有一个类别要分割(例如在 MRI 扫描中分割脑肿瘤),那么模型的输出只需要是(512x512)。对于掩码,每个像素将包含一个“1”,表示该像素属于肿瘤,或者是一个“0”,表示该像素不属于肿瘤。确保在模型的最终激活中也将“softmax”更改为“sigmoid”,并使用(Focal)二元交叉熵损失函数。
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号