☰
YOLOv11实例分割实战:自定义数据集训练与部署全攻略
2026/10/19 3:09:24 网站建设 项目流程

跑了三年目标检测,最近把YOLOv11的实例分割啃完了。这个版本相比之前的YOLO系列,在分割任务的收敛速度和掩码边界质量上都很能打,尤其是对自定义数据集的支持,文档虽然全,但对新手来说还是有不少隐性门槛。我这次拿一个实际的“屋顶分割”项目从头走了一遍,把踩过的坑、验证过的参数、还有几个网上很难搜到的细节整理出来,给正准备入坑的朋友一个参考。

1. 数据工程:比模型训练更决定成败的一步

很多人在COCO预训练模型上跑demo的时候觉得分割效果挺好,但一到自己标数据训练就开始拉胯。我负责任地告诉你,90%的问题都出在数据上,而不是模型上。YOLOv11的分割训练对标注格式、目录结构、类别ID的一致性要求非常严格,任何一环出错,轻则训练崩,重则模型学了个寂寞。

1.1 标注工具选型:LabelMe是首选,但不建议直接用它导出

我试过几款标注工具,最后留下的是LabelMe和Roboflow。LabelMe最大的优势是免费、开源、支持多边形标注,而且标注文件是纯粹的JSON,后期转换灵活。Roboflow虽然在线协作体验好,但免费额度有限,而且导出的格式有时候会附带一些冗余信息。

这里要说一个关键点:不要用LabelMe直接导出YOLO格式,除非你想花一下午排查坐标错乱的问题。LabelMe自带的导出脚本对分割的支持比较粗糙,最好还是走“JSON转YOLO分割txt”这个标准路径。

标注的时候,每张图我建议按这个标准来:

  • 一个对象一个多边形,不要把多个不相连的屋顶区域标成一个多边形。
  • 多边形顶点数量控制在50个以内,太密的顶点看起来精细,但训练时会让模型局部过拟合。
  • 孔洞单独处理:如果屋顶中间有天窗,单独标一个小多边形,同样归为“屋顶”类,模型能学会在预测时挖掉它。
  • 复杂边缘的地物往往标注时间远大于实际收益,多边形近似即可。

1.2 JSON坐标转YOLO分割txt的完整脚本

YOLO分割格式和检测格式最大的不同在于:检测是class_id x_center y_center width height,而分割是class_id x1 y1 x2 y2 ... xn yn,坐标全部归一化到0~1,顶点数量不固定。

这里是我实际在用的转换脚本,兼容LabelMe标注中文文件名、多类别、带孔洞等情况:

import json import os from glob import glob def labelme_to_yolo_seg(json_path, save_dir, class_names): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) # 获取图像宽高,用于坐标归一化 img_w = data['imageWidth'] img_h = data['imageHeight'] # 类别名转ID name_to_id = {name: idx for idx, name in enumerate(class_names)} txt_path = os.path.join(save_dir, os.path.basename(json_path).replace('.json', '.txt')) with open(txt_path, 'w', encoding='utf-8') as f: for shape in data['shapes']: label = shape['label'] if label not in name_to_id: continue # 跳过没在yaml里定义的类别 points = shape['points'] # 多边形顶点坐标 # 归一化 norm_points = [] for x, y in points: norm_x = x / img_w norm_y = y / img_h # 限制在0-1,防止标注时手滑超出边界 norm_x = min(max(norm_x, 0.0), 1.0) norm_y = min(max(norm_y, 0.0), 1.0) norm_points.append(f"{norm_x:.6f} {norm_y:.6f}") line = f"{name_to_id[label]} " + " ".join(norm_points) f.write(line + "\n") print(f"转换完成: {json_path} -> {txt_path}") # 使用示例 class_names = ['roof'] # 注意顺序必须和yaml一致 os.makedirs('labels', exist_ok=True) for json_file in glob('json_files/*.json'): labelme_to_yolo_seg(json_file, 'labels', class_names)

用这个脚本的时候有几个细节值得强调:

  • class_names的顺序必须和后面训练的yaml文件里的names完全一致,否则类别对应的ID会错乱,模型学了半天学了个张冠李戴。
  • 归一化坐标保留6位小数足够了,再长没有实际意义,反而让txt文件变大。
  • 如果单张图同一个类别有多个独立区域,脚本会自然输出多行,每行一个多边形,这是YOLO分割的正常格式,不要合并成分隔符那样写。

1.3 转换之后的必做验证:可视化检查

脚本跑完绝对不要直接开训练。我的规矩是:把txt重新画到原图上,肉眼检查至少三分之一的标签。这一步能挡掉百分之八十的“训练效果奇差”问题。

