图像分割(FCN/U-Net/Mask R-CNN)

像素级图像理解 —— 从全卷积网络到区域卷积网络的演进之路

核心主题: 深度学习图像分割方法全景解析

主要内容: 语义分割 vs 实例分割 vs 全景分割、全卷积网络(FCN)、U-Net 编码器-解码器架构、Mask R-CNN 实例分割、分割评估指标体系、PyTorch 完整代码实现

关键词:图像分割FCNU-NetMask R-CNN语义分割实例分割RoIAlignmIoUDice

一、图像分割任务概述

图像分割(Image Segmentation)是计算机视觉的核心任务之一,目标是将图像划分为具有语义含义的多个区域。与图像分类(给整张图打标签)和目标检测(用边界框圈出物体)不同,图像分割要求在像素级别进行分类,为每一个像素分配一个类别标签。根据输出粒度的不同,图像分割可细分为三大类任务。

1.1 语义分割(Semantic Segmentation)

语义分割将图像中的每个像素划分为预定义的语义类别,例如:人、车、道路、天空、建筑等。同类别不同实例不加以区分——如果图像中有三辆车,语义分割会将所有车像素标记为同一个"车"类别,而不区分车A、车B、车C。典型的语义分割应用包括:自动驾驶道路场景理解、遥感图像地物分类、医学图像器官分割等。代表方法包括FCN、U-Net、DeepLab系列、SegFormer等。

1.2 实例分割(Instance Segmentation)

实例分割在语义分割的基础上更进一步,不仅要给每个像素分配类别标签,还要区分同一类别中的不同个体。例如,图像中有三个人,实例分割会分别标记为"人1"、"人2"、"人3"。实例分割可以看作目标检测 + 语义分割的结合体:先用目标检测定位每个实例,再在边界框内做像素级分割。代表方法包括Mask R-CNN、YOLACT、SOLO等。应用场景包括:细胞图像分析、商品检测分割、视频监控人群分析等。

1.3 全景分割(Panoptic Segmentation)

全景分割由Kirillov等人于2019年提出,统一了语义分割和实例分割两大任务。它将图像中的物体分为两类:"stuff"(不可数/无定形物)"things"(可数物体)。Stuff类别(天空、道路、草地等)采用语义分割方式处理,每个像素只分配类别标签;Things类别(人、车、动物等)采用实例分割方式处理,每个像素同时分配类别标签和实例ID。全景分割要求输出既完整(覆盖所有像素)又一致(stuff和things不重叠),对模型的场景理解能力提出了更高的要求。

1.4 三类分割任务对比

对比维度语义分割实例分割全景分割
输出粒度逐像素类别逐像素类别+实例ID逐像素类别+实例ID
区分个体不区分区分(things only)区分(things)
覆盖范围全部像素仅things区域全部像素
代表方法FCN, U-Net, DeepLabMask R-CNN, YOLACTPanoptic FPN, UPSNet
典型应用自动驾驶、遥感医学细胞、电商自动驾驶、机器人

理解三类分割的递进关系:

语义分割是"这是什么"(像素级分类)-> 实例分割是"这是哪个"(区分个体)-> 全景分割是"这是哪个类别下的哪个个体"(统一框架)。三者构成从粗到细的场景理解层级链。

二、全卷积网络(FCN)

全卷积网络(Fully Convolutional Network, FCN)由Long等人于2015年在CVPR上提出(论文:Fully Convolutional Networks for Semantic Segmentation),是首个端到端的像素级语义分割深度学习模型。FCN的核心贡献在于证明了分类网络(如VGG、AlexNet)可以改造为分割网络,为后续所有分割方法奠定了理论基础。

2.1 核心思想:全连接层转卷积

传统分类CNN(如VGG-16)最后使用全连接层将特征图映射为固定长度的分类向量,但这种方法丢失了空间信息。FCN的关键创新是将全连接层替换为卷积层,使网络可以接受任意尺寸的输入图像,并输出相应尺寸的密集预测(dense prediction)。具体来说,将VGG-16最后的4096维全连接层视为一个卷积核大小为7x7的卷积层,将第二个4096维全连接层视为卷积核大小为1x1的卷积层,从而保留特征图的空间维度。

