语义分割:基于 TensorFlow 对 FCN 与迁移学习的探究

欢迎来到计算机视觉的世界,在这里,计算机学会"看"懂并理解图像。想象一下,计算机不仅能识别物体,还能在它们周围画出精确的轮廓线------这就是图像分割的强大之处。

生成式AI vs 预测式AI:揭秘人工智能领域的两大技术

在当今这个技术驱动的时代,TensorFlow 这一机器学习工具已成为必不可少的存在。再配合 Python 语言的简洁性,我们便能通过一个有趣的项目来揭开图像分割的神秘面纱。

无论你是新手还是编程高手,本文都将是你的向导。在深入动手实践之前,让我们先花点时间了解图像分割的不同类型。

图像分割的类型:基础概念
  • 语义分割(Semantic Segmentation):将图像中的每个像素分类到预定义的类别中。例如,在街景中,它会将每个像素标记为汽车、行人、道路或建筑物的一部分。其目标是理解场景的总体上下文,而无需区分不同的个体实例。
  • 实例分割(Instance Segmentation):更进一步,它不仅为每个像素分配一个类别,还能精确地识别并勾勒出该类别中每个不同对象实例的边界。在一张拥挤的图片中,它能出色地区分出不同的人,即使他们属于同一类别,也能以极高的精度识别并勾勒出群体中的每一个人。
  • 全景分割(Panoptic Segmentation):语义分割与实例分割的结合体。它不仅提供了对场景的详细语义理解,还分离了这些类别中的每个实例。此外,它还为背景指定了一个特殊类别,从而捕捉到整个视觉环境的整体视图。这就好比理解了一个场景的完整故事,既包括了主角,也包括了他们身后的背景。

既然我们已经了解了基础概念,让我们深入探讨实践部分。我们将重点关注语义分割,这是一种在像素级别上提供对图像详细理解的激动人心的技术。

到最后,你不仅会掌握理论知识,还可以使用 TensorFlow 和 Python 构建自己的语义分割模型。

解读openAI的文本图像模型-CLIP

图像分割的高层架构:拆解分析

在图像分割领域,我们遵循一种被称为encoder-decoder结构的高层架构。

Encoder ------ 揭示特征

Encoder充当特征提取器,通常使用卷积神经网络(CNN)来实现。这一部分从图像中提取特征,形成我们所说的"feature map"。初始层专注于线条等低级特征,然后逐渐将它们聚合成眼睛和耳朵等更复杂的特征。这种聚合是通过下采样(downsampling)来实现的,类似于减少像素表示------就像你的视频聊天画面变得有点颗粒感,但你仍然可以通过那些关键特征认出你的朋友。

Decoder ------ 创造预测

现在,让我们转向Decoder。它的作用是获取这些提取出的特征并生成模型的输出或预测。和编码器一样,解码器也是一个卷积神经网络。它为缩小尺寸的特征图中的每个像素分配中间类别标签。然后,它开始进行上采样(upsampling)的复杂任务,逐渐恢复原始图像的精细细节。这个过程一直持续,直到图像恢复到其原始尺寸。

最后的修饰 ------ 像素级标签

最终的结果是一张像素级的标签图。每个像素都被分配了一个最终的类别标签,捕捉到了该像素在图像更广泛背景中所代表的本质。

简单来说,你可以把编码器想象成一名侦探,勤奋地从图像中提取线索(特征)。解码器则是讲故事的人,将这些线索拼凑起来,逐像素地揭示出完整的叙事。这种架构中编码器和解码器的配合,构成了图像分割算法的骨干,为我们提供了关于视觉数据的详细、像素级的洞察。

