图像分割 — 初学者指南

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

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

分类器

264ac9845524a5480356306c03b4b857.jpeg

可爱的小狗

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

4b20bfe9db8d6e8042b43b7a643a6b02.png

可爱的小狗 + 边界框

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

图像分割

50946e305f659acc369ed6bc78ec0445.png

街道分割

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

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

a157ac1cecb585324c3e9139455e4c64.jpeg

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

  • 语义分割根据像素的语义类别对其进行分类。所有的鸟属于同一个类别。

  • 实例分割为不同的实例分配唯一的标签,即使它们是相同的语义类别。每只鸟都属于不同的类别。

  • 泛化分割结合了两者,提供了类别级别和实例级别的标签。每只鸟都有自己的类别,但它们都被识别为“鸟”。

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

U-Net

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

17ec391247697a52d0bffa43fa1e6de1.png

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 激活,因此每个像素向量包含该像素属于每个类别的概率。

f1b6230dd86aea97ac83bba9bc58048b.png

模型输出

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

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

a73ddb0244c65c11280df49763e002af.png

每个像素只能属于一个类别,因此它包含一个类别通道中的“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)二元交叉熵损失函数。

·  END  ·

HAPPY LIFE

62851a7bc2b853bc3049b0aa74959609.png

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

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值