SAM2+Label Studio+YOLO11半自动分割标注流水线实战
干标注这行的老哥们应该都有同感画分割掩码是纯纯的体力活一张复杂目标的mask手动描边少说要三五分钟一个几百张的数据集坐下来腰椎间盘都突出了。Label Studio SAM2 YOLO11这组工具链正好能把这套流程压成半自动流水线——SAM2负责快速出掩码Label Studio负责把标注管起来YOLO11负责最终的训练落地。这篇文章就把我实际跑通这条链路的完整过程、踩过的坑、调参心得全部放出来适合手里有视频或图片数据、被分割标注折磨得够呛、又想把标注完的数据喂给YOLO11训练的团队和个人开发者参考。1. 方案选型与整体思路为什么是这三件套1.1 分割标注的痛点和半自动标注的核心逻辑先聊聊需求侧。目标检测的标注已经很成熟了矩形框几个小时就能标几百张但分割掩码完全不是同一个量级。很多项目甲方的验收标准是要拿到实例分割结果比如息肉分割、渔网分割、车辆轮廓提取这类场景你不可能用检测框糊弄过去。手动用多边形或者画笔工具沿着目标边缘抠复杂形状一张图就得花上好几倍的时间。就算你标完了类别的边缘到底揣到哪、凹槽要不要抠纯靠人来对齐容易标歪后续训练出来的模型边界也好不到哪去。半自动标注的核心逻辑其实很简单让大模型先把大约正确的掩码全部生成出来人只做审阅微调把主要是修边、删错区、补漏区的工作量降下来。SAM2在这个链条里承担的就是自动出图的角色。SAM2全称是Segment Anything Model 2Meta在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都依赖PyTorchGPU建议至少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约38MB20-40ms快速出mask、低算力机器sam2_hiera_base_plus.pt约126MB60-100ms精度和速度均衡sam2_hiera_large.pt约224MB150-250ms复杂边界和高精度场景下载权重时注意官方同时在维护SAM2和SAM2.1两个系列的checkpointconfig文件和权重必须配套否则load的时候虽然不报错推理结果却可能完全跑偏。建议下载时把config文件的路径记好比如大模型对应的是sam2_hiera_l.yaml2.0系列或sam2.1_hiera_l.yaml2.1系列。2.2 Label Studio的安装与配置细节Label Studio的部署让我一度很痛苦但归根结底是版本问题。早期用3.x版本结果LSFLabel Studio Frontend子版本跟主版本对不上导致插件加载不出来。这里明确推荐直接用pip安装最新稳定版pip install -U label-studio label-studio start --port 8080首次启动会引导你创建管理员账号项目创建后关键的配置就是标签模板Label Config。SAM2集成或者离线导入预标注时最常用的两种标签类型是BrushLabels和PolygonLabels。BrushLabels是像素级掩码适合直接显示SAM2生成的maskPolygonLabels是多边形方便人工微调边缘。实际标注时我习惯用PolygonLabels作为主要标签因为导出的数据可以直接转成YOLO格式的polygon坐标不用再做像素到多边形的转换。Label Config的一个常用配置模板View Image nameimage value$image zoomtrue zoomControltrue rotateControltrue/ PolygonLabels namelabel toNameimage Label valuetarget background#FF0000/ /PolygonLabels /View如果你要用BrushLabels承载掩码就把它换成BrushLabels namelabel toNameimage。另外说一个不得不提的小坑Label Studio默认使用SQLite存储元数据大批量数据集导入时SQLite性能会骤降。如果你打算标注超过几千张图提前把后端切到PostgreSQL更稳妥。环境变量方式LABEL_STUDIO_DATABASEpostgresql://user:passlocalhost: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.2ultralytics可以自动识别GPU。输入yolo settings命令能看到默认配置如果有问题再手动指定CUDA_VISIBLE_DEVICES。3. 半自动标注核心流程从SAM2到Label Studio的完整落地3.1 视频抽帧与SAM2首帧提示如果标数据是视频第一步是抽帧。用ffmpeg直接按固定帧率抽比如每秒抽取2帧mkdir -p frames ffmpeg -i input.mp4 -vf fps2 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_fileconfigs/sam2.1/sam2.1_hiera_l.yaml, checkpointcheckpoints/sam2.1_hiera_large.pt, devicecuda, )初始化视频状态时SAM2会自动扫描目录下的所有图片并建立索引速度取决于图片数量。之后在某一帧上点击目标做prompt最常见的方式是给一个或多个点坐标inference_state predictor.init_state(video_pathframes) points np.array([[300, 200], [450, 500]], dtypenp.float32) labels np.array([1, 1], dtypenp.int32) frame_idx, object_ids, masks predictor.add_new_points_or_box( inference_stateinference_state, frame_idx0, obj_id1, pointspoints, labelslabels, )这里的labels1表示前景点labels0表示背景点。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 tasksegment modetrain \ modelyolo11s-seg.pt \ datamy_dataset.yaml \ epochs200 \ imgsz1024 \ batch8 \ device0 \ workers4 \ patience30参数这里有几个重点需要解释一下。imgsz保持1024对分割任务很重要。YOLO11默认是640但分割任务的目标细节比较敏感低分辨率会让边缘细节糊掉。我自己对比过1024比640在分割mAP上能高1-2个点但显存占用也翻倍。batch的取值要看显存。12GB显存上imgsz1024时batch8基本是上限了。如果遇到CUDA out of memory先把batch降到4。batch太小会带来梯度噪声大、训练波动明显的问题实测batch4时mAP曲线抖动比batch8要明显。patience30是早停参数30轮没有提升就自动停止避免把时间浪费在无效训练上。如果数据集比较小可以考虑关闭早停或者设大一些。训练过程中的实时指标重点看seg_loss和cls_loss如果seg_loss还在明显下降就不要急着停。另外要注意分割mAP看的是mAP50-95不是mAP50mAP50容易让你产生已经不错了的错觉。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 tasksegment modeval modelruns/segment/train/weights/best.pt datamy_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 OOMbatch或imgsz设置过大调低batch或把imgsz从1024降到768分割结果边缘锯齿严重低分辨率推理或插值方式不对用1024输入对输出mask用形态学闭运算平滑SAM2与Label Studio集成在线插件卡死显存不够或前后端并发loading改用离线生成mask再导入的方式从LS导出YOLO格式坐标全为0使用了LS内置YOLO导出分割支持不完善改用COCO导出自己写脚本转YOLO5.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负责最终把标注转化成能用的模型——三者搭配好一周时间完全可以完成以前一个月才能搞定的分割标注加训练迭代。当然流水线不会永远一帆风顺我上面写的那些坑基本都是自己实际踩出来的照着调整能少走不少弯路。