简介:这份PDF文档面向计算机视觉方向的学习者与工程开发者,聚焦多任务学习框架下YOLOv11同时实现目标检测与实例分割的完整工程实践,适合具备一定深度学习基础、希望掌握单阶段检测与像素级分割融合方案的中高级读者。文档共39页,以1个PDF文件形式打包,压缩包约2.19MB,支持目录章节跳转、阅读器左侧大纲显示与章节快速定位,查阅体验流畅。内容从多任务学习、目标检测与实例分割基础讲起,系统梳理YOLOv11的骨干网络、颈部网络与头部网络架构,并深入剖析特征共享机制、检测分支与分割分支设计及多任务损失权衡。工程实践部分覆盖环境搭建、数据准备与标注、模型训练、评估到部署全流程,配合代码实现解析与COCO及自定义数据集实验结果,还整理了环境配置、训练收敛、部署转换等常见问题与解决方案。目前已有99人学习,适合希望将检测与分割统一落地到实际项目的开发者参考。
1. 多任务学习框架下 YOLOv11 检测与分割的工程落地:一个模型如何干两件事
如果你正在做工业质检、自动驾驶感知或遥感解译,大概率遇到过这种局面:目标检测模型跑一遍拿到框,实例分割模型再跑一遍拿到掩码,两个模型各自占一份显存、各自维护一套预处理和后处理逻辑,推理延迟直接翻倍。多任务学习框架的核心思路就是让一个骨干网络同时输出检测框和分割掩码,YOLOv11 的 Segment 分支恰好提供了这个能力。它解决的不是“能不能检测”的问题,而是“能不能用一套权重、一次前向传播,同时拿到检测和分割结果”的工程效率问题。适合已经跑通过 YOLOv11 检测、想进一步压缩推理链路或减少模型维护成本的从业者。下面从选型理由、数据准备、训练配置到避坑排查,把这条路径完整走一遍。
2. YOLOv11 多任务头是怎么挂上去的:从网络结构到损失函数
2.1 检测头与分割头的共享与分叉
YOLOv11 的整体结构延续了 Ultralytics 系列的经典设计:CSPDarknet 风格的骨干网络负责特征提取,PAN-FPN 做多尺度特征融合,最后接检测头。多任务版本的关键改动在检测头之后——分割头并不是独立的一套网络,而是从 PAN-FPN 输出的 P3、P4、P5 三层特征中额外引出一条分支,经过若干卷积和上采样操作,生成与原图分辨率对齐的掩码原型(mask prototypes),再通过检测框区域内的掩码系数组合出每个实例的分割结果。
这种设计的工程意义在于:骨干网络和特征金字塔是检测与分割共享的,只有最后的预测头是分叉的。这意味着显存增量主要来自分割头的原型生成和掩码组装,而不是再跑一遍完整骨干。实际部署时,共享骨干带来的收益非常明显——单次前向传播同时输出两类结果,端到端延迟比双模型串联低 40% 到 60%,具体取决于输入分辨率和批大小。
常见做法是直接使用 Ultralytics 官方提供的yolo11n-seg.pt或yolo11s-seg.pt作为预训练起点。如果你只需要检测和分割两个任务,不需要额外改造网络结构,官方 Segment 模型已经内置了双头输出。真正需要自己动手的是数据标注格式、损失权重调节和推理后处理。
2.2 分割头的掩码生成逻辑与损失计算
分割头的输出不是直接一张二值掩码图,而是一组掩码原型加每个实例的掩码系数。推理时,检测框确定实例位置,掩码系数与原型做矩阵乘法,再裁剪到框内区域,得到该实例的分割掩码。训练阶段的损失由三部分组成:检测损失(分类 + 框回归 + DFL)、分割损失(掩码 BCE 损失)、以及可选的掩码系数损失。
这里有一个容易翻车的点:分割损失只在正样本锚点上计算,如果某个 batch 里正样本极少,分割分支的梯度会非常稀疏,导致掩码质量上不去。我一般会在训练初期把分割损失权重适当调高,等检测损失稳定后再回调。Ultralytics 的默认配置里seg任务的损失权重是内部平衡过的,但面对小目标密集场景,手动调整box、cls、dfl、seg四个 loss gain 仍然是必要的。
# 自定义训练配置片段:调整多任务损失权重 # 保存为 custom-seg.yaml,训练时通过 cfg 参数加载 task: segment mode: train model: yolo11s-seg.pt # 损失权重:小目标密集场景下适当提高 seg 和 cls box: 7.5 # 框回归损失增益,默认 7.5 cls: 0.8 # 分类损失增益,默认 0.5,小目标多可提到 0.8 dfl: 1.5 # 分布焦点损失增益,默认 1.5 seg: 1.2 # 分割损失增益,默认 1.0,掩码质量差时提到 1.2~1.5 # 数据增强:分割任务对几何变换更敏感 mosaic: 1.0 # 马赛克增强,分割任务建议保持 1.0 copy_paste: 0.3 # 复制粘贴增强,小目标分割有效但不宜过高 degrees: 10.0 # 旋转角度,分割任务不宜超过 15 度上面配置里seg参数控制分割损失在总损失中的占比。调高它会迫使网络更关注掩码边界质量,但过高会导致检测框回归精度下降。copy_paste是分割任务特有的增强方式,把实例抠出来粘贴到其他位置,对小目标分割提升明显,但超过 0.5 容易产生不自然的拼接边缘,反而让模型学到错误纹理。degrees旋转增强在分割任务里要谨慎,因为旋转后的掩码标注需要同步变换,Ultralytics 内部会处理,但角度过大时插值误差会累积。
2.3 骨干共享带来的显存与速度收益
用yolo11s-seg和分别加载yolo11s.pt(检测)+ 一个同量级分割模型做对比,在 RTX 4060 8GB 上实测:单模型多任务推理显存占用约 2.1GB(batch=1, imgsz=640),双模型串联约 3.8GB。推理延迟方面,单模型约 12ms,双模型约 22ms。这个差距在边缘设备上会被进一步放大,因为双模型意味着两次完整的内存搬运和两次后处理。
但共享骨干也有代价:两个任务的梯度会相互干扰。检测任务偏好语义级别的特征,分割任务对边缘和纹理更敏感。如果两个任务的难度差异很大(比如检测目标很大但分割边界很细),共享骨干可能两边都学不好。这时候可以考虑部分解耦——骨干共享,但 PAN-FPN 的后两层分开,各自接独立的检测头和分割头。Ultralytics 没有直接提供这个配置,需要改模型定义文件,适合对网络结构比较熟悉的团队。
3. 数据标注与格式转换:检测框和分割掩码怎么对齐
3.1 YOLO 分割格式的标注要求
YOLOv11 的分割任务要求标注文件是 YOLO 格式的多边形坐标,每行一个实例,格式为:class_id x1 y1 x2 y2 ... xn yn,坐标是归一化到 0 到 1 之间的浮点数。和检测格式的区别在于,检测格式是class_id cx cy w h,而分割格式直接列出多边形顶点。一个实例的顶点数可以不同,但至少需要 3 个点才能构成多边形。
实际标注时,我一般用 LabelMe 或 CVAT 画多边形,导出后转成 YOLO 分割格式。LabelMe 的 JSON 里shapes字段的points就是多边形顶点,label是类别名。转换脚本需要处理几个边界情况:多边形自交、顶点数少于 3、坐标超出图像边界。这些脏数据如果不处理,训练时 dataloader 会直接报错或产生错误掩码。
import json import os from pathlib import Path def labelme_to_yolo_seg(json_path, output_dir, class_map): """ 将 LabelMe 多边形标注转为 YOLO 分割格式 json_path: LabelMe JSON 文件路径 output_dir: 输出 txt 目录 class_map: 类别名到 id 的映射,如 {"person": 0, "car": 1} """ with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_w = data['imageWidth'] img_h = data['imageHeight'] lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue # 跳过未定义类别 points = shape['points'] if len(points) < 3: continue # 少于 3 个点无法构成多边形 # 归一化并限制在 [0, 1] 范围内 coords = [] for x, y in points: nx = max(0.0, min(1.0, x / img_w)) ny = max(0.0, min(1.0, y / img_h)) coords.extend([nx, ny]) # 格式:class_id x1 y1 x2 y2 ... line = f"{class_map[label]} " + " ".join(f"{c:.6f}" for c in coords) lines.append(line) # 写入同名 txt 文件 stem = Path(json_path).stem out_path = Path(output_dir) / f"{stem}.txt" with open(out_path, 'w', encoding='utf-8') as f: f.write("\n".join(lines)) return len(lines) # 使用示例 class_map = {"defect": 0, "scratch": 1, "dent": 2} labelme_to_yolo_seg("data/annotations/img_001.json", "data/labels/train", class_map)这个转换脚本的核心逻辑是坐标归一化和格式对齐。class_map必须和训练时data.yaml里的names顺序一致,否则类别 id 会错位。坐标归一化时做了截断,防止标注时鼠标拖出图像边界导致坐标大于 1。len(points) < 3的判断是必须的,LabelMe 允许画两个点的线段,但 YOLO 分割格式不接受。实际项目中,我还会加一个面积过滤——多边形面积小于图像面积 0.1% 的实例直接丢弃,这类极小掩码对训练只有干扰没有帮助。
3.2 data.yaml 的配置与路径陷阱
YOLOv11 训练时通过data.yaml指定数据集路径和类别信息。分割任务的data.yaml和检测任务基本一致,但有一个关键区别:task字段必须设为segment,否则 Ultralytics 会按检测任务解析标注文件,把多边形坐标当成框坐标处理,训练直接崩掉。
# data.yaml:分割任务数据集配置 path: /home/user/datasets/defect-seg # 数据集根目录 train: images/train # 训练集图像相对路径 val: images/val # 验证集图像相对路径 test: images/test # 测试集图像相对路径(可选) # 类别信息:names 的顺序决定 class_id names: 0: defect 1: scratch 2: dent # 分割任务必须指定 task task: segment路径配置有一个血泪经验:path用绝对路径最稳,train和val用相对路径。如果path写相对路径,Ultralytics 会相对于当前工作目录解析,换一台机器或换一个启动目录就找不到数据。另外,图像和标注文件的目录结构必须严格对应——images/train/img_001.jpg对应labels/train/img_001.txt,文件名相同、扩展名不同。如果标注文件缺失,训练时不会报错,但那个样本会被静默跳过,导致实际训练集比预期小。
3.3 标注质量对分割指标的影响
分割任务的标注质量比检测任务敏感得多。检测框稍微偏几个像素,IoU 可能只掉一两个点;但分割掩码边界偏几个像素,mask mAP 可能直接掉十几个点。我做过一组对比实验:同一批数据,一组用精细多边形标注(顶点数 50+),一组用粗略矩形近似(顶点数 4),在相同训练配置下,精细标注的 mask mAP@0.5 是 0.72,粗略标注只有 0.48。差距主要来自边界区域——粗略标注的掩码边界和真实边界偏差大,模型学到的边界特征模糊。
如果标注资源有限,优先保证以下三类样本的标注质量:小目标实例、边界复杂的实例(如树枝、裂缝)、密集重叠实例。大块规则目标(如整辆车、整块面板)的掩码边界稍微粗糙一点,对整体指标影响相对小。
4. 训练配置与调参:让检测和分割同时收敛
4.1 从预训练权重启动训练的最小命令
Ultralytics 的训练入口非常简洁,分割任务和检测任务的命令结构一致,只是模型文件换成-seg后缀。以下是最小可运行命令:
# 从 COCO 预训练的 yolo11s-seg 启动,在自定义数据集上微调 yolo segment train \ model=yolo11s-seg.pt \ data=/home/user/datasets/defect-seg/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ workers=8 \ project=runs/segment \ name=defect-seg-v1 \ pretrained=True \ optimizer=AdamW \ lr0=0.001 \ lrf=0.01 \ warmup_epochs=3 \ cos_lr=True \ patience=20 \ save_period=10model=yolo11s-seg.pt指定了预训练权重,pretrained=True确保加载 COCO 上训练好的骨干和分割头参数。imgsz=640是输入分辨率,分割任务不建议低于 640,因为掩码原型的分辨率直接受输入尺寸影响,太小会导致小目标掩码糊成一团。batch=16在 8GB 显存上跑yolo11s-seg比较稳,如果 OOM 就降到 8 或开启amp=True混合精度。workers=8是 dataloader 线程数,根据 CPU 核心数调整,太少会导致 GPU 等数据,太多会抢内存。
lr0=0.001是初始学习率,AdamW 优化器下这个值比较通用。lrf=0.01是最终学习率因子,配合cos_lr=True做余弦退火。warmup_epochs=3让学习率在前 3 个 epoch 从极小值线性升到lr0,避免一开始就大梯度冲击预训练权重。patience=20是早停耐心值,验证指标 20 个 epoch 不提升就停。save_period=10每 10 个 epoch 存一次中间权重,方便回滚。
4.2 关键超参数:imgsz、batch、学习率与损失权重
分割任务的超参数调节有几个和检测任务不同的侧重点。imgsz对分割指标的影响比检测更大,因为掩码质量直接和特征图分辨率挂钩。在显存允许的前提下,分割任务优先保证imgsz不低于 640,小目标密集场景可以提到 1024 甚至 1280。但要注意,imgsz翻倍后显存占用大约翻四倍,推理延迟也接近翻倍。
batch的选择要兼顾显存和梯度稳定性。分割损失在正样本少的时候梯度稀疏,太小的 batch 会让梯度噪声更大。我一般建议batch不低于 8,如果显存不够,宁可降imgsz也不要降batch到 4 以下。lr0在微调场景下用 0.001 比较安全,如果从头训练可以提到 0.01,但需要配合更长的warmup_epochs。
损失权重方面,box、cls、dfl、seg四个 gain 的默认值在大多数场景下可用。如果发现检测框准但掩码边界糊,把seg从 1.0 提到 1.2 到 1.5;如果掩码准但框漂,把box从 7.5 提到 8.5 到 10。cls在类别不平衡时适当提高,但超过 1.0 容易导致分类过拟合。
4.3 训练过程监控:看哪些指标、什么时候该停
Ultralytics 训练时会在控制台输出和results.csv里记录一系列指标。分割任务重点看这几个:metrics/mAP50-95(B)是检测框的 mAP,metrics/mAP50-95(M)是掩码的 mAP,train/seg_loss和val/seg_loss是分割损失。正常情况下,训练初期seg_loss下降比box_loss慢,因为掩码学习难度更高。如果seg_loss一直不降,检查标注格式是否正确、task是否设为segment。
判断过拟合的信号:train/seg_loss持续下降但val/seg_loss开始上升,同时metrics/mAP50-95(M)停滞或下降。这时候可以增大copy_paste或mosaic增强,或者加dropout。判断欠拟合的信号:训练和验证损失都还很高,指标远低于预期。这时候优先检查数据标注质量,而不是盲目加 epoch。
早停策略我一般设patience=20,但会同时看save_period保存的中间权重。有时候验证指标波动大,最佳权重可能出现在早停触发之前。训练结束后,用yolo segment val在验证集上跑一遍最佳权重,确认最终指标。
5. 避坑与排查:多任务分割训练里最容易翻车的五件事
5.1 掩码全黑或全白:标注格式和 task 字段的坑
现象:训练几个 epoch 后,推理可视化发现掩码要么全黑(没有分割结果),要么全白(整个图被当成一个实例)。val/seg_loss不降或异常低。
原因:最常见的是data.yaml里task没设为segment,Ultralytics 按检测格式解析多边形坐标,把归一化坐标当成框的cx cy w h,导致掩码生成逻辑完全错乱。另一个原因是标注文件里多边形坐标没有归一化,数值大于 1,掩码原型裁剪时越界。
解决:检查data.yaml的task: segment是否存在。用脚本抽查几个标注文件,确认坐标都在 0 到 1 之间。如果坐标没归一化,用 3.1 节的转换脚本重新处理。
5.2 小目标掩码糊成一团:imgsz 和掩码原型分辨率的限制
现象:大目标分割边界清晰,但小目标(像素面积小于 32x32)的掩码几乎看不出形状,mask mAP 在小目标上极低。
原因:分割头的掩码原型分辨率是输入尺寸的 1/4,imgsz=640时原型分辨率只有 160x160。小目标在原型图上只占几个像素,掩码系数组合后边界信息严重丢失。
解决:提高imgsz到 1024 或 1280,让原型分辨率翻倍。如果显存不够,可以只对包含小目标的图像做高分辨率推理,或者用切片推理(SAHI 思路)把大图切块后分别检测分割再合并。另一个方向是修改分割头上采样倍数,但需要改模型定义,工程成本较高。
5.3 检测和分割指标此消彼长:损失权重失衡
现象:训练过程中检测 mAP 上升但掩码 mAP 下降,或者反过来。两个指标很难同时达到最优。
原因:共享骨干下两个任务的梯度相互竞争。如果seg损失权重过高,骨干特征偏向边缘纹理,检测框回归精度下降;如果box权重过高,骨干偏向语义特征,掩码边界变糊。
解决:先固定box、cls、dfl为默认值,只调seg。从 1.0 开始,每次加 0.1,观察两个指标的变化。找到掩码 mAP 开始上升但检测 mAP 下降不超过 1 个点的平衡位置。如果怎么调都此消彼长,考虑部分解耦骨干后两层。
5.4 训练 loss 正常但推理结果错乱:预处理和后处理不一致
现象:训练日志里 loss 正常下降,验证指标也还行,但用model.predict()推理时结果完全不对,框和掩码错位。
原因:训练时的图像预处理(归一化、letterbox)和推理时的预处理不一致。Ultralytics 内部会处理,但如果你自己写了推理脚本,很容易在 letterbox 的 padding 计算上出错,导致坐标映射回原图时偏移。
解决:优先用 Ultralytics 的model.predict()接口,不要自己手写预处理。如果必须自己写,确保 letterbox 的缩放比例和 padding 计算与训练时完全一致。推理后处理时,掩码要按 letterbox 的逆变换裁剪回原图区域。
5.5 显存溢出:batch 和 imgsz 的取舍
现象:训练启动后报 CUDA out of memory,或者训练几个 batch 后突然 OOM。
原因:batch或imgsz设置过大,或者workers太多导致内存泄漏累积。分割任务比检测任务多一个掩码原型生成和掩码组装步骤,显存占用比同量级检测模型高 20% 到 30%。
解决:优先降batch,从 16 降到 8 再到 4。如果降到 4 还 OOM,降imgsz从 640 到 512。开启amp=True混合精度训练可以省 30% 左右显存。workers设为 CPU 核心数的一半,不要超过 8。如果还是 OOM,用yolo11n-seg替代yolo11s-seg,n 版本的参数量和显存占用都小很多。
6. 推理部署与效果验证:从 PyTorch 到 ONNX 的落地技巧
训练完成后,下一步是把模型部署到实际推理环境。Ultralytics 支持导出 ONNX、TensorRT、OpenVINO 等多种格式。分割模型的导出和检测模型基本一致,但有一个关键区别:ONNX 输出会多一个掩码原型张量,后处理时需要额外处理。
from ultralytics import YOLO # 加载训练好的分割模型 model = YOLO("runs/segment/defect-seg-v1/weights/best.pt") # 导出 ONNX,分割模型需要指定 opset 和 simplify model.export( format="onnx", imgsz=640, opset=12, # 分割模型建议 opset >= 11 simplify=True, # 简化计算图,减少冗余算子 dynamic=False, # 固定输入尺寸,推理更快 half=False # FP32 导出,FP16 在部分设备上兼容性差 ) # 推理并保存结果 results = model.predict( source="test_images/", imgsz=640, conf=0.25, # 置信度阈值,分割任务建议 0.25 起步 iou=0.45, # NMS IoU 阈值 save=True, # 保存可视化结果 save_txt=True, # 保存分割结果到 txt save_conf=True, # txt 里包含置信度 project="runs/predict", name="defect-seg-test" )导出 ONNX 时opset=12是分割模型比较稳的选择,低于 11 可能不支持某些掩码操作。simplify=True会调用 onnx-simplifier 去掉冗余节点,减小模型体积并提升推理速度。dynamic=False固定输入尺寸,TensorRT 和 OpenVINO 在固定尺寸下优化更充分。half=False导出 FP32,虽然 FP16 推理更快,但部分边缘设备对 FP16 的掩码后处理支持不完善,容易出现数值溢出。
推理时conf=0.25是分割任务的常用起点,比检测任务的 0.25 略高,因为低置信度的掩码往往边界模糊,可视化效果差。iou=0.45控制 NMS 的合并阈值,密集实例场景可以降到 0.4 减少漏检。save_txt=True会把每个实例的类别、框坐标、掩码多边形和置信度写到 txt,方便后续做定量分析。
验证部署效果时,我一般会做三组对比:PyTorch 原始模型、ONNX Runtime 推理、TensorRT 推理。重点看三个指标:单帧延迟、mask mAP 下降幅度、显存占用。ONNX Runtime 的 mask mAP 通常比 PyTorch 低 0.5 到 1 个点,主要来自算子精度差异;TensorRT 在 FP16 下可能低 1 到 2 个点,但延迟能降到 PyTorch 的 1/3 到 1/2。如果 mask mAP 下降超过 3 个点,检查导出时的opset和simplify设置,或者尝试 FP32 推理。
一个我踩过的坑:ONNX 分割模型的输出张量顺序和 PyTorch 不一致。PyTorch 输出是(preds, prototypes),ONNX 可能反过来。后处理代码里如果按固定顺序取张量,会导致掩码和框完全错位。解决办法是打印 ONNX 模型的输出名称和形状,确认哪个是预测张量、哪个是原型张量,再写后处理逻辑。
最后说一个验证技巧:用同一批测试图像,分别跑 PyTorch 和 ONNX 推理,把掩码结果叠加到原图上做像素级对比。如果发现某些实例的掩码在 ONNX 下偏移了几个像素,大概率是 letterbox 的 padding 计算在导出时被简化掉了。这时候可以在导出前把imgsz设为和训练时完全一致,并且确保推理脚本里的预处理和训练时一致。这个对比方法比只看 mAP 数字更直观,能发现指标掩盖的边界问题。
希望帮到你。
本文还有配套的精品资源,点击获取