SAM (Segment Anything Model)学习笔记
- 一、SAM 是什么
- 二、SAM 能做什么
- 2.1 三种分割模式
- 2.2 零样本迁移任务
- 2.3 在医学图像领域的应用
- 三、模型核心组件
- 3.1 Image Encoder(图像编码器)
- 3.2 Prompt Encoder(提示编码器)
- 3.3 Mask Decoder(掩膜解码器)
- 3.4 Image Encoder 原理:ViT(Vision Transformer)
- 3.5 Prompt Encoder 原理:四种 Prompt 统一编码
- (1)点(Point)的编码
- (2)框(Box)的编码
- (3)Mask 的编码
- (4)文本的编码(需 CLIP 版 SAM)
- 3.6 Mask Decoder 原理:从 Embedding 到掩膜
- 3.7 训练目标:三大损失函数
- (1)Focal Loss(掩膜损失)
- (2)IoU Loss(交并比损失)
- (3)判别损失(Prompt 判别损失)
- 四、关键技术亮点
- 4.1 Promptable Segmentation(可提示分割)
- 4.2 模糊歧义处理
- 4.3 高效实时推理
- 4.5 数据引擎(Data Engine):如何用模型自己生成训练数据
- 4.6 推理完整流程
- 4.7 与其他分割方法的本质区别
- 五、实验代码与结果
- 5.1 实验环境
- 5.2 完整实验代码(SAM + COCO 实例分割)
- 5.3 核心实验流程图
- 5.4 实验结果分析
- 5.5 实验结果
- 5.5.1 分割效果图
- 5.5.2 全局分数统计
- 六、模型优缺点
- 6.1 优点
- 6.2 缺点
- 6.3 SAM vs 其他分割模型对比
- 七、应用流程总结
一、SAM 是什么
SAM(Segment Anything Model)是 Meta AI 发布的通用图像分割基础模型,就像 NLP 领域的 GPT 一样——一个"万物皆可分割"的统一模型,给任意图片中的任意物体生成高质量分割掩膜。
一句话理解:一个 prompt(提示)驱动的分割模型,给点给框就能分割,给整张图自动分割图中所有物体,无需重新训练即可泛化到任意分割任务。
二、SAM 能做什么
2.1 三种分割模式
| 模式 | 输入 | 输出 | 典型场景 |
|---|---|---|---|
| 点分割 | 图片 + 任意一个点(前景/背景) | 该点所属物体的掩膜 | 交互式分割 |
| 框分割 | 图片 + 任意一个框 | 框内物体的掩膜 | 目标检测后续处理 |
| 万物分割 | 图片(无提示) | 图中所有物体的掩膜 | 预处理、全图分割 |
2.2 零样本迁移任务
SAM 在 SA-1B 数据集上预训练后,无需微调即可直接用于:
- 边缘检测→ 直接在全图模式输出所有物体轮廓
- 目标检测框→ 万物分割后提取外接矩形
- 实例分割→ 对每个物体独立输出掩膜
- 语义分割→ 对万物分割结果做类别映射
- 指代分割(Referring Segmentation)→ 结合文本 prompt 实现"说说哪个物体"
2.3 在医学图像领域的应用
| 方向 | 说明 |
|---|---|
| 超声图像分割 | 分割器官、病灶、肿瘤区域 |
| CT/MRI 分割 | 零样本或微调后分割解剖结构 |
| 息肉/病变检测 | 消化道内镜图像分割 |
| 细胞/组织分割 | 病理图像分析 |
| 手术器械分割 | 介入手术实时分割引导 |
三、模型核心组件
SAM 由三个核心部分组成:
输入图像 │ ▼ ┌─────────────────────────────────────┐ │ Image Encoder(图像编码器) │ ← 计算量大,一次性处理 │ ViT(Vision Transformer) │ └─────────────────────────────────────┘ │ ▼ image embedding ┌─────────────────────────────────────┐ │ Prompt Encoder(提示编码器) │ ← 轻量级,多种 prompt 融合 │ 点/框/Mask/文本编码 │ └─────────────────────────────────────┘ │ ▼ embedding 融合 ┌─────────────────────────────────────┐ │ Mask Decoder(掩膜解码器) │ ← 输出多尺度候选掩膜 │ 轻量级 Transformer × 2 blocks │ └─────────────────────────────────────┘ │ ▼ 多个候选分割掩膜 + 置信度分数3.1 Image Encoder(图像编码器)
作用:将输入图像编码为特征向量(image embedding),是 SAM 中计算量最大的部分。
架构:预训练的MAE 视觉Transformer(ViT),将图像分块(16×16 patch),通过 Transformer 多层编码。
| 配置 | 模型 | 参数量 | 特点 |
|---|---|---|---|
| SAM ViT-B | ViT-B/16 | ~75M | 最快,适合移动端 |
| SAM ViT-L | ViT-L/16 | ~308M | 平衡精度与速度 |
| SAM ViT-H | ViT-H/14 | ~636M | 精度最高,计算量最大 |
关键特性:同一张图只编码一次,所有后续 prompt 复用这个 embedding。
3.2 Prompt Encoder(提示编码器)
作用:将用户的分割提示编码为向量。
| Prompt 类型 | 编码方式 |
|---|---|
| 点(Point) | 位置编码 + 正/负标签(1/0)→ 点向量 |
| 框(Box) | 左上角 + 右下角点坐标 → 框向量 |
| Mask(掩膜) | 与 image embedding 逐元素卷积融合 → 低层特征补充 |
| 文本(Text) | CLIP 文本编码器 → 文本向量(需 CLIP 版本 SAM) |
特点:轻量级,几乎不增加计算量。
3.3 Mask Decoder(掩膜解码器)
作用:将 image embedding + prompt embedding 转换为分割掩膜。
架构:两阶段双向 Transformer + 三尺度输出头:
[image embedding] + [prompt embedding] │ ▼ ┌─────────────────┐ │ Transformer │ ← 双向 attention,更新 embedding │ Block × 2 │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ Output Head │ ← 生成 3 个不同尺度的掩膜 │ (高/中/低分辨率)│ └─────────────────┘ │ ▼ 3 个候选掩膜 → 取 score 最高 → 最终掩膜多候选设计(multimask_output):同一 prompt 可能对应多个物体,输出多个候选让用户选择。
3.4 Image Encoder 原理:ViT(Vision Transformer)
输入:图片(HxWx3)
输出:特征图(H/16 × W/16 × 嵌入维度)
处理流程:
图片(HxW×3) │ ▼ 切分为 16×16 的 patch patch 数量 = (H/16) × (W/16) 个 │ ▼ 每个 patch → 线性投影 → 嵌入维度向量 │ ▼ 加入位置编码(Position Embedding) │ ▼ 通过 N 层 Transformer Encoder │ 每层:Multi-Head Self-Attention + MLP │ ▼ 输出:H/16 × W/16 × d 的特征图为什么用 Transformer 而非 CNN?
| 方式 | 长距离依赖建模 | 计算方式 |
|---|---|---|
| CNN(卷积) | 需要多层堆叠才能建立,感受野有限 | 局部卷积逐步扩大 |
| Transformer | Self-Attention 一步到位,任意两个位置直接交互 | 全局并行注意力 |
三种规格对比:
| 规格 | patch 大小 | 层数 | 隐藏维度 | 参数量 | 推理速度 |
|---|---|---|---|---|---|
| ViT-B | 16×16 | 12 | 768 | ~86M | 快 |
| ViT-L | 16×16 | 24 | 1024 | ~304M | 中等 |
| ViT-H | 14×14 | 32 | 1280 | ~632M | 慢 |
3.5 Prompt Encoder 原理:四种 Prompt 统一编码
(1)点(Point)的编码
点坐标 [x, y] + 标签 [1=前景 / 0=背景] │ ▼ 位置编码(sin/cos 正余弦编码) │ ▼ 与标签嵌入 concat → 点向量 │ ▼ 与 image embedding 做 Cross-Attention → 融合(2)框(Box)的编码
框 [x1, y1, x2, y2] │ ▼ 角点位置编码:分别对 x1,y1,x2,y2 做位置编码 │ ▼ 两个角点向量 → 与 image embedding 融合(3)Mask 的编码
已知掩膜(低级先验) │ ▼ 降采样到与 image embedding 相同分辨率 │ ▼ 与 image embedding 逐像素做卷积融合 │ ▼ 输出:融合了先验信息的 embedding(4)文本的编码(需 CLIP 版 SAM)
文本字符串 "a cat" │ ▼ CLIP 文本编码器(预训练语言-图像对齐模型) │ ▼ 文本向量 → 与 image embedding 对齐融合3.6 Mask Decoder 原理:从 Embedding 到掩膜
输入:image embedding + prompt embedding
输出:多个候选分割掩膜
阶段 1:双向 Transformer 融合 ┌────────────────────────────────────────────┐ │ image embedding(来自 ViT) │ │ prompt embedding(来自 Prompt Encoder) │ │ ↓ │ │ Self-Attention(双向,image↔prompt) │ │ Cross-Attention(image↔prompt) │ │ MLP 前馈网络 │ │ ↓ │ │ 更新后的融合 embedding │ └────────────────────────────────────────────┘ │ ▼ 阶段 2:轻量级解码(重复 2 次) ┌────────────────────────────────────────────┐ │ 对融合 embedding 做: │ │ · Token 解码(读取 prompt 信息) │ │ · 上采样(从低分辨率恢复到原始分辨率) │ │ · 输出 3 个尺度的掩膜预测 │ └────────────────────────────────────────────┘ │ ▼ 3 个候选掩膜 [H×W] + 3 个置信度分数为什么输出 3 个尺度?
- 小物体 → 高分辨率掩膜更好
- 大物体 → 低分辨率掩膜更稳定
- 三选一,取置信度最高的
3.7 训练目标:三大损失函数
SAM 的训练是多任务联合训练,核心有三个损失:
(1)Focal Loss(掩膜损失)
L m a s k = − 1 N ∑ i = 1 N w i ⋅ log ( p ^ i ) \mathcal{L}_{mask} = -\frac{1}{N}\sum_{i=1}^{N} w_i \cdot \log(\hat{p}_i)Lmask=−N1i=1∑Nwi⋅log(p^i)
作用:像素级交叉熵损失,对难分割的像素(边缘、遮挡处)加大权重,让预测掩膜与真实掩膜尽量一致。
(2)IoU Loss(交并比损失)
L i o u = 1 − I o U ( m ^ , m ) \mathcal{L}_{iou} = 1 - IoU(\hat{m}, m)Liou=1−IoU(m^,m)
作用:直接优化整体重叠质量,Focal Loss 管像素精度,IoU Loss 管整体质量,两者互补。
(3)判别损失(Prompt 判别损失)
L d i s c = CrossEntropy ( s c o r e c o r r e c t , s c o r e w r o n g ) \mathcal{L}_{disc} = \text{CrossEntropy}(score_{correct}, score_{wrong})Ldisc=CrossEntropy(scorecorrect,scorewrong)
作用:训练模型区分正确 prompt(高分)和错误 prompt(低分),对同一张图给出多个 prompt,监督模型正确识别有效 prompt。
四、关键技术亮点
4.1 Promptable Segmentation(可提示分割)
传统分割:每张图、每个任务都需要专门训练一个模型。
SAM:一个大模型 + prompt → 泛化到任意分割任务。
4.2 模糊歧义处理
同一个点可能属于多个物体(如人和背景重叠处),SAM 的解决方案:输出多个候选掩膜,而非单一结果。
4.3 高效实时推理
尽管是 Transformer 架构,通过以下方式实现实时:
- 图像编码器只运行一次(embedding 可复用)
- 轻量级 prompt encoder
- 轻量级 mask decoder(仅 2 层 Transformer)
- ViT-B 在 CPU 上即可实时运行
4.5 数据引擎(Data Engine):如何用模型自己生成训练数据
SAM 最有创新性的部分——自举(Bootstrapping)数据生成:
Step 1: 用少量标注数据训练初始模型 (12 万张图,学会基本的"点/框→掩膜"映射) Step 2: 用初始模型对大量无标注图片做分割 → 自动生成"伪标签"(分割掩膜) Step 3: 用伪标签训练模型(自身) Step 4: 用训练后的模型重新标注 → 生成更高质量的伪标签 → 再训练 → 再标注 → 循环迭代| 阶段 | 数据量 | 标注方式 |
|---|---|---|
| 初始 | 12 万张 | 人工标注 |
| 第1轮 | 120 万张 | 模型自动生成 |
| 第2轮 | 610 万张 | 模型自动生成 |
| 最终(SA-1B) | 1100 万张 | 模型自动生成 |
本质上:用模型生成的数据训练自己,逐步提升,最终比纯人工标注快了 100 倍,数据量大了 100 倍。
4.6 推理完整流程
以 SAM + COCO 代码为例,完整推理流程如下:
输入:COCO val2017 中的任意一张图片 │ ▼ Step 1: read_image() → 图片读取 BGR 格式 → RGB 格式转换 │ ▼ Step 2: predictor.set_image() → 图像编码(耗时主要在这里) ViT-B 编码器前向传播一次 输出:image embedding(H/16 × W/16 × 768) ⚠️ 同一张图只执行一次,所有 bbox 共享 │ ▼ Step 3: 对每个 COCO bbox → 构造 input_box [x1,y1,x2,y2] → predictor.predict(box=input_box) │ ├── Prompt Encoder:对 bbox 编码 → prompt embedding ├── Mask Decoder:融合 image + prompt embedding ├── 输出 3 个候选掩膜 + 3 个 scores │ ▼ np.argmax(scores) → 选分数最高的掩膜 │ ▼ Step 4: 掩膜二值化(bool → uint8×255)→ 保存 PNG时间分布(估计):
| 步骤 | 时间占比 | 说明 |
|---|---|---|
set_image()(ViT 编码) | ~80–90% | 主要瓶颈 |
predict()(每个 bbox) | ~5–10% | 轻量级 prompt + decoder |
| 后处理(保存文件) | ~5% | 很小 |
关键设计:Image Encoder 只跑一次、然后所有 bbox 共享 embedding,这使得多物体分割的整体效率大幅提升。
4.7 与其他分割方法的本质区别
| 维度 | 传统语义分割(U-Net) | 目标检测(YOLO) | SAM |
|---|---|---|---|
| 训练方式 | 每个任务单独训练 | 每个任务单独训练 | 一个模型,干所有任务 |
| 输入方式 | 整张图进来直接分割 | bbox 由检测器内部生成 | 外部给 prompt |
| 输出范围 | 固定类别集合 | 固定类别集合 | 任意物体 |
| 新类别 | ❌ 需重新训练 | ❌ 需重新训练 | ✅ 直接分割 |
SAM 的本质突破:把"分割什么"的决定权,从模型内部转移到了用户输入(prompt)。
五、实验代码与结果
5.1 实验环境
importos,cv2,numpyasnp,torchfrompycocotools.cocoimportCOCOfromsegment_anythingimportsam_model_registry,SamPredictor5.2 完整实验代码(SAM + COCO 实例分割)
importosimportcsvimportcv2importnumpyasnpimporttorchfromtqdmimporttqdmfrompycocotools.cocoimportCOCOfromsegment_anythingimportsam_model_registry,SamPredictor# ============ 配置 ============project_root=r"E:\学习资料\研究生资料\机器学习\深度学习\segment-anything-main"checkpoint=os.path.join(project_root,"sam_vit_b_01ec64.pth")image_dir=os.path.join(project_root,"val2017")ann_file=os.path.join(project_root,"annotations","instances_val2017.json")output_dir=os.path.join(project_root,"sam_coco_output")mask_dir=os.path.join(output_dir,"masks")os.makedirs(mask_dir,exist_ok=True)model_type="vit_b"device="cuda"iftorch.cuda.is_available()else"cpu"# ============ 工具函数 ============defread_image(path):data=np.fromfile(path,dtype=np.uint8)returncv2.imdecode(data,cv2.IMREAD_COLOR)defwrite_image(path,image):ext=os.path.splitext(path)[1]ok,encoded=cv2.imencode(ext,image)ifnotok:raiseRuntimeError(f"Failed to encode image:{path}")encoded.tofile(path)# ============ 主循环 ============coco=COCO(ann_file)sam=sam_model_registry[model_type](checkpoint=checkpoint)sam.to(device=device)sam.eval()predictor=SamPredictor(sam)img_ids=coco.getImgIds()csv_path=os.path.join(output_dir,"results.csv")withopen(csv_path,"w",newline="",encoding="utf-8")asf:writer=csv.writer(f)writer.writerow(["image_id","file_name","annotation_id","category_id","category_name","score","mask_path",])forimg_idintqdm(img_ids,desc="Running SAM on COCO val2017"):img_info=coco.loadImgs(img_id)[0]image_path=os.path.join(image_dir,img_info["file_name"])image_bgr=read_image(image_path)ifimage_bgrisNone:continueimage_rgb=cv2.cvtColor(image_bgr,cv2.COLOR_BGR2RGB)predictor.set_image(image_rgb)# Image Encoder:只跑一次ann_ids=coco.getAnIds(imgIds=img_id,iscrowd=False)anns=coco.loadAnns(ann_ids)foranninanns:x,y,w,h=ann["bbox"]ifw<=1orh<=1:# 过滤无效框continue# COCO bbox [x,y,w,h] → SAM box [x1,y1,x2,y2]input_box=np.array([x,y,x+w,y+h])withtorch.no_grad():masks,scores,logits=predictor.predict(box=input_box,multimask_output=True,# 多候选,取最优)best_index=int(np.argmax(scores))best_mask=masks[best_index]best_score=float(scores[best_index])category=coco.loadCats([ann["category_id"]])[0]category_name=category["name"]mask_name=f"{img_id}_{ann['id']}_{category_name}_{best_score:.3f}.png"mask_path=os.path.join(mask_dir,mask_name)mask_uint8=best_mask.astype(np.uint8)*255write_image(mask_path,mask_uint8)writer.writerow([img_id,img_info["file_name"],ann["id"],ann["category_id"],category_name,best_score,mask_path,])print("done")5.3 核心实验流程图
COCO val2017 数据集(约 5000 张图片) │ │ 每张图遍历所有非 crowd 标注 ▼ Step 1: 读取图片 BGR → RGB 转换 │ ▼ Step 2: predictor.set_image() → Image Encoder(ViT-B) │ ⚠️ 每张图只执行一次,所有 bbox 共享 embedding ▼ Step 3: 对每个 COCO bbox → SAM prompt → 分割掩膜 │ ▼ Step 4: 多候选掩膜 → 取 scores.argmax() 最优 │ ▼ Step 5: 保存 mask PNG + 结果 CSV5.4 实验结果分析
# 按类别统计 SAM 分割置信度importnumpyasnpfromcollectionsimportdefaultdict results=[]# 从 CSV 读取category_scores=defaultdict(list)forrowinresults:category_scores[row["category_name"]].append(float(row["score"]))print("\n=== 分类别 SAM 置信度统计 ===")forcat,scoresinsorted(category_scores.items(),key=lambdax:np.mean(x[1]),reverse=True):print(f"{cat:20s}: mean={np.mean(scores):.3f}, "f"std={np.std(scores):.3f}, n={len(scores)}")5.5 实验结果
5.5.1 分割效果图
📸COCO数据集图片分割
原图:
分割效果:
5.5.2 全局分数统计
| 指标 | 数值 |
|---|---|
| 总预测样本数 | 36,334 |
| 处理图片数 | 4,952 |
| 类别数量 | 80 |
| 平均分数 | 0.9315 |
| 标准差 | 0.0546 |
| 最低分数 | 0.4909 |
| 最高分数 | 1.0268 |
| 分数 > 0.9 占比 | 78.5%(28,537/36,334) |
| 分数 > 0.8 占比 | 96.9%(35,215/36,334) |
| 分数 > 0.7 占比 | 99.6%(36,175/36,334) |
六、模型优缺点
6.1 优点
| 优点 | 说明 |
|---|---|
| ✅通用性极强 | 一个模型分割任意图像的任意物体,无需针对任务微调 |
| ✅Prompt 驱动 | 支持点、框、文本、全图多种交互方式 |
| ✅零样本迁移 | 预训练后直接用于边缘检测、实例分割等下游任务 |
| ✅高效实时 | Image Encoder 只跑一次,prompt 推理极快 |
| ✅模糊歧义处理 | 多候选掩膜输出,覆盖多种分割可能 |
| ✅大规模预训练 | 1100 万张图、10 亿+掩膜,泛化能力极强 |
| ✅开源可用 | 代码、模型、数据全部开源,社区活跃 |
6.2 缺点
| 缺点 | 说明 |
|---|---|
| ❌计算资源要求高 | ViT-H 参数量大,边缘设备部署困难 |
| ❌分割精度有限 | 对小目标、遮挡严重物体的分割精度不如专用模型 |
| ❌语义信息缺失 | 本身只输出掩膜,不包含类别标签(需额外分类器) |
| ❌视频处理能力弱 | SAM 只处理单帧图像,时序信息需配合 SAM 2 |
| ❌医学图像精度不足 | 通用 SAM 直接用于医学图像精度较低,需 MedSAM 等微调版本 |
| ❌依赖高质量 bbox | 用 COCO bbox 作为 prompt 时,bbox 质量直接影响分割效果 |
| ❌多候选增加计算 | multimask_output=True 输出多个候选,增加后处理负担 |
6.3 SAM vs 其他分割模型对比
| 对比维度 | 传统分割模型(U-Net等) | SAM(原生) | SAM + 微调(MedSAM) |
|---|---|---|---|
| 训练数据 | 小规模专用数据集 | 1100万张大规模 | 少量医学数据 |
| 泛化能力 | 只对训练类别有效 | 零样本分割任意物体 | 专精医学任务 |
| Prompt 驱动 | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 需微调 | 必须微调 | 可零样本直接用 | 少量数据微调 |
| 计算量 | 较小 | 中等(ViT-H 大) | 中等 |
| 医学图像精度 | 高(专用) | 中等(需微调) | 高(专用微调) |
七、应用流程总结
1. 加载 SAM 模型(基础版或 MedSAM 版) ↓ 2. 输入图像 ↓ 3. 提供 prompt(点/框/文本/无提示) ↓ 4. SAM 生成高质量分割掩膜 ↓ 5. 后处理: · 提取轮廓 → 边缘检测 · 计算面积 → 尺寸测量 · 三维重建 → 体数据建模 · 类别映射 → 语义分割