图像语义分割原理与实战:从FCN到U-Net、DeepLab的模型演进

📅 发布时间:2026/10/17 10:37:41
图像语义分割原理与实战:从FCN到U-Net、DeepLab的模型演进
图像语义分割说白了就是把图像里的每个像素都标上类别。和物体检测只画框不同分割要的是像素级的“地图”。这几年卷积神经网络几乎把这个任务的精度上限拉高了一个量级从最初的全卷积网络到后来的 U-Net、DeepLab每个阶段都在解决前人留下的具体痛点。这篇内容适合正在做相关毕设、课题或者想把分割模型真正用起来的同学我会从原理讲到实操顺带把踩过的坑都摊开说。你如果刚开始接触这个概念可以先记一个最直观的区别检测输出的是“框 类别”而语义分割输出的是和原图一样大的掩膜每个像素点都有自己的预测标签。它不区分同一类别的不同个体比如画面里有三辆车语义分割把所有车都标成一类如果你想区分车1、车2、车3那是实例分割的范畴。理解这个边界之后再去看网络结构、损失函数和评测指标会顺畅很多。1. 语义分割到底在解决什么问题1.1 像素级分类与任务定义语义分割在视觉任务里属于“稠密预测”这一档。图像分类只给整张图一个标签检测定位出几个矩形框而分割要对每一个像素做分类。一个 (H \times W) 的图像输入进来模型的输出也是空间尺寸约等于 (H \times W) 的类别图第 (i) 个像素的预测值表示它属于哪个语义类别。这种任务对模型的要求有两个维度一是要有足够的“感受野”看到足够的上下文才能判断一块像素是“道路”还是“人行道”因为单独看局部纹理可能完全一样二是要保留足够的空间细节不然物体边界会被糊掉。这两个要求天然存在张力——看全局往往靠下采样而细节又依赖高分辨率特征。很多时候我会把这个过程类比成给地图填色。检测是在地图上画几个包围圈分割却是要把每块区域按土地用途涂满颜色。涂色这个过程容不得模糊地带所以模型的输出必须经过上采样恢复到原始分辨率并且每个像素的类别概率要准确。举个例子一张街景照片里一块灰色的区域看起来既像人行道也像路面你需要看周围是否有马路牙子、是否有车辆行驶才能判断它的类别。这就是上下文信息的作用。卷积神经网络通过不断堆叠卷积层把局部边缘组合成部件语义再由部件组合成物体语义本质上就是从“看颜色”进化到“看物体”。1.2 为什么传统方法解决不了必须靠卷积神经网络在卷积神经网络普及之前图像分割基本靠手工特征。阈值分割按灰度区间切边缘检测找梯度突变点区域生长靠种子点邻域相似性扩散聚类方法按颜色、亮度聚类。这套思路只能处理“像素特征差异大”的场景一旦两个区域颜色纹理相似但语义不同比如蓝天和蓝衣服或者路灯和树后的边缘手工特征直接就崩了。卷积神经网络改变的是特征提取方式。卷积层用局部感受野扫描图像底层能提取边缘、角点高层逐渐组合出“车灯”“车轮”这类语义部件。权值共享让网络参数量大幅降低同时因为图像本身有平移不变性滑动卷积的归纳偏置非常契合。更关键的是层次化特征天然适配“既要语义又要细节”的分割需求。从网络设计角度看分割模型就是在平衡两件事编码器把分辨率降下来换取更大的感受野和更抽象的语义解码器再把分辨率升回去恢复出像素级预测。上采样过程如果只靠简单双线性插值边界就会模糊所以后来研究者们想出了各种机制让模型在上采样时重新利用编码器中的高分辨率特征这就是后面要讲的跳跃连接和空洞卷积等思路。这里面还有一个常被忽略的细节卷积操作本身不会把二维结构拍扁它只是在空间平面上滑动窗口因此每个输出位置都能拿到对应的局部感受野。分类网络末尾会用全局池化或全连接层把空间信息压缩成类别概率而分割网络要把这种空间结构保留到最后一层这就是全卷积架构出现的真正动机。2. 语义分割的核心模型演进从FCN到U-Net和DeepLab2.1 全卷积网络的历史性突破第一个真正端到端的深度语义分割模型就是全卷积网络核心改动很简单把分类网络最后的全连接层全部替换成卷积层。这样网络不再输出一个固定长度的类别向量而是输出一个带空间维度的特征图再通过转置卷积或者其他上采样方式恢复到原图尺寸。这个改动的重要性不在于结构复杂而在于它打破了分类网络对“固定输入尺寸”的限制并且保留了二维空间结构。以前我们提取特征之后把特征拉平做分类位置信息全丢了全卷积网络让每个位置的特征还能对应到原图坐标。FCN 里还有一个跳级结构把不同层级的特征图融合起来后再上采样后来几乎所有分割模型都吸收了这种跨层融合思想。FCN 的缺点也很明显上采样过程比较粗暴转置卷积很难把丢失的细节恢复回来所以预测出来的边界往往又粗又模糊。当年它在公开数据集上的 mIoU 只能做到及格水平和今天动辄0.8以上的结果相差很大但这扇门一旦打开后面模型的迭代速度就快了起来。2.2 U-Net的编码-解码结构为什么有效U-Net 是我个人最推荐初学者入手看的模型原因就是它的结构特别直白左边编码器一层层下采样右边解码器一层层上采样中间用跳跃连接把同分辨率级别的编码器特征拼到解码器特征上。名字叫 U-Net结构长得就像字母U。跳跃连接解决的是上采样过程中细节丢失的问题。下采样时特征图变小但感受野变大语义信息越来越强可是边界细节恰恰在浅层特征里最丰富。解码器上采样时如果只凭高层语义很难还原出锐利边界。U-Net 的做法相当于在上采样阶段不断把浅层的“高清但语义弱”的特征补充进来让模型在算每个像素类别时既能看懂大局又能看清边缘。在实际使用中U-Net 对训练数据的数量要求比较友好。我在一个规模很小的医学图像练手项目里数据量只有一百多张靠平移翻转和弹性形变增强U-Net 仍然能训出可用的分割结果。原因就在于编码器较小、参数不多而且跳跃连接让梯度传播更顺畅网络不容易过拟合到噪声上。不过要注意U-Net 的显存消耗会明显高于同深度的纯卷积网络因为跳跃连接要保存大量中间特征图训大图时记得把 crop 或者 batch size 降一降。2.3 DeepLab系列空洞卷积和多尺度感受野如果 U-Net 是“连接细节”的典型代表DeepLab 系列则是“扩大视野”的典型代表。DeepLab 的核心武器是空洞卷积做法是在卷积核的元素之间插入间隔比如一个 3×3 的空洞卷积膨胀率是2实际覆盖范围相当于7×7但参与计算的参数仍然只有9个。这样做的好处是不需要额外下采样就能扩大感受野也就不用反复让分辨率缩水。光有空洞卷积还不够因为不同物体的尺寸差异很大。DeepLab 的提出者设计了 ASPP 模块把不同膨胀率的空洞卷积并行堆在一起分别提取不同尺度上下文再拼在一起。这就像是让模型同时用几副不同倍率的望远镜去观察同一个区域小目标看到的是近处细节大目标看到的是整体结构最后融合起来。后来的迭代版本又加入了编码-解码结构、可变形卷积等设计一步步把复杂场景分割精度推到新高度。选型时DeepLab 系特别适合图像分辨率高、尺度变化大的任务比如遥感图像或者自动驾驶的多类别分割。它的一个实际短板是算子相对复杂在低算力嵌入式设备上实时性不好后面会再展开。2.4 注意力机制与Transformer对分割的影响到了2020年前后纯卷积的改进空间变小大家开始把注意力机制搬进分割模型。注意力机制的本质是让特征图上每个位置按照相关性去加权聚合其他位置的信息SENet 这类模块先在通道维度做注意力而自注意力则直接把所有位置两两建立关联。这个能力对分割帮助很大因为很多语义判断依赖长距离上下文比如远处的一辆小车被树挡了一半单看局部的卷积核不容易看出它是什么。Transformer 结构的视觉骨干网络出来后也衍生出了不少分割方案。它们的好处是全局建模能力更强大场景里的干扰纹理更少坏处是计算量和显存消耗普遍高于CNN。我在实际项目里只用过轻量化 Transformer 和简单解码器做对比发现标注充足的大数据集上精度确实能涨但小数据集、高实时性场景下往往不如调优好的 U-Net 或者 DeepLab。这个选择背后还是那句老话没有免费午餐模型越复杂需要的资源就越多。2.5 预训练与迁移学习的关键细节做分割实验基本上都会用到在分类任务上预训练过的骨干网络。预训练权重相当于让模型提前掌握“边缘、颜色块、纹理”这些通用视觉概念再针对分割任务去微调。这样收敛速度快很多尤其在数据量不大时效果更明显。但预训练并不是万能药如果你的图像域和预训练数据差异很大比如医学影像、红外图像、超声图像预训练权重带来的增益会变小某些情况下甚至会拖慢收敛因为底层特征分布完全对不上。我在做迁移学习时一般分两部分处理编码器加载预训练权重解码器随机初始化。训练时给编码器设一个较小的学习率比如解码器的十分之一防止分类任务学到的特征被分割任务一开始就打乱。冻结层数不建议冻结太多分割要求的特征比分类更精细冻结太多会让网络学不到领域特有问题。还有一个容易踩的坑是预训练模型对输入尺寸有预设比如有的骨干要求输入能被32整除如果你直接缩放数据集忘记了 padding训练过程不会报错但预测时边缘会莫名其妙多一圈噪声。3. 从零训练一个语义分割模型的完整实操3.1 数据集与预处理标注格式和增强策略训练语义分割模型第一步是准备标注数据。最常见的标注格式是单通道索引图每个像素值直接对应类别 ID比如0表示背景、1表示人、2表示车最后再留一个255作为忽略区域。训练时读取原图和标签图把它们缩放成模型可接受的尺寸再做归一化图像减均值除方差标签不要做这种数值变换。数据增强是分割训练里特别值得花心思的一环。几何增强随机翻转、旋转、缩放、裁剪一定要对图像和标签使用同一套变换矩阵否则标签错位会直接毒害模型。我在代码里一般用随机数种子绑定两个张量的变换翻转前生成一个随机标志图像和标签用同一个标志翻转。色彩增强亮度、饱和度、对比度扰动只作用在图像上因为标签是类别信息不受光照影响。还有一个很容易被忽略的细节是 padding。当输入尺寸不能被缩放因子整除时需要把图像和标签都 pad 到合适的尺寸pad 区域的标签不参与 loss通常把它置为 ignore_index。我见过不少次新手直接对标签做插值缩放导致边界类别被打乱这种情况训练不会崩但评估指标永远上不去。3.2 模型搭建与Loss选择模型部分我不打算贴完整的大段代码给一个最核心的训练循环就够。假设你已经定义好一个模型类 model输入是 (B,3,H,W)输出是 (B,C,H,W) 的 logitsC 是类别数。损失函数我优先用交叉熵这是很多分类问题的默认起点对分割也有效。import torch criterion torch.nn.CrossEntropyLoss(ignore_index255) optimizer torch.optim.AdamW(model.parameters(), lr1e-4, weight_decay1e-4) for images, labels in train_loader: images images.to(device) labels labels.to(device) logits model(images) # (B, C, H, W) loss criterion(logits, labels) # 标签为 (B, H, W) 长整型 optimizer.zero_grad() loss.backward() optimizer.step()交叉熵对每个像素独立计算逻辑上很直接。但如果类别分布严重不均衡比如道路占了大半个画面行人只占几十个像素交叉熵会让模型把精力全放在道路类别上。这时就要换思路常见方案有三类一是给每个类别一个权重把小众类别的 loss 放大二是改用 Dice Loss 或 Focal Loss让模型更关注难分样本三是重采样训练数据让每个批次里不同类别的比例尽量均衡。我看很多项目最后用的是“交叉熵 Dice”的组合既保留像素级监督又用 Dice 弥补对小目标的敏感度。举个直观的例子一个图像里有 95% 背景5% 目标。你把所有像素预测成背景准确率已经 95%但目标一点没框出来。交叉熵的梯度被背景淹没权重更新方向几乎不考虑目标。加权交叉熵的做法是给目标类一个比较大的权重比如 20让目标类贡献的损失在总量里占可观比例。Focal Loss 更细一点它会在训练过程中动态降低已预测正确的像素的权重让模型逐渐把注意力集中到那些“很难搞”的边界和目标上。3.3 训练配置与评估指标训练配置上优化器现在用得最多的是 AdamW初始学习率 1e-4 左右配合 warmup 和余弦退火。分割模型的收敛速度比分类慢很多我建议第一个模型先用小迭代数跑通流程再加大训练周期。batch size 受显卡显存限制一般 2 到 16 不等显存不够时可以减小裁剪尺寸而不是硬调 batch。评估阶段不能只看 loss要看 mIoU平均交并比。对每个类别计算“预测区域和真实区域的交集 / 并集”然后对所有类别取平均。mIoU 的取值范围是0到1越接近1越好。给你一段简洁的参考代码def compute_miou(pred, target, num_classes19, ignore_index255): ious [] pred pred.argmax(dim1) valid target ! ignore_index pred pred[valid] target target[valid] for cls in range(num_classes): pred_cls pred cls target_cls target cls inter (pred_cls target_cls).sum().float() union (pred_cls | target_cls).sum().float() iou (inter / (union 1e-6)).item() ious.append(iou) return sum(ious) / num_classes你可能注意到代码里先把忽略像素过滤掉了这是个好习惯。有些指标脚本没这么做把 ignore 像素算成错误结果会非常难看。除了 mIoU还要关注每类 IoU。整体 mIoU 高不代表模型可靠很可能只是因为背景类占了大头。比如工业质检里缺陷只占画幅 1%背景 IoU 99% 就把整体拉上去了缺陷 IoU 可能还是 0。所以真实项目里我几乎都会报告“少数类 IoU”和“总体 mIoU”两个数字早停也会基于少数类 IoU。3.4 实际训练中的参数记录与调参轨迹拿我自己在某公开街景数据集上的一个练手 demo 来说用的模型是 U-Net 基础版输入 512×512batch size 8学习率 1e-4跑了 80 个 epoch。训练刚开始 loss 在 1.8 附近掉得很快到第 20 个 epoch 就降到 0.6mIoU 大约 0.42。之后进入平台期loss 下降很慢mIoU 在 0.65 附近抖动。我果断把学习率降到 3e-5又训了 30 轮mIoU 最后到了 0.73。这个过程很典型前段是模型快速学习主流类别中段是慢慢磨难分像素后段需要降低学习率才能继续收敛。如果一开始就把学习率调太大loss 会出现震荡mIoU 甚至可能倒退。如果你在实验里看到 loss 下降但 mIoU 不涨优先怀疑类别不均衡如果 loss 不降再检查数据预处理和 label 是否有错。Epoch 20 | loss 0.61 | mIoU 0.42 Epoch 50 | loss 0.38 | mIoU 0.65 Epoch 80 | loss 0.27 | mIoU 0.72 Epoch 110 | loss 0.24 | mIoU 0.73上面这组曲线来自我当时随手记的日志可以看出后 30 轮提升非常有限。这种收益递减非常正常真实项目做在线评估时我会额外关注小类别的 IoU 是否提升而不是只看整体平均。3.5 消融实验与算法研究的正确打开方式标题加上了“算法研究”就不能只停留在“跑通一个模型”的层面。做对比实验要有结构基线模型、改进模块、完整模型。每一次只改一个变量这样才能确定精度提升来自什么地方。比如你想验证某个注意力模块有没有用就必须在同样数据、同样增强、同样随机种子下分别跑“不带模块”和“带模块”两个版本比较 mIoU 和每类 IoU。随机种子这一点很关键。分割训练里有很多随机操作数据增强、dropout、初始化参数。如果不固定随机种子同一份代码跑两次可能都会有 0.5 到 1 个点的 mIoU 浮动很小的模块差异很容易被噪声掩盖。我的习惯是至少用三种随机种子跑三遍报告均值重点看最优和最差的差值有没有大于模块带来的净收益。另外消融实验也要关注计算量。一个模块如果让 FLOPs 涨了 20%精度只涨 0.3 个点那在工程上其实不值得。研究型和落地型项目对“涨点”的判断标准不同前者看边际收益后者更看重性价比。所以我在做实验记录时会把参数量、推理耗时、显存峰值一起记录下来后续好横向对比。4. 项目落地中的常见问题与排查技巧4.1 类别不平衡和loss不收敛类别不平衡是分割任务里最常见的问题。我参与过一个路面积水检测项目训练图像里道路和天空占绝大多数积水的像素占比可能只有百分之几。用普通交叉熵训出来的模型几乎把所有像素都预测成背景mIoU 看着还行因为背景占比高但积水区域基本全错。排查时先统计训练集每个类别的像素占比然后针对性处理。一种做法是降低大类别的 Loss 权重让人为地放大目标类的作用另一种做法是加权抽样让每个批次尽量有足够的难例样本还有 Focal Loss它对已经被预测正确的像素降低贡献让模型关注错误像素。最终我在这里采用了“加权交叉熵 在验证时用小类别 IoU 作为早停指标”的组合效果比单纯改 loss 更好。记住评估指标也要跟着业务目标走。4.2 边界模糊和细节丢失分割结果最让人头疼的往往是边界。明明原始图像里边缘很清晰模型画出来却像锯齿或糊了一片。这是因为网络反复下采样和上采样之后高频细节信息大量丢失。模型对区域中间部位预测得准对边缘像素就犹豫不决。解决方向有几个。第一用带跳跃连接的模型比如 U-Net把浅层边缘特征引入解码器。第二在训练损失里加一个边界感知项比如单独计算边界区域的交叉熵让模型把注意力放到边界像素上。第三加强数据增强里的锐利度变化或者使用边缘检测分支辅助监督。还有一个后处理思路是用条件随机场做平滑现在已经不如以前流行因为深度模型自己就能学出来但对某些特定场景仍然有效。4.3 GPU显存不足与推理速度优化我在跑高分辨率遥感图像时遇到过好几次显存爆炸。遥感图动不动上万像素直接放大模型显然不现实。常见的处理方法是把大图切成若干块训练和推理每块尺寸设成 512×512 或 1024×1024推理时再重叠滑窗并做融合。落到工程上减少显存还有几招把 batch size 调小、使用梯度累积、开启混合精度。混合精度尤其好用在支持算力高的设备上能明显提速省显存但要注意浮点稳定性必要时在 loss scale 上做调整。推理速度优化是另一个维度。很多落地场景要求实时嵌入式设备算力有限。我先说结论先做模型轻量化再做推理加速。轻量化可以直接换 MobileNet 之类的轻量骨干也可以对现有模型做通道剪枝推理加速可以上 TensorRT、ONNX Runtime 这些工具把模型转换成优化图显存和时延都能降不少。我在某个实习项目里试过把 U-Net 用 TensorRT 做 FP16性能在嵌入式设备上大约提升了 1.5 倍误差几乎看不出来。补充一点滑动窗口推理时如果窗口之间没有重叠拼接处会出现很明显的边界伪影。我的做法是让相邻窗口有 10% 到 20% 的重叠在重叠区对预测概率做加权平均权重偏向窗口中心。这样虽然推理时间增加了一些但很大程度避免了“被切成块”的视觉断层。4.4 标注噪声和数据质量问题标注噪声对分割的影响往往被低估。语义分割的标注成本很高一张街景图可能标几十分钟所以真实项目里常会出现边缘不一致、漏标小物体甚至类别错标。模型为了拟合这些不一致会把边界学得怪异小目标时而出现时而消失。处理标注噪声的办法第一是设置 ignore_index把标注者不确定的区域直接忽略掉不要在 loss 里逼模型学它。第二是在训练中做置信度过滤连续多轮预测都异常稳定的区域如果出现高 loss很可能是坏标签。第三如果条件允许做一次标注复核把模型预测结果和原始标签叠在一起给标注人员检查。个人体会是宁可只标几百张干净图也不要几千张带脏标的图脏标会带来持续的精度损失而且很难定位。4.5 小目标漏检与多尺度问题语义分割另一个高发问题是小目标漏检。一张图里远处的小人、交通标志、路面积水可能只有几十个像素经过若干次 stride 为 2 的下采样后到了高层特征图上只剩几个点甚至消失。常见改进办法是减少总下采样倍数比如让特征图最终是原图的 1/8 而不是 1/32但这会增加显存也可以用空洞卷积保持分辨率。还可以引入特征金字塔或者 ASPP把不同尺度的特征都接出来用于预测。如果你暂时不想换模型先试试测试时增强把原图放大 1.5 倍预测一次再原尺寸预测一次把两次概率平均。这个方法在很多数据集上能稳定提升小目标 IoU代价是推理时间翻倍。我在一些小目标缺陷检测任务上实测mIoU 提升约 2 到 3 个点属于便宜又有效的技巧。5. 典型应用场景与选型建议5.1 自动驾驶与道路场景分割自动驾驶里的语义分割主要用于理解可行驶区域、车道线、行人、车辆和交通标志。这类场景有几个特点类别多、目标尺度差异大、实时性要求极高。基于视觉的感知通常在车端芯片上跑算力有限所以模型不光要准还要快。我了解到的一些落地做法是先用离线大模型蒸馏出轻量化学生模型学生模型用实时性高的网络结构部署时再做 INT8 量化。这样能保留大部分精度又把时延控制在几十毫秒以内。如果你刚入门可以从一个简化版场景入手只分割道路和背景专注把数据平衡和边界问题处理清楚再逐步加类别。5.2 医学影像分割医学影像是分割技术落地最深的领域之一。比如 CT 图像里分割某个器官或者病理切片里分割病变区域。这类数据和自然图像差异很大视场相对稳定器官形态位置有一定规律但个体差异、设备差异会带来域偏移。U-Net 及变体是医学分割的主流因为医学样本少、标注昂贵跳跃连接带来的细节保留非常宝贵。训练时一般会用到弹性形变来增强这样能模拟组织变形。另外医学分割通常要求结果平滑、连通性好因为器官不能碎成几块所以后处理里开闭运算或者条件随机场仍有一定价值。我个人的建议是先花时间把数据格式和设备参数统一否则跨中心测试时精度掉得吓人。5.3 遥感图像与工业质检遥感图像分割常用于土地利用分类、建筑物提取、道路提取。大图和复杂背景是主要痛点单个目标可能只有十几像素也可能是一整个稻田。解决方案通常是裁块 多尺度推理。有的项目会额外引入高程或光谱通道把多模态数据拼到输入里网络需要相应调整第一层卷积输入的通道数。工业质检是更偏向成本敏感的小样本场景比如产品表面缺陷分割。缺陷面积小而形态多样普通交叉熵很难训练好。我的做法是先用少量标注数据训一个初始模型再靠模型预测生成伪标签不断用半监督方式扩充训练集。这类场景里模型精度不是唯一指标漏检率和误报率往往更加重要所以评估时不能只看 mIoU可以额外统计逐像素的精确率和召回率。5.4 如何根据硬件和效果选择模型选择模型没有万能答案但有几个常见路线可以参考。下面这张表是我在多个项目里总结出来的粗略经验场景推荐模型族核心原因注意点小样本医学图像U-Net 及变体参数少、细节保留好注意过拟合增强要足大尺度街景/遥感DeepLab 系空洞卷积多尺度强计算量大需优化实时边缘设备轻量化骨干解码器速度快精度靠蒸馏补极强全局上下文Transformer 分割长距离建模强显存和数据要求高如果你的显卡显存只有8G左右我的建议是不要一上来就跑最大的模型。先把 U-Net 或 DeepLabV3 的轻量版本跑通确认数据流程和指标没问题再逐步扩大。选模型本质是在效果、速度和开发成本之间做权衡很多时候调优一个好的中等模型效果比盲目上大模型更好。从我自己的实验经历来看与其一开始追新模型不如先把数据质量、增强策略、损失函数和评估指标处理干净。很多时候你们以为是模型的问题多看看数据往往会有意外收获。这是我踩过不少坑之后比较深的体会希望对你有用。