在本文中,我们将剖析用于语义分割的两个强大的架构:全卷积网络(FCN)和 U-Net。我们将与您一起深入探讨这些框架的核心,并使用 Python 和 TensorFlow 来实现并揭开它们的神秘面纱。我们将提供清晰、简洁的代码片段,并辅以富有见地的解释,以满足初学者和资深从业者的需求。准备好进行一次理论与实践相结合的动手探索,让您能够在项目中有效地利用 FCN 和 U-Net。无论您是好奇的爱好者还是经验丰富的开发者,本文都是您掌握语义分割的指南。

全卷积网络(FCN)

在传统的卷积神经网络(CNN)中,最后几层通常是全连接层,这意味着它们将每个神经元连接到其他的每个神经元。对于图像分类等任务来说,这没问题,因为在这些任务中空间信息并不那么关键。

介绍基于 C++ 的运动恢复结构(SfM)的三维重建

然而,对于语义分割等任务,目标是将图像中的每个像素分类到特定的类别(例如,人、车、背景)中,保留空间信息至关重要。Long、Shelhamer 和 Darrell 在他们的论文《Fully Convolutional Neural Networks for Semantic Segmentation》中提出了全卷积神经网络(FCN)。FCN 通过用卷积层替换全连接层来解决这个问题。这使得网络可以接受任何大小的输入,并产生相应大小的输出。

在语义分割中,FCN 通过在整个网络中保留空间信息,实现了像素级的预测。网络学习将输入图像中的每个像素与特定类别相关联,从而生成详细的分割图。这对于计算机视觉应用特别有用,例如目标检测、场景理解和图像分割。

在继续探索全卷积网络(FCN)的过程中,让我们深入研究一下被称为 FCN-8、FCN-16 和 FCN-32 的变体。这些都是 FCN 架构的不同配置,各自拥有独特的特性。

FCN-8、FCN-16 和 FCN-32:配置解析

FCN-32

FCN-32 在最后一层采用了 32 像素的步幅,导致输出分辨率仅为输入的 1/32。这种配置优先考虑计算效率,而非详细的空间信息。FCN-32 在处理速度上更快,使其适用于对速度要求极高、而对精细分割细节要求不那么严格的场景。

FCN-8

FCN-8 指的是最后一层具有 8 像素步幅的全卷积网络。换句话说,最后一层产生的输出分辨率是输入分辨率的 1/8。这种配置能够实现更精细的预测,捕捉分割图中更细微的差别。FCN-8 的解码器涉及融合来自不同层(包括空间信息较粗糙的层)的预测结果。这种融合过程提升了整体的分割质量。

FCN-16

FCN-16 则是在最后一层采用了 16 像素的步幅。这意味着输出分辨率是输入分辨率的 1/16。虽然与 FCN-8 相比,FCN-16 牺牲了一些空间细节,但它在分辨率和计算效率之间取得了很好的平衡。中间层有助于最终的分割图,提供了精细细节和计算资源之间的折衷方案。

选择合适的配置

FCN-8 通过 8 像素的步幅实现详细预测,捕捉细微差别;FCN-16 通过 16 像素的步幅在分辨率和效率之间取得平衡;而 FCN-32 则通过 32 像素的步幅优先考虑计算效率。

这些配置之间的选择取决于项目需求。在接下来的内容中,我们将使用 TensorFlow 和 Python 来实现每一种配置,从而释放 FCN 变体在语义分割中的潜力。

但这还不是全部------让我们通过结合迁移学习,利用 VGG16 或 ResNet 等预训练编码器,来为我们的 FCN 模型"超级充电"。这一变革性方法可以加速训练并提高准确性,尤其是在标记数据有限的情况下。

万字详解具身智能用的VLA:视觉-语言-动作模型(VLA)概述与Octo、OpenVLA、π0、GR00T N1等模型介绍

接下来是动手实践环节,我将提供代码片段和解释,提升您在计算机视觉领域的理解和熟练度。利用迁移学习的额外优势,释放 FCN-8、FCN-16 和 FCN-32 的全部潜力!

FCN-编码器:VGG16 迁移学习