import torch import torch.nn as nn import torch.nn.functional as F class FCN32s(nn.Module): """FCN-32s: 直接32倍上采样,无跳跃连接""" def __init__(self, num_classes): super().__init__() self.num_classes = num_classes # 骨干网络:VGG-16 特征提取部分(去掉分类头) self.features = nn.Sequential( # Block 1 nn.Conv2d(3, 64, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(64, 64, 3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2, stride=2, ceil_mode=True), # 1/2 # Block 2 nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(128, 128, 3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2, stride=2, ceil_mode=True), # 1/4 # Block 3 nn.Conv2d(128, 256, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(256, 256, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(256, 256, 3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2, stride=2, ceil_mode=True), # 1/8 # Block 4 nn.Conv2d(256, 512, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2, stride=2, ceil_mode=True), # 1/16 # Block 5 nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2, stride=2, ceil_mode=True), # 1/32 ) # 全连接层 -> 卷积层 self.fc6 = nn.Conv2d(512, 4096, 7, padding=3) self.fc7 = nn.Conv2d(4096, 4096, 1) self.score = nn.Conv2d(4096, num_classes, 1) # 32倍反卷积上采样 self.upscore = nn.ConvTranspose2d( num_classes, num_classes, 64, stride=32, padding=16, bias=False ) def forward(self, x): x = self.features(x) x = F.relu(self.fc6(x)) x = F.dropout(x, p=0.5, training=self.training) x = F.relu(self.fc7(x)) x = F.dropout(x, p=0.5, training=self.training) x = self.score(x) x = self.upscore(x) # 裁剪到输入尺寸(去除额外padding引入的边界) x = x[:, :, 19:19 + x.size()[2]] return x

2.2 反卷积上采样(Transposed Convolution)

由于经过多次池化和步长为2的卷积操作后,特征图分辨率降低为输入的1/32,需要将低分辨率的热力图(heatmap)上采样到原始输入尺寸。FCN使用转置卷积(Transposed Convolution, 也称反卷积)来实现可学习的上采样。转置卷积的核参数可以通过网络训练学习得到,相比双线性插值等固定上采样方法能更好地恢复细节信息。FCN-32s使用步长为32的大核转置卷积直接上采样32倍,虽然简单直接,但预测结果较为粗糙。

2.3 跳跃连接(Skip Connection)

直接32倍上采样导致分割结果丢失了大量细节信息。FCN提出了跳跃连接(Skip Connection)来融合浅层高分辨率特征和深层语义特征。核心思路是:浅层(如pool3、pool4)感受野小、分辨率高,包含丰富的空间位置信息;深层(如pool5/conv7)感受野大、语义信息丰富,但分辨率低。通过将深层预测上采样后与浅层预测逐元素相加(element-wise addition),可以融合多尺度特征,显著提升分割精度。

FCN-32s: conv7 (1/32) ----32x upsampling----> prediction

FCN-16s: conv7 (1/32) --2x up--> pool4 (1/16) --16x up--> prediction

FCN-8s: conv7 (1/32) --2x up--> pool4 --2x up--> pool3 (1/8) --8x up--> prediction

