做图像分割的朋友,应该没人不知道Segment Anything(SAM)了。这个东西出来的时候,业内最大的感觉是:图像分割的玩法真的要换一种方式了。传统分割模型不管是语义分割还是实例分割,都绕不开“预先定义类别”这堵墙——你能分什么,取决于训练集里有什么。而SAM作为一个通用图像分割模型,直接把这堵墙拆了:你给它一个点、一个框、一句话(当前官方实现主要支持前两种),它就能把对应的目标从图里抠出来,而且是零样本,不需要重新训练,不需要微调,直接推理就能用。
这篇文章我想从“一个实际搞视觉落地的人”的视角,把SAM从原理到上手再到避坑完整梳理一遍。不堆公式,不罗列论文结论,重点说清楚它到底怎么工作、为什么能工作、以及你把它接进自己的业务里会遇到哪些真实问题。适合刚入门的同学,也适合已经跑了几天代码但效果不理想的老手。
1. 先搞清楚SAM到底解决什么问题
1.1 这些年的分割模型,痛点在哪
图像分割的目标很简单:把图片里的每个像素归类到某个对象上。语义分割回答“这个像素属于哪一类”,实例分割进一步问“这些像素属于哪一个独立的对象”。听起来差距不大,但落地的时候全是麻烦。
最大的麻烦之一是标签体系封闭。比如你训练一个能分割汽车、行人、道路的分割模型,模型只能输出这三种东西。换一拨需求——要分雨伞、分猫、分细碎的杂物,就得重新采集数据、重新标、重新训,周期动辄以周为单位。哪怕你只需要新增一个类别,整个流程都要重走一遍。
另一个麻烦是标注成本。高质量的分割标注非常贵。画一个精细的轮廓掩码,远比拉一个检测框耗时。我见过不少标注项目,单人单张图要标十几分钟到半小时。同时标注质量还容易波动,边界毛糙、漏标小目标的情况屡见不鲜。于是大量团队只敢标注少量类别,模型泛化也就无从谈起。
SAM就是冲着这两个痛点来的。它的思路是:不直接回答“这个东西是什么类别”,而是回答“你指哪里,我就分割哪里”。用户提供一个输入提示,模型生成对应的掩码。这里完全绕开了类别体系,所以它可以在任意图片上分割任意对象。这也是它被称为通用图像分割模型的原因。
1.2 这套模型到底适合谁来用
我先给出一个结论:如果你只是想要一个“全图所有物体自动分割”的模型,SAM不一定是最终答案;但如果你需要“任意目标快速分割”“交互式分割”“从分割结果里提炼训练数据”,SAM会给你很大的帮助。
第一类受益者是数据标注团队。传统标注工具里画掩码靠手绘多边形,累且慢。接上SAM之后,只要点击目标的中心或者拖一个框,掩码基本就出来了,人工再修一修边缘就行。这个效率提升不是百分之几十,而是几倍。就算不是完全自动,半自动也比纯手工香太多。
第二类受益者是做垂直场景的算法工程师。你手里的任务往往高度专一,比如工业零件缺陷分割、细胞实例分割、卫星遥感地物分割。问题是数据量和标注都很贵。于是很多人先用SAM在少量图上做粗分割,把结果交给人工校正,再拿校正后的数据去训练你自己的轻量模型。SAM在这里更像一个“数据生产线上的预切割机”。
第三类受益者是做产品原型的开发者。想快速试一个“点选物品就能抠图”的功能,SAM天然合适。不用先攒一堆训练数据,不用训几天模型,下载模型即可跑通。对于验证需求和给老板演示,效率极高。不过正式上线做高并发推理就得另外考虑了,后面我会讲性能取舍。
2. 原理拆解:为什么SAM能“分割一切”
2.1 三个模块的分工
SAM的整体结构其实非常好记:一个图像编码器、一个提示编码器、一个掩码解码器。三个模块各管一段,像一条完整的流水线。
图像编码器的任务是把整张输入图片转成一组高维特征。SAM选用了视觉Transformer这类结构,输入图片会被缩放成固定尺寸,经过编码后得到每个局部区域的语义特征。你可以把它理解成给图片做了一次“深度阅读理解”,把每个位置的视觉信息浓缩成向量。这个部分计算量最大,也是推理时最耗时的环节。自己在本地跑的时候,大尺寸图片编码一次经常占掉大半显存。
提示编码器的作用是把用户输入的点、框等转换成同一空间里的特征表达。比如你给了一个坐标点,编码器会把这个点转成一组向量;给了一个框,就编码四条边与几何信息。这些提示特征会和图像特征组合起来,告诉解码器“我需要的是这个位置附近的东西”。
掩码解码器是真正产出掩码的模块。它会接收前面两部分的特征,交叉融合并逐步上采样,最终输出目标区域的二值掩码。值得注意的是,解码器一次会输出多个候选掩码,对应不同的分割粒度,系统再根据置信度选一个最优的给你。实际使用时会发现同一个点,模型可能同时给出一整只动物、动物的某一部分、以及某个条纹花纹等几种候选,这个设计是非常实用主义的。
三个模块分开看都不算特别新奇,新奇的是它们被组合成一个能够被提示引导的端到端结构。这也是SAM能够打破类别限制的根本原因:它在设计上就不是一个“分类器”,而是一个“跟随提示的分割器”。
2.2 提示机制是怎么工作的
如果你用过SAM的在线Demo,一定体验过“点一下,出掩码”的过程。这个过程背后是几种提示类型的组合:
点提示(Point):最简单直观。给一个“前景点”模型就尝试分割包含该点的对象;给“背景点”模型会尝试排除该点所在的区域。多个点可以同时混合使用,比如给一个正点加一个负点,用来纠正误分割。
框提示(Box):在目标周围画一个框,模型在这个限定范围内寻找目标。日常几乎不会失手,比单点还要稳定。框和点也能组合使用,先用框粗定范围,再用点修正细节。
掩码提示(Mask):在交互式编辑场景里,已经有了一张粗略掩码,把它作为输入让模型细化边缘。这个能力在标注修正阶段非常有用。
在代码层面,你可以传入多个点,甚至给每个点标上1(前景)或者0(背景)。判断原则是:多点提示比单点提示更明确,框加点的组合通常比仅用点更好。从工程角度看,这些提示本质上都是在给模型提供“空间约束”,提示越具体,模型输出的掩码就越贴合用户意图。
2.3 训练数据是怎么“滚”出来的
SAM能有这么强的泛化能力,不是靠网络结构奇迹,而是靠训练数据规模。研发团队构建了一个包含超过十亿掩码、覆盖上千万张图片的数据集。这个量级在分割领域以前是完全不敢想的。问题来了:这么多掩码靠人工标注根本不现实,靠自动算法又不够准。于是他们设计了一套“数据引擎”,分三个阶段交替推进。
第一阶段是全自动生成。想方设法把图片里所有可分割的对象都用算法自动标注出来,质量可以粗糙,但覆盖度要足够广。第二阶段是人机交互辅助标注。由模型给出候选掩码,标注员通过点击和修正来迭代,模型一边标注一边把新的数据吸收回训练集,再训练出更好的模型。第三阶段是完全人工标注,用于处理前两阶段容易失败的小物体、细长物体和语义模糊区域。最终把三个阶段的数据合在一起训练,才得到最终的版本。
这个流程本身对做数据工作的团队也很有启发:先用模型自发产出粗标注,再让“模型+人”协同纠错,最后人工攻坚难例。类似思路后来被很多自动标注平台借鉴了。但也要清楚,普通团队很难复现这种规模,直接使用官方预训练权重才是更现实的做法。
2.4 零样本能力来自哪里
很多人问:SAM没见它专门学过“分割一只陌生的鹦鹉”,为什么第一次见也能分出来?这是因为它的训练目标就不是“识别鹦鹉”,而是在海量数据中学习“什么东西更像一个独立物体”。这种泛化能力来自两个层面。
一是任务本身是通用的。训练时模型见过数亿个目标掩码,它学到的不是某个类别的专属特征,而是“物体的边界感”。二是模型架构具备条件预测能力。给定提示后,解码器从特征图里“找”对应区域,而不是在有限类别集合里“选”一个标签。所以只要图像编码器提取的特征足够好,提示与目标区域匹配得上,模型就能输出掩码,哪怕它从未见过这个目标。
配合巨大的数据规模,SAM在常见视觉目标上的表现非常稳定,在医学影像、遥感、显微镜图像等跨领域图片上也有不错的迁移能力。但“不是万能的”这句话我必须说在前面。对某些边缘极其不清晰、语义模棱两可的图片,输出会不太稳定。零样本更多是指“不需要重新训练即可用”,而不是“所有场景都精确可用”。落地的时候,这个分寸感很重要。
3. 五步上手:跑通一个SAM分割Demo
3.1 搭建运行环境
跑SAM没有想象中那么麻烦,一个带有PyTorch的Python环境就够了。建议Python版本用3.9以上,CUDA至少11.3。核心依赖就是PyTorch、torchvision、OpenCV和segment-anything这个包。安装命令很简单:
pip install torch torchvision opencv-python pip install segment-anything如果你的环境里已经装了其他深度学习框架,不建议混装太多版本,容易把依赖搞乱。我习惯新建一个虚拟环境专门跑SAM。显存方面,如果你用官方最大的“vit_h”权重,推理一张1024尺寸的图片需要大约8GB到10GB显存;显存不够的话可以用“vit_b”这种更小的权重,4GB左右也能跑。没有独立显卡也可以跑CPU推理,但速度会比较慢,适合调试不适合批量处理。
3.2 下载权重与初始化模型
SAM官方的预训练权重不是pip包里自动带的,需要单独下载。通常会有三个规格可选:vit_h(最大、效果最好)、vit_l(中间档)、vit_b(最小、最快)。选择原则很直接:设备显存够优先用vit_h;需要大规模推理再考虑降级到vit_b。我把模型加载的代码写成下面这样:
import torch from segment_anything import sam_model_registry sam_checkpoint = "./sam_vit_h_4b8939.pth" model_type = "vit_h" device = "cuda" if torch.cuda.is_available() else "cpu" sam = sam_model_registry[model_type](checkpoint=sam_checkpoint) sam.to(device)这里有几个容易踩坑的细节。权重文件路径必须正确,否则会报找不到模型键;模型类型必须和权重的规格一致,用vit_h的权重却声明成vit_b会直接加载失败。如果你的机器是MPS架构,可以尝试把device改成“mps”,但部分层可能存在兼容问题,跑之前最好确认一下。
3.3 单点提示做实例掩码
模型加载成功之后,下一步就是核心推理。推荐用SamPredictor封装类,它会把图像编码缓存下来,方便对同一张图做多个提示,避免重复编码图像。
import numpy as np from PIL import Image from segment_anything import SamPredictor predictor = SamPredictor(sam) image = np.array(Image.open("demo.jpg").convert("RGB")) predictor.set_image(image) input_point = np.array([[320, 240]]) # 目标位置,假设是图片里的某个物体 input_label = np.array([1]) # 1表示前景点,0表示背景点 masks, scores, logits = predictor.predict( point_coords=input_point, point_labels=input_label, multimask_output=True, )predict返回三个值。masks的形状是“候选数量、高、宽”,每一层都是二值掩码;scores是对应每个候选的置信度;logits是未二值化的原始分数。单点提示时multimask_output建议设成True,因为模型会给出多个候选,你可以通过scores挑选置信度最高的掩码。但在生产流程里,我经常反而会看多个候选,因为置信度最高的不一定是用户想要的,特别是目标旁边有遮挡或相似纹理时。
掩码拿到后,常规操作是用NumPy把它转成uint8图像,再存盘或用OpenCV叠加在原图上显示。如果你要叠加,记得把掩码从单通道复制成三通道,再与原图做加权融合,否则颜色会发灰。
3.4 框提示与多点组合
如果单点效果不够好,下一个升级策略就是加框提示。框提示在代码里是直接传入一个坐标数组,格式是[x_min, y_min, x_max, y_max]:
input_box = np.array([120, 80, 480, 360]) masks, scores, logits = predictor.predict( box=input_box, multimask_output=True, )框给得越准,分割结果越稳定。很多交互标注工具的后端就是这么设计的:用户画一个大致包围盒,SAM立刻返回掩码。实际业务中我更推荐“框+点”组合:框锁定范围,点修正具体归属。如果目标周围有与它粘连的干扰物体,框可以先把干扰排除在外,正负点再进一步细化。多点传入的写法是把多个坐标放在同一个数组里,标签数组一一对应:
input_point = np.array([[320, 240], [180, 420]]) input_label = np.array([1, 0]) # 第二点是背景点,用于排除错误区域3.5 全图扫描式分割
有些场景你不想让人手动提示,而是希望自动分割出图中所有物体。SAM官方也提供了SamAutomaticMaskGenerator,不需要提示点,会对整张图生成一批掩码。
from segment_anything import SamAutomaticMaskGenerator mask_generator = SamAutomaticMaskGenerator(sam) masks = mask_generator.generate(image)输出的是一个列表,每个元素包含掩码、面积、预测的IoU分数、稳定度分数等字段。这个模式计算量比单点推理大不少,因为内部相当于在全图密集铺提示点再逐一推理,跑一张大图会明显变慢。用的时候要控制图片尺寸,并且做好掩码后处理,比如过滤掉面积太小的噪点、合并重叠度过高的掩码。自动模式适合批量初筛,离“完美分割”还有距离,但作为素材库已经是能用的水平。
3.6 批量处理的性能优化
如果你要拿SAM跑成百上千张图,最快的一步优化是预处理图片尺寸。SAM内部会把输入缩放到固定的1024像素尺度处理,如果你直接把几千万像素的原图喂进去,内存和显存都会很紧张。建议先把长边控制在1024到2048之间,再调用模型。第二个优化是复用图像编码结果。同一张图要基于多个不同提示做多次分割,用SamPredictor只需调用一次set_image,后续多次predict,不要重复编码。对于超大批量,还可以把图片编码部分放到GPU上并行预计算,编码好的特征存到内存或磁盘,之后再由解码器批量消费。
如果依然觉得太慢,可以考虑换用轻量级的分割模型变体。市面上有一些蒸馏压缩版,能在几乎不影响效果的情况下把速度提升数倍,但在边缘细节上通常略弱于原版。具体取舍要看你的业务容忍度:交互式软件里速度优先;离线离线处理里效果优先。另外,批量推理时不要使用太小的batch_size也别开太大,SAM解码器对batch支持没问题,但图像编码器吃显存,建议自己压测一个阈值。
4. 常见报错与排查思路
4.1 高频运行时报错速查
| 报错现象 | 可能原因 | 处理办法 |
|---|---|---|
| 加载权重时Key不匹配 | 模型的type与权重规格不一致 | 检查sam_model_registry传入的type与权重文件是否对应 |
| CUDA out of memory | 输入图片过大或显存不足 | 压缩图片尺寸;换成vit_b;使用CPU推理做测试 |
| 提示坐标报错Shape mismatch | 坐标数组维度不对 | 确认point_coords是(N,2)形状,labels是(N,)形状 |
| 输出掩码全黑 | 提示点落在空白或背景上 | 检查坐标是否越界;尝试加一个框提示 |
| 图片颜色异常 | Pillow与OpenCV通道顺序不一致 | 统一用RGB顺序取数,别混着用BGR和RGB |
| 解码器scores偏低 | 目标区域过小或边缘模糊 | 换成vit_h;或者用多点提示加强约束 |
我实际遇到过最隐蔽的问题是set_image之后图片对象占用的内存没有释放,导致循环处理多张图时显存持续上涨。解决办法是及时调用torch.cuda.empty_cache(),并且尽量复用一个SamPredictor实例而不是反复新建。
4.2 掩码效果不对的排查
掩码不对的情况分两种。一种是“分出了错误的目标”,提示点选在了两个物体的交界处。这时不要把点移得太远,更有效的做法是额外加一个背景点,压低错误目标的响应。另一种是“掩码边界不贴合”,尤其是毛发、透明物体、细小结构。这类情况SAM天然不占优势,你可以把输出掩码交给后续的精细分割模块处理,或者用相关图像处理算法对边界做二次修正。
很多时候问题不在模型本身,而在图像输入。输入图像畸变严重、分辨率过低、光照过强都会影响效果。建议在接入业务前先写一个简单的预处理流程:统一尺寸、做一次直方图均衡、检查是否带水印或文字干扰。这能提高不少成功率。
4.3 效果不行?先别急着换模型
遇到分割效果不理想,先不要怀疑SAM能力不足。先复盘你的提示方式。我见过不少同学用自动点阵切割一片密集排列的小物体,出来的结果一团乱。其实这种场景更适合先用目标检测器识别出每个物体的大致中心,再以这些中心点作为SAM的输入,针对性分割。也就是说,SAM和检测模型不是替代关系,而是协作关系。检测器负责“定位候选”,SAM负责“精细成形”。这类组合在工业质检、医学细胞分析里非常常见。
5. 在落地时踩过的坑
5.1 提示点位置对结果的影响比想象中更大
同样是给一个前景点,点在物体中心的产物和点在物体边缘一小块区域上的产物,经常是完全不同的两个掩码。原因在于:点提示本身带有“粒度暗示”。点在大片区域上时,模型倾向于分割整个对象;点在某个局部纹理上时,模型可能只分割那片纹理。如果你希望分割完整目标,尽量让点靠近目标中心;如果希望分割某个部件,就把点放在部件内部。这个规律对自动生成提示点特别重要,比如用检测框中心作为提示点时,分割会更稳定。
5.2 掩码后处理不能省
SAM输出的掩码是“相对干净的”,但放进业务里直接使用通常不行。典型问题是边缘有锯齿、有孤立小孔、多个掩码重叠覆盖。我建议统一做三步后处理:先用连通域分析剔除面积过小的碎片;再对掩码做一次形态学闭运算填孔;最后如果掩码边缘超出预期,做一次轮廓平滑。后处理不会提升模型本身的上限,但能避开很多下游算法被掩码噪声干扰的问题。
如果你拿SAM去生成训练数据,后处理更重要。脏标签训练出来的模型,性能会一路被拖垮。我还会用“预测IoU分数”过滤低置信度掩码,只保留高质量的样本。几千张图跑完之后,先按scores排序,肉眼抽查一下高分部分和低分部分,再决定阈值,通常效果不错。
5.3 从SAM到业务模型:蒸馏和微调
很多业务场景不能直接把SAM部署上线,尤其对延迟和吞吐量高度敏感的服务,比如电商拍照搜同款、实时视频分割。SAM模型体积大、计算开销高,不适合嵌入式或高并发场景。稳妥的路线是把SAM当成一个“老师”,用它生成大量精确掩码,然后去训练一个自己设计的轻量分割模型。这个蒸馏流程里,关键是提示采样策略:提示点要覆盖目标的中心和边缘,确保掩码多样性;输入图片要覆盖不同光照、尺度、遮挡情况,防止蒸馏后的模型见过的情况太少。
我也试过在少量专业图像上微调SAM。对微小目标、特殊域图像,微调确实能带来可观提升,但要控制学习率和迭代轮次,防止在大模型上过拟合。我见过最简单有效的做法是:冻结图像编码器,只训练掩码解码器那部分参数,数据量只要一两百张分割标注就能看到变化。既省资源又不容易训坏。
5.4 交互式标注工具的最小实现
如果你打算做内部交互标注工具,SAM接入的整体流程并不复杂。用户点击图片,前端把像素坐标传给后端;后端调用SamPredictor预测掩码,把掩码返回前端绘制出来;用户继续点击修正,再传新的点。整个循环里最关键的是后端不要每次重新编码图像。把图片编码结果缓存起来,新的点进来后只跑提示编码和解码,交互延迟可以控制在几百毫秒级别。实际开发中,把SAM服务化时还需要注意并发控制,同一张图的多个提示请求最好走同一个实例,否则不同请求互相重建图像编码,资源利用率会非常难看。
结语前再聊两句
我在实际项目中用SAM最多的两个场景,一个是自动化数据标注,一个是作为检测模型的后处理模块。前者帮我把标注成本压到了原来的三分之一,后者让我把一个原本需要大量标注的分割任务缩短到两周内上线。它确实不是所有问题的终点,但作为通用工具,它的存在让很多原本要重头训练的任务变得“先跑通再优化”。如果你正在做跟分割相关的事,找几张典型业务图,拉一个最小Demo,点一点、拖一拖,感受一下“提示分割”和自己以前用分割模型的感觉到底差在哪里。很多想法,都是在亲手跑完一次之后才会冒出来的。