我常用的验证方法是直接用OpenCV把多边形画回去:

import cv2 import numpy as np def visualize_verify(img_path, txt_path, class_names): img = cv2.imread(img_path) h, w = img.shape[:2] with open(txt_path, 'r') as f: for line in f.readlines(): parts = line.strip().split() cls_id = int(parts[0]) points = np.array(parts[1:], dtype=np.float32).reshape(-1, 2) points[:, 0] *= w points[:, 1] *= h points = points.astype(np.int32) # 画边界和类别 cv2.polylines(img, [points], True, (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (points[0][0], points[0][1]), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) # 保存或显示 cv2.imshow('verify', img) cv2.waitKey(0) # 随机抽查3-5张即可

如果发现某个标注的轮廓偏离物体边缘超过两三个像素,或者坐标明显聚集到图像边缘,马上回到标注阶段修。记住:分割模型对标注质量比检测模型敏感得多,检测只要框大概准就行,分割要求的是像素级一致。

1.4 数据增强策略:第一版训练不要开太多

Ultralytics框架默认带一堆增强:翻转、旋转、缩放、颜色扰动等。但第一版训练我强烈建议只开轻微的翻转和缩放,其他全部关掉。原因很简单:自定义数据集的原始标注是珍贵的“真值”,过强的增强会让模型学到扭曲过的形状,尤其在屋顶分割这种结构相对规整的场景里,旋转90度的屋顶根本不存在,模型学出幻觉反而不好。

我的初始配置是:

fliplr: 0.5 # 水平翻转 scale: 0.3 # 缩放扰动 hsv_h: 0.01 hsv_s: 0.5 hsv_v: 0.4

这些参数在训练代码里动态传入即可,不需要写在数据集yaml里。等第一版模型收敛后,如果发现泛化不足,再逐步加强增强,一次只加一个维度,方便定位问题。

2. Ultralytics YOLOv11分割训练的配置逻辑与启动细节

数据备好了,接下来是训练配置。这一节的内容全部基于我实测过的经验和Ultralytics官方文档的逻辑梳理,按步骤来即可。

2.1 数据集目录结构与自定义yaml

标准目录结构如下:

dataset/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 对应的txt标注 │ └── val/ └── data.yaml

这里有个坑:images和labels目录必须同级,且内部子目录名称要严格对应。如果你把train.txt放在别的位置,或者让labels目录结构跟images不一致,训练时会有一堆unexpected警告,甚至直接跳过所有样本。

自定义的data.yaml长这样:

path: D:/projects/roof_seg/dataset # 数据集绝对路径 train: images/train val: images/val names: 0: roof

写下这个文件时我要重申两个高频翻车点:

  • names索引必须从0开始,这是根植于YOLO系列内部ID映射的硬性要求,不是从1开始。
  • path用绝对路径,不要用相对路径,除非你保证训练命令和数据集目录的相对关系永远不会变。相对路径出过太多次“训练找不到数据集”的诡异问题。

2.2 训练命令与参数初步设定

训练命令非常简单,核心就这一条:

yolo segment train data=roof_seg.yaml model=yolo11n-seg.pt epochs=100 imgsz=640 batch=16 patience=20 project=roof_exp name=run1

各参数我的初始推荐如下:

参数推荐值备注
modelyolo11n-seg.pt从官方预训练权重开始,不要随机初始化
epochs100配合早停,实际会提前停下
imgsz640COCO预训练的标准输入尺寸,不要上来就用1280
batch能塞进GPU的最大值显存不够就调小,但不要低于8
patience20连续20轮验证集mAP不涨就停
device0指定GPU,多卡用0,1,2,3

这里预训练权重的选择值得说道说道。不少人直接用yolo11n-seg.pt训练自定义数据集,发现验证集mAP50很高,但mAP50-95一直上不去,就说模型不行。不是这样的——n版本是nano,参数量极小,适合做快速验证和端侧部署,如果你最终目标是高精度,一开始就应该用s或m版本。我的建议是:

  • 样本量小于500:用yolo11n-seg.pt先跑通流程,然后换s或m做正式训练。
  • 样本量1000以上:直接用yolo11m-seg.pt起步。
  • 显存8GB以下:老老实实n或s,或者降低imgsz到480。

2.3 训练过程中需要盯住的指标

训练日志里会出现box_loss、seg_loss、cls_loss、dfl_loss和mAP这些指标。前几个是损失函数的组成部分,正确趋势是平滑下降并逐步稳定;mAP50和mAP50-95是离最终效果最近的客观指标。