VGG16(Visual Geometry Group 16)是一种著名的卷积神经网络,以其简单性和在图像分类中的高效性而闻名。它拥有统一的结构和 16 层,擅长学习分层特征。在我们的 FCN-编码器中,我们将利用 VGG16 进行迁移学习,利用其预训练权重来增强语义分割任务中的特征提取能力。以下是获取 VGG-16 预训练权重的链接。

随后,我们利用 VGG16 函数基于上述图像创建 VGG16 网络(仅包含卷积部分 conv1、conv2、conv3、conv4 和 conv5)。接着,我们加载下载的权重,并获取 FCN 解码器部分所需的关键层。

下载权重 vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5

权重文件

代码:

复制代码
def VGG_16(image_input):    x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3,3), activation='relu', padding='same', name='conv1-1')(image_input)    x = tf.keras.layers.Conv2D(filters=64, kernel_size=(3,3), activation='relu', padding='same', name='conv1-2')(x)    x = tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2), name='max1')(x)    p1 = x    x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3,3), activation='relu', padding='same', name='conv2-1')(x)    x = tf.keras.layers.Conv2D(filters=128, kernel_size=(3,3), activation='relu', padding='same', name='conv2-2')(x)    x = tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2), name='max2')(x)    p2 = x    x = tf.keras.layers.Conv2D(filters=256, kernel_size=(3,3), activation='relu', padding='same', name='conv3-1')(x)    x = tf.keras.layers.Conv2D(filters=256, kernel_size=(3,3), activation='relu', padding='same', name='conv3-2')(x)    x = tf.keras.layers.Conv2D(filters=256, kernel_size=(3,3), activation='relu', padding='same', name='conv3-3')(x)    x = tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2), name='max3')(x)    p3 = x    x = tf.keras.layers.Conv2D(filters=512, kernel_size=(3,3), activation='relu', padding='same', name='conv4-1')(x)    x = tf.keras.layers.Conv2D(filters=512, kernel_size=(3,3), activation='relu', padding='same', name='conv4-2')(x)    x = tf.keras.layers.Conv2D(filters=512, kernel_size=(3,3), activation='relu', padding='same', name='conv4-3')(x)    x = tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2), name='max4')(x)    p4 = x    x = tf.keras.layers.Conv2D(filters=512, kernel_size=(3,3), activation='relu', padding='same', name='conv5-1')(x)    x = tf.keras.layers.Conv2D(filters=512, kernel_size=(3,3), activation='relu', padding='same', name='conv5-2')(x)    x = tf.keras.layers.Conv2D(filters=512, kernel_size=(3,3), activation='relu', padding='same', name='conv5-3')(x)    x = tf.keras.layers.MaxPooling2D(pool_size=(2,2), strides=(2,2), name='max5')(x)    p5 = x    vgg = tf.keras.Model(image_input, p5)    vgg.load_weights('vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5')    return (p1, p2, p3, p4, p5)

FCN-8-解码器:上采样

fcn8_decoder 函数利用 Conv2DTranspose 进行上采样。此操作对于增加特征图的空间分辨率至关重要。与通过步幅(stride)降低分辨率的传统卷积不同,Conv2DTranspose 使用步幅来提高分辨率。

简单来说,它"填充"了现有像素之间的间隙,有效地扩大了图像尺寸。这在语义分割中至关重要,因为它有助于恢复在编码器下采样过程中丢失的精细细节。

在代码 o = tf.keras.layers.Conv2DTranspose(n_classes , kernel_size=(4,4) , strides=(2,2) , use_bias=False)(f5) 中,转置卷积操作使空间维度加倍,随后的操作进一步优化了分割图。

总而言之,Conv2DTranspose 通过扩大特征图的分辨率,成为了在语义分割中重建详细信息的关键。

