引言:SAM模型概述与上海交大课程背景

Segment Anything Model (SAM) 是由 Meta AI (Facebook Research) 在 2023 年推出的一个革命性基础模型,旨在实现“万物皆可分割”的目标。它能够根据提示(如点、框或文本)对图像中的任意对象进行零样本(Zero-shot)分割,无需针对特定任务进行微调。

上海交通大学(SJTU)作为国内顶尖高校,其计算机视觉或深度学习相关课程(如 ACM 班、电子信息与电气工程学院的研究生课程)往往会将 SAM 作为前沿模型进行深入剖析。本指南旨在模拟上海交大 SAM 课程的实战教学路径,从零基础出发,逐步深入到核心代码实现、微调技巧及高级应用,帮助你真正掌握这一核心技能。


第一阶段:环境搭建与基础认知

在开始编写代码之前,必须确保你的开发环境配置正确。SAM 模型对 PyTorch 和 GPU 有较高的要求。

1.1 硬件与软件要求

  • 硬件:推荐使用 NVIDIA GPU(显存 >= 8GB,推荐 RTX 30904090 或 A100)。
  • 操作系统:Linux (Ubuntu) 或 Windows (WSL2)。
  • Python 版本:Python 3.8 - 3.10。

1.2 创建 Conda 环境

为了避免依赖冲突,建议使用 Conda 创建独立环境。

# 创建环境
conda create -n sam_course python=3.9 -y

# 激活环境
conda activate sam_course

# 安装 PyTorch (请根据 CUDA 版本选择,此处以 CUDA 11.8 为例)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 安装基础依赖
pip install opencv-python pycocotools matplotlib onnxruntime onnx

1.3 获取 SAM 源码与模型权重

上海交大课程通常会要求阅读源码,因此我们直接从 GitHub 克隆官方仓库。

git clone https://github.com/facebookresearch/segment-anything.git
cd segment-anything

# 安装 SAM 依赖
pip install -e .

# 下载预训练权重 (官方提供了几种大小的模型,这里下载最常用的 ViT-H)
# 如果下载慢,可以使用国内镜像或手动下载后放入 checkpoints 文件夹
wget https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth

第二阶段:核心代码实战——零样本推理

这一阶段是课程的入门部分,重点在于理解 SAM 的推理流程:图像编码 -> 提示编码 -> 掩码解码

2.1 基础单点/多点分割

SAM 最直观的用法是通过点击(Click)来指定要分割的对象。

代码示例:

import sys
sys.path.append("..") # 确保能找到 segment_anything 模块
from segment_anything import SamPredictor, sam_model_registry
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 1. 加载模型
# 这里的 'sam_vit_h_4b8939.pth' 是我们下载的权重路径
# 'vit_h' 是模型类型
sam = sam_model_registry["vit_h"](checkpoint="sam_vit_h_4b8939.pth")
predictor = SamPredictor(sam)

# 2. 加载并处理图像
# 假设你有一张名为 'demo.jpg' 的图片
image = cv2.imread('demo.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 3. 图像编码
# 这一步是 SAM 的核心耗时操作,它将图像转换为嵌入向量(Embedding)
# 一旦编码,可以对不同的提示重复使用
predictor.set_image(image)

# 4. 定义提示(Prompts)
# 假设我们要分割图像中心的一个物体,坐标为 (x, y)
# 注意:坐标是 (H, W) 格式,即 (行, 列)
input_point = np.array([[500, 300]]) 
input_label = np.array([1]) # 1 表示前景点,0 表示背景点

# 5. 预测掩码
masks, scores, logits = predictor.predict(
    point_coords=input_point,
    point_labels=input_label,
    multimask_output=True # True 返回 3 个候选掩码,False 返回 1 个
)

# 6. 可视化结果
def show_mask(mask, ax, random_color=False):
    if random_color:
        color = np.concatenate([np.random.random(3), np.array([0.6])], axis=0)
    else:
        color = np.array([30/255, 144/255, 255/255, 0.6])
    h, w = mask.shape[-2:]
    mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, -1)
    ax.imshow(mask_image)

def show_points(coords, labels, ax, marker_size=375):
    pos_points = coords[labels == 1]
    neg_points = coords[labels == 0]
    ax.scatter(pos_points[:, 0], pos_points[:, 1], color='green', marker='*', s=marker_size, edgecolor='white', linewidth=1.25)
    ax.scatter(neg_points[:, 0], neg_points[:, 1], color='red', marker='*', s=marker_size, edgecolor='white', linewidth=1.25)

plt.figure(figsize=(10, 10))
plt.imshow(image)
show_mask(masks[0], plt.gca()) # 显示得分最高的掩码
show_points(input_point, input_label, plt.gca())
plt.title(f"Score: {scores[0]:.3f}", fontsize=18)
plt.axis('off')
plt.show()