我最关注的是seg_loss,这是分割分支的损失。如果发现box_loss已经收敛、但seg_loss还在剧烈震荡,八成是标注里出现了大幅偏出物体边界的错误多边形,回到1.3节的可视化验证去补救。

另一个容易误判的点是验证集mAP在训练中后期不升反降,但这不一定代表过拟合。要看训练集同期的mAP——如果训练集持续上升而验证集下降,那是真过拟合;如果训练集验证集一起下降,通常是学习率调度进入末期、把模型推开了一个次优解,这种情况可以尝试降低最终学习率或者提前截断epoch重新从较优的checkpoint续训。

3. 训练踩坑记录与参数调优的完整排查链路

这一节我会把几个实际项目中遇到的、极具代表性的坑完整复盘一遍,方便你遇到类似问题时能照葫芦画瓢去排查。

3.1 坑一:模型预测的掩码边界粗糙、锯齿感极强

现象:训练完成后跑验证集,检测框很准,但mask边缘有明显的台阶感,像低分辨率图放大似的。

排查:

第一反应不是调参,而是先看是不是imgsz和标注质量的问题。分割模型的掩码输出质量直接受输入分辨率影响:imgsz=640训练时,mask头输出的特征图分辨率是40x40,每个格子对应16个像素,边缘锯齿自然明显。我用imgsz=1280重新训练了一个对比组,锯齿问题立刻减轻,但显存占用翻倍,训练速度也慢了很多。

处理方案:在显存允许的前提下,把imgsz提升到960或1280。这条改动对分割任务的有效性远比检测任务显著。如果显存实在不够,试试batch=8加梯度累积,把有效batch补回来。我实测下来,1280的输入配合batch=8在12GB显存上可以跑得动yolo11s-seg。

3.2 坑二:训练loss正常下降,但mAP50停留在0.3上不去

现象:loss降得很漂亮,就是mAP死活上不去。

排查:

先检查是不是类别不平衡。我那次项目里“屋顶”类占了98%的样本,“光伏板”类只有2%,模型理所当然地把光伏板全忽略了。YOLO分割的训练函数自带cls损失,但类别极度不平衡时,少数类在损失中的贡献实在有限。

我的解决方式是:对少数类做数据增强和复制策略。找到所有含有“光伏板”的样本,通过水平翻转、小幅旋转和缩放生成扩充副本,加入训练集,让两类数量比缩小到大约5:1。这样处理后mAP从0.3抬到了0.6以上。

另一类常见原因是标签类别标错了。我有个朋友训练的模型把车顶识别为屋顶,排查后发现他的数据集中有一部分“屋顶”标注到了车的轮廓上,Loss自然不讲道理。这种情况只有靠可视化验证才能发现。

3.3 坑三:训练很快收敛,但验证集上完全预测不到物体

现象:训练集上mAP能到0.9,验证集全是漏检。

排查:

第一步,看验证集的标注有没有明显错误,比如多边形顶点超出图像边界、面积过小。第二步,看验证集图片的拍摄时间和地点跟训练集差多远——如果是无人机在不同高度、季节拍的屋顶,模型的泛化能力不够是正常的。解决方案是收集更多覆盖不同光照、角度、地物的数据,比盲目调参有效得多。

这类问题也常见于标注时把高分辨率大图缩成小图来标注,导致多边形坐标在缩放过程中出现精度损失。标注时应该使用原始分辨率,一直到转换脚本里才归一化。

3.4 坑四:训练到一半显存溢出

现象:跑着跑着报CUDA out of memory。

排查:

这个跟参数直接相关。我自己的经验法则:imgsz=640、batch=16、yolo11m-seg在12GB显存以下是极限边缘,保留给系统和数据加载的显存不超过2GB很容易炸。处理路径按顺序尝试:

# 1. 调小batch yolo segment train ... batch=8 # 2. 调小imgsz yolo segment train ... imgsz=544 # 3. 换更小的模型 yolo segment train ... model=yolo11s-seg.pt # 4. 梯度累积(Ultralytics原生支持优化器参数,但这里需要手动在代码里设置)

最省事的顺序是:先降batch,不行再降imgsz,最后才是换模型。换模型等于重开训练,最耗时。

3.5 坑五:自定义数据集只有一个类,训练完mAP50-95虚高

现象:单类数据集训练后mAP50-95有0.8,看起来不错,但实际预测准确率并不高。

排查:

这个现象的本质是单类+高IOU下的分数失真。当你只有一个类时,如果标注足够好、目标足够大,模型确实很容易拿到高mAP。但一旦有误检,mAP50-95照样会因为“有检测结果”而得到偏高的分数。