复制代码
def fcn8_decoder(convs, n_classes):    """    FCN-8解码器:将编码器特征图逐步上采样并与浅层特征融合    参数:        convs: 编码器的五个特征图 [f1, f2, f3, f4, p5]        n_classes: 分割类别数    """    f1, f2, f3, f4, p5 = convs    n = 4096  # 卷积层的通道数    # 对编码器输出进行两次卷积    c6 = tf.keras.layers.Conv2D(n, (7, 7), activation='relu', padding='same', name="conv6")(p5)    c7 = tf.keras.layers.Conv2D(n, (1, 1), activation='relu', padding='same', name="conv7")(c6)    f5 = c7    # 对编码器输出进行2倍上采样,并裁剪多余的像素    o = tf.keras.layers.Conv2DTranspose(n_classes, kernel_size=(4, 4), strides=(2, 2), use_bias=False)(f5)    o = tf.keras.layers.Cropping2D(cropping=(1, 1))(o)    # 处理pool4特征:通过1x1卷积调整通道数与上采样结果一致    o2 = f4    o2 = tf.keras.layers.Conv2D(n_classes, (1, 1), activation='relu', padding='same')(o2)    # 将上采样结果与pool4预测结果相加(特征融合)    o = tf.keras.layers.Add()([o, o2])    # 对融合结果进行2倍上采样    o = tf.keras.layers.Conv2DTranspose(n_classes, kernel_size=(4, 4), strides=(2, 2), use_bias=False)(o)    o = tf.keras.layers.Cropping2D(cropping=(1, 1))(o)    # 处理pool3特征:通过1x1卷积调整通道数    o2 = f3    o2 = tf.keras.layers.Conv2D(n_classes, (1, 1), activation='relu', padding='same')(o2)    # 将上采样结果与pool3预测结果相加(特征融合)    o = tf.keras.layers.Add()([o, o2])    # 最终8倍上采样,恢复到原始图像尺寸    o = tf.keras.layers.Conv2DTranspose(n_classes, kernel_size=(8, 8), strides=(8, 8), use_bias=False)(o)    # 添加softmax激活函数获取类别概率    o = tf.keras.layers.Activation('softmax')(o)    return o

FCN-16-解码器:上采样

FCN-16s 架构结合了最终层和步幅为 16 的 pool4 层的预测结果。这使得网络能够在保留最终层高层语义信息的同时,利用来自 pool4 的高分辨率信息来预测更精细的细节。它在细节信息和语义上下文之间取得了平衡。

复制代码
def fcn16_decoder(convs, n_classes):    """    FCN-16解码器:将编码器特征图与中间特征融合,直接上采样到原始尺寸    参数:        convs: 编码器的五个特征图 [f1, f2, f3, f4, p5]        n_classes: 分割类别数    """    f1, f2, f3, f4, p5 = convs    n = 4096  # 卷积层的通道数    # 对编码器输出进行两次卷积    c6 = tf.keras.layers.Conv2D(n, (7, 7), activation='relu', padding='same', name="conv6")(p5)    c7 = tf.keras.layers.Conv2D(n, (1, 1), activation='relu', padding='same', name="conv7")(c6)    f5 = c7    # 对编码器输出进行2倍上采样,并裁剪多余的像素    o = tf.keras.layers.Conv2DTranspose(n_classes, kernel_size=(4, 4), strides=(2, 2), use_bias=False)(f5)    o = tf.keras.layers.Cropping2D(cropping=(1, 1))(o)    # 处理pool4特征:通过1x1卷积调整通道数与上采样结果一致    o2 = f4    o2 = tf.keras.layers.Conv2D(n_classes, (1, 1), activation='relu', padding='same')(o2)    # 将上采样结果与pool4预测结果相加(特征融合)    o = tf.keras.layers.Add()([o, o2])    # 直接16倍上采样,恢复到原始图像尺寸    o = tf.keras.layers.Conv2DTranspose(n_classes, kernel_size=(8, 8), strides=(16, 16), use_bias=False)(o)    # 添加softmax激活函数获取类别概率    o = tf.keras.layers.Activation('softmax')(o)    return o