class FCN8s(nn.Module): """FCN-8s: 融合 pool3, pool4, pool5 三层特征的跳跃连接版本""" def __init__(self, num_classes): super().__init__() self.num_classes = num_classes # 使用预训练的 VGG-16 特征层 self.pool3 = nn.Sequential( nn.Conv2d(3, 64, 3, padding=1), nn.ReLU(True), nn.Conv2d(64, 64, 3, padding=1), nn.ReLU(True), nn.MaxPool2d(2, stride=2, ceil_mode=True), nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(True), nn.Conv2d(128, 128, 3, padding=1), nn.ReLU(True), nn.MaxPool2d(2, stride=2, ceil_mode=True), nn.Conv2d(128, 256, 3, padding=1), nn.ReLU(True), nn.Conv2d(256, 256, 3, padding=1), nn.ReLU(True), nn.Conv2d(256, 256, 3, padding=1), nn.ReLU(True), nn.MaxPool2d(2, stride=2, ceil_mode=True), ) self.pool4 = nn.Sequential( nn.Conv2d(256, 512, 3, padding=1), nn.ReLU(True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(True), nn.MaxPool2d(2, stride=2, ceil_mode=True), ) self.pool5 = nn.Sequential( nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(True), nn.Conv2d(512, 512, 3, padding=1), nn.ReLU(True), nn.MaxPool2d(2, stride=2, ceil_mode=True), ) # 全连接->卷积分类器 self.classifier = nn.Sequential( nn.Conv2d(512, 4096, 7, padding=3), nn.ReLU(True), nn.Dropout2d(p=0.5), nn.Conv2d(4096, 4096, 1), nn.ReLU(True), nn.Dropout2d(p=0.5), nn.Conv2d(4096, num_classes, 1), ) # 1x1卷积将中间层降维到 num_classes 通道 self.score_pool4 = nn.Conv2d(512, num_classes, 1) self.score_pool3 = nn.Conv2d(256, num_classes, 1) # 转置卷积上采样层 self.upscore2 = nn.ConvTranspose2d( num_classes, num_classes, 4, stride=2, padding=1, bias=False ) self.upscore4 = nn.ConvTranspose2d( num_classes, num_classes, 4, stride=2, padding=1, bias=False ) self.upscore8 = nn.ConvTranspose2d( num_classes, num_classes, 16, stride=8, padding=4, bias=False ) def forward(self, x): input_shape = x.shape[2:] # 提取三个阶段的特征 x3 = self.pool3(x) # 1/8 x4 = self.pool4(x3) # 1/16 x5 = self.pool5(x4) # 1/32 # 深层预测 score = self.classifier(x5) # 1/32 # 融合 pool4 特征 score = self.upscore2(score) # -> 1/16 score_pool4 = self.score_pool4(x4) score = score + score_pool4 # 融合 pool3 特征 score = self.upscore4(score) # -> 1/8 score_pool3 = self.score_pool3(x3) score = score + score_pool3 # 8倍上采样回到原图尺寸 score = self.upscore8(score) # 裁剪对齐 score = score[:, :, :input_shape[0], :input_shape[1]] return score

FCN的历史意义与局限性:

  • 开创性贡献:证明了全卷积架构可以端到端地完成像素级预测任务,为后续所有分割模型(U-Net、DeepLab、Mask R-CNN等)奠定了方法论基础
  • 效率优势:一次前向传播即可生成密集预测,无需像传统方法那样使用滑动窗口或区域提案
  • 局限性:上采样结果仍不够精细,缺乏对全局上下文的显式建模,对物体边界的处理不够精确
  • 继承关系:FCN的跳跃连接思想直接启发了U-Net的编码器-解码器架构

三、U-Net:编码器-解码器对称架构

U-Net由Ronneberger等人于2015年在MICCAI上提出(论文:U-Net: Convolutional Networks for Biomedical Image Segmentation),是医学图像分割领域最具影响力的模型。其名称来源于网络结构的U形对称形状——左侧为编码器(下采样路径),右侧为解码器(上采样路径),底部为瓶颈层。U-Net在FCN的跳跃连接思想上进一步发展,将编码器每一层的特征通过拼接(concatenate)方式传递给解码器的对应层,形成更丰富的多尺度特征融合。

3.1 编码器-解码器结构

U-Net的编码器(收缩路径)由重复的两个3x3卷积 + ReLU + 2x2最大池化(步长2)组成,每次下采样后特征通道数加倍。解码器(扩展路径)由2x2转置卷积上采样 + 跳跃连接拼接 + 两个3x3卷积 + ReLU组成,每次上采样后特征通道数减半。最后一层使用1x1卷积将特征映射到目标类别数。这种对称结构使得网络能够同时利用浅层的精确定位信息和深层的语义信息。

Input(1,568,568) -> Conv(64) -> Conv(64) -> Pool -> [Skip] ------+

