1. 从“大而重”到“小而精”:为什么我们需要SegFormer?
如果你玩过图像分割,肯定对FCN、U-Net这些老牌明星不陌生。它们靠着卷积神经网络(CNN)在像素级分类任务上立下了汗马功劳。但不知道你有没有发现,随着我们对分割精度要求越来越高,模型也变得越来越“胖”——层数更深、参数更多、计算量爆炸。想在自己的电脑上跑一个高精度的分割模型?显卡风扇的呼啸声可能就是最直接的抗议。
后来,Transformer来了,尤其是Vision Transformer(ViT),它在图像分类任务上大放异彩,让人看到了“注意力”机制横扫视觉领域的潜力。于是,大家自然想把Transformer搬到分割任务上。早期的尝试,比如SETR,直接把ViT当特征提取器(backbone),后面接一个复杂的解码器。想法很好,但实测下来问题不少:ViT输出是单一尺度的低分辨率特征图,对需要精细边界的分割任务来说,就像用低像素照片去抠图,细节全糊了。更头疼的是计算量,自注意力机制的计算复杂度是序列长度的平方,一张高分辨率图片切成的小块(patch)数量巨大,算力根本吃不消。
所以,当时的分割领域面临一个尴尬局面:CNN模型精度遇到瓶颈,而原生的ViT又太“笨重”,不适合密集预测任务。这时候,SegFormer出现了。它的目标非常明确:设计一个既轻量又强大的Transformer分割网络,让效率和精度不再是选择题。
我第一次读到SegFormer论文时,最吸引我的就是它的“务实”。它没有追求更复杂的结构或更大的参数量,而是回过头来,重新审视Transformer在分割任务中到底需要什么。它发现,关键不在于堆叠更多的注意力头,而在于如何高效地构建多尺度特征,以及如何用最简洁的方式融合它们。这就像组装一台高性能电脑,不是无脑堆最贵的配件,而是讲究搭配和平衡。SegFormer正是通过其层次化Transformer编码器和轻量级全MLP解码器的巧妙设计,实现了这种平衡,在多个公开数据集上达到了当时的最优水平,而且模型尺寸和计算量还大幅下降。接下来,我们就一层层剥开它的设计,看看它是怎么做到的。
2. 核心引擎:层次化Transformer编码器详解
SegFormer的编码器是整个网络的智慧核心。它不像ViT那样“一根筋”地处理图像,而是像一位经验丰富的画家,先勾勒轮廓,再描绘细节。这个编码器能同时产出高分辨率的精细特征和低分辨率的语义特征,为后续的分割提供丰富的信息。
2.1 重叠块嵌入:保留更多的局部上下文
传统ViT的第一步,是把图像切成一个个不重叠的、像马赛克一样的小块(比如16x16像素),然后把这些小块展平,送入Transformer。但问题来了,切割线两侧的像素原本是邻居,现在却被强行分开,它们之间的局部联系就丢失了。这对于需要精确边界的语义分割来说,是个不小的损失。
SegFormer的重叠块嵌入模块,就是为了解决这个问题。它本质上是一个有重叠的卷积操作。想象一下,你用一个小窗口(比如7x7)在图像上滑动来提取特征,但每次滑动的步长(比如4)小于窗口大小,这样相邻窗口之间就有重叠区域。这个操作有两个好处:第一,它保留了小块边缘的连续性,让模型能更好地感知局部细节;第二,它通过调整卷积的步长,自然地实现了特征图的下采样,形成了我们需要的多尺度特征金字塔。
我们来看看代码里是怎么实现的。在OverlapPatchEmbed类中,核心就是一个nn.Conv2d卷积层。通过设置不同的kernel_size(块大小)和stride(步长),它就能把输入图像一步步变成不同尺度的特征图。
class OverlapPatchEmbed(nn.Module):
def __init__(self, img_size=224, patch_size=7, stride=4, in_chans=3, embed_dim=768):
super().__init__()
# 关键在这里:使用卷积实现重叠块嵌入,padding保证输出尺寸符合预期
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=stride,
padding=(patch_size // 2, patch_size // 2))
self.norm = nn.LayerNorm(embed_dim)
def forward(self, x):
x = self.proj(x) # 形状从 [B, C, H, W] 变为 [B, embed_dim, H', W']
_, _, H, W = x.shape
x = x.flatten(2).transpose(1, 2) # 展平为序列 [B, H'*W', embed_dim]
x = self.norm(x)
return x, H, W
在SegFormer的编码器中,这样的模块会堆叠四次,每次的stride和embed_dim(输出通道数)都不同,从而生成原图1/4、1/8、1/16、1/32大小的特征图,通道数也逐渐增加。这就构成了一个层次化的特征金字塔,为解码器提供了


1万+

被折叠的 条评论
为什么被折叠?



