干标注这行的老哥们应该都有同感:画分割掩码是纯纯的体力活,一张复杂目标的mask手动描边少说要三五分钟,一个几百张的数据集坐下来腰椎间盘都突出了。Label Studio + SAM2 + YOLO11这组工具链,正好能把这套流程压成"半自动流水线"——SAM2负责快速出掩码,Label Studio负责把标注管起来,YOLO11负责最终的训练落地。这篇文章就把我实际跑通这条链路的完整过程、踩过的坑、调参心得全部放出来,适合手里有视频或图片数据、被分割标注折磨得够呛、又想把标注完的数据喂给YOLO11训练的团队和个人开发者参考。
1. 方案选型与整体思路:为什么是这三件套
1.1 分割标注的痛点和半自动标注的核心逻辑
先聊聊需求侧。目标检测的标注已经很成熟了,矩形框几个小时就能标几百张,但分割掩码完全不是同一个量级。很多项目甲方的验收标准是要拿到实例分割结果,比如息肉分割、渔网分割、车辆轮廓提取这类场景,你不可能用检测框糊弄过去。手动用多边形或者画笔工具沿着目标边缘抠,复杂形状一张图就得花上好几倍的时间。就算你标完了,类别的边缘到底揣到哪、凹槽要不要抠,纯靠人来对齐容易标歪,后续训练出来的模型边界也好不到哪去。
半自动标注的核心逻辑其实很简单:让大模型先把"大约正确"的掩码全部生成出来,人只做"审阅+微调",把主要是修边、删错区、补漏区的工作量降下来。SAM2在这个链条里承担的就是"自动出图"的角色。SAM2全称是Segment Anything Model 2,Meta在2024年发布的第二代分割模型,相比第一代SAM最实用的提升是支持了视频分割——你只需要在某一帧上点一下或者框一下目标,它能在后续帧里自动做追踪,掩码跟着目标走。这个能力太适合标注了,因为它天然就是"标注序列帧"的场景。
1.2 工具选型对比:SAM2、Label Studio、YOLO11凭什么胜出
做这套方案之前,我也对比过不少替代品。标注工具这块,最常被拿出来对比的是Labelme和CVAT。Labelme的特点是轻量、单机可用,但做项目管理、多人协作、数据校验都很原始,而且默认的交互方式就是纯手动画多边形,半自动能力几乎为零。CVAT有自动标注的插件体系,也在集成SAM2,但它部署复杂度高,前端界面相对重,对小团队来说学习成本不小。Label Studio胜在开源免费、有清晰的标注管理和导入导出API,而且它支持通过Machine Learning Backend接入外部模型做辅助标注,社区里已有现成的SAM2集成方案,整体生态跟"标注后直接出数据集"这条路径最贴合。
模型侧,SAM2对比第一代SAM,核心优势就是视频上下文记忆能力。第一代SAM处理视频只能一帧一帧手动prompt,每一帧都要重新点,效率不大行。SAM2把视频帧的memory机制带进来了,可以自动传播掩码。而且它支持任意数量的物体ID共同追踪,这在多目标场景下非常实用。
训练侧,YOLO11是Ultralytics在2024年发布的版本,全系列支持检测、实例分割、姿态估计、旋转框、分类五个任务。选它的理由很务实:社区生态好、配置简单、分割头效果够用,而且自己只负责推理和训练的话,根本不需要写一行业务代码。对比Mask R-CNN这套老派路线,YOLO11在工程落地速度和部署便利性上优势巨大。
1.3 两条路线怎么选:在线集成SAM2还是离线生成再导入
实际操作中,半自动标注存在两种路径。一种是"在线集成",把SAM2封装成Label Studio的ML Backend,用户在LS界面点一下图像,SAM2的mask结果直接实时返回到前端。这条路好看、直观,但坑也大:既要维持两个Python环境的依赖兼容,又要处理显存占用,还要面对前端交互延迟问题。我实际试下来,在资源紧张的开发机上经常把LS拖到卡死。
另一种是"离线分割再导入",先用SAM2把整段视频或者整批图片的掩码批量生成出来,存成带有标注信息的结果文件,再通过Label Studio的导入功能把这些结果当成预标注导入项目,人工只需要打开每张图检查并修正。这条路的好处是解耦:SAM2的推理和LS的运行完全不互相干扰,即使SAM2在生成阶段用满了显存也没关系,等它干完活退出了LS再介入。这个方案的稳定性和可控性明显高得多。我在下面分享的也主要是这条离线路线。
2. 环境搭建与依赖准备:SAM2和Label Studio的踩坑重灾区
2.1 SAM2模型环境配置与权重选择
先把机器要求的底子说清楚。SAM2和YOLO11都依赖PyTorch,GPU建议至少8GB显存起步,实测量级通常是resize到1024x1024或更低分辨率跑,8GB勉强能跑HieraLarge模型做推理,用Tiny模型就非常流畅。我自己用的是RTX 4070 12GB,跑SAM2的大模型没有问题。
安装环节,网上教程一抓一大把,但版本不匹配的问题十有八九会遇到。我踩过的坑主要集中在:SAM2的官方仓库版本更新很快,老版本代码跟新版本checkpoint的config对不上。这里给一个我验证过的稳定组合:
git clone https://github.com/facebookresearch/sam2.git cd sam2 pip install -e . pip list | grep torchPyTorch我用的是2.2.2 + CUDA 12.1的组合,实测没有出现算子编译报错。如果你的机器是CUDA 11.8环境,建议用PyTorch 2.1.0 + torchvision 0.16.0,再对应安装别的依赖。SAM2在编译C++算子时对GCC版本有要求,如果遇到报错提示找不到g++,先sudo apt install build-essential装好编译链。
权重文件的选择直接影响速度和效果,建议按需取用:
| 权重文件 | 模型大小 | 推理耗时(单张1024图,12GB卡) | 适用场景 |
|---|---|---|---|
| sam2_hiera_tiny.pt | 约38MB | 20-40ms | 快速出mask、低算力机器 |
| sam2_hiera_base_plus.pt | 约126MB | 60-100ms | 精度和速度均衡 |
| sam2_hiera_large.pt | 约224MB | 150-250ms | 复杂边界和高精度场景 |
下载权重时注意,官方同时在维护SAM2和SAM2.1两个系列的checkpoint,config文件和权重必须配套,否则load的时候虽然不报错,推理结果却可能完全跑偏。建议下载时把config文件的路径记好,比如大模型对应的是sam2_hiera_l.yaml(2.0系列)或sam2.1_hiera_l.yaml(2.1系列)。
2.2 Label Studio的安装与配置细节
Label Studio的部署让我一度很痛苦,但归根结底是版本问题。早期用3.x版本,结果LSF(Label Studio Frontend)子版本跟主版本对不上,导致插件加载不出来。这里明确推荐直接用pip安装最新稳定版:
pip install -U label-studio label-studio start --port 8080首次启动会引导你创建管理员账号,项目创建后关键的配置就是标签模板Label Config。SAM2集成或者离线导入预标注时,最常用的两种标签类型是BrushLabels和PolygonLabels。BrushLabels是像素级掩码,适合直接显示SAM2生成的mask;PolygonLabels是多边形,方便人工微调边缘。实际标注时我习惯用PolygonLabels作为主要标签,因为导出的数据可以直接转成YOLO格式的polygon坐标,不用再做像素到多边形的转换。
Label Config的一个常用配置模板:
<View> <Image name="image" value="$image" zoom="true" zoomControl="true" rotateControl="true"/> <PolygonLabels name="label" toName="image"> <Label value="target" background="#FF0000"/> </PolygonLabels> </View>如果你要用BrushLabels承载掩码,就把它换成<BrushLabels name="label" toName="image">。
另外说一个不得不提的小坑:Label Studio默认使用SQLite存储元数据,大批量数据集导入时,SQLite性能会骤降。如果你打算标注超过几千张图,提前把后端切到PostgreSQL更稳妥。环境变量方式:LABEL_STUDIO_DATABASE=postgresql://user:pass@localhost:5432/labelstudio。
2.3 YOLO11环境准备:别在版本上翻车
YOLO11的安装相比SAM2和LS要顺利得多,一条pip install ultralytics就完事。但有个之前容易踩的坑:ultralytics库对Python版本有要求,Python 3.10/3.11都没问题,3.8以下的新功能可能会报错。YOLO11官方要求Python 3.9+,建议直接用3.10或3.11。
显卡驱动和CUDA要跟PyTorch版本对应,我用的是CUDA 12.1 + PyTorch 2.2.2,ultralytics可以自动识别GPU。输入yolo settings命令能看到默认配置,如果有问题再手动指定CUDA_VISIBLE_DEVICES。
3. 半自动标注核心流程:从SAM2到Label Studio的完整落地
3.1 视频抽帧与SAM2首帧提示
如果标数据是视频,第一步是抽帧。用ffmpeg直接按固定帧率抽,比如每秒抽取2帧:
mkdir -p frames ffmpeg -i input.mp4 -vf "fps=2" frames/frame_%05d.jpg抽完帧,把图片按顺序放入一个干净的目录,SAM2的视频预测器要求目录中的图片名为顺序的数字,让它能按序号读取。启动预测器的核心代码如下:
from sam2.build_sam import build_sam2_video_predictor from sam2.sam2_video_predictor import SAM2VideoPredictor predictor = build_sam2_video_predictor( config_file="configs/sam2.1/sam2.1_hiera_l.yaml", checkpoint="checkpoints/sam2.1_hiera_large.pt", device="cuda", )初始化视频状态时,SAM2会自动扫描目录下的所有图片并建立索引,速度取决于图片数量。之后在某一帧上点击目标做prompt,最常见的方式是给一个或多个点坐标:
inference_state = predictor.init_state(video_path="frames") points = np.array([[300, 200], [450, 500]], dtype=np.float32) labels = np.array([1, 1], dtype=np.int32) frame_idx, object_ids, masks = predictor.add_new_points_or_box( inference_state=inference_state, frame_idx=0, obj_id=1, points=points, labels=labels, )这里的labels=1表示前景点,labels=0表示背景点。SAM2支持正负提示点一起给出,对区分相近物体特别有效。我第一次用的时候就遇到一个问题:两个目标挨得太近,单点提示出来的掩码会包含旁边的东西。后来同一帧上补了一个背景点,mask立刻干净了。所以提示点不是越多越好,关键是"目标内外各给一两个"。
3.2 掩码自动传播与批量导出
首帧提示做完,剩下的工作就交给SAM2的视频传播:
for frame_idx, object_ids, out_masks in predictor.propagate_in_video(inference_state): mask = out_masks[0] > 0.0 # mask shape 是 (height, width),这里是256x256,需要resize回原图尺寸这里有个细节需要特别注意:SAM2输出的mask是低分辨率的,比如256x256,需要还原到原始图像尺寸才能用于标注。用OpenCV的resize插值就够,但不要用最近邻插值,否则边缘会明显锯齿化。我习惯用cv2.INTER_CUBIC,对边缘质量提升明显。
保存掩码的时候别忘了归一化。最常用的中间格式是COCO RLE,或者直接存成PNG二值图,一张图一个obj。假如我有三个object,就保存三张单通道mask,再用一个简单的解析脚本合成多类mask图。比如每个物体一个类别ID,第n类用n-1的值填充,最后输出一张多值PNG图。
到了这一步,你可以得到整段视频所有帧的"伪标注"掩码。但需要注意,SAM2的传播在帧与帧之间如果出现目标被遮挡、快速运动、剧烈形变,很有可能会跑丢。跑丢的帧并不多,一般占比不会太高,但这正是后面交给Label Studio精修的原因。
3.3 把掩码导入Label Studio做人工精修
伪标注数据导进LS之前,先要把mask转换成一个LS能识别的格式。最通用的做法是用label-studio-converter把COCO格式转成LS原生JSON任务文件,再调用LS的API直接创建项目并导入。
流程分三步:
第一步,把SAM2的PNG mask格式整理成COCO格式。COCO JSON要求每个图片有id、width、height、file_name,每个标注有id、image_id、category_id、segmentation(用多边形或RLE表达)、bbox、area。这一步可以写一个简短的Python脚本:遍历图片,读取PNG mask,用OpenCV的cv2.findContours()提取轮廓点,然后输出到COCO JSON。注意,如果轮廓点太多,先做cv2.approxPolyDP()简化,否则LS前端加载起来会很卡。
第二步,把COCO JSON转成LS的label格式任务。LS导入COCO时可以直接选择任务类型为"COCO",它会自动生成配置文件。但更稳定的做法是用LS的API创建项目并设置标签配置,然后把COCO JSON通过project/import接口导入。
第三步,也是最重要的,人工精修。打开LS的标注界面,你会看到每个物体已经被预标注好了一个多边形或者mask区域,你只需要针对SAM2跑丢的帧做操作:删掉错误区域、手动微调边界、补充漏掉的物体。整个修正过程通常比从零画要快得多,因为大部分帧是准确的,你只是改少量硬样本。
3.4 导出训练数据:从Label Studio到YOLO格式的关键转换
LS标注完成后,可以通过UI或者API导出多种格式。训YOLO11的实例分割模型,建议选择COCO JSON格式导出,然后转成YOLO的分割格式。为什么不用LS直接导出YOLO?因为LS内置的YOLO导出对分割支持不稳定,导出后坐标精度会打折,而且类别ID经常对不上。
YOLO11的分割标注格式是每行一条记录:class_id x1 y1 x2 y2 ... xn yn,坐标是相对于图像宽度和高度的归一化小数。转换成这个格式时,需要把COCO JSON里的多边形坐标取出来,对所有点做归一化:x / image_width和y / image_height。
转换脚本简要骨架:
import json import numpy as np with open("coco.json") as f: coco = json.load(f) img_id_to_size = {img["id"]: (img["width"], img["height"]) for img in coco["images"]} out_lines = [] for ann in coco["annotations"]: w, h = img_id_to_size[ann["image_id"]] cat_id = ann["category_id"] - 1 # 从0开始 seg = ann["segmentation"] if isinstance(seg, dict): # RLE转polygon,可以用pycocotools continue # 多边形坐标 [x1, y1, x2, y2, ...] poly = seg[0] normalized = [] for i in range(0, len(poly), 2): normalized.append(poly[i] / w) normalized.append(poly[i + 1] / h) out_lines.append(f"{cat_id} " + " ".join(f"{v:.6f}" for v in normalized))导出时注意,如果polygon的点数太大(比如超过1000个),YOLO训练时内存占用会飙升,建议在导出前做一次轮廓简化。
4. YOLO11训练:从标注数据到走通分割模型
4.1 数据集目录结构与YAML配置
YOLO11的分割数据集目录结构有严格要求,没有按这个结构组织根本训不起来。标准的组织方式:
datasets/MySegDataset/ ├── images/ │ ├── train/ # 训练集图片 │ └── val/ # 验证集图片 └── labels/ ├── train/ # 对应的txt标注 └── val/ # 对应的txt标注注意,train图片文件夹里的每张图,文件名必须与labels/train里对应的txt文件保持一致,后缀不同没关系。我自己犯过一个错误:把图片都放在images根目录下训练,结果YOLO直接报找不到标签。目录结构搞定后,接下来是yaml数据配置文件:
path: /path/to/MySegDataset train: images/train val: images/val names: 0: target注意names的索引是从0开始的,且顺序必须和标注文件里的class_id一致。我这边项目只有一个类别,所以只定义了一个。
4.2 训练参数设置与实时监控
YOLO11提供了几种不同大小的预训练分割模型:yolo11n-seg.pt、yolo11s-seg.pt、yolo11m-seg.pt、yolo11l-seg.pt、yolo11x-seg.pt。从ImageNet预训练模型开始而不是从零训,收敛速度会快很多。
训练命令直接一行搞定:
yolo task=segment mode=train \ model=yolo11s-seg.pt \ data=my_dataset.yaml \ epochs=200 \ imgsz=1024 \ batch=8 \ device=0 \ workers=4 \ patience=30参数这里有几个重点需要解释一下。
imgsz保持1024对分割任务很重要。YOLO11默认是640,但分割任务的目标细节比较敏感,低分辨率会让边缘细节糊掉。我自己对比过,1024比640在分割mAP上能高1-2个点,但显存占用也翻倍。
batch的取值要看显存。12GB显存上imgsz=1024时batch=8基本是上限了。如果遇到CUDA out of memory,先把batch降到4。batch太小会带来梯度噪声大、训练波动明显的问题,实测batch=4时mAP曲线抖动比batch=8要明显。
patience=30是早停参数,30轮没有提升就自动停止,避免把时间浪费在无效训练上。如果数据集比较小,可以考虑关闭早停或者设大一些。
训练过程中的实时指标重点看seg_loss和cls_loss,如果seg_loss还在明显下降就不要急着停。另外要注意,分割mAP看的是mAP50-95,不是mAP50,mAP50容易让你产生"已经不错了"的错觉。
4.3 训练中的网络结构微调技巧:加P2检测头
很多项目里目标尺寸较小,比如息肉分割数据集、渔网分割这类场景,小目标在分割任务中容易被忽略。YOLO11默认的检测层是从P3开始,即下采样8倍后的特征层,对于小目标来说信息损失已经不少。这时候可以在网络结构层面加一个P2检测头,即下采样4倍的更高分辨率特征层。
加P2头需要先复制一份yaml配置文件修改。拿yolo11s-seg.yaml做基础,把backbone输出的P2层加入neck,同时修改head部分。在yolo11s-seg.yaml中主干网络里的P2参数从False改成True,然后head部分会增加对应的输出。实测这个方法在目标尺寸小于32x32像素的数据集上确实能提升召回,但代价是显存占用增加、训练速度变慢。小目标不多时我反而建议不上P2,否则过拟合风险会增加。
4.4 模型评估与推理验证
训练结束后,在runs/segment/train/weights/下会生成best.pt和last.pt。先用验证集评估一下:
yolo task=segment mode=val model=runs/segment/train/weights/best.pt data=my_dataset.yaml评估完要注意看all类的mAP指标,而不是单看一个类别。然后挑几张完全没有参与训练的图片做推理检查,看预测的mask是否贴合目标边缘、有没有把背景画进去。这一步非常必要,很多模型训练指标不错,但实际推理时在边缘细节上一塌糊涂。
5. 常见问题与排查技巧实录
5.1 实操中遇到的典型问题速查表
把这套链路从头到尾走过的坑整理成一个速查表,以后遇到问题直接照方抓药:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| SAM2初始化视频时内存暴涨 | 视频帧目录里图片太多且尺寸过大 | 先抽帧压缩到1280宽度内,再传给SAM2 |
| 传播过程中掩码大面积跑丢 | 首帧提示点给得太少或目标太小 | 增加提示点,必要时用box提示,并在目标附近补背景点 |
| Label Studio导入COCO后没有标注显示 | COCO JSON中category_id和配置的label不一致 | 检查COCO category_id从1开始,LS的标签value要对应 |
| YOLO训练报错找不到label文件 | 目录结构不规范或文件名不匹配 | 严格检查images/labels下的train/val子目录和文件名 |
| 训练时CUDA OOM | batch或imgsz设置过大 | 调低batch或把imgsz从1024降到768 |
| 分割结果边缘锯齿严重 | 低分辨率推理或插值方式不对 | 用1024输入,对输出mask用形态学闭运算平滑 |
| SAM2与Label Studio集成在线插件卡死 | 显存不够或前后端并发loading | 改用离线生成mask再导入的方式 |
| 从LS导出YOLO格式坐标全为0 | 使用了LS内置YOLO导出,分割支持不完善 | 改用COCO导出,自己写脚本转YOLO |
5.2 标注质量的三个关键细节
半自动标注省力归省力,质量把控一定要做到位,不然模型训出来就是灾难。
第一,边界检查。SAM2在部分高纹理目标上会把纹理中的孔洞识别为背景,这会形成一些不连续的碎块mask。人工精修时可以用LS的"填充工具"直接填充碎块区域,不需要逐点polygon编辑。实测下来,针对这类问题,LS的多边形编辑效率比基于像素的mask编辑效率低,所以配置标签时BrushLabels和PolygonLabels都预留是最好的。
第二,类别不平衡。如果你的项目有多个类别,SAM2提示点一次只能对一个obj_id生效,我建议一次只标一个类别,跑完该类别的传播后再另起obj_id标下一个。否则object id混乱会导致LS导入时类别ID对不上,后续转换YOLO格式也会造成串类。
第三,训练集与验证集的划分。LS导出COCO时,默认是全体数据一起导出的。我一般习惯导出后在转YOLO格式时用Python按比例随机切分train/val,而不是在LS里费劲地做分组。切分时保证每个类别都有样本进入验证集,尤其类别少的更要注意。
5.3 流程优化的两个小技巧
如果项目时间紧张,这里有两个亲测有效的提速技巧。
一个是先跑小模型。在SAM2出掩码阶段,先用sam2_hiera_tiny跑一遍全部帧,把明显的目标位置、大致轮廓都标出来,然后跳着抽帧只精修关键帧,再让YOLO11在关键帧标注上先训一版粗模型。有了粗模型后,可以用粗模型对剩余帧做检测,把检测框再塞回SAM2做提示,循环迭代,这就是"模型辅助标注"的进阶玩法了。
另一个是善用LS的API做批量操作。比如批量确认某个任务已标注、批量修改标签、批量导出局部数据,都直接调用API脚本执行,比在网页里点点点效率高一个量级。这个技能在后续持续更新数据集时也会反复用到。
整套流程走下来,我个人的体会是:半自动标注省下的不只是画掩码的时间,更重要的是把人的精力从机械重复里解放出来,放到那些真正需要判断力的帧上去。SAM2负责快速出图,LS负责人工修正和数据流转,YOLO11负责最终把标注转化成能用的模型——三者搭配好,一周时间完全可以完成以前一个月才能搞定的分割标注加训练迭代。当然,流水线不会永远一帆风顺,我上面写的那些坑基本都是自己实际踩出来的,照着调整,能少走不少弯路。