图像分割网络选型指南:从FCN到SegFormer的演进与实战
图像分割这个方向,我从五年前第一次跑通 FCN 的 demo 开始,到现在手里迭代过十几个分割项目,踩过的坑比跑过的 epoch 还多。很多人入门分割的时候,最大的困惑不是“模型怎么搭”,而是“这么多网络到底该选哪个、为什么选它、换了场景又该怎么改”。FCN、U-Net、SegNet、Deeplab、SegFormer 这五个名字几乎出现在每一份分割学习路线里,但真正把它们串起来讲清楚“演进逻辑”和“落地取舍”的资料并不多。这篇内容就是想把这条线捋直:从全卷积的开山之作,到编码器-解码器的经典范式,再到空洞卷积和 Transformer 的介入,每一代解决了上一代的什么问题,以及在广告牌分割、地毯图像分割这类实际场景里,我会怎么做选型和调参。不管你是刚接触计算机视觉的新手,还是已经能跑通训练但不知道怎么优化的同学,应该都能从里面找到能直接用的东西。
1. 图像分割到底在解决一个什么问题
1.1 从分类、检测到分割的任务递进
要理解分割,先得把它和分类、检测摆在一起看。图像分类是给整张图打一个标签,比如“这是一张猫的图”;目标检测是在图上框出若干个矩形,告诉你“这里有一只猫、那里有一只狗”;而图像分割要求更细,它要给每一个像素都分配一个类别,输出的是和原图同尺寸的掩码(mask)。换句话说,分类回答“是什么”,检测回答“在哪里、有几个”,分割回答“每个像素属于谁”。
这个差别直接决定了网络结构的不同。分类网络最后接全连接层,把空间信息压成一个向量;检测网络在特征图上做回归和分类;分割网络则必须保留空间分辨率,因为一旦空间信息被压没了,像素级的预测就无从谈起。这也是为什么 FCN 的出现是一个分水岭——它第一次证明了卷积网络可以端到端地做像素级预测,而不需要全连接层。
从任务形态上,分割又分成语义分割、实例分割和全景分割。语义分割只关心类别,不区分同一类的不同个体,比如图里三只猫都标成“猫”;实例分割要把每只猫分开标;全景分割则是两者的结合。FCN、U-Net、SegNet、Deeplab、SegFormer 这几个,主要都是语义分割范畴的骨干,理解这一点很重要,因为它们的损失函数、评价指标都是围绕语义分割设计的。
1.2 语义分割的评价指标与损失函数
做分割绕不开两个东西:怎么衡量好坏,怎么训练。评价指标里最常用的是 mIoU(平均交并比)。IoU 是预测区域和真实区域的交集除以并集,mIoU 就是把每个类别的 IoU 算出来再平均。它比像素准确率更可靠,因为当背景占绝大多数时,像素准确率会被背景“刷”得很高,但 mIoU 能暴露小类别分割差的问题。
还有一个常被忽略的指标是 Dice 系数,它在医学图像分割里用得特别多,因为医学图像里前景(病灶)往往很小,Dice 对不平衡更敏感。实际项目里我一般两个都看,mIoU 看整体,Dice 看小目标。
损失函数方面,最基础的是逐像素交叉熵。但它有个问题:当类别极度不平衡时,占多数的背景会主导梯度。解决办法有几种,一是给不同类别加权,二是用 Focal Loss 降低易分样本的权重,三是直接用 Dice Loss 或者交叉熵和 Dice 的组合。我在做广告牌分割的时候就遇到过,广告牌区域只占整图不到 5%,纯交叉熵训出来的模型几乎全预测成背景,换成加权交叉熵加 Dice 之后才正常收敛。
提示:不要一上来就堆复杂损失。先用交叉熵跑通,观察每个类别的 IoU,如果发现某个小类别 IoU 长期接近 0,再针对性地上加权或 Dice。
1.3 数据标注:分割项目里最容易被低估的成本
很多人以为分割的难点在模型,其实真正拖垮项目进度的是标注。像素级标注比画框贵得多,一张复杂场景图标注可能要十几分钟甚至更久。我见过太多团队模型选得很 fancy,结果标注数据只有几百张,最后效果还不如老老实实标几千张用 U-Net。
标注工具上,常用的有 LabelMe、CVAT 这些,导出格式一般是 COCO 或者 Pascal VOC 的掩码。这里有个实操细节:标注时一定要统一类别定义和边界规则。比如广告牌分割里,“广告牌”到底包不包括边框、包不包括被遮挡的部分,如果标注员理解不一致,训出来的模型会非常不稳定。我的做法是先标 50 张做一轮对齐,几个人交叉检查,把边界规则写成文档,再批量开工。
另外,数据增强在分割里几乎是必须的。随机翻转、随机裁剪、颜色抖动、旋转都是常规操作。但要注意,几何变换必须同时作用在图像和掩码上,而且掩码要用最近邻插值,不能用双线性,否则类别边界会被插值出“中间值”,变成不存在的类别。这个坑我早期踩过,训练 loss 一直下不去,排查半天才发现是掩码插值方式错了。
2. FCN:全卷积网络为什么是分割的起点
2.1 把全连接层换成卷积层的核心动机
在 FCN 之前,大家做分割的思路是“滑窗”——用一个分类网络在图上逐块滑动,每滑一次预测中心像素的类别。这个方法慢得离谱,而且重复计算严重。FCN 的核心贡献就一句话:把分类网络末尾的全连接层换成卷积层,让网络可以接受任意尺寸输入,输出同尺寸的密集预测。
为什么换成卷积就行?因为全连接层要求输入维度固定,而卷积层对空间尺寸没有要求。一个 1x1 卷积本质上就是在每个像素位置上做一次全连接,但它保留了空间维度。这样一来,网络输出的不再是“一个类别”,而是一张“每个位置一个类别”的热力图。这个思路现在看很自然,但在当时是颠覆性的。
FCN 的另一个关键设计是上采样。卷积和池化会让特征图越来越小,最后要恢复到原图尺寸,就得靠反卷积(转置卷积)或者双线性插值上采样。FCN 用的是转置卷积,并且提出了跳跃连接(skip connection),把浅层的高分辨率特征和深层的语义特征融合,缓解上采样带来的细节丢失。
2.2 FCN-8s 的跳跃连接结构拆解
FCN 有三个版本:FCN-32s、FCN-16s、FCN-8s,数字代表上采样的倍率。32s 是直接把最后一层特征图上采样 32 倍,效果最粗糙;16s 融合了 pool4 的特征;8s 进一步融合了 pool3 的特征,效果最好。
以 FCN-8s 为例,它的流程大致是:主干网络(比如 VGG)逐层下采样,得到 1/32 分辨率的特征图;这个特征图先上采样 2 倍变成 1/16,和 pool4 的输出相加;再上采样 2 倍变成 1/8,和 pool3 的输出相加;最后上采样 8 倍恢复到原图尺寸。每一次融合,都是在把深层的“语义”和浅层的“位置细节”结合起来。
这个设计背后的逻辑很直白:深层特征知道“这是什么”,但不知道“在哪里”,因为分辨率太低;浅层特征知道“在哪里”,但不知道“是什么”。跳跃连接就是把两者的优势拼起来。这个思想后来被 U-Net、SegNet 等大量借鉴,可以说是分割网络的一个通用范式。
2.3 FCN 的局限与它留下的坑
FCN 虽然开创了先河,但它的缺点也很明显。第一,上采样用的转置卷积容易产生“棋盘效应”,就是输出图上出现规律性的网格状伪影,原因是卷积核重叠不均匀。第二,FCN 的感受野固定,对多尺度目标的适应能力差,大目标和小目标很难同时分好。第三,跳跃连接只是简单相加,融合方式比较粗糙。
我在复现 FCN 的时候,棋盘效应是最头疼的。后来换成双线性插值初始化转置卷积核,或者干脆用插值上采样加普通卷积,伪影就明显减轻了。这个经验后来在 U-Net 和 SegNet 里同样适用。所以如果你现在还在用转置卷积做上采样,建议检查一下输出图有没有网格纹,有的话优先考虑插值方案。
3. U-Net:编码器-解码器与跳跃连接的经典范式
3.1 U 型结构的对称设计逻辑
U-Net 最初是为医学图像分割设计的,但它的影响力早就超出了医学领域。它的结构是一个对称的 U 型:左边是编码器,逐层下采样提取语义;右边是解码器,逐层上采样恢复分辨率;中间用跳跃连接把编码器每一层的特征拼接到解码器对应层。
为什么是拼接(concatenation)而不是像 FCN 那样相加?这是 U-Net 的一个关键改进。拼接保留了浅层特征的完整通道信息,让解码器可以自己学习怎么融合,而不是被迫接受一个相加后的结果。实测下来,拼接的融合效果通常比相加更好,代价是通道数翻倍,显存占用更高。
U-Net 的另一个特点是它能在小数据集上表现很好。医学图像往往只有几百张,U-Net 靠强大的数据增强和跳跃连接,依然能训出可用的模型。这也是为什么很多工业缺陷检测、遥感分割项目,数据量不大的时候首选 U-Net。
3.2 跳跃连接在广告牌分割中的实际作用
我拿广告牌分割举个例子。广告牌的特点是形状规整(多为矩形)、边界清晰,但背景复杂,可能出现在楼顶、路边、高速旁,光照和角度变化大。用 U-Net 做的时候,跳跃连接的作用非常明显:浅层特征能提供广告牌的边缘和角点信息,深层特征负责判断“这块区域是不是广告牌”。如果没有跳跃连接,边界会糊成一团,尤其是广告牌和背景颜色接近的时候。
实操上,我会在跳跃连接处加一个 1x1 卷积先降通道,再拼接,这样能控制显存。另外,编码器用预训练的 ResNet 或 EfficientNet 比从头训 VGG 效果好很多,因为预训练权重已经学到了通用的边缘和纹理特征。这一点在数据量不足时尤其关键。
3.3 U-Net 变体:从 U-Net++ 到 Attention U-Net
U-Net 之后出现了大量变体。U-Net++ 把跳跃连接改成了密集连接,中间加了多个嵌套的卷积块,让不同尺度的特征融合得更充分。Attention U-Net 在跳跃连接上加了注意力门控,让解码器只关注编码器里和当前任务相关的区域,抑制无关背景。
这些变体不是越复杂越好。U-Net++ 参数量大、训练慢,在小数据集上容易过拟合;Attention U-Net 的注意力模块会增加计算量,但如果你做的是目标边界模糊的任务(比如地毯图像分割里不同花纹的边界),注意力机制确实能带来提升。我的建议是先用原版 U-Net 建立 baseline,再根据具体问题决定要不要上变体,不要一上来就堆结构。
4. SegNet:把池化索引玩出花的下采样-上采样方案
4.1 SegNet 与 U-Net 的核心差异
SegNet 和 U-Net 都是编码器-解码器结构,但它们的解码方式完全不同。U-Net 靠转置卷积或插值上采样,SegNet 则用了一个很巧妙的办法:在编码器做最大池化的时候,记录下每个池化窗口里最大值的位置索引;解码器上采样时,直接把值放回这些记录的位置,其余位置填零。
这个设计的优点是省参数。因为上采样不需要学习,只是查表放值,所以 SegNet 的解码器比 U-Net 轻很多。缺点是信息恢复能力有限,因为非最大值位置直接填零,细节会有损失。实测下来,SegNet 在道路、建筑这类结构规整的场景表现不错,但在边界复杂、纹理细腻的场景(比如地毯花纹分割)就不如 U-Net。
4.2 池化索引上采样的适用边界
池化索引上采样本质上是一种“稀疏恢复”。它假设最大值的位置就是最重要的信息位置,这在很多情况下成立,但当一个池化窗口里有多个重要目标时就会丢信息。所以 SegNet 更适合单目标或目标稀疏的场景。
我在做地毯图像分割的时候试过 SegNet,效果一般。地毯的花纹是连续、密集的,池化索引上采样会把很多花纹细节丢掉,分割出来的边界呈锯齿状。后来换成 U-Net 加空洞卷积,边界才平滑下来。所以选型的时候,一定要先看你的目标是不是“稀疏、规整”,如果是,SegNet 的轻量优势值得考虑;如果是“密集、细腻”,还是老老实实用 U-Net 系。
4.3 SegNet 的显存优势与部署考量
SegNet 最大的工程价值在于显存占用低。因为解码器不需要存储转置卷积的中间结果,也不需要拼接特征图,整体显存比 U-Net 小不少。这在边缘设备部署时是个实打实的优势。如果你的场景是车载、监控摄像头这类算力有限的设备,SegNet 或者它的轻量变体是值得评估的。
不过要注意,显存省了,精度可能也省了。部署前一定要在目标设备上做完整的精度-速度权衡测试,不要只看论文里的 mIoU。我一般会准备两套模型,一套高精度离线用,一套轻量在线用,根据业务对延迟的容忍度来切换。
5. Deeplab 系列:空洞卷积与多尺度特征提取
5.1 空洞卷积如何在不降分辨率的前提下扩大感受野
Deeplab 系列的核心武器是空洞卷积(也叫膨胀卷积)。普通卷积的感受野随层数线性增长,要扩大感受野就得不断下采样,但下采样会丢分辨率。空洞卷积的做法是在卷积核的元素之间插入“空洞”,让同样的参数量覆盖更大的区域。比如一个 3x3 的卷积核,膨胀率为 2 时,实际覆盖的是 5x5 的区域,但参数量还是 9 个。
这个设计对分割特别友好,因为分割既要大感受野(判断语义),又要高分辨率(定位边界)。Deeplab 用空洞卷积替换了部分下采样,让网络在保持分辨率的同时获得大感受野。这是它相比 FCN、U-Net 的一个本质进步。
5.2 ASPP 模块:多尺度并行的思路
Deeplab v2 提出了 ASPP(空洞空间金字塔池化),用不同膨胀率的空洞卷积并行处理同一张特征图,再把结果拼接。膨胀率通常取 6、12、18 这种,覆盖小、中、大三种尺度。这样网络就能同时“看到”不同大小的目标,对多尺度场景适应性很强。
Deeplab v3 对 ASPP 做了改进,加入了全局平均池化分支(图像级特征),并且把 BN 加进了 ASPP。v3+ 则引入了编码器-解码器结构,把 ASPP 的输出和浅层特征融合,进一步改善边界。这个演进路线很清楚:v1 解决感受野,v2 解决多尺度,v3 优化 ASPP,v3+ 补边界。
5.3 Deeplab 在复杂场景下的调参经验
Deeplab 的调参有几个关键点。第一,膨胀率的选择要和特征图分辨率匹配。如果特征图已经很小了,再用大膨胀率,卷积核会“溢出”到有效区域之外,产生边界伪影。第二,ASPP 的输出通道数不要设太大,否则显存吃不消,一般 256 就够。第三,输出步长(output stride)建议设成 16 或 8,8 精度更高但更吃显存。
我在做广告牌分割时用 Deeplab v3+ 做过对比,output stride 设 16 时 mIoU 比 8 低大概 1.5 个点,但速度快了近一倍。如果业务对实时性有要求,16 是更划算的选择。另外,Deeplab 对学习率比较敏感,用多项式衰减(poly)比阶梯衰减更稳,初始学习率我一般从 0.01 开始试。
6. SegFormer:Transformer 进入分割领域的轻量方案
6.1 分层 Transformer 编码器的设计取舍
SegFormer 是 Transformer 路线在分割里的代表。它的编码器是一个分层的 Transformer(类似 PVT、Swin 的思路),输出多尺度特征;解码器则非常轻,就是一个简单的 MLP 融合模块。这个“重编码器、轻解码器”的设计和传统 CNN 分割网络正好相反。
为什么这么设计?因为 Transformer 的自注意力机制本身就有全局建模能力,不需要靠复杂的解码器来扩大感受野。分层结构则解决了 Transformer 计算量随分辨率平方增长的问题——通过逐层下采样,把注意力计算控制在可接受范围内。SegFormer 的另一个亮点是它去掉了位置编码,改用卷积式的混合注意力,对不同分辨率的输入更鲁棒。
6.2 SegFormer 相比 CNN 分割网络的优势场景
SegFormer 在几个场景下优势明显。一是大目标、全局上下文重要的场景,比如遥感图像里的大面积地块分割,自注意力能捕捉长距离依赖。二是多尺度目标共存的场景,分层结构天然支持。三是需要轻量部署的场景,SegFormer 的 B0 版本参数量很小,速度也快。
但 Transformer 也有短板。它通常需要更多数据才能训好,小数据集上不如 CNN 稳。而且它的归纳偏置弱,对边缘、纹理这类局部特征的捕捉不如 CNN 直接。我在数据量少于两千张的时候,一般还是优先 CNN;数据量上来了,再考虑 SegFormer。
6.3 从 CNN 迁移到 Transformer 的实操注意事项
如果你打算从 U-Net 或 Deeplab 迁移到 SegFormer,有几个点要注意。第一,优化器通常用 AdamW,学习率比 CNN 小一个量级,6e-5 是常见起点。第二,训练轮数要够,Transformer 收敛比 CNN 慢,别训几十轮没效果就放弃。第三,数据增强要更强,因为 Transformer 对数据量更敏感。第四,预训练权重很重要,用 ImageNet 预训练的编码器比从头训效果好太多。
另外,SegFormer 的输入尺寸最好是 32 的倍数,因为分层下采样有 4 次,尺寸不整除会在拼接时出问题。这个细节官方代码里有处理,但自己改数据管道时容易忽略。
7. 五个网络怎么选:一张表说清楚
7.1 按数据量、场景、算力三维度选型
选型没有绝对的最优,只有最适合。我一般从三个维度判断:数据量、场景复杂度、部署算力。
| 网络 | 数据需求 | 适合场景 | 算力需求 | 边界精度 |
|---|---|---|---|---|
| FCN | 中 | 入门学习、简单场景 | 低 | 一般 |
| U-Net | 低 | 医学、工业缺陷、小数据 | 中 | 高 |
| SegNet | 中 | 道路、建筑、稀疏目标 | 低 | 中 |
| Deeplab | 中高 | 多尺度、复杂背景 | 中高 | 高 |
| SegFormer | 高 | 大数据、全局上下文 | 中 | 高 |
数据少(千张以内)优先 U-Net;目标稀疏且要轻量优先 SegNet;多尺度复杂场景优先 Deeplab;数据充足且追求全局建模优先 SegFormer;纯粹学习原理从 FCN 入手。
7.2 迁移学习与预训练权重的使用建议
不管选哪个网络,预训练权重都建议用。编码器用 ImageNet 预训练的 ResNet、EfficientNet 或 MiT,能省大量训练时间,还能提升小数据下的泛化。加载预训练时要注意通道匹配,比如你把输入从 3 通道改成 4 通道(加了深度图),第一层卷积的权重需要特殊处理,常见做法是把多出来的通道权重初始化为原三通道的均值。
微调策略上,我一般先冻结编码器训几轮解码器,再解冻全部一起训。这样能避免随机初始化的解码器在早期把预训练编码器的权重带偏。学习率用差分学习率,编码器小、解码器大,效果更稳。
7.3 从 mIoU 到实际业务指标的落地转换
论文看 mIoU,业务看的是实际指标。比如广告牌分割,业务关心的是“广告牌有没有被完整框出来”“边界误差有多大”,这时候 mIoU 高不一定业务就好。我一般会额外算边界 F1 或者 Hausdorff 距离,前者衡量边界贴合度,后者衡量最坏情况下的边界偏差。
还有一个容易被忽略的点是推理速度。mIoU 高 2 个点,但推理慢一倍,在很多业务里是不划算的。所以选型时一定要把精度和速度放在一起看,做一条精度-速度曲线,找性价比最高的那个点。
8. 训练分割模型时最容易踩的五个坑
8.1 掩码插值方式错误导致类别污染
前面提过一次,这里再强调。几何增强时掩码必须用最近邻插值。用双线性插值会让类别边界产生 0 到 1 之间的中间值,这些值在计算 loss 时会被当成某个类别或者被忽略,导致边界区域训练信号混乱。这个 bug 很隐蔽,因为 loss 曲线看起来正常,但边界精度就是上不去。
8.2 类别不平衡处理过度导致背景误检
加权交叉熵和 Dice 能救小类别,但权重给太大,模型会变得“激进”,把很多背景预测成前景,造成误检。我一般先把权重设成类别频率的倒数开根号,而不是直接取倒数,这样更温和。然后根据验证集上的误检率微调,不要一次性给太猛。
8.3 验证集划分不合理导致指标虚高
分割数据往往来自视频或连续拍摄,相邻帧高度相似。如果随机划分验证集,训练集和验证集会有大量近似重复的图,指标虚高。正确做法是按场景、按拍摄批次划分,确保验证集和训练集在分布上有区分度。这个坑我在做地毯图像分割时踩过,随机划分时 mIoU 0.85,按批次划分后掉到 0.72,这才是真实水平。
8.4 上采样方式选择不当引入棋盘伪影
转置卷积的棋盘效应前面说过。除了换插值,还可以用“插值上采样 + 普通卷积”的组合,先双线性放大再卷积细化,既没有棋盘纹,又能学习上采样。这个方案现在很多开源实现都在用,推荐优先考虑。
8.5 忽略推理阶段的尺寸对齐问题
训练时输入尺寸固定,推理时如果输入尺寸和训练不一致,很多网络会出问题。尤其是带下采样的网络,输入尺寸不是 2 的幂次倍时,编码器和解码器的特征图尺寸对不上,拼接会报错。解决办法是推理前把输入 padding 到合适尺寸,推理后再裁回来。这个细节在部署时特别重要,我见过不止一个项目因为这个问题上线后崩掉。
9. 从零跑通一个分割项目的完整流程
9.1 数据准备与标注规范落地
第一步永远是数据。先明确类别定义,写标注规范文档,标 50 张做对齐,再批量标。标注格式统一成一种,比如 COCO 或者 VOC,不要混用。标完后做一轮质量抽检,随机抽 10% 检查边界和类别是否正确。数据量目标上,语义分割一般每类至少几百个实例才比较稳,太少的话优先考虑数据增强和迁移学习。
9.2 基线模型搭建与快速验证
不要一上来就调参。先用 U-Net 或 Deeplab 搭一个 baseline,用默认配置跑通,看 mIoU 和 loss 曲线。baseline 的作用是给你一个参照系,后面所有改动都跟它比。我一般会记录 baseline 的 mIoU、每类 IoU、训练时间、推理速度,作为后续优化的锚点。
9.3 迭代优化与消融实验设计
优化要有章法,一次只改一个变量。比如先试数据增强,再试损失函数,再试 backbone,再试上采样方式。每改一次记录指标变化,做消融表。这样你才能知道哪个改动真正有用。我见过很多人一次性改一堆,最后效果好不知道为啥好,效果差也不知道是哪儿的锅。
9.4 模型导出与部署前的检查清单
部署前检查这几项:输入尺寸是否固定、预处理是否和训练一致(归一化参数尤其重要)、输出后处理是否正确(argmax 还是阈值)、推理速度是否达标、显存占用是否可接受。归一化不一致是最常见的部署事故,训练用 ImageNet 均值方差,部署忘了加,指标直接崩。导出 ONNX 或 TensorRT 后,一定要用同一张图对比导出前后的输出,确保数值一致。
10. 一些关于学习路径的个人建议
如果你是刚入门,我的建议是先别急着追新。把 FCN 的论文读一遍,自己用 PyTorch 复现一个最小版本,理解全卷积和上采样;然后手撸一个 U-Net,把跳跃连接、损失函数、数据管道都跑通;再去看 SegNet 的池化索引、Deeplab 的空洞卷积和 ASPP、SegFormer 的 Transformer 编码器。这个顺序是沿着“感受野-多尺度-全局建模”的主线走的,每一步都在解决前一步的遗留问题,理解起来会顺很多。
代码方面,不要只跑开源仓库的 demo。试着改一改,比如把 U-Net 的 backbone 换成 ResNet,把损失换成 Dice,把上采样换成插值,观察指标怎么变。这种“改-测-想”的循环,比看十篇论文都管用。分割这个方向,理论重要,但手感更重要,而手感只能从一次次跑实验里来。
最后说一个我自己的习惯:每做一个项目,我都会建一个实验记录表,记下每次改动的配置、指标、耗时和结论。时间长了,这张表就是你自己的经验库,下次遇到类似场景,翻一翻就知道该从哪儿下手。分割网络更新很快,但“先看数据、再定基线、小步迭代、重视部署”这套方法论,几年下来一直没变过。