U-Net 图像分割:编码-解码与跳跃连接

FreeGuideOnline 最新 2026-06-16

U-Net:用编码-解码与跳跃连接实现精准图像分割

什么是图像分割与 U-Net?

图像分割是计算机视觉中的一项核心任务,目标是将图像中的每一个像素都分配一个类别标签,从而实现对目标的像素级理解。与仅输出整图类别或边界框的目标检测不同,分割能够精确描绘出物体的轮廓和形状,在医学影像分析、自动驾驶、卫星图像解析等领域有着不可替代的价值。

U-Net 是 2015 年由 Olaf Ronneberger 等人为生物医学图像分割提出的一种全卷积网络架构。它以其优雅的对称结构和强大的特征复用能力,极大地提升了小样本场景下的分割精度。U-Net 的名字来源于其形状酷似字母 U,由一条收缩路径(编码器)和一条扩张路径(解码器)组成,并通过跳跃连接将浅层特征与深层特征融合,保留了大量精细的空间信息。

U-Net 的核心思想:编码-解码

U-Net 本质上是一个编码器-解码器(Encoder-Decoder) 结构,但它对标准的语义分割编码-解码器做了关键改进,尤其适合需要精细化边缘和纹理的分割任务。

编码器:收缩路径(Contracting Path)

编码器的任务是从输入图像中逐层提取抽象语义特征,同时降低空间分辨率,扩大感受野。

  • 基础模块:典型的编码器由多个卷积块堆叠而成,每个块包含两次 3x3 卷积(带非线性激活,如 ReLU),后接一个 2x2 最大池化层进行下采样。
  • 通道变化:初始输入为 RGB 或灰度图(如 1 或 3 通道)。每经过一次下采样,空间尺寸减半,但特征通道数加倍。例如:64 → 128 → 256 → 512 → 1024。
  • 作用:高层语义信息(如“圆形”、“纹理”、“病变区域”等概念)被提取出来,但具体的位置细节(如边缘、定位)会因池化而丢失。这种丢失对于精确的像素级分割是致命的,因此 U-Net 设计了特殊的跳跃连接来弥补。

解码器:扩张路径(Expanding Path)

解码器的目标是利用编码器提取的特征逐步恢复空间分辨率,并最终生成与输入同样大小的分割图。

  • 上采样方式:U-Net 使用 2x2 转置卷积(或称为反卷积)进行上采样,将特征图的尺寸扩大一倍,同时将通道数减半。
  • 拼接操作:每一次上采样后,都会与编码路径中对应的特征图在通道维度上进行连接(concatenate),这就是著名的跳跃连接。
  • 后续卷积:连接后的特征图通道数较多(如编码阶段的 128 通道加上上采样后的 128 通道,共 256 通道),再次经过两次 3x3 卷积处理,以融合深浅层信息并减少通道数。

最终,解码器输出的特征图通过一个 1x1 卷积将通道数映射为类别数,生成每个像素的类别预测概率(通常接 Softmax 或 Sigmoid 激活)。

跳跃连接:U-Net 的决胜关键

跳跃连接(Skip Connections) 是 U-Net 区别于普通编码-解码结构的最重要设计。它将编码器中的高分辨率、低语义特征直接传递给解码器,解决了三个关键问题:

  1. 恢复空间细节:深层特征虽然语义强,但尺寸小、定位粗糙。浅层特征含有丰富的边缘、纹理和形状信息。将它们连接后,解码器既能利用深层语义判断“这是什么”,又能利用浅层信息精确勾勒“它的边界在哪里”。
  2. 改善梯度传播:在深度网络中,梯度容易在反向传播时消失。跳跃连接提供了梯度的“高速公路”,使得网络更容易优化,训练更加稳定。
  3. 多尺度特征融合:网络同时看到了不同尺度的特征。例如,一个大的器官轮廓可以在深层捕获,而小的细胞边界则依赖浅层细节。

在实际实现中:假设编码器某一层的特征图尺寸为 H×W×C,经过池化后降为 H/2×W/2×2C。解码器在对下一层特征上采样后得到 H×W×2C,此时将编码器保存的 H×W×C 特征与上采样结果连接,形成 H×W×3C 的特征堆,再通过卷积将该通道数压缩,实现特征融合。

如果没有跳跃连接,仅依靠解码器从最低分辨率逐步放大,分割结果会非常模糊,丢失大量细节。跳跃连接使得 U-Net 在边界分割上具有出色的性能。

U-Net 的网络结构详解

一个经典的 U-Net 结构(以输入 572×572 的单通道图像为例)如下所示,但现代实现常使用零填充使输入输出同尺寸(如 256×256):

  • 输入:1×572×572
  • 编码路径
    • 块1:两次 3×3 conv, ReLU → 通道数64,输出尺寸568×568。池化至 64×284×284。
    • 块2:两次 3×3 conv → 128×280×280。池化至 128×140×140。
    • 块3:两次 3×3 conv → 256×136×136。池化至 256×68×68。
    • 块4:两次 3×3 conv → 512×64×64。池化至 512×32×32。
    • 底层:两次 3×3 conv → 1024×28×28(不池化)。
  • 解码路径
    • 上采样:2×2 转置卷积,1024→512,输出 512×56×56。与块4的512×64×64裁剪后进行连接,得到 1024×56×56。两次卷积→512×52×52。
    • 上采样:512→256,得到 256×104×104。与块3连接→512×104×104。卷积→256×100×100。
    • 上采样:256→128,200×200,连接→256×200×200,卷积→128×196×196。
    • 上采样:128→64,392×392,连接→128×392×392,卷积→64×388×388。
  • 输出层:1×1 卷积将64通道映射为2(二值分割)或 N 类,得到 2×388×388

