简介这是一份结合SAM与YOLOv8实现开集实例分割与目标检测的实战项目面向计算机视觉研究人员、算法工程师及希望深入理解分割检测原理的学习者。项目覆盖数据准备、模型微调、推理与评估完整流程核心在于借助SAM的提示分割能力和YOLOv8的高效实时检测优势解决未见过类别目标的像素级识别问题对提升模型泛化能力具有参考价值。压缩包内共6个文件包括3个Python脚本、2个Jupyter Notebook示例和1个流程说明文档脚本涵盖多目标检测与掩膜可视化等实现Notebook则提供从基础到单/多目标的逐步演示配合教程可快速上手复现。包体约1018KB轻量易部署。该资源已有948人学习下载内容组织清晰既适合系统学习算法组合思路也可作为实际项目的可扩展基础。1. 开集实例分割的实际痛点SAM 加 YOLOv8 到底解决什么做实例分割项目的同学大概率都遇到过同一个卡点你辛辛苦苦标了几百张图、训出一个分割模型客户现场突然指着一种训练集里从没出现过的新目标说“这个我也要框出来”。重新标注、重新训练少则一周多则半个月项目节奏直接被打乱。把 SAM 和 YOLOv8 拼到一起本质上就是为了应对这种“类别表永远跟不上现场需求”的局面目标检测阶段仍然用 YOLOv8 那种又快又稳的框定位分割阶段交给 SAM 去做泛化能力极强的前景轮廓提取。两者一前一后组合就形成了一条实用的开集实例分割 目标检测管线。这条路线的价值不在于打榜指标而在于交付速度。今天这篇文章我会把这条组合链路的原理、数据流、可复现代码和部署时最容易翻车的几个细节一次说清让新手能照着搭让老手能直接补上自己踩过的那几个坑。2. 为什么说这是个“检测 分割”联合框架开集能力的核心原理2.1 开集与闭集的差距不是检测头里多写几个类名传统 YOLOv8 做目标检测最后一个检测头输出的是一张固定长度的类别概率表比如 COCO 的 80 类。训练时这些类别被锁死推理时模型只能在“这 80 类里挑一个最可能的”给你。这种做法的本质是闭集假设所有会出现在场景里的类别训练集里都已经见过。但实际交付中这种假设十次有八次不成立。我见过最典型的场景是做鸟类目标检测的数据集客户今天说只需要麻雀、喜鹊下周又拿了一批新鸟种的照片过来说“这个也要识别”如果走重新训练流程光是标注和调参就能把项目拖垮。所谓开集并不是让模型凭空认识一个它从未见过的类别而是让“分割能力”不会因为类别表的变化而失效。当我们把类别的判定和实例的像素分离后开集的问题就变成了两个独立的小问题检测器负责回答“这里有没有一个前景物体、它的框在哪”分割器负责回答“这个框里哪些像素属于这个物体”。SAM 恰好是一个不挑类别的分割器给它一个框提示它能把框内主体的大致轮廓抠出来它根本不关心这个物体属于哪个物种。所以在复现标题里这套方案时我一般会先把工程上的预期管理做好严格意义上的开集指的是把检测器也换成具备开放词汇能力的目标检测器比如 YOLO-World 或 Grounding DINO如果只沿用原生 YOLOv8我们可以用“前景区分”的思路把所有类别统一当作前景再把框交给 SAM。这个细节很多人容易理解偏后文第 5 章我会专门讲它的坑。2.2 YOLOv8 与 SAM 的分工一个定框一个抠图SAM 的提示方式有四种点提示、框提示、掩码提示和文本提示。但标准开源的 SAM 权重文本提示实际没法直接用真正稳定、落地时大家最爱用的是框提示。这正好和 YOLOv8 的输出对上YOLOv8 的检测头输出 xyxy 的边界框我们直接把这些框扔给 SAM 作为 promptSAM 就会只在这个框范围内做前景/背景分割输出一个二值掩码。这个“检测 分割”联合框架示意图在论文和开源项目里见过很多回画出来很清爽输入图像先进 YOLOv8 检测头得到若干个检测框再把原图连同检测框一起送进 SAM 的 Image Encoder 和 Mask Decoder最后把 SAM 输出的掩码叠回原图。实际工程中SAM 的 Image Encoder 只需要对整张图跑一次得到图像嵌入后每个框只是一个轻量的 Mask Decoder 查询所以整体推理开销并不是简单的“单张图跑 N 次 SAM”这一点也是它能上生产的前提。为什么不直接用 YOLOv8 自带的分割分支去做全套因为 YOLOv8-seg 的分割头是基于 COCO 训练数据收敛的它的掩码质量对未见过的类别偏差很明显而 SAM 在 SA-1B 这类大规模分割语料上见过极大量的物体形态即使是一个没有语义标签的新类别它也能根据边界纹理把轮廓补出来。所以这里更合理的分工是检测器保证速度、负责定位SAM 负责质量、负责抠图谁也别越界。2.3 选型权衡yolov8n-sam-vit-b 是复现性价比最高的组合选型这件事直接决定后续能不能顺利部署。先看 YOLOv8 这一侧它的网络结构图我已经背得很熟主干 C2f 模块负责特征提取PAN-FPN 负责多尺度融合检测头负责回归框和类别。这里面最关键的实践是不要贪大不是越大的模型就越好。做开集项目时检测器只是上游的“框来源”真正拖慢速度的往往是 SAM所以检测器选 yolov8n 或 yolov8s 通常就够用了。GTX 1660 Ti 跑 YOLOv8n 可以做到实时而给 SAM 的 ViT-B 做 Image Encoder 编码一张 1024 分辨率图像大约要一两秒整个管线的瓶颈其实在 SAM 那侧。SAM 这一侧官方开源了 vit_b、vit_l、vit_h 三档。标题这类工程复现我会优先选 vit_b它体积小、内存占用低掩码质量比起 vit_h 虽然有差距但在大多数业务场景里已经足够。如果你后续要部署到 rk3588 这类边缘设备ViT-B 的算子转换和量化也要比 ViT-L 容易得多。真到了追求极致边界精度的阶段再考虑用 MobileSAM 或 TinySAM 之类的轻量替代但这是后话。综合下来除非客户有明确的高精度硬指标否则我的默认配置就是YOLOv8s 负责检测框SAM-ViT-B 负责掩码整套管线在一张 GTX 1660 Ti 上大概能做到单张图 2~4 秒基本满足准实时的业务交付。3. 任务流程设计从目标检测框到 SAM 提示的完整数据链路3.1 常见流程的成败点letterbox 坐标还原和原图分辨率整套流程看起来简单YOLOv8 出框SAM 出掩码。但真正把代码跑通后你会发现框和掩码经常是错位的。这个问题的根源几乎都出在坐标空间不统一上。YOLOv8 推理时默认会对输入图像做 letterbox 预处理把长边缩放到固定尺寸并加灰边而 SAM 的set_image内部也会把输入图像按长边 1024 重新缩放。两边各自做了一次缩放如果你把 YOLOv8 输出的框直接塞给 SAM掩码偏出目标物体会是必然结果。正确的数据链路应该这样设计先用原图画检测框、再做 SAM 提示、最后在原始图像分辨率空间里输出掩码。实际操作时我一般会让 YOLOv8 先对原图做推理拿到的是经过它内部 padding 后坐标空间的框这一步必须先把 letterbox 的 pad 和 scale 还原把框映射回原始图像坐标SAM 那边则直接把原始图像交给set_image让 SAM 用自己内部的自动缩放来处理我们只管把已经还原好的原始坐标框传进去。这样两个模型各处理各的缩放交界处只传递“原图坐标”就不会出坐标偏差。如果项目里特意要提升小目标分割质量也可以把 YOLOv8 检测框稍微向外扩 10~20 个像素作为 SAM 的输入框给 SAM 多留一些边缘上下文。这个技巧对鸟类目标检测的数据集这类小目标场景特别有效后面第 6 章会展开。3.2 图像预处理代码把检测框对齐到 SAM 的输入空间这里给出一段我在本地复现流程时经常用来做坐标对齐的代码。它解决的是 YOLOv8 的 letterbox 输出与原始图像之间的映射关系import cv2 import numpy as np def letterbox_to_original(boxes, scale, pad, original_shape): 把 YOLOv8 内部 letterbox 坐标还原回原始图像坐标。 参数: boxes: YOLO 输出的 xyxy 数组, shape 为 (N, 4) scale: letterbox 缩放比例, 从 YOLO 预处理结果中拿到 pad: (left, top) 灰边偏移量 original_shape: 原始图像 (H, W) 返回: 还原到原始图像坐标系的 xyxy 数组 boxes boxes.copy() # 先减去 pad, 再除以 scale boxes[:, [0, 2]] (boxes[:, [0, 2]] - pad[0]) / scale boxes[:, [1, 3]] (boxes[:, [1, 3]] - pad[1]) / scale # 防止浮点误差把框推出去 boxes[:, [0, 2]] np.clip(boxes[:, [0, 2]], 0, original_shape[1]) boxes[:, [1, 3]] np.clip(boxes[:, [1, 3]], 0, original_shape[0]) return boxes.astype(np.float32)这段代码的逻辑很直白YOLO 在做 letterbox 时先按比例缩放图像到目标尺寸再把剩余区域用灰边填充。因此它的框坐标是“缩放后图像”的坐标要还原回去就必须先减掉灰边偏移量、再除回缩放比例。后面两个np.clip是保险操作因为坐标落在边界外时 SAM 的框提示会产生异常 mask。拿到 scale 和 pad 的方式常见做法是在调用YOLO.predict时打开verboseTrue从日志里读更稳妥的是直接对输入图调用letterbox函数手动预处理然后传给YOLO并关闭自动预处理。我一般倾向后者因为能明确定义坐标空间少猜一次。3.3 预处理统一是“玄学”其实是 RGB/BGR 和归一化没对齐很多新手在跑通这个流程后发现 SAM 分割出来的掩码虽然位置对了但边缘像被狗啃过或者某些目标整个丢掉。这种问题十有八九不是模型能力不行而是图像颜色空间不统一。YOLOv8 的predict方法默认读入 BGR 图像内部也按 BGR 处理而 SAM 的 Image Encoder 期望的是 RGB 输入。如果你用 OpenCV 读图后直接传给 SAM通道顺序反了分割质量会肉眼可见地下降。所以我在流程里会固定一个标准所有图像统一用 OpenCV 读取转成 RGB 后再给 SAMYOLO 那边为了保持稳定也主动传 RGB 数组并且把device、half这些参数固定下来。此外EXIF 旋转也是容易被忽略的坑。手机拍出来的照片可能带方向信息OpenCV 读图时不会自动旋转而 YOLO 内部会做某些处理两边不一致时框和掩码的整体位置就会偏。保险的做法是在进入管线前先用cv2.getRotationMatrix2D根据 EXIF 把图像摆正再做后续一切操作。我的习惯是封装一个统一入口函数输入一个图像路径输出被摆正的、RGB 顺序的、且带有原图尺寸信息的字典。这样后续无论调 YOLO 还是 SAM拿到的都是同一个“标准视图”两边的坐标空间也只用维护一份映射关系。把预处理做到这一步后面部署到 rk3588 上做推理需要排查的点才不会被坐标问题干扰。4. 核心代码用 Python 把 YOLOv8 的检测框转成 SAM 提示并输出掩码4.1 OpenSetSegmenter 类的最小可运行实现把 YOLOv8 和 SAM 串起来最朴素的方式是写一个类内部持有两个模型实例。下面这段代码是我在本地复现流程时常用到的最小骨架去掉了所有项目专属逻辑只保留核心链路import cv2 import numpy as np from ultralytics import YOLO from segment_anything import sam_model_registry, SamPredictor class OpenSetSegmenter: def __init__(self, yolo_weights, sam_checkpoint, devicecuda): self.device device self.yolo YOLO(yolo_weights) sam sam_model_registry[vit_b](checkpointsam_checkpoint) sam.to(device) sam.eval() self.predictor SamPredictor(sam) def detect_boxes(self, image_rgb, conf0.3, iou0.7, max_det100): # image_rgb 已确保是 RGB 顺序 results self.yolo.predict( sourceimage_rgb, confconf, iouiou, max_detmax_det, classesNone, # 不过滤类别, 把所有前景都当作候选 agnostic_nmsTrue, # 不同类别之间统一做 NMS verboseFalse ) boxes results[0].boxes.xyxy.cpu().numpy() return boxes def segment_boxes(self, image_rgb, boxes): # 注意: set_image只需要调用一次, 整张图的embedding会被缓存 self.predictor.set_image(image_rgb) all_results [] for box in boxes: masks, scores, _ self.predictor.predict( boxbox, multimask_outputTrue, # 返回 3 个候选掩码 ) best int(np.argmax(scores)) all_results.append({ box: box.astype(int), mask: masks[best], score: float(scores[best]), }) return all_results代码的逻辑说明如下detect_boxes负责拿到原始坐标的边界框segment_boxes负责把每个框送给 SAM 并取回当前框的最佳掩码。这里有一个值得重视的参数agnostic_nmsTrue。开集场景下我们不关心物体属于哪一类类别编号已经没有意义如果关闭类别无关 NMS同一个物体会因为类别概率抖动被留下多个重复框SAM 会莫名其妙地分割出同一个物体的多个变体。设置成 True 后所有框统一按空间重叠度去重整个输出的稳定性明显提升。multimask_outputTrue是 SAM 的一个很聪明的机制。它会对同一个 box 生成三个不同细化程度的候选掩码scores代表 SAM 对每个候选的置信度。取分数最高的那个通常最稳但如果你想抠出更精细的边缘也可以手动从三个里挑一个 IoU 更大的这个后处理项会在 4.2 节展开。4.2 参数怎么调conf、iou、multimask_output、max_det 的取值参考新手最容易犯的错是把 YOLO 当开集检测器用时直接把conf调到很低结果一张图出来几百个框。下表是我通常采用的参考值能在框召回和效率之间取一个平衡参数建议值说明conf0.25 ~ 0.35开集场景不要低于 0.2低于 0.2 垃圾框会大幅增加iou0.7用于 NMS 去重目标密集时可降到 0.65agnostic_nmsTrue开集下强制开启避免同物多框max_det100防止高密度场景把 SAM 的推理次数拉爆classesNone保留所有类别输出由后续 mask 质量二次筛选上面这些参数落实下去后你得到的results里会带上原始图像尺寸的掩码。SAM 预测返回的mask是 float32 类型数值为 0 或 1并且尺寸和输入给set_image的图像完全一致不需要额外 resize。很多人在这一部会画蛇添足地对掩码做缩放结果边界直接漂移掉这点一定要忍住。4.3 给 0 基础读者留的命令行入口不碰网络结构也能跑通完整包装成命令行脚本后入口长这样python run_open_set.py \ --source ./images/ \ --yolo-weights yolov8s.pt \ --sam-checkpoint sam_vit_b_01ec64.pth \ --conf 0.30 \ --max-det 100 \ --output ./output/对应解析参数并执行主流程的逻辑也很直白先遍历目录读图转 RGB然后依次调用detect_boxes和segment_boxes最后用cv2.imwrite把掩码叠在原图上。对 0 基础读者来说真正值得关注的是两件事一是权重文件的路径必须对应上实际文件二是--source接受的是目录而不是单张图时要有意识地处理视频帧的抽取。这个命令行的价值在于即使你不理解 SAM 的 prompt 原理也可以先把端到端流程跑通再回头改参数。跑通之后建议你立刻做一个可视化小工具把每张图上叠加掩码的结果单独存盘。因为 SAM 在未见类别上会有肉眼可见的“探索性分割”你只有多看结果才能就知道conf和multimask_output应该往哪个方向调。光看控制台日志是定位不了这些问题的。5. 避坑排查与快速验证复现这套流程最容易翻车的三个环节5.1 坑一分割掩码老是错位一个固定偏移甚至飘到图外现象YOLOv8 的框明明准确框住了物体SAM 生成的掩码却整体向某个方向偏移小目标上尤其明显有时候掩码直接落在目标旁边。原因几乎可以肯定是坐标空间没有统一。YOLO 内部 letterbox 后的框直接被 SAM 当成了原图坐标的提示或者你给 SAM 用的是 resize 后的图却拿原始坐标的框去提示。另一个常见变体是把 BGR 图像误传给 SAM通道顺序导致的特征偏差会让掩码产生系统性漂移。解决每次调用 SAM 前先把检测框映射回原始图像坐标。最稳妥的做法是用第 3.2 节里的letterbox_to_original函数保证进入segment_boxes的boxes是原始分辨率。如果发现掩码仍偏一点打印results[0].boxes.orig_shape和image_rgb.shape来核对两个图像的尺寸是否一致。这个打印习惯能帮你定位八成以上坐标问题。5.2 坑二SAM 一上来显卡直接爆显存现象GTX 1660 Ti 跑 YOLOv8 非常流畅帧率感人但把 SAM 串进来后显存占用立刻飙升甚至直接 OOM把 batch 调到 1 也无济于事。或者部署到 rk3588 上YOLOv8 转 NPU 后速度尚可SAM 的 ViT 算子却各种不支持跑一遍比 CPU 还慢。原因SAM 的 Image Encoder 本身就要占用不小的显存和计算量而且代码里如果无意间在循环中对每个框重复调用了set_image等于每处理一个框就把整张图的 embedding 重新算一遍资源被反复浪费。边缘设备上的算子适配问题则是另一个层次NPU 对 ViT 的算子支持并不完整部分算子会走到 CPU 回退速度反而更慢。解决核心代码里已经把set_image提到segment_boxes的外层循环之前这一步必须遵守。如果你发现显存还是吃紧就把输入图像缩放到 768 或 640 再进 SAM精度会略降但显存压力骤减如果是 rk3588 这样的板子通常的做法是把 SAM 转成 ONNX 后分段部署只把 Mask Decoder 放到算力有限的设备上Image Encoder 放到服务端。这条路虽然丑但在边缘交付场景里管用。5.3 坑三开集检测结果里全是“垃圾框”SAM 把背景也抠出来了现象把conf调低后图像里的边缘纹理、天空、阴影都被 YOLO 当成前景框了出来SAM 也老老实实地把这些背景区域分割得轮廓分明结果界面上全是噪点掩码。原因这个坑来自对“开集”的误解。YOLOv8 原生检测头仍然是闭集的它在 COCO 上学到的 decoder 只会对接近训练样本分布的图像区域给出较高置信度并不能从逻辑上理解“这到底是不是前景区”。当你关掉类别过滤时它其实只是把输出层所有类别的概率堆在一起并不表示它具备开放词汇识别能力。解决我一般用两层过滤来止损。第一层是 SAM 侧的稳定性分得分低于 0.8 的掩码直接丢掉第二层是空间先验检测框中心周围四分之一的区域必须与掩码中心区域有足够重叠否则认为 SAM 没“聚焦”到框内主体。如果项目允许引入额外模型我更建议把 YOLO 换成具备开放词汇能力的检测器把“开集”真正落实到检测层。老实说只靠 YOLOv8-SAM 做严格意义的开集是做不到的它更像是一个“类别不可知的优质分割器”这个预期要先对齐。5.4 坑四掩码有空洞、边缘像果冻现象SEM 输出的掩码边缘出现大量锯齿内部偶尔有空洞同一个物体换一帧图像掩码面积忽大忽小。原因multimask_outputTrue返回的三个候选掩码不一定都是“紧致主体”的。SAM 的最佳候选可能更偏轮廓也可能更偏子区域取argmax(scores)虽然是默认做法但并不是在所有场景都最优。另外输入图像本身分辨率过低SAM 的上采样分支拿不到足够的边缘信息也会出现果冻边缘。解决可以先把三个候选掩码都拿到计算它们与检测框的 IoU选一个与框重叠度高且内部空洞较少的如果还是不满意就把检测框向外扩一点再补一次分割让 SAM 看到更多边界上下文。至于内部空洞一个很实用的后处理是用scipy.ndimage.binary_fill_holes把掩码内部的孔洞填平。这一步对后续做目标裁剪、求最小外接矩形帮助很大。排查到这里还有一条经验想分享做这套流程时建立“一天只看图不看指标”的习惯。SAM 的误差模式和 YOLO 完全不同前者是边界级误差后者是语义级误差光用 mAP 判断好坏很容易被平均结果骗过去。每次改参数后把输出图片在屏幕上过一遍比盯十轮监督指标都管用。6. 进阶验证方法三张卡判断可交付性以及小目标分割的提速技巧验证这套开集实例分割方案能不能交付我一般不看花哨的总体指标而是准备三张卡第一张是“高密度小目标图”看 SAM 能否在每个小框内保持轮廓完整第二张是“未见类别图”大概率是从客户现场采集的、训练集里完全没有过的物体看掩码是否仍然能贴合边界第三张是“低对比度图”目标与背景颜色接近看框提示之后 SAM 能否稳住前景。三张图定生死比跑一遍完整测试集更快。配合验证我习惯把两个附属信号也记录到工程里。一个是 YOLOv8 训练侧的损失函数曲线图如果检测器本身收敛不稳定框的抖动会直接传导给 SAM另一个是给 YOLO 的特征图做可视化热力图确认它把注意力放在目标主体而不是背景纹理上。这两个信号不算这套流程的必需品但能帮你在“检测框导致掩码质量下降”和“SAM 本身能力不足”之间做出更干净的归因。小目标场景里还有一个很好用的提速技巧不要总把整张图交给 SAM而是先从原始图像里按检测框位置裁剪出带 padding 的局部图把局部图作为 SAM 的新输入。这样做有两个效果一是让 SAM 在更高有效分辨率下看到目标边缘精度明显提升二是裁剪后的图更小Image Encoder 的编码耗时反而下降。裁剪后记得把掩码按坐标偏移搬回原图代码逻辑如下def crop_with_padding(image, box, pad_ratio0.25): x1, y1, x2, y2 [int(v) for v in box] w, h x2 - x1, y2 - y1 px, py int(w * pad_ratio), int(h * pad_ratio) x1 max(0, x1 - px) y1 max(0, y1 - py) x2 min(image.shape[1], x2 px) y2 min(image.shape[0], y2 py) crop image[y1:y2, x1:x2] local_box np.array([x1 - x1, y1 - y1, x2 - x1, y2 - y1]) return crop, local_box, (x1, y1)用这段逻辑把裁剪图交给 SAM 后得到的 mask 尺寸和裁剪图一致最后只需要按(x1, y1)偏移把 mask 贴回原图即可。这个“局部放大”的思路和我在遥感目标检测场景里用到的策略是相通的对边缘细节要求越高的业务收益越明显。最后说一个我自己在这类项目里养成的习惯永远不要把 SAM 的输出直接当最终交付物一定要预留一个“人工修正层”可以是边缘微调也可以是最小外接矩形的规则约束。SAM 的分割能力再强它也是基于视觉信号而不是业务语义的客户真正关心的可能是某个零件的最外侧边界而不是 SAM 认为的视觉边界。当前这套流程最值得投入的方向依然是对不可见类别的像素级泛化能力它能让你的目标检测系统在类别表失控时有兜底方案。希望这个思路和代码骨架能帮你少走我当年走过的弯路也希望你在自己的项目里踩到新坑之后记得先检查坐标空间再检查颜色空间最后再怀疑模型能力希望帮到你。本文还有配套的精品资源点击获取