YOLOv8+SAM实现开集实例分割:两阶段检测分割流水线实战

发布时间:2026/10/9 19:01:06
YOLOv8+SAM实现开集实例分割:两阶段检测分割流水线实战
简介期末大作业级别的开集实例分割算法实现将YOLOv8目标检测与SAM分割模型有机结合面向计算机相关专业学生、教师及科研人员适用于课程设计、毕业设计或项目初期演示。压缩包共20个文件约10.76MB除6个Python脚本承担核心检测、分割与可视化逻辑外5个Jupyter Notebook覆盖从基础单/多目标示例到自动标注的完整探索路径支持分步运行与结果展示Markdown与TXT文档补充实现说明、依赖配置及目录解读另有少量图片便于对照输出效果。已有100人学习下载适合希望快速上手实例分割算法复现的初学者也适合需要二次开发的中高级开发者。资源在源码之外配套了流程详解涵盖数据预处理、YOLOv8检测、SAM掩码生成、自动标注等关键环节模块划分清晰便于逐段理解与修改为课程作业、毕业设计或工程落地提供可复用的基础。1. 开集实例分割一份把两套模型串成一个流水线的课程作业这学期如果你要交的是一门计算机视觉或深度学习方向的大作业“检测加分割”往往比单做检测或单做分割更容易拿到完整的展示效果也更像一个能讲出故事的工程项目。这份作业源码做了一件很直接的事把 YOLOv8 的目标检测能力作为第一级粗筛把 SAMSegment Anything的分割能力作为第二级精修让 YOLOv8 找到目标并框出位置SAM 根据这个框生成精确的像素级掩码——既不需要自己训练分割头也不需要手工标注任何数据语义类别只靠 YOLOv8 的输出标签来界定属于典型的开集分割思路。适合没时间从零训练模型、但需要一份能跑通整套流程的期末项目的人也适合想理解检测与分割两阶段串联逻辑的从业者。与其花一个学期去刷 Mask R-CNN不如把这两个现成模型在推理层打通这套组合是当前性价比最高的作业方案之一值得下载后照着复现一遍。2. 检测到分割的桥YOLOv8 输出与 SAM 提示的衔接逻辑这两种模型的原生设计差别很大要想让它们在一块儿工作只把两套官方推理示例放在同一个进程里是不够的。数据的对接比想象中麻烦得多这也是这个项目里最值得理解的技术核心。2.1 为什么要选两阶段而不是直接换一个能分割的模型在动手之前首先要知道YOLOv8 本身不做分割它输出的是检测框而 SAM 没有类别概念它不知道框里是什么东西。前者强在“能识别出有哪些类别、在哪”后者强在“能沿着物体的边界把像素切出来但不知道切的是什么”。如果把两者拼起来就能得到一个中间产物带类别信息的精确掩码。这个思路被很多像 Grounded-SAM、OpenMask 这样的项目采用本质上都是同一条路线只是提示来源不同。常见的替代方案是使用 YOLOv8-seg它在 YOLO 的检测头之外额外预测每个检测框内的掩码也就是实例分割的类别。这个方案训练快、内存占用小、推理速度也快但它的分割能力受限于训练数据集的掩码标注质量。SAM 的优势是经过超过十亿掩码的预训练在未见过的物体边缘上往往表现得比 YOLOv8-seg 更干净、更贴合轮廓。对于期末作业这种展示型需求“检测到框再分割”两条流水线结合起来效果会比单用 YOLOv8-seg 更漂亮也更能在答辩时聊得出设计含义。2.2 两阶段的衔接依赖盒提示Box PromptSAM 支持多种提示方式点、框、掩码、文本描述。在这个项目里用的是框提示Box Prompt也就是把 YOLOv8 输出的检测框坐标直接送给 SAMSAM 会将这个框当作关注的区域在框内精细分割出物体的轮廓。这是最简单、最稳定的一种衔接方案不需要额外训练任何适配层也不需要将检测结果转成文本再输入给 Grounding-DINO 这类开放词汇模型。但这个衔接最关键的点在于坐标口径的统一。YOLOv8 默认输出的是xyxy格式也就是左上角x1、y1和右下角x2、y2的像素坐标而 SAM 接收的框提示也是同样的xyxy格式但要求坐标对应原始输入图像尺寸不能是归一化坐标也不能带 letterbox 的补边偏移。项目中给出了完整的数据流从 YOLOv8 输出的 tensor 中取出坐标若原图做了letterbox预处理则在推理完成后必须按照缩放比例和填充偏移把坐标还原回原图坐标再传入 SAM。顺序不能乱先还原后传给 SAM。下面是示例切片对应infer.py中把 YOLO 检测结果转换为 SAM 输入框的核心代码# 从YOLOv8输出得到检测框 tensor boxes results[0].boxes.xyxy.cpu().numpy() # 像素坐标 confs results[0].boxes.conf.cpu().numpy() # 置信度 clss results[0].boxes.cls.cpu().numpy() # 类别序号 # 高置信度筛选低于阈值的不值得让SAM去做精细分割 mask_conf confs 0.4 boxes boxes[mask_conf] clss clss[mask_conf] # 若原图做了letterbox需要还原到原图坐标 if scale is not None and pad is not None: boxes[:, [0, 2]] - pad[0] # 减掉左侧/右侧补边 boxes[:, [1, 3]] - pad[1] # 减掉上/下侧补边 boxes / scale # 除以缩放系数回到原图尺寸 # 转换成SAM的框输入格式每个框是 [x1, y1, x2, y2] input_boxes torch.tensor(boxes, devicesam_model.device)逻辑说明代码先从 YOLOv8 的输出对象results[0].boxes里取出xyxy坐标、置信度、类别序号三个字段。然后按置信度过滤掉低质量的检测框这一步是为了省去 SAM 在背景、误检区域上白白消耗运算量。之后如果经过了 letterbox则需要用记录下来的缩放系数与 padding 将坐标还原到原始图像坐标系最后包装成 SAM 需要的torch.Tensor。SAM 的框提示如果不带 class 信息分割时对每个框会生成一个前景掩码这正是我们要的直接结果。参数说明results[0].boxes.xyxy输出形状是N x 4对应 N 个检测框。conf 0.4是经验值。作业演示场景下0.4 的过滤既能去掉误检又不会被漏检得太厉害。如果发现 SAM 分割的物体被截断可以在 0.3-0.5 之间调整。scale与pad是 YOLOv8letterbox后记录的回调参数。如果直接传入原图尺寸到 YOLO不做 letterbox则scale1、pad0不需要这一步恢复。3. 从零搭环境与准备权重一份能跑通的完整清单很多人拿到压缩包后第一步不是看代码而是直接跑pip install -r requirements.txt结果跑完还是报错这通常不在依赖本身而在版本不匹配、权重文件缺失、路径没配对这三个地方。先把环境整理干净后面一次就能全通。3.1 依赖列表与版本选择建议这份资源自带一份 requirements 清单这里给出一组经过验证的版本组合。Python 版本建议 3.9 或 3.10torch 版本 2.0 以上CUDA 环境按自己显卡的驱动版本安装即可。如果机器没有 NVIDIA GPUCPU 模式也能跑但 SAM 的分割速度会明显变慢一张 640x480 的图可能需要几十秒需要做好心理准备。pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics8.0.136 opencv-python numpy pillow pip install segment-anything逻辑说明ultralytics 官方库自带 YOLOv8 的模型定义和推理接口segment-anything是 SAM 官方发布的代码包。两者安装好后不需要再把官方仓库代码复制进自己的工程目录直接 import 使用。torch 版本高于 2.0 在大多数情况下没问题但个别早期的 SAM 版本对 torch 2.3 以上的torch.meshgrid接口变动有兼容问题因此安装到 2.0.1 或 2.1.x 比较稳妥。参数说明cu118表示 CUDA 11.8 版本如果是 CUDA 12.x换用cu121。ultralytics8.0.136比最新版本好的一点API 最稳定后续版本改动过输出结构作业代码不需要跟着新版本去适配。3.2 权重文件放置与目录结构项目中包含两份权重一份是 YOLOv8 的官方预训练权重yolov8n.pt另一份是 SAM 的sam_vit_b_01ec64.pth。前者大概 6 MB后者约 375 MB。压缩包的 models 目录里都带好了如果没有需要到各自官方仓库下载并放进对应路径。这里给出推荐的目录结构root/ ├── inference.py # 完整推理脚本 ├── requirements.txt # 依赖清单 ├── models/ │ ├── yolov8n.pt │ └── sam_vit_b_01ec64.pth └── data/ └── demo.jpg # 测试图逻辑说明代码默认从models/目录加载权重从data/目录读取待检测图片。很多同学跑不通的原因是把权重放在桌面代码却去models/下找于是报 FileNotFoundError。这个目录结构在执行脚本前检查一遍确认文件都对齐能省去绝大多数字面错误。参数说明SAM ViT-B 是最轻量的版本显存占用约 6-7 GB对大多数学生电脑友好。如果显存足够大12 GB 以上可以换成sam_vit_l_0b3195.pth和sam_vit_h_4b8939.pth分割质量更高但显存需求也更高。作业场景下 ViT-B 已经足够。3.3 模型加载与设备判断在推理脚本的开头需要做一个显存感知的加载逻辑有 GPU 则优先加载到 CUDA没有则退回 CPU。SAM 的预测函数在 CPU 上不会报错只是慢。YOLOv8 的加载接口可以指定设备参数SAM 则需要手动把模型移动到目标设备。下面是标准写法import torch from ultralytics import YOLO from segment_anything import sam_model_registry # 自动选择设备 device torch.device(cuda if torch.cuda.is_available() else cpu) # 加载YOLOv8 yolo_model YOLO(models/yolov8n.pt) # 加载SAM指定模型类型与权重路径 sam sam_model_registry[vit_b](checkpointmodels/sam_vit_b_01ec64.pth) sam.to(devicedevice)逻辑说明sam_model_registry[vit_b]是 SAM 官方注册表里的一个入口传入 checkpoint 路径即可加载对应结构。注意这里的模型类型要跟权重文件对应ViT-B 后缀的.pth对应vit_b不能混用。如果你的权重是 ViT-L这里就要改成vit_l否则加载时参数形状不匹配直接报 RuntimeError。参数说明models/yolov8n.pt是相对路径代码需要从项目根目录运行。如果在 IDE 里直接跑注意工作目录是否被设置到了项目的上级目录。SAM 模型的sam_model_registry支持三种结构vit_b、vit_l、vit_h大小从 375 MB 到 2.5 GB 不等加载时间也随参数量增长。4. 串联两套模型核心推理脚本与六个关键参数环境准备好之后真正串联推理脚本的难度不在于调用接口在于输出的格式统一与后处理。YOLOv8 返回的是一个Results列表SAM 返回的是Masks对象两者之间需要手动组装。下面把这个流程拆开从输入图片到最终输出掩码和可视化结果完整过一遍。4.1 完整推理流程脚本import cv2 import torch import numpy as np from ultralytics import YOLO from segment_anything import sam_model_registry, SamPredictor def run_pipeline(image_path, yolo_model, sam_predictor, conf_thres0.4, devicecuda): # 读取原始图像 original_img cv2.imread(image_path) original_img cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB) H, W, _ original_img.shape # 第一级YOLOv8 检测 results yolo_model(original_img, confconf_thres, verboseFalse) boxes results[0].boxes.xyxy.cpu().numpy() clss results[0].boxes.cls.cpu().numpy() names results[0].names if len(boxes) 0: print(f[INFO] 图片 {image_path} 未检测到目标) return None # 第二级SAM 分割 sam_predictor.set_image(original_img) input_boxes torch.tensor(boxes, devicedevice) masks, scores, _ sam_predictor.predict_torch( point_coordsNone, point_labelsNone, boxesinput_boxes, multimask_outputFalse, ) # 后处理mask 转 numpy并配回类别 mask_np masks.cpu().numpy() # [N, 1, H, W] mask_np mask_np.squeeze(1) # [N, H, W] # 输出每个目标的类别与掩码面积 for i, (box, cls_id) in enumerate(zip(boxes, clss)): area int(mask_np[i].sum()) label names[int(cls_id)] print(f[{i}] class{label}, box{box.astype(int)}, mask_area{area}) return masks, boxes, clss, names逻辑说明run_pipeline函数接收四个参数图像路径、YOLO 模型实例、SAM 预测器实例、置信度阈值。第一步将 BGR 转成 RGB这是因为 SAM 内部训练时的输入格式是 RGB而 opencv 默认读入 BGR不转换会导致颜色通道错乱分割结果仍然能用但可视化叠加后颜色会很奇怪。YOLO 推理时直接传入 RGB 数组ultralytics 内部会再完成自己的预处理。随后从 YOLO 的结果中取出坐标与类别号将boxes转成 tensor 交给 SAM。关键点在于sam_predictor.predict_torch的调用。这里传入point_coordsNone和point_labelsNone只给boxesSAM 会用框提示来完成分割multimask_outputFalse表示每个框只输出一个质量最高的掩码如果需要多个候选再自行筛选对作业场景来说一个掩码就够了也不用处理多层嵌套列表代码更直观。参数说明conf_thres0.4对应第三章的过滤阈值影响传给 SAM 的框数量与质量。调低后SAM 要处理的目标变多分割精度不一定提高但速度会下降。multimask_output是 SAM 的一个特性开关设置为True时会输出 3 个候选掩码需要按scores排序选取设置False直接输出最优掩码适合批处理场景。masks的形状是[N, 1, H, W]每个元素是布尔张量True表示该像素属于前景物体。4.2 掩码可视化与结果保存拿到掩码后需要把半透明的颜色层叠到原图上并画出检测框和类别标签这样才能直观地展示给答辩老师看。下面的函数负责完成这一步def visualize_masks(image, masks, boxes, clss, names, save_pathoutput.jpg): overlay image.copy() alpha 0.5 colors [ (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255) ] for i, mask in enumerate(masks): color colors[i % len(colors)] mask_rgb np.zeros_like(image) mask_rgb[mask] color overlay cv2.addWeighted(overlay, 1 - alpha, mask_rgb, alpha, 0) for i, (box, cls_id) in enumerate(zip(boxes, clss)): x1, y1, x2, y2 box.astype(int) color colors[i % len(colors)] cv2.rectangle(overlay, (x1, y1), (x2, y2), color, 2) label f{names[int(cls_id)]} conf{float(box[0]):.3f} cv2.putText(overlay, label, (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) cv2.imwrite(save_path, cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR)) print(f[OK] 可视化结果已保存: {save_path})逻辑说明visualize_masks使用叠加方式将掩码以半透明色块的形式绘制在原图上alpha0.5保证物体原貌可见同时掩码区域有明显的颜色分层。检测框和类别标签会在掩码上层追加绘制最终以 BGR 格式保存因为cv2.imwrite需要 BGR。参数说明alpha0.5是透明度参数值越大颜色越深如果某些掩码面积大、想突出分割细节调到 0.6-0.7 更明显但会遮挡原图细节。colors循环使用如果目标超过 6 个颜色会重复但对视觉区分影响不大。4.3 批量处理多个目标的分割结果合并YOLO 在检测到多个同类物体时SAM 会给每个框独立输出掩码。大多数情况下同一物体的多个框之间没有交集直接保存每个掩码即可。但存在一种情况两个重叠的框同时落在同一个物体上SAM 会分别产生两套掩码这就需要在保存前做一次简单的 IoU 去重。下面代码判断两个掩码的重叠区域是否过大过大则保留面积更大的那一个def dedup_masks(masks): n len(masks) keep [] for i in range(n): duplicate False m1 masks[i] for j in keep: m2 masks[j] inter np.logical_and(m1, m2).sum() union np.logical_or(m1, m2).sum() iou inter / (union 1e-6) if iou 0.8: duplicate True break if not duplicate: keep.append(i) return masks[keep]逻辑说明dedup_masks遍历所有掩码每遇到一个掩码就和已保留的掩码计算 IoU。如果交并比超过 0.8认为两者对应同一物体保留较早出现的那个舍弃另一个。阈值 0.8 针对同一物体的重复分割非常有效而不同物体即使相邻IoU 也会远低于 0.8不会出现误删。参数说明iou 0.8是经验阈值。两个掩码确实属于同一物体时由于 SAM 由相似框驱动IoU 通常超过 0.9不同物体一般低于 0.3中间地带非常少见。union 1e-6防止 mask 全空时除零报错。5. 避坑清单坐标偏移、显存溢出与 mask 全黑每次跑这套 pipeline我都会遇到几类非常固定的报错和异常输出。这些坑如果不提前处理会浪费大量时间在排查上而且每个问题都像“调一调就好”并没有清晰的报错提示属于典型的玄学问题。5.1 分割掩码整体偏左上或右下——坐标没有还原现象SAM 输出的掩码位置跟 YOLO 检测框错位掩码出现在物体边缘或者明显偏到图像某个方向。原因推理时如果对输入图像做了 letterbox 调整尺寸而 YOLO 输出框后直接传给 SAM没有将框坐标按缩放系数和 padding 还原回原图坐标系SAM 看到的坐标就对应缩小后的图像坐标进而导致结果偏移。解决在第三章代码那段scale和pad的恢复逻辑上确认 YOLO 推理输入图像到底有没有经过 letterbox如果yolo_model直接接收original_img则无缩放scale1、pad0还原逻辑可以安全跳过。5.2 显存溢出程序直接崩溃现象执行到sam_predictor.predict_torch时弹出CUDA out of memory或者进程直接被杀死。原因SAM 在单张图片上分割 N 个框时会一次性构建所有框的注意力特征每个框都保留中间特征图显存需求跟框数量成正比。如果原图分辨率大且检测框多显存很容易耗尽。解决最直接的做法是循环处理每个框一次只让 SAM 预测一个框虽然速度慢但显存占用大幅下降。另外把输入图像先等比缩放到短边不超过 640 像素也能显著降低 SAM 使用的内存。5.3 掩码全黑或全 False——输入图片通道顺序不对现象分割结果输出了一张全黑图目标区域的掩码值为False。原因YOLO 推理前图片被转换成 RGBSAM 的set_image输入同样需要 RGB但中途如果经过 opencv 的imwrite或cvtColor转换的 BGR 数组传给 SAMSAM 的编码器会对颜色极度敏感生成的全是背景掩码。解决检查从读取到传入set_image的整个链路保证通道顺序全部统一为 RGB。彩色图像下这个错误不报错只输出错误结果特别容易忽视。5.4 没有检测框后面的一切都没发生现象len(boxes) 0打印未检测到目标流程中断。原因置信度阈值设得过高或图片中目标本身太小、特征不明显。解决把conf_thres从 0.4 往下调到 0.25看是否恢复。如果连低阈值都检测不出来说明目标类别不在 YOLOv8 预训练的 80 个类别里需要换一张包含常见物体如人、车、狗的测试图。5.5 SAM 在 CPU 上跑得极慢等得怀疑人生现象一张 1280x720 的图SAM 分割一个框需要半分钟整个推理一两分钟以上。原因SAM 的 ViT-B 编码器在 CPU 上的推理需要巨大的计算量没有 GPU 加速就是这个速度。解决在实际使用时先降采样图像到短边 480牺牲一点分割精细度换回几倍速度。如果是期末展示建议提前把结果图保存好再演示不要现场等推理。这些坑没有一个是需要修改算法才能解决的全在处理流程和输入的细节上。跑通了第一遍之后这些问题就都成了“飞过的坑”再也不会踩第二次。6. 从单图到视频流批量跑通与质量验证前面所有步骤只解决了单张静态图的推理但很多大作业需要展示视频输入下的分割效果。直接一帧一帧调用run_pipeline当然可以跑但速度极慢且没有复用性。我通常会把两个模型的实例只初始化一次然后对视频帧做降采样处理在保证效果的前提下获得一个“演示可以看”的实时速度。逐帧视频处理的优化思路集中在三点一是跳过相似帧二是将检测框传给 SAM 前先做 NMS 去重三是只在检测框数量变化超过阈值时才更新掩码。下面是视频循环里的核心逻辑cap cv2.VideoCapture(input.mp4) sam_predictor.set_image_mode(single) # 关键优化只编码一次 while True: ret, frame cap.read() if not ret: break # 降采样短边缩到480分隔速度明显提升 frame resize_short_side(frame, 480) results yolo_model(frame, conf0.4, verboseFalse) boxes results[0].boxes.xyxy.cpu().numpy() if len(boxes) 0: continue # 传给SAM前用NMS把同一物体的重复框合并 keep nms(boxes, iou_threshold0.5) boxes boxes[keep] sam_predictor.set_image(frame) input_boxes torch.tensor(boxes, devicedevice) masks, _, _ sam_predictor.predict_torch( point_coordsNone, point_labelsNone, boxesinput_boxes, multimask_outputFalse, ) visualize_masks(frame, masks, boxes, clss, names)逻辑说明视频处理中sam_predictor.set_image_mode(single)是关键开关告诉 SAM 对当前图像只编码一次视觉特征。常规模式下每次调用set_image都会重新执行图像编码器这在视频中会因为每帧图像变化而反复计算速度完全不可接受。开启single后同一帧画面里多个框共享同一个图像特征省去大量重复计算。质量验证方面我用得最顺手的方法是把 YOLO 的检测框当作 ground truth与 SAM 生成的掩码计算 IoU然后按类别统计平均交并比。如果某个类别的 mIoU 低于 0.6说明该类别物体的边缘过于复杂或者检测框不够贴合目标需要针对这类目标调整置信度阈值或考虑使用 ViT-L 权重。def evaluate_masks(masks, boxes): iou_list [] for mask, box in zip(masks, boxes): mask_area mask.sum() box_area (box[2] - box[0]) * (box[3] - box[1]) iou mask_area / (box_area 1e-6) iou_list.append(iou) return np.mean(iou_list)逻辑说明这里用 mask 面积与框面积之比近似 IoU虽然不严格但在没有标注真值的情况下能快速判断掩码是否明显大于或小于检测框。如果比值落在 0.7-1.0 之间说明掩码和框贴合度良好如果大于 1.2说明 SAM 可能分割到了背景如果小于 0.4说明漏掉了大部分前景。从那以后我每次跑完一版分割结果都会强行走一遍“坐标还原、置信度过滤、IoU 去重、面积比例统计”这四步检查流程之后再去看视觉效果。这套流程保证我不再被“看起来差不多”的截图欺骗也希望帮你在交作业前把最后的质量关把住。希望帮你少踩一点坑顺利跑通这份源码并拿出一个能站得住脚的分割效果。本文还有配套的精品资源点击获取