+-> Conv(128) -> Conv(128) -> Pool -> [Skip] ---+ |

+-> Conv(256) -> Conv(256) -> Pool -> [Skip] -+---+ |

+-> Conv(512) -> Conv(512) -> Pool | | |

+-> Conv(1024) -> Conv(1024) | | |

+- UpConv(512) -> Concat[512] -> Conv(512) -+ | |

+- UpConv(256) -> Concat[256] -> Conv(256) ------+ |

+- UpConv(128) -> Concat[128] -> Conv(128) ---------------+

+- UpConv(64) -> Concat[64] -> Conv(64) -> Conv(num_classes)

3.2 跳跃连接:Concatenate vs Add

U-Net与FCN在跳跃连接上的关键区别在于:FCN使用逐元素相加(Add)的方式融合特征,而U-Net使用通道拼接(Concatenate)。Concatenate方式将编码器特征图和解码器特征图在通道维度上拼接在一起,保留了原始特征的所有信息,让网络自行学习如何融合。实验证明,Concatenate比Add能保留更多的空间定位细节,特别适合医学图像中精细结构的分割任务。

3.3 医学图像分割的优势

U-Net在医学图像分割领域取得了巨大成功,主要原因包括:

3.4 PyTorch完整实现

import torch import torch.nn as nn import torch.nn.functional as F class DoubleConv(nn.Module): """双卷积块: Conv2d + BN + ReLU (两次)""" def __init__(self, in_ch, out_ch): super().__init__() self.conv = nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), ) def forward(self, x): return self.conv(x) class Down(nn.Module): """下采样: MaxPool + DoubleConv""" def __init__(self, in_ch, out_ch): super().__init__() self.mpconv = nn.Sequential( nn.MaxPool2d(2), DoubleConv(in_ch, out_ch), ) def forward(self, x): return self.mpconv(x) class Up(nn.Module): """上采样: 转置卷积 + 拼接 + DoubleConv""" def __init__(self, in_ch, out_ch, bilinear=False): super().__init__() # 上采样方式选择 if bilinear: self.up = nn.Upsample( scale_factor=2, mode='bilinear', align_corners=True ) else: self.up = nn.ConvTranspose2d( in_ch, in_ch // 2, 2, stride=2 ) self.conv = DoubleConv(in_ch, out_ch) def forward(self, x1, x2): x1 = self.up(x1) # 处理尺寸不匹配(padding导致的尺寸差) diff_h = x2.size()[2] - x1.size()[2] diff_w = x2.size()[3] - x1.size()[3] x1 = F.pad(x1, [ diff_w // 2, diff_w - diff_w // 2, diff_h // 2, diff_h - diff_h // 2, ]) # 跳跃连接:通道拼接(Concatenate) x = torch.cat([x2, x1], dim=1) return self.conv(x) class UNet(nn.Module): """U-Net: 编码器-解码器对称架构""" def __init__(self, n_channels, n_classes, base_filters=64): super().__init__() self.inc = DoubleConv(n_channels, base_filters) # 64 self.down1 = Down(base_filters, base_filters * 2) # 128 self.down2 = Down(base_filters * 2, base_filters * 4) # 256 self.down3 = Down(base_filters * 4, base_filters * 8) # 512 self.down4 = Down(base_filters * 8, base_filters * 8) # 512 (bottleneck) self.up1 = Up(base_filters * 8, base_filters * 4) # 256 self.up2 = Up(base_filters * 4, base_filters * 2) # 128 self.up3 = Up(base_filters * 2, base_filters) # 64 self.up4 = Up(base_filters, base_filters) self.outc = nn.Conv2d(base_filters, n_classes, 1) def forward(self, x): # 编码器(收缩路径) x1 = self.inc(x) x2 = self.down1(x1) x3 = self.down2(x2) x4 = self.down3(x3) x5 = self.down4(x4) # 解码器(扩展路径)+ 跳跃连接 x = self.up1(x5, x4) x = self.up2(x, x3) x = self.up3(x, x2) x = self.up4(x, x1) logits = self.outc(x) return logits def test_unet(): """验证U-Net输出尺寸""" model = UNet(n_channels=3, n_classes=2, base_filters=64) x = torch.randn(4, 3, 256, 256) # batch=4, RGB, 256x256 out = model(x) print(f"Input shape: {x.shape}") print(f"Output shape: {out.shape}") # Expected: torch.Size([4, 2, 256, 256]) assert out.shape == (4, 2, 256, 256), "Output shape mismatch!" print("U-Net test passed!") if __name__ == "__main__": test_unet()