代码解析:

  • SamPredictor: 这是用于推理的类,它封装了图像编码和提示处理的逻辑。
  • set_image: 执行 ViT (Vision Transformer) 编码,生成图像特征图。这是计算量最大的一步。
  • multimask_output: SAM 会预测 3 个掩码(对应粗略、中等、精细),并给出置信度分数。通常选择 masks[0](分数最高的)。

2.2 框(Bounding Box)提示

除了点,SAM 也支持框输入,这在目标检测任务中非常有用。

# 接上面的代码,predictor 已经 set_image 了

# 定义框:[x_min, y_min, x_max, y_max]
input_box = np.array([400, 200, 600, 400])

# 预测
masks, _, _ = predictor.predict(
    point_coords=None,
    point_labels=None,
    box=input_box[None, :], # 需要增加一维变成 (1, 4)
    multimask_output=False
)

# 可视化框
import matplotlib.patches as patches
plt.figure(figsize=(10, 10))
plt.imshow(image)
show_mask(masks[0], plt.gca())
# 画框
rect = patches.Rectangle((input_box[0], input_box[1]), input_box[2]-input_box[0], input_box[3]-input_box[1], 
                         linewidth=2, edgecolor='r', facecolor='none')
plt.gca().add_patch(rect)
plt.axis('off')
plt.show()

第三阶段:深入架构——理解 SAM 的三大组件

上海交大课程的深度在于不仅会用,还要懂原理。SAM 由三个主要部分组成:

  1. 图像编码器 (Image Encoder):

    • 通常是一个 ViT (Vision Transformer)
    • 作用:处理高分辨率图像,提取全局和局部特征。
    • 特点:计算量大,但只需运行一次(Once)。
  2. 提示编码器 (Prompt Encoder):

    • 处理点(Positional Encoding)和框(Embedding)。
    • 处理稀疏提示(点/框)和稠密提示(文本/掩码)。
    • 特点:轻量级,计算非常快。
  3. 掩码解码器 (Mask Decoder):

    • 基于 Transformer 的解码器。
    • 输入:图像嵌入 + 提示嵌入。
    • 输出:掩码 + 置信度分数。
    • 特点:非常快,可以在几毫秒内生成掩码。

实战思考:为什么 SAM 能做到“实时”? 因为图像编码可以离线完成,而解码器极快。在实际应用(如视频编辑)中,我们通常先对视频帧进行离线编码,然后在用户交互时实时解码。


第四阶段:进阶应用——自动化与批量处理

在实际工程中,手动点击是不现实的。我们需要结合其他算法(如边缘检测、显著性检测)来自动化生成提示。

4.1 结合 OpenCV 自动化分割

我们可以使用 OpenCV 的阈值处理或边缘检测来获取初始点,然后喂给 SAM。

策略:

  1. 使用 OpenCV 找到图像中最大的轮廓。
  2. 计算轮廓的中心点作为 SAM 的正样本点。
  3. 调用 SAM 获取高质量掩码。

代码示例:

def auto_segment_sam(image_path, predictor):
    # 读图
    img = cv2.imread(image_path)
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    # 预处理:灰度化 + 二值化
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    
    # 寻找轮廓
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    if not contours:
        print("未找到轮廓")
        return
    
    # 找到最大的轮廓
    c = max(contours, key=cv2.contourArea)
    
    # 计算轮廓中心
    M = cv2.moments(c)
    if M["m00"] != 0:
        cX = int(M["m10"] / M["m00"])
        cY = int(M["m01"] / M["m00"])
    else:
        cX, cY = 0, 0
        
    # SAM 推理
    predictor.set_image(img_rgb)
    input_point = np.array([[cX, cY]])
    input_label = np.array([1])
    
    masks, scores, _ = predictor.predict(point_coords=input_point, point_labels=input_label)
    
    # 选择最佳掩码
    best_mask = masks[np.argmax(scores)]
    
    return best_mask

# 使用示例
# mask = auto_segment_sam('demo.jpg', predictor)

第五阶段:模型微调 (Fine-tuning) 与 自定义训练

虽然 SAM 是基础模型,但在特定领域(如医疗影像、卫星图)可能表现不佳。上海交大课程通常会涉及如何利用 LoRA (Low-Rank Adaptation) 或全量微调来适配特定数据集。

5.1 数据集准备

你需要准备 COCO 格式或自定义格式的数据。关键在于提示生成策略

  • 正样本:掩码内部的随机点。
  • 负样本:掩码外部的随机点。
  • :掩码的最小外接矩形。

5.2 微调代码逻辑 (PyTorch)

微调的核心是定义 Loss。SAM 的原始训练 Loss 包含:

  • Mask Loss: 掩码的 Dice Loss 或 Focal Loss。
  • Iou Loss: 预测 IoU 与真实 IoU 的 MSE Loss。

微调伪代码结构:

import torch
from segment_anything.utils.transforms import ResizeLongestSide

# 1. 准备模型
sam = sam_model_registry["vit_h"](checkpoint="sam_vit_h_4b8939.pth")

# 2. 冻结图像编码器 (通常只微调解码器,或者使用 LoRA)
for param in sam.image_encoder.parameters():
    param.requires_grad = False

# 3. 定义优化器 (针对解码器)
optimizer = torch.optim.AdamW(sam.mask_decoder.parameters(), lr=1e-4, weight_decay=0.01)

# 4. 数据加载器 (假设已实现)
# train_loader 返回: image, ground_truth_mask, prompts (points/boxes)
resize_transform = ResizeLongestSide(sam.image_encoder.img_size)

def train_step(image, gt_mask, input_points, input_labels):
    # 预处理图像
    input_image = resize_transform.apply_image_torch(image)
    
    # 图像编码 (如果图像编码器未冻结,这里会更新梯度)
    image_embedding = sam.image_encoder(input_image)
    
    # 提示编码
    sparse_embeddings, dense_embeddings = sam.prompt_encoder(
        points=(input_points, input_labels),
        boxes=None,
        masks=None
    )
    
    # 掩码解码
    low_res_masks, iou_predictions = sam.mask_decoder(
        image_embeddings=image_embedding,
        image_pe=sam.prompt_encoder.get_dense_pe(),
        sparse_prompt_embeddings=sparse_embeddings,
        dense_prompt_embeddings=dense_embeddings,
        multimask_output=True
    )
    
    # 计算 Loss (示例:Focal Loss + Dice Loss)
    # 这里需要将 low_res_masks 上采样回原尺寸与 gt_mask 比较
    # 实际实现中通常使用官方提供的 calculate_loss 函数
    
    # 反向传播
    loss = ... # 计算 Loss
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    return loss

微调技巧 (Tips):

  • LoRA: 使用 peft 库对 qkv 层添加低秩适配器,显存占用极低,效果接近全量微调。
  • 数据增强: 对输入图像进行翻转、旋转,但要注意提示点(Point)也要随之变换。

第六阶段:高级应用与部署

掌握了核心技能后,我们需要考虑如何将 SAM 应用到实际项目中。

6.1 视频对象分割 (Video Object Segmentation)

利用 SAM 处理视频的关键是时序一致性

  1. 第一帧:用户手动点击或使用检测器生成掩码。
  2. 后续帧:使用光流(Optical Flow)或目标追踪算法(如 Tracktor)将第一帧的点坐标传播到后续帧。
  3. 修正:如果追踪失败,使用 SAM 重新生成。

6.2 3D 医学影像分割

SAM 在医学领域(CT/MRI)应用广泛。

  • 切片处理:将 3D Volume 视为一系列 2D 图像。
  • 提示策略:在中心切片点击一次,生成掩码,然后传播到相邻切片作为初始提示。

6.3 部署优化

  • ONNX 导出:将 PyTorch 模型转换为 ONNX 格式,便于在 C++ 或 TensorRT 环境部署。
  • 量化:使用 INT8 量化减小模型体积,加速推理。
# 导出 ONNX 示例
import torch.onnx

# 假设 dummy_input 是符合模型输入的张量
dummy_input = (dummy_image_embedding, dummy_pe, dummy_sparse, dummy_dense)
torch.onnx.export(
    sam.mask_decoder,
    dummy_input,
    "sam_mask_decoder.onnx",
    opset_version=11,
    input_names=['image_embedding', 'image_pe', 'sparse_prompt_embeddings', 'dense_prompt_embeddings'],
    output_names=['masks', 'iou_predictions']
)

总结:从零基础到精通的学习路线

通过上海交大 SAM 课程实战指南的学习,你应该掌握了以下路径:

  1. 基础层:环境配置、API 调用、点/框推理。
  2. 原理层:理解 ViT 编码器、提示器、解码器的交互机制。
  3. 工程层:结合 OpenCV 实现自动化提示生成,处理批量数据。
  4. 进阶层:掌握 LoRA 微调技术,适配垂直领域数据。
  5. 应用层:探索视频、3D 医疗及 ONNX 部署。

SAM 不仅仅是一个工具,它是提示工程(Prompt Engineering)在视觉领域的集大成者。精通 SAM,意味着你掌握了如何通过“交互”来引导 AI 理解视觉世界的边界。继续深入阅读源码,尝试复现 segment-anything-2 的最新进展,你的计算机视觉技能将迈上一个新的台阶。