FCN-32-解码器:上采样

在 FCN-32s 中,单流网络直接通过步幅为 32 的上采样,一步到位地将预测结果还原到像素级别。这种方法简化了上采样过程,为每个像素提供了整体的预测。

复制代码
def fcn32_decoder(convs, n_classes):    """    FCN-32解码器:直接将编码器特征图上采样到原始尺寸    参数:        convs: 编码器的五个特征图 [f1, f2, f3, f4, p5]        n_classes: 分割类别数    """    f1, f2, f3, f4, p5 = convs    n = 4096  # 卷积层的通道数    # 对编码器输出进行两次卷积    c6 = tf.keras.layers.Conv2D(n, (7, 7), activation='relu', padding='same', name="conv6")(p5)    c7 = tf.keras.layers.Conv2D(n, (1, 1), activation='relu', padding='same', name="conv7")(c6)    f5 = c7    # 直接32倍上采样,恢复到原始图像尺寸    o = tf.keras.layers.Conv2DTranspose(n_classes, kernel_size=(8, 8), strides=(32, 32), use_bias=False)(f5)    # 添加softmax激活函数获取类别概率    o = tf.keras.layers.Activation('softmax')(o)    return o

在上采样的背景下,步幅(stride)至关重要,因为它决定了采样点之间的间距,从而影响输出尺寸。另一方面,卷积核大小(kernel size)影响滤波器的大小以及每个单元收集的信息,但在上采样期间它并不直接影响输出的空间分辨率。选择合适的步幅是控制网络在像素级别预测精细程度的关键。

计算掩码评估指标

该函数计算预测掩码与真实标签掩码之间的两个核心指标------交并比(Intersection over Union, IOU)和 Dice 系数(Dice Score)。这些指标由以下公式表示:

为了方便您使用,提供的代码会自动执行此计算。此外,我们在分母中加入了一个微小的平滑因子,以防止可能出现的除零情况。

复制代码
def IOU_diceScore(y_true, y_pred):    class_wise_iou = []    class_wise_dice_score = []    smoothening_factor = 0.00001    for i in range(12):        intersection = np.sum((y_pred==i) * (y_true==i))        y_true_area = np.sum((y_true==1))        y_pred_area = np.sum((y_pred==i))        combined_area = y_true_area + y_pred_area        iou = (intersection + smoothening_factor) / (combined_area-intersection+smoothening_factor)        class_wise_iou.append(iou)        dice_score = 2 * ((intersection+smoothening_factor)) / (combined_area + smoothening_factor)        class_wise_dice_score.append(dice_score)    return class_wise_iou, class_wise_dice_score
结语

在结束对 TensorFlow 语义分割的探索时,我们涵盖了基础知识,解析了图像分割的类型,并深入探讨了 FCN 的高层架构。文章剖析了 FCN 的不同变体,强调了根据项目需求在 FCN-8、FCN-16 和 FCN-32 之间进行战略性选择的重要性。

通过引入迁移学习(特别是使用 VGG16 作为编码器),我们的 FCN 模型得到了增强,这不仅提升了训练效率和准确性,尤其在标记数据有限的情况下更是如此。

我们对 FCN-8、FCN-16 和 FCN-32 解码器的详细剖析,加上用于模型评估的 IOU 和 Dice Score 等指标,使读者能够有效地实现这些架构。

无论您是初学者还是经验丰富的开发者,本文都提供了一份简洁的指南,包含清晰的代码片段和动手实践环节。要获取所有的实现代码和数据预处理细节,请私信我。

什么是无监督学习?理解人工智能中无监督学习的机制、各类算法的类型与应用

相关推荐
NAGNIP4 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab5 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab5 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP9 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年9 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼9 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS9 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区10 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈11 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang11 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx