首页 / 资讯中心 / 文章详情

语义分割与实例分割:从模型选型到部署优化的实战指南

语义分割与实例分割:从模型选型到部署优化的实战指南 ★ FEATURED ARTICLE
在落地实际视觉项目时我经常被问到一个问题能做分割的模型这么多到底用哪个、怎么用这个问题背后其实对应着两种截然不同的任务——语义分割和实例分割。我做过的几个项目里既有从航拍图里区分农田和道路的也有给货架上的每个商品画精确轮廓的两套技术路线差异很大。这篇内容我就围绕这两类分割模型从任务本质、模型选型、数据标注、训练调优到评估和部署把完整的思路和踩坑经验整理出来希望给正要做分割的开发者一些实在的参考。1. 分割任务的灵魂语义分割与实例分割到底在解什么问题很多刚接触视觉的开发者会把分割和检测混在一起实际上检测输出的是矩形框分割输出的是逐像素的类别或对象轮廓。两者最大的差异在于“精确到像素”这件事而语义分割和实例分割又是在这个基础上针对不同问题给出的解决方案。1.1 语义分割给每个像素贴类别标签语义分割的核心逻辑是“像素级分类”它不关心某一类物体有几个个体只关心每个像素属于哪个类别。比如一张街景图里有天空、道路、行人、车辆语义分割会把所有道路像素标为一类所有车辆像素标为一类但如果画面里有三辆车这三辆车的像素会共享同一个标签。我在做遥感地块分析时用的就是这种思路模型只需要知道每个像素属于农田、建筑、水体还是道路不需要区分哪块农田是张三家的。这样设计的好处是标注成本低、模型结构相对简单、训练数据更容易获取。语义分割的上游视觉任务包括场景理解、自动驾驶感知、医学影像器官划分等统一的特点是“像素类别”本身就足够支撑业务。1.2 实例分割同一类也要分你我实例分割是在语义分割基础上加了一层“个体意识”。它要求模型不仅判断像素属于哪一类还要判断这个像素属于该类中的第几个个体。还是拿街景图举例三辆车在实例分割结果里会呈现三种不同颜色每辆车都有独立的轮廓。我在做一个货架商品盘点项目时深切体会到实例分割的必要性货架上有十几个同款饮料瓶如果只做语义分割系统只能算出“饮料像素占比多少”完全没法统计“具体有几瓶”。加上个体区分之后每瓶饮料的坐标、轮廓、数量都能直接拿到了。实例分割的典型应用包括商品识别计数、医学细胞计数、养殖场个体检测、自动驾驶中区分同一类别的不同目标等。1.3 两类任务之间的关键差异对照我习惯用一张表来帮团队理清两种任务的差别维度语义分割实例分割输出粒度像素级类别标签像素级类别标签个体编号是否区分同类个体否是标注成本相对低按类画区域较高每个个体单独描边典型模型FCN、U-Net、DeepLab系列Mask R-CNN、YOLACT、SOLO核心评价指标mIoU、PAmask AP、AP[0.5:0.95]适用场景地块分类、道路分割、器官划分商品盘点、细胞计数、自动驾驶多目标实际项目里还有一种更细的分割叫全景分割它要求兼顾语义和实例把“stuff”类天空、地面等背景类按语义处理把“thing”类人、车等可数物体按实例处理不过多数业务需求用语义分割或实例分割之一就能覆盖所以日常开发中先确定“是否需要区分个体”才是关键决策点。2. 从FCN到Transformer主流模型选型思路与实际对比模型选型是我在做分割项目时最花时间的一步因为不同模型在精度、速度、显存占用和易用性上差别很大而且每个模型都有自己的“脾气”。我会把语义分割和实例分割的代表模型分开讲顺便给出我自己的选型判断。2.1 语义分割模型家族与发展脉络说到语义分割绕不开FCN。我在入门时跑的第一个分割模型就是它FCN把全连接层换成卷积层让网络可以接受任意尺寸输入并输出像素级预测但它的感受野和细节保持能力有限分割结果比较粗糙现在已经很少直接用了。U-Net是我在医学影像和少量样本场景下的首选。它的核心是编码-解码结构和跳跃连接下采样提取高层语义上采样恢复空间细节跳跃连接则把浅层细节和高层语义融合在一起。我在一个只有两百多张标注图的项目里用U-Net在几天内就拿到了不错的精度这种小数据友好性是其他模型很难替代的。DeepLab系列在工程里也很常见尤其是DeepLabV3。它用空洞卷积扩大感受野在保持特征图分辨率的同时看到更大范围还加入了ASPP模块用多个不同扩张率的空洞卷积并行提取多尺度信息。我在遥感地块分割项目中对比过DeepLabV3对大面积连续区域的边界处理明显比U-Net干净但训练显存消耗也更大。PSPNet则是靠金字塔池化模块解决多尺度问题它把特征图切成不同大小的区域做池化再接回去对场景里的全局上下文把握很到位。不过在目前的实际项目里我看到越来越多团队转向Transformer架构的分割模型比如SegFormer它用自注意力机制建模长距离依赖处理大场景时上下文连贯性更好代价是推理速度相对慢一些。2.2 实例分割模型怎么选实例分割的选型思路比语义分割更复杂一些因为它要同时处理“检测”和“分割”两条任务线。我最早用的是Mask R-CNN它是在Faster R-CNN检测框架上增加了一个mask分支先用RPN生成候选框再对每个框内的区域做像素级二分类。优点是精度高、稳定、社区资料丰富缺点是速度慢两阶段结构在实时性要求高的场景里撑不住。后来我在需要实时处理的工业场景里换用了YOLACT和SOLO。YOLACT是一阶段方法通过生成一系列原型掩码和每个实例的线性组合系数来得到最终掩码不需要RoI池化这种慢操作在GPU上可以做到接近实时的速度。SOLO则换了个思路把图像划分成网格每个网格负责预测中心点落在其中的实例掩码完全摆脱了检测框依赖实现起来直接训练也比较省心。近几年Transformer结构也开始渗透到实例分割领域Mask2Former用统一查询的方式同时处理语义和实例分割在精度上刷新了很多榜单我试用过之后发现它的边界质量确实高但工程化部署时对加速库的兼容性还在逐步完善中所以生产环境里我目前还是以Mask R-CNN和YOLACT为主。2.3 选型之前的三个判断维度我每次选型都会先问三个问题数据量有多大、实时性要求多高、部署环境是GPU还是边缘设备。如果数据量在千张以内优先考虑U-Net这类高效利用数据的结构如果有上万张数据且对精度要求高DeepLabV3或SegFormer更合适。实时性上60fps以上必须考虑轻量结构或模型裁剪15fps以下可以放宽模型复杂度。部署层面边缘设备跑Transformer结构目前还很吃力卷积结构反而是更稳的选择。还有一点容易被忽略团队对模型的熟悉程度。我在一个紧急项目里选了Mask R-CNN而不是当时精度更高的SOLO就是因为团队对前者的调参经验更足出了问题能快速定位。模型再先进没人会调也是白搭。3. 数据是上限标注与预处理的那些实操细节我在分割项目上吃过最大的亏就是低估了数据准备的工作量。很多论文里的模型结构只占整个项目三分之一的时间剩下的全在跟数据较劲。3.1 两类分割任务的标注方式差异语义分割的标注相对直观常见的工具是LabelMe或Labelbox标注员沿着目标边缘画多边形软件自动填充为掩码。保存格式上我推荐直接用灰度PNG每个像素值对应类别id0是背景1是第一类2是第二类以此类推。这种格式读取方便训练时直接把图像和标签一起喂给模型就行。实例分割的标注要繁琐得多每个实例都要单独画一个多边形并记录类别和编号。由于我用得最多的是Mask R-CNN所以习惯把标注结果导出成COCO格式的JSON文件里面包含images、annotations、categories三个主要字段每个annotation都带segmentations多边形和area、bbox等信息。团队里标注质量参差不齐时我会在标注规范里明确规定遮挡部分的轮廓按可见区域标注还是按完整轮廓推测这个决定直接影响最终效果。3.2 标签同步变换与过采样的重要细节数据增强在分割任务里比在分类任务里更敏感因为标签和图像必须严格同步变换。我用albumentations库最多它封装好了同一组变换同时作用于图像和掩码的流程。像随机翻转、随机旋转、随机缩放、随机裁剪这些基础增强可以直接套用但要注意旋转和缩放会改变物体形状在一些尺寸敏感的场景比如测量任务里要谨慎使用。类别不平衡是分割任务里非常常见的问题尤其是医学图像和遥感图像背景像素常常占90%以上。我在一个血管分割项目里初始模型几乎把所有像素都预测成背景后来在损失函数里加了Dice Loss的权重项同时在采样策略上做了过采样让包含前景区域的样本在训练集中占更高比例才真正把前景区域训练起来。3.3 标注质量控制的几个土办法数据质量把控上我没有完全依赖自动校验工具一直保留几个手工检查的习惯。第一训练前抽看五十张图片的标签叠加效果用半透明方式把掩码覆盖在原图上一眼就能看出标注有没有偏移第二检查类别id映射表有没有建错我有一次把类别顺序搞混了导致模型训练了两个epoch才反应过来第三对于细长物体比如道路、血管要求标注员使用密集点而不是稀疏点否则掩码边缘会出现明显锯齿。还有一个容易被忽略的点统一所有图像的尺寸和分辨率。如果数据集里混着1920x1080和512x512两种尺寸模型训练时的batch组成会很混乱我通常会在预处理阶段把图像统一缩放到模型输入尺寸同时记录缩放比例方便后续还原到原始坐标。4. 训练中的关键设置与踩坑经验分割模型的训练和其他视觉任务有一些共同点但也有不少独特之处。我在这里把损失函数、学习率、显存管理以及几个我真实踩过的坑集中说一说。4.1 损失函数选型背后的逻辑语义分割最基础的是交叉熵损失每个像素独立计算分类损失再取平均。但交叉熵在类别不平衡时表现很差就是前面提到的“背景吞噬前景”问题。我在项目里最常用的是交叉熵和Dice Loss的加权组合Dice Loss直接优化区域重叠度对前景占比小的情况非常友好。公式上Dice Loss是1减去两倍交叠面积除以像素总和简单理解就是让预测区域和真实区域尽可能重合。实例分割的损失函数通常是多任务加权组合。以Mask R-CNN为例总损失等于分类损失加框回归损失加掩码损失掩码损失用的是平均二值交叉熵。实践里这个权重组合一般不需要自己调整框架默认值就能正常工作真正影响效果的是正负样本的采样比例。有一段时间我很迷信Focal Loss它在检测任务里效果惊艳但放到分割任务里我发现它带来的提升并不显著反而让训练时间变长了。后来想明白检测里正负样本悬殊Focal Loss有大作用而分割里的像素分类经过采样后平衡度已经好很多收益自然有限。选损失函数真的要看任务具体情况不能照搬。4.2 学习率与训练时长的经验区间学习率方面我在分割任务里普遍使用poly策略也就是基础学习率乘以(1减去当前迭代比例)的0.9次方这样前期大步收敛、后期精细微调。基础学习率从0.01开始试语义分割用SGD时0.01是个很稳的起点用Adam优化器时则一般在0.0001到0.0003之间。训练时长取决于数据和模型复杂度以我的经验单一GPU跑DeepLabV3训练五万张城市街景图大约要三到五天才能稳定。我习惯每训练两三个epoch就保存一次checkpoint方便回溯。还有一个技巧先跑几十个iteration直接看loss曲线是否下降如果初始loss纹丝不动大概率是数据加载或者Label映射出了问题不需要傻等完整训练。4.3 显存溢出与Batch Size的博弈显存是分割任务里绕不开的坎。语义分割模型输出的是和输入同尺寸的多通道概率图解码阶段需要保存大量中间特征显存消耗比检测任务大很多。我第一次跑DeepLabV3时输入尺寸设成1024x1024batch size直接爆了显存。解决办法有三个方向缩小输入尺寸、减小batch size、使用混合精度训练。如果模型里有BatchNorm层batch size不能太小一般不要低于4否则统计量不稳定。这里有个折中做法输入尺寸降为512或768batch size保持8到16配上混合精度我的显存占用能降到原来的六成左右。4.4 我可被坑过的几个训练现场第一个坑是类别映射表错位。我给一组数据做语义分割按“背景、道路、建筑、植被”的顺序设id中途切换标注工具后新导出的标注把类别顺序改成了“背景、植被、道路、建筑”模型训练loss一直不降排查了两天才发现是数据问题。从那以后我每次换工具或数据集都要检查idx2label的映射表。第二个坑是标签图格式问题。标注导出得到的PNG是彩色图每个类别一种颜色如果不做索引化映射直接送入网络类别数量会爆炸。正确做法是转成灰度图或构建颜色到id的映射表。我有一次忘记做转换模型输出类别数变成了几千个。第三个坑是验证集的切分方式。分割任务的样本之间相关性很强相近地理位置的遥感图像或同一患者的多层切片如果被同时分到训练集和验证集指标会虚高得很离谱。我现在做遥感项目时会按地理位置分块切分数据做医学项目时按患者维度划分这样才能让评估指标真正反映泛化能力。5. 评估指标mIoU与mask AP怎么算、怎么看评估环节如果没有理解透彻很容易被漂亮的数字骗了。分割任务里最常用的两个指标是mIoU和mask AP它们分别对应语义分割和实例分割计算逻辑和业务含义差异很大。5.1 mIoU的计算过程与常见误解mIoU的完整名称是平均交并比计算方式是先对每个类别求预测结果和真实结果的交并比再对所有类别取平均。每个类别的IoU是预测正确的像素数除以“预测为该类或真实为该类的像素总数”用公式表达就是交集面积除以并集面积。如果一个类别的预测区域和真实区域完全重合IoU就是1完全没重合就是0。我在给团队新人讲mIoU时总会强调一个点mIoU对每一类平等对待不会因为背景类像素多就给它更高权重。所以当一个模型整体accuracy很高但mIoU不高说明它可能在小类别上表现很差。我做道路分割时道路类别占比不大但又是业务核心这时候mIoU就比accuracy更能反映问题。还有两个相关指标容易被混淆PA是像素准确率直接统计所有预测正确的像素占比FWIoU是频率加权IoU按各类别出现频率加权。如果业务更关心“总数对多少”PA就够了如果关心“每一类都画得准不准”必须看mIoU。5.2 实例分割为什么沿用目标检测的API实例分割的评估标准直接沿用了目标检测领域的AP体系最常见的是COCO风格的AP[0.5:0.95]计算时对IoU阈值从0.5到0.95每间隔0.05取一次然后求平均。实例分割中的IoU计算的是预测掩码和真实掩码的像素级交并比而不是检测框的交并比这一点很关键。为什么实例分割不继续用mIoU因为mIoU只关心类别像素比例不关心个体匹配。实例分割的业务核心是“每个个体是否被正确检出、轮廓是否准确”这就需要先判断预测实例和真实实例是否匹配再计算匹配上的实例的掩码质量AP天然就是按个体匹配来设计的。我在货架商品识别项目里就用AP[0.5:0.95]作为核心指标因为它对所有IoU阈值下的表现取平均既惩罚漏检错检又惩罚轮廓画得不准。只看单点IoU阈值的话模型可能把边界画得乱七八糟也能获得高分这不符合业务需要。5.3 指标之外还要看的隐藏问题指标只能告诉你“好不好”不能告诉你“好在哪里”。我每次看评估结果时都会额外做两件事第一把每个类别的IoU单独拉出来排序找出表现最差的前三个类别第二可视化一批预测结果直接用肉眼看边界质量和小目标表现。可视化里经常发现的问题是小目标的mIoU很低但整体mIoU还过得去因为小目标在总面积中占比太小。小物体漏检在语义分割里很常见解决思路包括改进损失函数对小目标的权重、调整训练时的切片尺寸、或者增加小目标过采样。还有一个常被忽略的点是掩码边界的锯齿效应像素级预测天然是离散的后处理里适当用条件随机场或形态学操作可以让边界更平滑但在工业部署里我一般不用CRF因为推理耗时增加明显。6. 从离线到上线部署优化的一些个人体会模型在验证集上的指标好不代表能直接上线。我把分割模型从实验环境搬到生产环境时遇到了不少和训练完全不同的挑战这里说几个对实际项目最有帮助的心得。6.1 输入尺寸与后处理策略的取舍训练时为了捕捉细节我常用大输入尺寸比如768x768或1024x1024但推理时为了速度经常会压缩到512x512甚至更小。压缩后小目标更容易丢这个隐患很现实。我的做法是训练时把输入尺寸定在512x512通过数据增强模拟多尺度推理时保持同等尺寸把浮点计算换成TensorRT优化而不是简单粗暴改小输入。后处理方面语义分割可以直接取每个像素的argmax得到类别图但实例分割需要额外的分组操作。Mask R-CNN在推理时输出的是一堆带分数的掩码我要先按置信度阈值过滤掉低质量预测再做非极大值抑制去掉重叠框最后根据mask的像素数量筛掉过小的噪声区域。这一步在后端代码里看似简单但阈值设置不当会让输出结果忽多忽少我在项目里会用一小批验证集来调这个阈值把它固定下来。6.2 模型加速的推进顺序我做模型加速时遵循一个固定的优先级先做量化再做剪枝最后才考虑换轻量模型。量化是最省力的方案我用TensorRT把FP32模型转成FP16或INT8在保持精度损失很小的情况下推理速度能提升两到三倍。INT8量化更激进但需要少量校准数据来确认量化前后的输出分布我在实际项目里会准备一百张代表性样本来做校准。剪枝需要重新微调模型操作起来比较耗时。我在一个遥感分割任务里用结构化剪枝把卷积核数量减掉约四分之一再微调几十个epoch最终模型体积缩小了约百分之三十推理速度提升约百分之二十而mIoU只下降了一个百分点左右这个性价比是可以接受的。如果量化加剪枝之后速度还不够我会考虑用轻量骨干网络替换backbone比如把ResNet系列换成MobileNet系列。需要注意的是骨干网络改动后需要重新训练不能直接加载原先的权重继续推理。我在第一次做这种替换时图省事直接加载旧权重导致输出全是乱码后来规规矩矩重新训练才算跑通。6.3 部署环境与推理框架的选型心得部署环境决定了推理框架的选型。如果是NVIDIA GPU服务器TensorRT是首选如果走ONNX Runtime兼容性极好方便在不同硬件之间迁移如果目标是手机或嵌入式设备那要考虑NCNN或MNN这类轻量推理库。我在选框架时不光看推理速度还会看它对这个模型结构的算子支持情况。有一次我选了TensorRT部署一个包含自定义上采样层的模型结果某些算子没有对应实现只能手工改写网络结构浪费了不少时间。经验是先用ONNX Runtime快速验证整个推理流程确认模型结构没有障碍后再针对性能瓶颈做TensorRT优化。6.4 一个实际的边缘部署项目复盘最后回顾一个我做的边缘设备部署项目任务是在某智能摄像头里检测河道漂浮物。模型用的是一个经过轻量化改进的语义分割结构输入尺寸被压到320x320部署在边缘设备的NPU上。量化后模型大小从21MB降到6MB单帧推理时间从90毫秒降到约35毫秒基本满足实时性要求。这个项目里最有价值的经验有两条。第一边缘设备对模型结构有很强的约束我在设计网络时就要考虑每一个算子是否被NPU支持不能在训练完成后才做适配。第二部署后一定要做端到端的联调我在边缘设备上测试时发现前处理归一化放的位置不对导致输出的概率值整体偏小花了一个晚上查出来是图像缩放后没有把像素值除以255再输入给模型这类问题在离线评估阶段很难暴露必须在真实设备上实测才能发现。做图像分割项目这几年我最大的体会是模型结构再怎么换数据质量和任务定义的清晰度才是决定项目上限的关键。无论是语义分割还是实例分割先想清楚业务到底需要什么输出——是像素类别还是个体实例——再去选模型、做数据、定指标整个过程会顺畅很多。如果让我给刚入门的开发者一个建议我会说与其到处追新模型不如把一个成熟模型的原理吃透用自己项目的数据把它调教到极致这个过程学到的东西比刷十个模型的名字有用得多。
阅读完成 · 觉得有帮助?
咨询建站