YOLOv11自定义分割数据集实战:从数据标注到训练部署全流程

发布时间:2026/10/19 10:02:09
YOLOv11自定义分割数据集实战:从数据标注到训练部署全流程
最近这两周我密集处理了三个基于 YOLOv11 的分割需求某工业场景里零件表面缺陷的轮廓提取、农业大棚里果实成熟区域的面积估算、还有一个监控画面中的人体实例分割。三个项目的数据来源完全不同但完整走完一遍之后我发现用 YOLOv11 训练自定义分割数据集这件事真正决定成败的往往不在模型本身而在数据标注、标签格式和训练参数这三个最容易忽略的环节。今天我把从零开始的完整流程、每一步为什么要这么做、以及实际踩过的坑都写出来。这篇文章适合已经跑通过 YOLO 检测、想切到分割任务的人也适合刚接触自定义数据集、正准备做实例分割的初学者。1. 整体思路拆解为什么选择 YOLOv11 做分割任务1.1 从检测到分割YOLOv11 解决的场景痛点YOLO 系列最早是做目标检测的输出结果是矩形框每个框标记出目标的位置和类别。但很多真实场景只给矩形框根本不够用。比如零件表面裂痕矩形框会框进大量背景比如农作物病斑你需要精确的像素边缘去计算病斑面积占比框的边缘和实际轮廓误差一大面积估算就失去意义。再比如机械臂抓取场景抓取点往往要落在目标的实际几何中心而不是矩形框的中心。YOLOv11 分割版本做的事情就是同时输出目标框和像素级掩码。你既知道每个目标在哪、是什么也知道它精确到像素的轮廓。它本质上是在 YOLO 的检测骨架上加了一个分割头没有搞成两阶段方案所以速度依然很快适合实时场景同时由于检测分支和分割分支共享特征训练时两个任务互相约束分割边缘的稳定性比单独用分割模型再接检测前处理好不少。从网络结构上看YOLOv11 的 backbone 和 neck 与检测版本基本一致backbone 负责提取多尺度特征neck 做特征融合差异集中在 head 部分。分割 head 会输出两类东西一类是 mask 原型prototype masks相当于一组描述图像底层轮廓模式的模板另一类是每个检测框对应的 mask 系数。最终分割结果由原型和系数相乘后经过 sigmoid 激活得到再根据检测框的位置裁剪出该目标的掩码。这个设计让分割分支没有显著增加计算量这也是为什么 YOLOv11-seg 能在实时场景下做出比较干净的轮廓。1.2 环境与版本选型我在实际项目中建议的软件组合是 Python 3.10 以上、PyTorch 2.1 以上、CUDA 11.8 或 12.1安装 YOLOv11 对应的 ultralytics 库。这个组合我验证过最省心陷入低版本 torch 环境会碰到很多算子兼容问题尤其是分割任务的 mask 解码逻辑依赖较新的操作符报错信息又不太直观排查起来很浪费时间。pip install ultralytics装完可以顺手验证一下版本和 GPU 可用性import torch print(torch.__version__) print(torch.cuda.is_available())如果输出 True说明 PyTorch 的 CUDA 环境是通的。很多人这里会踩坑系统里明明有 NVIDIA 显卡但 torch 装的是 CPU 版本训练速度慢到怀疑人生。装 PyTorch 时务必去对应官网页面选择 GPU 版本命令不要随手 pip install torch 装成默认的 CPU 包。另外不要用 Windows 用户名或项目路径里带中文和空格YOLO 的标签路径拼接在中文目录下偶尔会出莫名其妙的乱码我在两个项目上都吃过这个亏后面会展开讲。2. 自定义分割数据集的准备与制作2.1 图像采集与划分原则准备分割数据集和检测数据集的要求差别很大。检测任务里一个目标只要框进来了、类别对大致位置差不多就能用。分割任务对边缘细节极度敏感边缘标注差两三个像素训练出来的 mask 就会有一圈模糊的“毛边”。所以图像采集阶段就要保证分辨率足够模糊、严重压缩、曝光过度的图像宁可不保留。数量方面我建议每一类至少准备 300 到 500 个目标实例。注意我说的是目标实例不是图像张数。一张图像里可能同时出现 10 个目标那这张图就能贡献 10 个样本。类别少、背景简单的话300 张足够跑通但类别多、物体形态变化大比如要做几十种零件缺陷分类分割那每个类别最好往 800 张努力。太少的话分割分支很容易过拟合模型在训练集上轮廓画得很漂亮一到验证集就露馅。数据划分建议按 8:2 或 9:1 分出训练集和验证集如果有条件再单独留一个测试集。比例只是基础更关键的是按类别分布做分层划分。怎么理解呢假如某个类别在原始数据里只出现在上午的固定光照条件下你不做分层抽样就可能出现验证集里恰好没几个该类样本训练指标虚高但实际不泛化。我习惯写个小脚本统计每张图的类别标签分布然后按类别比例分配到 train 和 val确保每个类别在两边都有足够数量。还要警惕数据泄漏。同一个目标的不同角度照片尽量放在同一个集里不要训练一张、验证一张。尤其是视频抽帧出来的数据集相邻帧画面几乎一样如果不按视频片段分组而是随机打乱验证集会泄漏到训练信息模型看起来 mAP 高得吓人到了真实场景直接拉胯。2.2 标注工具选择与标注注意事项分割任务标注的核心是沿着目标边缘画多边形。标注时我用的是一种开源的多边形标注工具界面简单支持导出标记文件。选工具时重点看三件事支不支持多边形标注、能不能缩放逐点微调、导出格式是否方便转换成 YOLO 分割标签。至于工具本身叫什么名字不重要很多团队甚至用自己的标注平台关键是把标注规范定清楚。标注规范一定要提前写出来发给每个参与标注的人。我遇到最典型的翻车例子是两个人标同一个缺陷类别一个人习惯把凹坑边缘圈得很紧另一个人习惯把周围一圈渐变区域也圈进去结果训练集里同类目标的 mask 形态差异巨大模型怎么调参都学不好。标注规范至少要包含边缘贴紧实际边界还是包含过渡区、遮挡情况下怎么处理被挡住部分、多目标紧挨着时两边的分割线怎么划定。多边形顶点不是越多越好。我见过新手把轮廓描了上百个点每一像素都抠出来看起来精确实际训练时反而引入大量噪声。正常的做法是在轮廓曲率大的地方多打点在直线段尽量少打点一个中等复杂度物体控制在 30 到 80 个顶点以内完全足够。细小物体更要控制顶点数否则归一化之后坐标点之间挤在一起多边形退化成一团乱线。遮挡情况下的标注原则是只标可见部分还是把完整轮廓补齐这取决于你的业务需求。如果是计算可见面积标可见部分如果是目标完整跟踪就靠标注人员的经验补全被遮挡边缘。这个决策必须在项目开始前确定不能标到一半来回换。我个人的建议是优先标可见部分因为补全轮廓会引入主观猜测不同人补出来的差异很大。2.3 标签格式转换与数据集结构整理YOLOv11 分割训练需要的数据集结构如下datasets/ myseg/ images/ train/ val/ labels/ train/ val/ data.yamlimage 目录放图片labels 目录放同名 txt 标签文件。标签文件每一行格式是class_id x1 y1 x2 y2 x3 y3 ...class_id 从 0 开始后面是一串归一化后的多边形顶点坐标。归一化公式很简单x_norm x_pixel / image_width y_norm y_pixel / image_height为什么要归一化因为训练时会做随机缩放、拼接等数据增强如果标签是绝对像素坐标图像一变尺寸标签就全错乱。归一化之后所有坐标都落在 0 到 1 区间不管输入图像缩放到 640 还是 512坐标关系都不变。从标注工具导出到 YOLO 格式通常会经过一次中间格式转换。最常见的中间格式是用 JSON 保存的多边形坐标。我给出一个转换思路你完全可以照这个逻辑写脚本import json import os def convert_to_yolo(json_path, img_width, img_height, out_label_path): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for obj in data[shapes]: cls_id class_name_to_id[obj[label]] points obj[points] # [[x, y], [x, y], ...] norm_points [] for x, y in points: norm_points.append(x / img_width) norm_points.append(y / img_height) # 确保坐标在 [0, 1] 范围内 norm_points [max(0, min(1, p)) for p in norm_points] line str(cls_id) .join(map(str, norm_points)) lines.append(line) with open(out_label_path, w, encodingutf-8) as f: f.write(\n.join(lines))转换之后必须做一次标签体检。我见过有人转换完才发现坐标除错了宽高或者部分图片的宽高信息读取失败导致全部坐标除以了 0。检查方法很简单随机抽出几十个标签把点和原图画在一起肉眼看轮廓是否正确贴合。这个步骤 20 分钟就能做完但能避免后面整个训练阶段白跑。另外YOLOv11 要求图像文件名和标签文件名严格同名只是扩展名不同。比如 image 是frame_001.jpg标签必须是frame_001.txt。目录结构里不要多放无关文件ultralytics 在扫描时会按扩展名过滤但同名文件冲突会带来不必要的麻烦。最后建议把 labels 和 images 放到同一个父目录下data.yaml 里用相对路径描述子目录这样整个数据集目录可以整体迁移换机器训练不用改配置。3. 配置文件修改与训练参数调优3.1 data.yaml 的编写细节分割训练的配置入口是 data.yaml这个文件决定了数据集位置和类别映射。我的一个典型配置如下path: /home/user/datasets/myseg train: images/train val: images/val names: 0: defect 1: fruit需要注意names 里类别的顺序和标注文件里的 class_id 必须严格一致。很多人会把 names 写成字典形式{0: defect, 1: fruit}或者直接写 list[defect, fruit]两种写法都支持但顺序不能错。如果哪次改了标签顺序模型类别和实际含义就错位了训练出来模型输出的类别完全对不上排查起来非常痛苦。path 字段我推荐用绝对路径。相对路径虽然也能用但依赖你执行命令时的当前目录一旦脚本里切换工作目录很容易出现找不到数据集的情况。用绝对路径一劳永逸唯一的问题是换机器时改一处即可。3.2 关键训练超参的选择逻辑参数这块我系统讲一次我自己的调参顺序。默认情况下YOLOv11 分割训练最常用的几个参数是这样配合的我列在表格里方便对照参数默认值我的推荐范围说明imgsz640512 ~ 1024输入分辨率影响显存和精度batch168 ~ 32受显存限制爆显存就减半epochs100100 ~ 300先跑 100 验证流程再加大lr00.010.005 ~ 0.02初始学习率小数据集调低lrf0.010.01最终学习率 lr0 * lrfoptimizerautoauto 或 SGD/AdamW自动选择省心mosaic1.00.5 ~ 1.0拼接增强数据少时调低workers84 ~ 16数据加载线程数按 CPU 核数patience10050 ~ 100早停等待轮数防浪费cacheFalseTrue显存够就开 True提速明显先说 imgsz。YOLOv11 的默认输入是 640x640。你的原始图片分辨率如果远高于 640模型训练时会先缩放到 640目标很小的话缩放后细节丢失严重。这时候把 imgsz 调成 1024 或 1280 往往能明显提升分割精度但显存占用按面积增长640 到 1024 就是约 2.6 倍显存开销。显存不够时我不会硬上通常会先保持 640 跑通流程后面再通过验证集指标判断是否值得提高分辨率。再说 batch。显存不够是最常见的问题。我的经验是 8G 显存跑 yolo11s-seg 用默认 640 分辨率batch 设为 16 多半会爆。爆了不要慌先减半再爆再减减到 4 如果还爆就要把 imgsz 调低到 512。还有一个经验规律batch 改变时学习率也应该跟着变。batch 从 16 翻倍到 32lr0 可以从 0.01 适当提高到 0.012 到 0.015batch 减半lr0 就适当降低。这个不是精确数学关系但符合梯度累计的直觉实测效果比完全不改好。epochs 不要一开始就无脑设 300。我建议第一轮训练设 100 甚至 50目的不是追求最好模型而是验证数据流程通不通、loss 会不会正常下降。等确认一切正常了再启动一轮正式长训。正式长训时开启早停参数 patience验证集指标多轮不涨会自动停掉能省出大量时间。mosaic 是 YOLO 系列很经典的数据增强把四张图拼成一张训练小目标检测效果很好。但分割任务里如果目标轮廓复杂或者数据集本身不大强烈的 mosaic 增强会让模型学到很多拼图边界噪声。我踩过的坑是数据量只有 400 张时用默认 mosaic1.0训练集 loss 降得很快验证集分割边缘却非常碎。后来把 mosaic 降到 0.5 并配合关闭一部分旋转增强轮廓质量立刻提升。3.3 训练命令与断点续训准备好数据和配置后训练命令是yolo segment train modelyolo11n-seg.pt datadata.yaml epochs200 imgsz640 batch16 device0model 参数用官方预训练的 yolo11n-seg.ptn 是 nano 版本还有 s、m、l、x 几个尺度。我的建议是先用 nano 模型跑通流程确认数据集没问题再换 s 或 m 做正式训练。nano 模型训练速度快虽然精度低一些但足够验证数据质量和训练链路。正式训练时我一般用 yolo11s-seg.pt 或 yolo11m-seg.pt具体看显存和精度需求。训练中断了不要慌用断点续训yolo segment train resumeTrue或者在命令里指定yolo segment train modelruns/segment/train/exp/weights/last.pt datadata.yaml epochs200训练结束后权重会存在runs/segment/train/exp/weights/目录下其中best.pt是验证集指标最好的权重last.pt是最后一轮权重。测试部署首选 best.pt。有一点要注意best.pt 和 last.pt 可能差别很大如果训练过程后期过拟合last.pt 的泛化能力明显下降而 best.pt 因为是在验证集上挑出来的相对更可靠。训练时我还习惯加一条cacheTrue把图片缓存到内存里数据读取不再成为瓶颈训练速度能提升不少。如果显存紧张就cachedisk把标签和图像路径缓存到磁盘。要注意 cache 模式会在第一次构建缓存时花一些时间之后复用就很快了。4. 训练过程监控与常见问题排查4.1 训练日志与指标怎么看训练过程中终端会滚动输出一堆指标第一次看可能有点懵。核心指标分成两类一类是损失函数另一类是 mAP。分割训练里你会同时看到 box 分支的 mAP 和 mask 分支的 mAPmask 指标才是分割任务真正需要关注的box 指标只是参考。具体来说box_loss 反映检测框回归的质量seg_loss 反映掩码预测的质量cls_loss 反映分类质量dfl_loss 是检测框分布损失。如果 seg_loss 一直在下降但验证集 mAP 不涨可能是过拟合也可能是验证集标签质量不行。如果 seg_loss 降得很慢检查学习率和数据集标注精度。训练过程会自动保存results.csv文件在权重目录下这是一个很实用的监控文件。我一般用 pandas 直接读取画图不用瞪着眼睛盯终端import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/segment/train/exp/results.csv) plt.plot(df[epoch], df[metrics/mAP50-95(M)], labelmask mAP50-95) plt.plot(df[epoch], df[train/seg_loss], labelseg_loss) plt.legend() plt.show()这里要强调一个判断经验不要只看训练集 loss更不要因为训练 loss 降到很低就高兴。训练 loss 低而验证集 mAP 停滞这是典型的过拟合信号。我在某个数据集上遇到过 100 轮以后 box_loss 还在降mask mAP 却原地踏步这时候加大 epochs 已经没有意义应该回头查数据多样性和标注一致性。4.2 常见报错与解决方案速查我整理了训练分割数据集时最常撞到的几个问题直接做成速查表报错或现象原因解决办法CUDA out of memory显存不足降低 batch降低 imgsz关掉 cacheTrueNo labels found in ...标签路径不对或文件名不匹配检查 labels 目录结构、图片与标签同名label shape 异常或 assert 报错txt 标签格式不对检查 class_id 是否整数、坐标是否归一化NaN loss学习率过高、标签越界、AMP 问题降低 lr0标签体检设 ampFalse中文路径导致读图失败路径编码问题整个项目路径改成英文训练很慢且 CPU 占用低workers 太少或数据解码瓶颈增加 workers开启 cacheTrueCUDA out of memory 是最常见的。除了减 batch 之外还有一个容易被忽略的点训练过程中如果开着其他程序占显存比如浏览器硬件加速也可能引起偶发爆显存。训练前用nvidia-smi看一眼显存占用是基本操作。No labels found 这个问题我见过太多次了。大多数情况是 images 和 labels 路径对不上或者标签扩展名写错。YOLO 标签文件必须是.txt。还有一种情况是数据集中某些图片没有对应标签比如漏标了一张图ultralytics 扫描时会跳过这张图但如果你错误地把空标签文件放了进去反而会报错。我的建议是训练之前写脚本检查每一张图片都有非空标签文件不要让训练过程来替你发现。NaN loss 的处理也要冷静。先检查标签有没有坐标越界比如归一化后某个坐标写成 1.5这会让损失计算发散。如果标签没问题再考虑学习率过高把 lr0 从 0.01 降到 0.005 重试。新版本训练有时在 AMP 混合精度下出现 NaN可以尝试ampFalse关掉混合精度训练速度略降但稳定性提升。4.3 过拟合与欠拟合的实战调整训练到中后期最常纠结的就是指标不涨怎么办。我按现象分三类讲自己的处理思路。第一类训练集 mAP 很高验证集 mAP 明显低。这是过拟合。优先检查数据集本身的问题比如某个类别样本太少或者某类图片背景单一、光照条件单一。数据层面的解决方法是扩充数据多样性做更多的增强组合而不是继续加 epochs。参数层面把 mosaic 增强保持适当强度或者把 lr0 稍微调低让模型学得更保守一些。第二类训练集和验证集 mAP 都低seg_loss 下不去。这是欠拟合或者标注质量问题。先排除标注问题我多次发现同一类目标不同标注者的边缘差异过大时模型很难收敛到一致的轮廓。如果标注没问题就升级模型规格从 nano 换到 small 或 medium同时把训练轮数拉长。还可以把 imgsz 从 640 调到 768 或 1024细节信息更多分割头更容易收敛。第三类mask 边缘粗糙形状支离破碎。这种情况在小目标上尤其常见。根本原因是缩放到固定输入尺寸后小目标只占几十个像素mask 原型分辨率不足以表达精细边缘。处理方案是提高 imgsz 和调整 mosaic不要过分依赖拼接增强导致小目标再被缩小。如果条件允许对包含小目标的图片做切图训练把大图切成小块分别标注也能显著改善边缘质量。类不平衡问题在分割里同样存在。如果某一类目标数量特别少mask mAP 会明显被拖低。我的做法是先统计实例数量对小类别做简单的过采样也就是复制这些样本的标注和图片多跑几轮。这个方法比调复杂损失函数容易控制也不用改框架代码。数据量少的时候不要指望模型凭空学会新类别。5. 模型评估、导出与部署应用5.1 验证集指标解读与 mAP 计算训练结束后用 best.pt 在验证集上重新评估yolo segment val modelruns/segment/train/exp/weights/best.pt datadata.yaml imgsz640输出里你会看到两组 mAP一组 Box一组 Mask。Mask 的 mAP50 和 mAP50-95 是分割任务的核心指标。简单解释一下mAP50 是指在 IoU 阈值为 0.5 时计算的平均精度这个指标宽松主要反映模型“能不能找到目标”mAP50-95 是 IoU 从 0.5 到 0.95 每隔 0.05 取一次阈值再取平均这个指标严格很多对掩码边缘重合度的要求极高。同样一个模型mAP50 可能到 0.85mAP50-95 可能只有 0.55这是正常的不要被这个差距吓到。看验证集指标时我一般先看 mask mAP50-95再看单类别的 AP 差异。如果多数类别都挺好只有一两个类别 AP 特别低优先补充那几类的数据而不是整体调参。另外输出的 PR 曲线和混淆矩阵也不能不看混淆矩阵能直观告诉你哪些类别之间容易互相误判。比如某工业场景里划痕和擦伤在视觉上极其相似混淆矩阵里这两个类别的交叉项肯定会偏高这提示你可能需要合并类别重新考虑了。5.2 模型导出为 ONNX 与 TensorRT 注意事项训练好的 PyTorch 模型要上生产环境通常得导出成标准部署格式。最常用的两个目标是 ONNX 和 TensorRT。基础导出命令yolo export modelbest.pt formatonnx opset12 imgsz640 dynamicFalse导出后可以用 ONNX Runtime 直接推理不依赖 PyTorch 环境。这里有一个实操细节如果部署时需要支持任意输入尺寸要加dynamicTrue但 TensorRT 转换 dynamic 模型会比固定尺寸麻烦很多情况下我们业务输入尺寸本来就是固定的比如摄像头采集后统一缩放到 640x640那就不开 dynamic部署简单稳定。TensorRT 导出需要额外安装对应版本的 tensorrt 库yolo export modelbest.pt formatengine device0engine 模型在 GPU 上的推理速度比 PyTorch 快很多但不同显卡架构生成的 engine 文件不能通用换卡必须重新导出。还有精度问题如果想做 INT8 量化通常需要准备校准数据集量化后 mAP 会掉一些如果你对分割边缘精度要求很高建议先试 FP16。FP16 在绝大多数业务场景都能无损或接近无损地保留精度同时显存占用减半。ONNX 导出后自己写推理脚本时分割模型的输出比检测模型多一部分。你需要解析出三块内容检测框坐标、类别和置信度、掩码系数和原型。然后用矩阵乘法还原出掩码再按检测框裁剪。很多人会在这里困惑为什么 ONNX 输出的张量不如 PyTorch 直接调用那么直观这是因为导出时把完整的后处理逻辑剥离了。你可以用 ultralytics 自带的推理逻辑做基线验证再逐步迁移到 ONNX Runtime 上。5.3 推理脚本与业务集成思路先用 PyTorch 模型做快速验证这是最直接的方式from ultralytics import YOLO model YOLO(best.pt) results model.predict(test.jpg, imgsz640, conf0.25) for r in results: masks r.masks # 分割掩码 boxes r.boxes # 检测框 names r.names r.save(filenameoutput.jpg)拿到 masks 之后每个 mask 是一个二值矩阵可以通过面积过滤去掉太小的噪声区域。如果业务上只需要最大目标就按 mask 面积排序取 Top1。如果需要计算面积占比就把 mask 像素数除以图像总像素数。这里的像素精度换算逻辑要提前和业务方确认好比如工业面积测量还要结合相机标定每个像素对应多少物理尺寸。集成到 Web 服务时我建议把模型推理和业务逻辑分两个线程池。模型推理使用独占的 GPU业务接口只负责接收图片和返回结果。并发请求超过推理能力时用队列做缓冲而不是让每个请求都同步占用显存否则会偶发显存溢出。ONNX Runtime 的 session 在多线程并发下性能不错但要注意把 session 做成单例别每个请求都重新加载模型加载时间可能比推理还长。部署阶段还有一个常被忽略的问题前后处理的尺寸必须和训练时一致。你在训练时用 imgsz640部署时也要把输入图按同样方式缩放到 640包括 letterbox 的填充方式。如果部署时直接 resize 成 640 而不做 letterbox目标的长宽比变了模型推理精度会下降。这个坑我印象太深了某次上线后客户反馈同一个目标在线上测试比线下验证集分数低不少排查了半天才发现是部署脚本用了朴素 resize而不是训练时用的 letterbox 填充。最后分享一点我个人的实际体会。我见过很多分割项目最后效果不好不是模型不行而是标注团队对边缘的标准不统一导致训练集里同一类物体的轮廓形态差异极大。训练前真的值得多花两天把标注规范定细。另一个小技巧是先用 yolo11n-seg 这种最小模型把数据流程跑通确认标签没问题、mAP 能正常出来之后再换成 s 或者 m 模型正式训练这样能省掉大量试错时间。我当初就是忽略了这个步骤第一次用 m 模型开训结果标签路径配置错误白跑了八个多小时。后来再接手新数据集我宁愿花二十分钟先用 nano 模型验证一轮这笔时间花得比什么都值。