U-Net训练关键技巧:

  • 损失函数:推荐使用Dice Loss + Cross-Entropy Loss的组合(混合损失),在类别不平衡时表现优异
  • 数据增强:弹性形变(ElasticTransform)、随机旋转、缩放、平移、亮度调整等
  • 权重初始化:使用Kaiming初始化(He init)配合ReLU激活函数
  • 学习率策略:建议使用余弦退火(Cosine Annealing)或ReduceLROnPlateau
  • 批量归一化:在每次卷积后添加BatchNorm层,加速收敛并提高稳定性
  • Patch-based训练:对于大尺寸医学图像(如病理切片),采用随机裁剪的patch方式训练

3.5 U-Net变体与演进

U-Net自提出以来衍生出大量变体,形成了U-Net家族:3D U-Net将所有2D操作替换为3D操作,适用于CT/MRI等三维医学图像;Attention U-Net引入注意力门控(Attention Gate)机制,让网络自动聚焦于目标区域;UNet++通过嵌套密集跳跃连接(Nested Dense Skip Pathways)缩短编码器与解码器之间的语义鸿沟;nnU-Net提出自适应框架,能根据数据集自动配置网络结构、预处理和训练策略,在多个医学分割挑战赛中夺冠。

四、Mask R-CNN:实例分割的里程碑

Mask R-CNN由He等人于2017年在ICCV上提出(论文:Mask R-CNN),获得了当年的最佳论文奖。它是在Faster R-CNN目标检测框架的基础上,通过添加一个并行的分割分支(Mask Branch)来实现实例分割。Mask R-CNN在速度和精度上都达到了当时的最优水平,成为实例分割的事实标准。

4.1 Faster R-CNN扩展

Faster R-CNN包含两个阶段:第一阶段使用区域提案网络(RPN)生成候选区域(Region Proposals);第二阶段对每个候选区域进行RoI池化(RoIPool),然后通过分类头和回归头预测类别和边界框。Mask R-CNN在第二阶段增加了第三个并行分支——分割头(Mask Head),对每个RoI区域预测一个二值分割掩码(binary mask)。这种多任务学习框架同时进行目标检测和像素级分割,不同任务之间共享骨干网络的特征,相互促进。

Input Image -> Backbone (ResNet+FPN) -> Feature Maps

+---> RPN (Region Proposal Network) ---> RoIs

+---> RoIAlign ---> [Classification Branch]

| +-> [BBox Regression Branch]

+---> RoIAlign ---> [Mask Branch (Segmentation)]

4.2 RoIAlign:像素级对齐的关键

这是Mask R-CNN最核心的技术创新之一。Faster R-CNN使用的RoIPool(RoI池化)在量化操作中损失了空间精度——当候选区域坐标是浮点数时,RoIPool会通过取整操作将其量化为整数,导致像素级别的偏移。对于目标检测(只需预测边界框坐标),这种偏移影响不大;但对于像素级分割任务,亚像素精度的损失会直接影响掩码质量

RoIAlign通过双线性插值(Bilinear Interpolation)解决这一问题:在采样过程中不进行任何取整操作,而是保留浮点数坐标,在特征图上通过双线性插值计算出每个采样点的值。这使得RoIAlign能够保持像素级的空间对齐,显著提升了分割掩码的精度。