注:因无填充卷积导致尺寸逐层缩小,所以输入与输出尺寸不同。现代实现普遍使用填充(padding=1)以保持尺寸一致,降低实现复杂度。

损失函数与训练技巧

U-Net 通常用于类别不平衡的分割任务(如医学图像中病灶区域远小于背景),因此简单的交叉熵损失往往不足以得到好结果。常用损失函数包括:

  • 加权交叉熵(Weighted Cross-Entropy):为不同类别分配不同权重,给小目标区域更高权重。
  • Dice 损失(Dice Loss):基于 Dice 系数的损失,直接优化分割的重叠度,公式为 (1 - \frac{2|X \cap Y| + \epsilon}{|X| + |Y| + \epsilon})。对于极度不平衡的数据非常有效。
  • 组合损失:如 Dice 损失 + 交叉熵损失,有时也加入边界损失(Boundary Loss)等。

训练时还会使用数据增强来对抗小样本过拟合,弹性形变、旋转、缩放、镜像等是 U-Net 原论文成功的重要因素。

U-Net 的典型变体与应用

U-Net 结构影响深远,催生了大量改进版本,应用领域覆盖几乎所有需要像素级预测的场景。

  • 3D U-Net:处理三维医学影像(CT、MRI 体积数据),将 2D 卷积扩展为 3D 卷积。
  • Attention U-Net:在跳跃连接前加入注意力门控(Attention Gate),让模型自动学习抑制无关区域,突出重要特征。
  • U-Net++:引入密集跳跃连接和深度监督,用嵌套的 U 型结构捕获更精细的多尺度特征。
  • Residual U-Net:将卷积块替换为残差单元,便于训练更深的网络。

热门应用

  • 医学图像:器官分割、肿瘤分割、细胞计数、视网膜血管提取。
  • 遥感:道路、建筑提取,土地覆盖分类。
  • 自动驾驶:可行驶区域分割、车道线分割。
  • 工业:缺陷检测、零件分割。
  • 影视:人像抠图、背景替换。

使用 PyTorch 实现极简 U-Net 示例

以下代码展示了一个最简化的 U-Net 架构实现(使用填充保持尺寸一致)。实际工程中可在此基础上添加批归一化、Dropout 等优化。

import torch
import torch.nn as nn

class DoubleConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(inplace=True)
        )
    def forward(self, x):
        return self.conv(x)

class UNet(nn.Module):
    def __init__(self, n_channels=1, n_classes=2):
        super().__init__()
        # 编码器
        self.enc1 = DoubleConv(n_channels, 64)
        self.enc2 = DoubleConv(64, 128)
        self.enc3 = DoubleConv(128, 256)
        self.enc4 = DoubleConv(256, 512)
        # 底层
        self.bottom = DoubleConv(512, 1024)
        # 池化
        self.pool = nn.MaxPool2d(2)
        # 上采样
        self.up4 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)
        self.dec4 = DoubleConv(1024, 512)  # 输入:上采样512 + 跳接512 = 1024
        self.up3 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)
        self.dec3 = DoubleConv(512, 256)   # 256+256
        self.up2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)
        self.dec2 = DoubleConv(256, 128)   # 128+128
        self.up1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)
        self.dec1 = DoubleConv(128, 64)    # 64+64
        # 输出层
        self.out_conv = nn.Conv2d(64, n_classes, kernel_size=1)

    def forward(self, x):
        # 编码阶段
        e1 = self.enc1(x)       # [B, 64, H, W]
        e2 = self.enc2(self.pool(e1))  # [B, 128, H/2, W/2]
        e3 = self.enc3(self.pool(e2))  # [B, 256, H/4, W/4]
        e4 = self.enc4(self.pool(e3))  # [B, 512, H/8, W/8]
        # 底层
        b = self.bottom(self.pool(e4)) # [B, 1024, H/16, W/16]
        # 解码阶段(含跳跃连接)
        d4 = self.up4(b)                # [B, 512, H/8, W/8]
        d4 = torch.cat([d4, e4], dim=1)# [B, 1024, H/8, W/8]
        d4 = self.dec4(d4)              # [B, 512, H/8, W/8]
        d3 = self.up3(d4)               # [B, 256, H/4, W/4]
        d3 = torch.cat([d3, e3], dim=1)# [B, 512, H/4, W/4]
        d3 = self.dec3(d3)              # [B, 256, H/4, W/4]
        d2 = self.up2(d3)               # [B, 128, H/2, W/2]
        d2 = torch.cat([d2, e2], dim=1)# [B, 256, H/2, W/2]
        d2 = self.dec2(d2)              # [B, 128, H/2, W/2]
        d1 = self.up1(d2)               # [B, 64, H, W]
        d1 = torch.cat([d1, e1], dim=1)# [B, 128, H, W]
        d1 = self.dec1(d1)              # [B, 64, H, W]
        return self.out_conv(d1)        # [B, n_classes, H, W]

该代码清晰地展示了编码-解码的对称结构和跳跃连接的具体实现(torch.cat)。初学者可以直接在此基础上添加批归一化或替换损失函数进行实验。

小结

U-Net 凭借其编码器-解码器对称结构跳跃连接,完美平衡了语义信息与空间细节,使得它在小数据集、高精度要求的图像分割任务中成为首选的基线模型。理解它的核心思想,将会为你深入探索各类分割网络打下坚实的基础。

尝试从 Medical Segmentation Decathlon 等公开数据集入手,搭建属于你的第一个 U-Net 分割模型,亲手体验跳跃连接带来的精度提升。