有用的建议是设置conf=0.6等更高的置信度阈值来过滤,不要直接取默认的0.25,否则会产生大量低置信度误检框,拉到工程里就被用户骂了。

4. 模型评估、导出与部署时的关键检查项

训练完成后,不是直接拿着best.pt就算了。模型评估和部署转换是分割项目里流程感最强的一部分,这一步整理不到位,前面训练得再好也会在落地上翻车。

4.1 评估指标的解读与阈值选择

用官方验证命令:

yolo segment val model=best.pt data=roof_seg.yaml

输出里我最看重几个:

  • mAP50和mAP50-95的差距:差距小说明掩码质量稳定;差距大说明掩码存在大量边缘粗糙、甚至目标定位不准的情况。
  • per-class mAP:这一项最容易被忽视。如果某个类别的AP极低,不要整体下结论说模型不行,而是去看这个类别的样本量、标注质量和形态多样性。

实际部署时,置信度阈值往往不是我拍脑袋定的,而是先在验证集上跑一遍,画出不同阈值下的精确率召回率曲线,找到精确率和召回率的平衡点。这个点落在哪,阈值就取哪。分割任务里建议把IOU阈值设为0.5,太苛刻的IOU阈值会让召回率掉得很惨。

4.2 导出ONNX时要注意的分割分支细节

很多人的目标是部署到服务端或端侧,所以通常跑:

yolo export model=best.pt format=onnx opset=12

但分割模型的ONNX导出比检测要麻烦一点。导出的输出里,前4个是检测框的[x1, y1, x2, y2, objectness, class_score...],后面跟随的是一个(1, mask_dim, 160, 160)的mask张量。解码mask时需要用对应的检测框坐标去裁剪和插值。如果你直接拿输出做二值化,得不到任何有意义的东西。

实际部署中我踩过最深的坑是:opset=12在旧版本ONNX Runtime上解析mask分支的输出尺寸会出错,换到opset=17后问题才消失。所以导出时我统一用:

yolo export model=best.pt format=onnx opset=17 simplify=True

simplify=True会把一些多余的shape操作剪掉,减少部署时的兼容性问题。

4.3 分割部署中的常见问题和我的对策

  • mask边缘毛刺多:部署后适当做一次morphologyEx开运算或者小尺寸高斯模糊,消除单个像素的噪声。不要用大面积腐蚀膨胀——那次我把屋顶角给修没了。
  • 掩码位姿错乱:这是最典型的解码问题。如果发现mask整体偏移或旋转了90度,十有八九是坐标归一化或图像预处理时忘了还原尺寸,检查你的预处理是不是把图片缩放到640x640又没做letterbox的坐标对齐。
  • 单类别场景下的类ID偏移:部署代码里如果硬编码了类别ID为1但训练时类别ID是0,会导致所有掩码都判到“背景”。检查yaml的names索引和部署端class_id映射。我自己就把这个错犯过一次,排查了半小时才发现是0和1的差别。
  • 速度优化:mask分支的输入输出尺寸较大,是推理的主要瓶颈。可以考虑把mask分支裁剪到128x128,结合检测框局部裁剪,能获得将近20%的加速收益。

5. 从数据量到业务落地的实用经验小结

回头复盘这个分割项目,我最想告诉你的一个经验是:先拿30张图走通全流程,再回头发数据量和精调。第一次做分割自定义训练的朋友,一定不要拿几百张标注直接开干,先标注20到30张图,转换、训练、预测、部署跑通一遍,确认每个环节都没有系统性错误。我在第一次做分割时就是图省事,一次性标了200多张,结果因为类别名和yaml顺序不一致,白白训练了一个全天。第二次我学乖了,用30张图从标注到部署花了半天全部跑通,确认无误后才把剩下的标注数据量补进去。

另一个心法是:模型版本的差距远没有你想象的大,数据质量的差距比你想象的大得多。在屋顶分割这个场景里,yolo11s-seg和yolo11m-seg最终mAP只差两个百分点,但把误标注的多边形修正后,mAP直接涨了五六个点。与其纠结换大模型,不如先把标注质量抠到位。

最后给个参数快查表,是按我这次经验做出来的,适用于屋顶分割这种单一语义类别、中等复杂度的场景:

场景显存imgszbatch模型epochs
快速验证8G64016yolo11n-seg50
正式训练12G9608yolo11s-seg150
高精度24G12808yolo11m-seg200

如果你正卡在某个奇怪的分割训练问题上,不妨先按3.2到3.5的排查路数过一遍,多数问题出在数据和配置的交叉点上。有一说一,YOLOv11分割的框架代码已经做得相当省心了,真正费心气的,永远是数据工程本身。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询