import torch import torch.nn as nn import torchvision from torchvision.ops import roi_align # RoIAlign 使用示例 # 特征图: batch=1, channels=256, height=32, width=32 feature_map = torch.randn(1, 256, 32, 32) # RoI 提议区域 [batch_idx, x1, y1, x2, y2] 坐标在原图尺度 rois = torch.tensor([ [0, 10.0, 15.0, 100.0, 120.0], # 第一个RoI [0, 50.0, 30.0, 180.0, 200.0], # 第二个RoI ]) # 输出尺寸: 7x7, 空间尺度: 原图/特征图 = 32 (下采样倍数) output = roi_align( feature_map, rois, output_size=(7, 7), spatial_scale=1.0 / 32.0, # 原图到特征图的缩放比例 aligned=True, # 启用精确对齐(RoIAlign vs RoIPool) ) print(f"Output shape: {output.shape}") # torch.Size([2, 256, 7, 7])

4.3 分割分支与Keypoint分支

Mask R-CNN的分割分支是一个小型FCN,对每个RoI预测一个m x m的掩码(通常28x28)。与语义分割不同,分割分支为每个类别独立预测掩码(class-specific),通过分类分支预测的类别来索引对应的掩码,避免了类别间的竞争。这种解耦设计使得分割分支可以专注于学习像素级定位,而将分类任务交给分类分支。

除了分割分支,Mask R-CNN还可以轻松扩展其他分支。原论文展示了人体关键点检测(Keypoint Detection)分支,将Mask R-CNN扩展到多人姿态估计任务。这种灵活的多任务架构是Mask R-CNN的一大优势——不同任务共享骨干网络的特征提取能力,各任务头专注于自身的预测目标。

4.4 实例分割完整流程

  1. 特征提取:输入图像经过ResNet+FPN骨干网络,生成多尺度特征图(P2-P6)
  2. 区域提案:RPN在特征图的每个位置生成锚框(anchors),筛选出前景候选区域
  3. RoIAlign:对每个候选区域,从对应的特征层级提取固定尺寸(7x7)的特征
  4. 目标检测:分类头预测类别,回归头微调边界框坐标(使用NMS去除重复检测)
  5. 掩码预测:对每个检测到的目标,分割头在14x14的RoI上预测28x28的二值掩码
  6. 上采样融合:将预测掩码上采样到边界框尺寸,融合到原图对应位置

Mask R-CNN的设计哲学:

将实例分割分解为"检测+分割"两个子任务,让每个子任务专注于自己擅长的部分。检测任务(分类+回归)负责"找到物体在哪里、是什么类别",分割任务负责"在检测到的区域内精确勾勒物体轮廓"。这种分而治之的策略不仅使模型更易优化,也为后续的模块化改进提供了清晰的接口。

五、分割评估指标

要客观衡量图像分割模型的性能,需要一套标准化的评估指标。不同的指标从不同角度反映了分割质量。

5.1 IoU(Intersection over Union)与 mIoU

IoU(交并比)是分割任务中最核心的评估指标,计算预测区域与真实标注区域的交集面积除以并集面积。IoU的取值范围为[0,1],值越大表示预测越准确。mIoU(Mean IoU)是所有类别IoU的平均值,是语义分割最常用的单一指标。

def compute_iou(pred_mask, gt_mask): """计算单个类别的IoU Args: pred_mask, gt_mask: 二值掩码 (H, W), 值为0或1 Returns: IoU 标量 """ intersection = (pred_mask & gt_mask).sum().float() union = (pred_mask | gt_mask).sum().float() if union == 0: return 1.0 # 真负例:都预测为背景,IoU定义为1 return (intersection / union).item() def compute_miou(pred, gt, num_classes): """计算 mIoU(所有类别的平均IoU) Args: pred: 预测标签图 (H, W), 每个像素值为类别索引 gt: 真实标签图 (H, W) num_classes: 类别数(包括背景) """ ious = [] for cls in range(num_classes): pred_mask = (pred == cls) gt_mask = (gt == cls) iou = compute_iou(pred_mask, gt_mask) ious.append(iou) return sum(ious) / len(ious)

5.2 Dice系数(Dice Coefficient / F1 Score)

