第7讲:分割大模型——SAM(Segment Anything Model)
第7讲:分割大模型——SAM(Segment Anything Model)
一、从一个"抠图"需求开始
1.1 传统分割的痛点
想象你有一张合影,想把背景换成海边:
传统图像分割方法:
方法1:手动抠图
用Photoshop的魔棒工具一点点选
头发丝?手指边缘?抠到崩溃!
方法2:自动分割(传统深度学习)
训练一个"人"的分割模型
问题:只能分割"人",不能分割"猫"、"汽车"、"椅子"...
换个类别就要重新训练!
方法3:语义分割
模型输出每个像素的类别(人/车/路/树)
问题:只能识别训练时见过的类别
遇到"新型滑板车"?不认识,就分不出来
核心痛点:传统模型是"封闭式"的——只能处理预定义的类别。
1.2 SAM的革命:“任意分割” anything
Meta(Facebook)2023年发布的SAM,提出了全新范式:
传统分割:
输入:图片
模型:训练好的"人分割器"
输出:人的掩码
限制:只能分人,不能分猫
SAM的分割:
输入:图片 + 你的提示(Prompt)
↓
提示可以是:
- "点这里"(鼠标点击)
- "框住这个区域"(画矩形框)
- "分割这只猫"(文本描述)
- "在这个大致形状上细化"(粗糙掩码)
↓
输出:你提示的那个对象的精确掩码
能力:任何对象!训练时没见过也能分!
通俗理解:
传统模型像"专科医生":只看眼科,不看牙科。
SAM像"全科医生+听诊器":你指哪里,我看哪里,什么都能看。
二、SAM的核心思想:提示驱动分割
2.1 什么是"提示"(Prompt)?
提示就是你给模型的"指令",告诉它"我要分割什么"。
| 提示类型 | 形式 | 例子 | 适合场景 |
|---|---|---|---|
| 点提示 | 单点/多点坐标 | 点击猫的身体 | 快速选择对象 |
| 框提示 | 矩形框坐标 | 画框框住汽车 | 对象边界清晰 |
| 文本提示 | 自然语言 | “分割红色的椅子” | 语义明确的对象 |
| 掩码提示 | 粗略掩码 | 手绘大致轮廓 | 需要细化边缘 |
关键洞察:
传统模型:模型自己决定"要分什么"(预定义类别)
SAM:用户通过Prompt告诉模型"要分什么"(开放类别)
结果:SAM不需要认识"猫"这个类别,
你只需要点一下猫,它就帮你把猫抠出来!
2.2 为什么"任意分割"可能?
秘密1:强大的图像编码器
用ViT-Huge(632M参数)把图像变成"超级特征图"
这个特征图包含了图像的所有视觉信息
不只是"类别信息",而是"一切视觉线索"
秘密2:提示编码器理解你的意图
把你的点击/框/文本变成"查询向量"
这个向量指向特征图中的特定区域
秘密3:掩码解码器做精细分割
结合图像特征 + 提示查询 → 输出精确掩码
甚至输出多个候选掩码供你选择!
三、SAM架构三件套
3.1 整体架构
┌─────────────────────────────────────────────────────────┐
│ 输入 │
│ 图像 [3, 1024, 1024] + 提示(点/框/文本/掩码) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────┴─────────────────┐
↓ ↓
┌───────────────┐ ┌───────────────┐
│ Image Encoder │ │ Prompt Encoder │
│ 图像编码器 │ │ 提示编码器 │
│ │ │ │
│ • ViT-Huge │ │ • 点/框编码 │
│ • MAE预训练 │ │ • 文本编码(CLIP) │
│ • 输出: 64×64 │ │ • 掩码编码 │
│ 特征图 │ │ │
│ │ │ 输出: 查询向量 │
│ [256, 64, 64] │ │ [N, 256] │
└───────────────┘ └───────────────┘
↓ ↓
└─────────────────┬─────────────────┘
↓
┌─────────────────┐
│ 特征融合 │
│ 图像特征 + 提示查询 │
└─────────────────┘
↓
┌─────────────────┐
│ Mask Decoder │
│ 掩码解码器 │
│ │
│ • 两层Transformer│
│ • 动态掩码预测 │
│ • 输出3个候选掩码│
│ (解决歧义性) │
└─────────────────┘
↓
┌─────────────────┐
│ 输出: 分割掩码 │
│ [1, 1024, 1024] │
│ 二值图:1=前景,0=背景│
└─────────────────┘
3.2 组件1:Image Encoder(图像编码器)
作用:把整张图变成"语义地图"
架构:ViT-Huge(Vision Transformer Huge)
- 输入:1024×1024的图像
- 分块:16×16的patch → 64×64=4096个块
- 处理:32层Transformer
- 输出:256维的特征图 [256, 64, 64]
关键:用MAE(Masked Autoencoder)预训练
MAE把图像大块遮住,让模型重建原图
强迫模型学会"理解图像内容"而不仅是"记住标签"
为什么用这么大的编码器?
分割需要精细的边界信息。小模型只能捕捉大致形状,大模型能学到毛发、透明物体、阴影等细节。
3.3 组件2:Prompt Encoder(提示编码器)
作用:把你的"指示"变成模型能理解的向量
┌────────────────────────────────────────┐
│ 点提示 (Point Prompt) │
│ ───────────────── │
│ 输入:坐标(x,y) + 标签(前景点/背景点) │
│ 编码:位置嵌入 + 类型嵌入 │
│ 输出:256维向量 │
└────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ 框提示 (Box Prompt) │
│ ───────────────── │
│ 输入:左上角(x1,y1) + 右下角(x2,y2) │
│ 编码:把框变成一对点(左上+右下)的嵌入 │
│ 输出:2×256维向量 │
└────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ 文本提示 (Text Prompt) │
│ ───────────────── │
│ 输入:"一只橘色的猫" │
│ 编码:CLIP文本编码器 │
│ 输出:256维向量(与图像特征对齐) │
└────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ 掩码提示 (Mask Prompt) │
│ ───────────────── │
│ 输入:粗略的二值掩码(手绘或上一轮输出) │
│ 编码:小型CNN下采样 + 卷积嵌入 │
│ 输出:256维向量 │
└────────────────────────────────────────┘
3.4 组件3:Mask Decoder(掩码解码器)
作用:把"图像理解" + "用户意图" → 精确掩码
架构:
输入1:图像特征 [256, 64, 64]
输入2:提示查询 [N, 256](N=提示数量)
处理:
1. 提示查询做自注意力(多个提示之间交互)
2. 提示查询与图像特征做交叉注意力
3. 上采样到原始分辨率
4. MLP预测每个像素的"前景概率"
输出:3个候选掩码 + 置信度分数
为什么输出3个?
解决"歧义性"!
例子:你点了一个点,但这个点在"杯子把手"上
掩码1:只分手把(局部)
掩码2:分整个杯子(整体)
掩码3:分杯子和手(如果手在旁边)
让用户选最符合意图的那个!
四、SAM 2:从图片到视频
4.1 视频分割的挑战
图片分割:
点一下 → 出掩码 → 结束
视频分割:
第1帧:点一下 → 出掩码
第2帧:对象移动了!掩码怎么跟过去?
第3帧:对象被遮挡了!怎么记住它?
第100帧:对象变形了!怎么适应?
4.2 SAM 2的记忆机制
SAM 2的核心创新:记忆编码器 + 记忆库
┌─────────────────────────────────────────┐
│ 第1帧处理 │
│ 输入:图像 + 提示 │
│ 输出:掩码 + 记忆特征 │
│ ↓ │
│ 存入记忆库 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 第2帧处理 │
│ 输入:新图像 + 记忆库(第1帧的信息) │
│ 模型:"根据上一帧的记忆,找同一个对象" │
│ 输出:新掩码 + 更新记忆 │
│ ↓ │
│ 更新记忆库 │
└─────────────────────────────────────────┘
↓
...循环...
记忆库组成:
1. 对象记忆:目标对象的视觉特征
2. 位置记忆:对象在帧间的运动轨迹
3. 外观记忆:对象的不同角度/姿态
通俗理解:
SAM像"追踪侦探":第1帧记住嫌疑人的脸,之后每帧都翻档案对比,即使嫌疑人换了衣服、戴了帽子,也能认出来。
五、动手实验:用SAM做分割
5.1 环境准备
# 安装SAM
pip install git+https://github.com/facebookresearch/segment-anything.git
# 安装SAM 2(如果需要视频分割)
pip install git+https://github.com/facebookresearch/segment-anything-2.git
# 下载预训练权重
wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth # Huge
wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_l_0b3195.pth # Large
wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth # Base
5.2 实验1:自动分割一切(Everything Mode)
import numpy as np
import torch
import matplotlib.pyplot as plt
from PIL import Image
import cv2
# 导入SAM
try:
from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor
SAM_AVAILABLE = True
except ImportError:
SAM_AVAILABLE = False
print("SAM未安装,请先安装:pip install segment-anything")
if SAM_AVAILABLE:
print("=" * 60)
print("【实验1】SAM自动分割所有对象")
print("=" * 60)
# 加载模型
model_type = "vit_h" # 可选: vit_b, vit_l, vit_h
checkpoint = "sam_vit_h_4b8939.pth"
device = "cuda" if torch.cuda.is_available() else "cpu"
sam = sam_model_registry[model_type](checkpoint=checkpoint)
sam.to(device)
print(f"✅ 加载SAM-{model_type.upper()},设备:{device}")
print(f"参数量:{sum(p.numel() for p in sam.parameters())/1e6:.0f}M")
# 创建自动分割器
mask_generator = SamAutomaticMaskGenerator(
model=sam,
points_per_side=32, # 每边采样32个点
pred_iou_thresh=0.9, # IoU阈值
stability_score_thresh=0.95,
crop_n_layers=1, # 多层裁剪(处理不同尺度)
crop_n_points_downscale_factor=2,
min_mask_region_area=100, # 最小掩码面积
)
# 加载图片(替换为你的图片路径)
# 这里用随机生成的演示图
np.random.seed(42)
image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
# 实际使用时:
# image = cv2.imread("your_image.jpg")
# image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
print(f"\n输入图像形状: {image.shape}")
# 生成所有掩码
print("正在生成掩码(可能需要几秒到几分钟)...")
masks = mask_generator.generate(image)
print(f"✅ 生成了 {len(masks)} 个掩码!")
# 可视化
def show_anns(anns, image):
if len(anns) == 0:
return image
sorted_anns = sorted(anns, key=(lambda x: x['area']), reverse=True)
# 创建叠加图
overlay = image.copy().astype(np.float32) / 255.0
for ann in sorted_anns:
m = ann['segmentation']
color_mask = np.random.random(3)
# 创建彩色掩码
img_mask = np.zeros_like(overlay)
for c in range(3):
img_mask[:, :, c] = color_mask[c]
# 叠加
overlay[m > 0] = overlay[m > 0] * 0.5 + img_mask[m > 0] * 0.5
return (overlay * 255).astype(np.uint8)
result = show_anns(masks, image)
# 显示
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
axes[0].imshow(image)
axes[0].set_title('Original Image')
axes[0].axis('off')
axes[1].imshow(result)
axes[1].set_title(f'SAM Auto-Mask: {len(masks)} objects')
axes[1].axis('off')
plt.tight_layout()
plt.savefig('/mnt/agents/output/sam_auto_mask.png', dpi=150)
plt.show()
# 打印掩码信息
print(f"\n掩码信息示例(前3个):")
for i, mask in enumerate(masks[:3]):
print(f" 掩码{i+1}:")
print(f" 面积: {mask['area']} 像素")
print(f" 边界框: {mask['bbox']}")
print(f" 置信度: {mask['stability_score']:.3f}")
print(f" 预测IoU: {mask['predicted_iou']:.3f}")
print("\n📊 自动分割结果已保存!")
5.3 实验2:交互式分割(点提示)
if SAM_AVAILABLE:
print("\n" + "=" * 60)
print("【实验2】交互式分割:用点击选择对象")
print("=" * 60)
# 创建预测器
predictor = SamPredictor(sam)
# 设置图像
predictor.set_image(image)
# 模拟点击:选择图像中心点
h, w = image.shape[:2]
input_point = np.array([[w//2, h//2]]) # 中心点
input_label = np.array([1]) # 1=前景点,0=背景点
print(f"点击位置: ({w//2}, {h//2})")
print(f"标签: 前景点(要分割的对象)")
# 预测掩码
masks, scores, logits = predictor.predict(
point_coords=input_point,
point_labels=input_label,
multimask_output=True, # 输出3个候选掩码
)
print(f"\n生成了 {len(masks)} 个候选掩码,置信度:")
for i, score in enumerate(scores):
print(f" 掩码{i+1}: {score:.3f}")
# 可视化
fig, axes = plt.subplots(1, 4, figsize=(16, 4))
# 原图+点击点
axes[0].imshow(image)
axes[0].scatter(input_point[:, 0], input_point[:, 1],
c='red', s=200, marker='*', edgecolors='yellow', linewidths=2)
axes[0].set_title('Click Point')
axes[0].axis('off')
# 3个候选掩码
for i in range(3):
ax = axes[i+1]
ax.imshow(image)
# 显示掩码轮廓
mask = masks[i]
contours, _ = cv2.findContours(
mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
ax.contour(mask, colors=['yellow'], linewidths=2)
# 填充半透明
colored_mask = np.zeros_like(image)
colored_mask[mask > 0] = [255, 0, 0] # 红色
ax.imshow(colored_mask, alpha=0.3)
ax.set_title(f'Mask {i+1}\nScore: {scores[i]:.3f}')
ax.axis('off')
plt.tight_layout()
plt.savefig('/mnt/agents/output/sam_interactive_point.png', dpi=150)
plt.show()
print("\n📊 交互式分割结果已保存!")
print("""
解读3个候选掩码:
掩码1(高分):通常是最合理的整体分割
掩码2(中分):可能是局部部分(如只分手把)
掩码3(低分):可能是更大范围(如包含相邻对象)
用户可以根据意图选择最合适的!
""")
5.4 实验3:框提示 + 多点提示
if SAM_AVAILABLE:
print("\n" + "=" * 60)
print("【实验3】框提示与多点提示")
print("=" * 60)
# 框提示
input_box = np.array([w//4, h//4, 3*w//4, 3*h//4]) # 中心区域
masks_box, scores_box, _ = predictor.predict(
point_coords=None,
point_labels=None,
box=input_box[None, :], # [1, 4]
multimask_output=True,
)
print(f"框提示: [{input_box[0]}, {input_box[1]}, {input_box[2]}, {input_box[3]}]")
print(f"置信度: {scores_box}")
# 多点提示(前景+背景)
input_points = np.array([
[w//2, h//2], # 前景点:中心
[w//8, h//8], # 背景点:左上角(排除区域)
[7*w//8, 7*h//8], # 背景点:右下角(排除区域)
])
input_labels = np.array([1, 0, 0]) # 1=前景, 0=背景
masks_multi, scores_multi, _ = predictor.predict(
point_coords=input_points,
point_labels=input_labels,
multimask_output=True,
)
print(f"\n多点提示:")
print(f" 前景点: ({w//2}, {h//2})")
print(f" 背景点: ({w//8}, {h//8}), ({7*w//8}, {7*h//8})")
print(f" 置信度: {scores_multi}")
# 可视化对比
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
# 框提示结果
for i in range(3):
ax = axes[0, i]
ax.imshow(image)
# 画框
rect = plt.Rectangle(
(input_box[0], input_box[1]),
input_box[2]-input_box[0],
input_box[3]-input_box[1],
fill=False, edgecolor='green', linewidth=2
)
ax.add_patch(rect)
if i < len(masks_box):
mask = masks_box[i]
colored_mask = np.zeros_like(image)
colored_mask[mask > 0] = [0, 255, 0]
ax.imshow(colored_mask, alpha=0.3)
ax.set_title(f'Box Prompt\nMask {i+1}: {scores_box[i]:.3f}')
else:
ax.set_title('Box Prompt\n(No mask)')
ax.axis('off')
# 多点提示结果
colors_pt = ['red', 'blue', 'blue'] # 前景红,背景蓝
for i in range(3):
ax = axes[1, i]
ax.imshow(image)
# 画点
for pt, label, color in zip(input_points, input_labels, colors_pt):
marker = '*' if label == 1 else 'x'
ax.scatter(pt[0], pt[1], c=color, s=200, marker=marker,
edgecolors='white', linewidths=2)
if i < len(masks_multi):
mask = masks_multi[i]
colored_mask = np.zeros_like(image)
colored_mask[mask > 0] = [255, 165, 0] # 橙色
ax.imshow(colored_mask, alpha=0.3)
ax.set_title(f'Multi-Point\nMask {i+1}: {scores_multi[i]:.3f}')
else:
ax.set_title('Multi-Point\n(No mask)')
ax.axis('off')
plt.tight_layout()
plt.savefig('/mnt/agents/output/sam_box_multipoint.png', dpi=150)
plt.show()
print("\n📊 框提示与多点提示结果已保存!")
print("\n" + "=" * 60)
print("【总结】SAM提示类型对比")
print("=" * 60)
print("""
点提示:
✅ 最快速,单次点击
⚠️ 可能有歧义(点在手把 vs 杯子)
框提示:
✅ 明确范围,减少歧义
✅ 适合对象边界清晰
⚠️ 需要画框,稍慢
多点提示:
✅ 最精确,可以排除区域
✅ 前景+背景点组合
⚠️ 需要多次点击
文本提示(SAM 3/GLIP):
✅ 最自然,说就行
⚠️ 需要额外的文本编码器
""")
5.5 实验4:SAM 2视频分割(概念演示)
print("\n" + "=" * 60)
print("【实验4】SAM 2视频分割原理(代码框架)")
print("=" * 60)
print("""
SAM 2视频分割流程:
步骤1:初始化
from sam2.build_sam import build_sam2_video_predictor
predictor = build_sam2_video_predictor(
"sam2_hiera_l.yaml",
"sam2_hiera_large.pt"
)
步骤2:加载视频
video_path = "your_video.mp4"
inference_state = predictor.init_state(video_path=video_path)
步骤3:第1帧加提示
frame_idx = 0
obj_id = 1 # 对象ID
predictor.add_new_points_or_boxes(
inference_state=inference_state,
frame_idx=frame_idx,
obj_id=obj_id,
points=[[x, y]], # 点击位置
labels=[1], # 前景点
)
步骤4:传播到整个视频
video_segments = {} # 存储每帧的掩码
for out_frame_idx, out_obj_ids, out_mask_logits in predictor.propagate_in_video(
inference_state
):
masks = (out_mask_logits > 0.0).cpu().numpy()
video_segments[out_frame_idx] = {
obj_id: masks[i] for i, obj_id in enumerate(out_obj_ids)
}
关键机制:
1. 记忆编码器:把每帧的特征存入记忆库
2. 记忆注意力:当前帧查询记忆库,找相似对象
3. 对象指针:跟踪多个对象(obj_id区分)
4. 自适应更新:对象外观变化时更新记忆
""")
# 由于SAM 2需要特定环境,这里提供伪代码框架
# 实际使用时参考官方仓库
print("\n✅ SAM 2视频分割框架说明完成!")
六、应用场景
6.1 医学影像
任务:分割肿瘤、器官、病灶
传统方法:
需要大量标注数据,且只能分割预定义类别
"这个罕见肿瘤类型?模型不认识。"
SAM方案:
医生点一下肿瘤区域 → SAM自动分割
不需要训练!零样本分割!
应用:
- CT/MRI中的器官分割
- 病理切片中的细胞分割
- 内镜视频中的病灶追踪
6.2 自动驾驶
任务:分割道路、车辆、行人、障碍物
挑战:
- 开放世界:总有没见过的物体(新型滑板车、掉落货物)
- 实时性:需要30FPS+
SAM应用:
数据标注:自动生成分割掩码,人工只需检查修正
训练效率提升10倍!
在线检测:结合SAM 2做视频对象追踪
即使对象被遮挡,也能记住并重新找到
6.3 图像编辑
任务:抠图换背景、对象移除、风格迁移
传统Photoshop:
魔棒工具 → 调整边缘 → 蒙版细化 → 1小时
SAM方案:
点一下对象 → 3秒出精确掩码 → 直接编辑
应用:
- 电商:商品自动抠图换白底
- 影视:绿幕替换
- 设计:对象提取重组
七、核心总结
| 概念 | 一句话解释 |
|---|---|
| SAM | 提示驱动的任意分割模型,点/框/文本/掩码都能用 |
| Image Encoder | ViT-Huge提取图像全局特征 |
| Prompt Encoder | 把用户提示变成查询向量 |
| Mask Decoder | 融合特征+查询,输出精确掩码(3个候选) |
| 歧义性解决 | 输出3个掩码供选择:局部/整体/相关 |
| SAM 2 | 加入记忆机制,支持视频分割和对象追踪 |
| 零样本 | 没见过也能分,不需要任务特定训练 |
八、面试高频题
Q1:SAM和传统分割模型(如U-Net)的区别?
答:U-Net等是"封闭式"模型:预定义类别,只能分割训练时见过的类。SAM是"开放式"模型:通过提示(点/框/文本)指定分割目标,零样本分割任何对象。SAM把"分割什么"的决策权交给用户,而不是模型自己决定。
Q2:SAM为什么输出3个掩码?
答:解决提示的歧义性。一个点或框可能对应多个合理分割(如手把 vs 整个杯子)。3个掩码分别代表局部、整体、相关区域,让用户选择最符合意图的。这体现了SAM对"用户意图不确定性"的设计考虑。
Q3:SAM 2的记忆机制怎么工作?
答:SAM 2维护一个记忆库,存储已处理帧的对象特征、位置、外观。新帧处理时,通过记忆注意力查询记忆库,匹配同一对象。即使对象移动、变形、短暂遮挡,也能持续追踪。记忆会自适应更新,适应对象外观变化。
Q4:SAM在工业部署中的挑战?
答:1)Image Encoder太大(ViT-Huge,632M参数),推理慢;2)高分辨率输入(1024×1024)计算量大;3)提示编码和掩码解码需要多次前向传播。优化方向:模型蒸馏(MobileSAM)、量化、TensorRT加速、分辨率自适应。
九、课后作业
作业1:多提示组合实验
# 尝试组合提示:
# 1. 先点前景,再加背景点排除误分区域
# 2. 先用框提示大致范围,再用点提示精确调整
# 3. 对比单点 vs 多点 vs 框的精度差异
作业2:SAM辅助标注
# 用SAM自动生成COCO格式的分割标注
# 人工只需点击检查,大幅节省标注时间
# 目标:把标注效率提升10倍
作业3:思考SAM的局限
问题:SAM有什么做不到?
提示:
1. 非常细小的结构(如头发丝)?
2. 透明/反光物体(如玻璃、镜子)?
3. 需要语义理解的分割(如"分割所有红色的东西")?
4. 3D场景的分割?
十、下讲预告
第8讲:自监督视觉大模型——DINO与DINOv2
我们将:
- 理解"自己教自己"的自监督学习
- 探索知识蒸馏和动量编码器
- 用DINOv2提取特征做相似度检索
- 了解无标签数据训练的威力
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)