Dice系数是医学图像分割中最常用的指标之一,其计算公式为 2 * |A & B| / (|A| + |B|)。与IoU高度相关(Dice = 2*IoU / (1+IoU)),但Dice对交集更加敏感,在训练中作为损失函数时梯度更平滑,尤其在类别极度不平衡时优势明显。

5.3 Pixel Accuracy(像素准确率)

Pixel Accuracy计算正确分类的像素数占总像素数的比例。虽然直观易理解,但在类别不平衡场景下存在严重缺陷——如果背景占图像的95%,模型将所有像素预测为背景就能达到95%的准确率,但这显然不是好的分割结果。因此,Pixel Accuracy通常需要结合其他指标使用。

5.4 边界F1-score(Boundary F1-score)

边界F1-score专门评估分割边界的质量,计算预测边界与真实边界之间的精确率(Precision)、召回率(Recall)和F1值。这一指标对分割结果的边界平滑度、连续性非常敏感,能够在IoU相近的情况下区分不同模型的边界质量。对于医学图像分割(如肿瘤边界勾画)和自动驾驶(如道路边缘检测),边界质量往往比内部区域的完整性更重要。

指标公式优点缺点 IoU / mIoU|A & B| / |A | B|标准统一,直观易懂对小物体敏感度低 Dice2|A & B| / (|A|+|B|)不平衡数据友好过度强调重合区域 Pixel Acc正确像素 / 总像素计算简单受背景主导 Boundary F1边界Precision/Recall F1评估边界质量实现复杂度高

六、核心要点总结

  • 图像分割三大任务:语义分割(像素级分类)、实例分割(像素级分类+区分个体)、全景分割(统一stuff和things的全覆盖分割),构成了从粗到细的场景理解层级
  • FCN核心贡献:全连接层转卷积使密集预测成为可能;跳跃连接融合多尺度特征;转置卷积实现可学习上采样
  • U-Net设计精髓:编码器-解码器对称结构 + 通道拼接(Concatenate)跳跃连接,特别适合医学图像等数据量有限但需要精细定位的任务
  • Mask R-CNN创新:在Faster R-CNN基础上增加分割分支,提出RoIAlign解决像素对齐问题,实现高精度实例分割
  • 评估指标取舍:mIoU是通用标准,Dice适合医学图像,Boundary F1关注边界质量,应根据具体任务选择合适的指标
  • 演进趋势:从FCN到U-Net再到Mask R-CNN,图像分割经历了"端到端密集预测 -> 编码器-解码器融合 -> 检测+分割多任务"的技术演进路线

七、进一步思考与实践建议

学习路线建议:

  • 入门:先理解FCN的核心思想(全连接转卷积、上采样、跳跃连接),这是所有分割模型的基础
  • 进阶:深入U-Net的PyTorch实现,亲手训练一个医学图像分割模型(如ISBI细胞分割数据集)
  • 拓展:学习Mask R-CNN的实现(推荐detectron2或mmdetection框架),理解实例分割的完整流程
  • 前沿:关注Transformer-based分割模型(如SETR、SegFormer、Mask2Former)和SAM(Segment Anything Model),了解分割领域的最新进展

工程实践提示:

  • 使用Albumentations库做数据增强,支持图像和掩码的同步变换
  • 对于大尺寸图像,使用patch-based策略降低显存消耗
  • Dice Loss + Cross-Entropy联合训练通常优于单一损失函数
  • 后处理:条件随机场(CRF)可以进一步优化分割边界(DeepLab系列常见做法)
  • 模型部署:ONNX导出 + TensorRT加速可实现实时分割推理

本学习笔记为深度学习图像分割方向的知识整理与总结

本学习笔记为本人学习资料,不得转载

免责声明:本学习笔记只供学习使用,内容参考了相关论文和开源教程,如有错误欢迎指正。

参考来源:Long et al. "Fully Convolutional Networks for Semantic Segmentation" CVPR 2015 | Ronneberger et al. "U-Net: Convolutional Networks for Biomedical Image Segmentation" MICCAI 2015 | He et al. "Mask R-CNN" ICCV 2017 | Kirillov et al. "Panoptic Segmentation" CVPR 2019