1. 环境准备:搭建你的第一个SAM实验场

想玩转SAM,第一步就是把“厨房”收拾好。我见过太多新手卡在这一步,不是环境冲突就是依赖报错,最后热情都被磨没了。咱们今天的目标是,用最稳、最省心的方式,把SAM跑起来。别怕,跟着我一步步来,保证你能避开我当年踩过的那些坑。

首先,我强烈建议你使用 Anaconda 来管理你的Python环境。这就像给你的每个项目准备一个独立的“工作间”,里面工具齐全,而且不会和别的项目打架。如果你还没安装Anaconda,去官网下载一个,安装过程一路点“下一步”就行,没什么技术含量。

安装好之后,我们打开命令行(Windows用户用Anaconda Prompt,Mac/Linux用户用终端)。我们来创建一个专门给SAM用的环境,名字就叫 sam_env,Python版本用3.8或3.9都行,兼容性最好。

conda create -n sam_env python=3.9 -y

创建完成后,激活这个环境。这个动作相当于你走进了“sam_env”这个工作间。

conda activate sam_env

接下来,安装最核心的依赖:PyTorch。这是SAM运行的引擎。这里有个小技巧,如果你有NVIDIA的显卡并且想用GPU加速,安装命令会不一样。我们先按最通用的CPU版本来,确保所有人都能跑通。去PyTorch官网(pytorch.org)根据你的系统选择最合适的安装命令。对于大多数只想快速体验的初学者,直接用下面这个CPU版本命令最稳妥:

pip install torch torchvision torchaudio

然后,安装一些图像处理和可视化的必备工具包:

pip install opencv-python matplotlib numpy

这些工具就像你的“锅碗瓢盆”,后面读图、画图、处理数据全靠它们。环境准备好之后,我们就要去“搬”SAM这个核心算法了。

2. 获取模型与代码:把“大脑”请回家

SAM的“身体”是代码,而“大脑”是预训练好的模型文件。代码告诉SAM怎么工作,模型文件则是它通过学习海量图片获得的“经验”和“知识”。我们需要把这两样东西都准备好。

代码部分,我们需要从Meta的官方仓库获取。你可以在GitHub上搜索“facebookresearch/segment-anything”。这里我提供一个更直接的方法:如果你有Git,可以直接克隆仓库到本地。如果没有,也不用慌,我们可以直接下载ZIP包。

方法一(推荐,使用Git): 在你想要存放项目的文件夹里(比如 D:\MyProjects\),打开命令行(确保已经在 sam_env 环境里),执行:

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

方法二(下载ZIP): 直接访问上面那个GitHub链接,点击绿色的“Code”按钮,选择“Download ZIP”。下载后解压到一个你找得到的文件夹里,然后用命令行进入这个解压后的文件夹。

接下来是关键一步:安装SAM的Python包。进入 segment-anything 目录后,你会看到一个 setup.py 文件。运行下面的命令,它会自动安装SAM包以及所有必要的依赖。

pip install -e .

注意那个“-e”参数,它代表“可编辑模式”安装。这有什么好处呢?好处就是,你之后如果修改了SAM源码里的任何文件,Python都能直接识别到最新的改动,而不用反复重新安装。对于想深入研究代码的同学,这个操作非常友好。

现在,代码“身体”安装好了,该请“大脑”了。SAM提供了几个不同大小的预训练模型,你可以理解为“大聪明”、“中聪明”和“小聪明”。对于精度要求高、不差算力的,选最大的 vit_h;想平衡速度和精度的,选 vit_l;追求极致速度、在移动端尝试的,选 vit_b。

模型文件需要单独下载。官方在GitHub仓库的README里提供了下载链接。这里我直接给出三个模型的下载方式(以最大的 vit_h 为例):

  1. 官方链接:访问仓库首页,找到“Model Checkpoints”部分,点击 vit_h 对应的链接。
  2. 备用方式:由于模型文件较大(vit_h约2.4GB),网络不稳时容易失败。你可以用一些下载工具,或者寻找国内的镜像源。

下载完成后,你会得到一个 .pth 文件,比如 sam_vit_h_4b8939.pth。我建议你把这个模型文件放在你的项目根目录下,或者专门建一个 models 文件夹放进去,方便代码调用。记住这个文件的路径,等下写代码的时候要用到。

3. 核心代码解读:理解SAM如何“听话”

环境、代码、模型三件套齐了,现在我们打开“黑箱”,看看SAM到底是怎么通过我们点的几个点,就明白我们想分割什么的。理解了原理,你才能玩得转,而不是只会复制粘贴。

我们从一个最简单的脚本开始。新建一个Python文件,比如叫 sam_demo.py。先导入必要的“工具包”:

import numpy as np
import cv2
import matplotlib.pyplot as plt
from segment_anything import sam_model_registry, SamPredictor
import torch

这里 sam_model_registry 是模型“仓库”,根据你指定的类型(vit_h, vit_l, vit_b)来加载对应的模型架构。SamPredictor 是预测器,是我们和模型交互的主要接口。

接下来,加载模型。这里需要指定两个关键信息:模型类型和模型权重文件的路径。

# 1. 定义模型路径和类型
sam_checkpoint = "./models/sam_vit_h_4b8939.pth"  # 你下载的.pth文件路径
model_type = "vit_h"  # 与你下载的模型对应
device = "cuda" if torch.cuda.is_available() else "cpu"  # 自动检测,有GPU就用GPU

# 2. 加载模型
sam = sam_model_registry[model_type](checkpoint=sam_checkpoint)
sam.to(device=device)

# 3. 创建预测器
predictor = SamPredictor(sam)

这段代码做了三件事:第一,告诉程序去哪儿找模型文件(sam_checkpoint)和用哪种模型结构(model_type)。第二,把模型从硬盘加载到内存(sam_model_registry),并送到GPU或CPU上(.to(device))。第三,创建一个预测器(predictor),后续所有预测操作都通过它来完成。

现在,我们让SAM来看一张图。假设我们有一张名为 cat.jpg 的图片。

# 4. 读取并预处理图像
image = cv2.imread('cat.jpg')  # 用OpenCV读取,颜色通道是BGR
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # SAM需要RGB格式,所以转换一下

# 5. 为图像生成图像嵌入(Image Embedding)
predictor.set_image(image)

predictor.set_image(image) 这一步是SAM高效的关键!它不是在简单地存储图片,而是在用模型的编码器(Encoder)对整张图片进行一次深度“理解”,计算出一个称为“图像嵌入”的特征图。这个操作计算量较大,但只需要做一次。之后无论你在这张图上点多少个点、做多少次交互,SAM都基于这个预先计算好的“嵌入”进行快速解码,所以响应速度非常快。这就像你先花时间把整本书通读了一遍做了笔记(set_image),之后别人问你任何关于这本书的问题,你都能快速翻看笔记找到答案(predict)。

3.1 点提示的艺术:如何告诉SAM“我要这个”

重头戏来了——点提示(Point Prompts)。这是SAM最神奇、最直观的交互方式。我们的目标是指着图片说:“嘿,我要这个(前景点)”,“那个不要(背景点)”。

首先,你需要知道点的坐标。坐标原点 (0, 0) 在图片的左上角。X轴向右增加,Y轴向下增加。你可以用画图软件打开图片,把鼠标放在目标位置,状态栏就会显示坐标。假设我们想分割图中的一只猫。

# 6. 定义输入点(Point Prompts)和对应的标签(Point Labels)
# 点的格式是:[[x1, y1], [x2, y2], ...]
input_point = np.array([[400, 250], [420, 300]])  # 两个点,都点在猫身上
input_label = np.array([1, 1])  # 标签都是1,表示“这是前景,我要分割它”

input_label 里的 1 代表前景点(Foreground Point),意思是“这个点所在的东西是我想要的”。0 代表背景点(Background Point),意思是“这个点所在的东西是我不想要的,请排除”。

有时候,只告诉模型“要什么”还不够,还得告诉它“不要什么”。比如猫趴在沙发上,你点了猫(前景点1),但分割结果可能把一小部分沙发也带进来了。这时,你可以在沙发上点一个背景点(标签0),告诉SAM:“沙发我不要”。

input_point = np.array([[400, 250], [420, 300], [500, 350]])  # 第三个点
input_label = np.array([1, 1, 0])  # 前两个是前景点(猫),第三个是背景点(沙发)

3.2 生成与选择掩码

点信息准备好后,就可以让SAM进行预测了。

# 7. 进行预测
masks, scores, logits = predictor.predict(
    point_coords=input_point,
    point_labels=input_label,
    multimask_output=True,  # 关键参数!
)

predict 函数返回三个东西:

  • masks: 一个三维数组,形状是 (num_masks, H, W),即多个分割掩码。
  • scores: 每个掩码对应的预测质量分数,范围在0~1之间,越高越好。
  • logits: 模型的原始输出,可用于进一步处理。

这里有个非常重要的参数 multimask_output。当设置为 True 时,SAM会为同一组输入点生成三个可能的掩码。为什么是三个?因为SAM很“谦虚”,它知道你的提示可能有多义性。比如你点了一个动物的身体,它不确定你是要整个动物,还是只要头部,或者是一个更精细的轮廓。这三个掩码通常对应着“整体对象”、“部分对象”和“更精细边界”三种猜测。scores 会告诉你它认为哪个猜测最好。

当 multimask_output=False 时,SAM只返回它认为最好的那一个掩码。对于点提示非常明确的情况(比如前景点、背景点都给得很清楚),可以直接用 False。对于不确定的情况,让SAM给出多个选项,你自己选一个,会更灵活。

拿到多个掩码后,我们可以根据分数来选择最好的那个,或者把三个都显示出来让你挑。

# 8. 选择分数最高的掩码(当multimask_output=True时)
best_mask_idx = np.argmax(scores)
best_mask = masks[best_mask_idx]
print(f"最佳掩码的分数是: {scores[best_mask_idx]:.3f}")

4. 可视化与结果融合:让分割结果“跃然纸上”

光有数据还不够,我们得把分割结果可视化出来,看看效果。掩码(mask)是一个二维数组,和原图等高宽,其中 True(或1)代表“是目标”,False(或0)代表“是背景”。我们需要把它以一种直观的方式叠加到原图上。

我写了一个通用的可视化函数,它比原始文章里的更高效(用到了NumPy的向量化操作,避免了低效的Python循环),也支持自定义颜色和透明度。

def show_mask_on_image(image, mask, color=(0, 255, 0), alpha=0.5):
    """
    将掩码以半透明颜色叠加到原图上。
    参数:
        image: 原图,RGB格式,numpy数组。
        mask: 分割掩码,2维布尔型或0/1数组。
        color: 掩码颜色,默认绿色。
        alpha: 掩码透明度,0为完全透明,1为完全不透明。
    """
    # 创建一个带颜色的掩码图像
    colored_mask = np.zeros_like(image)
    colored_mask[mask] = color  # 只有mask为True的地方被上色

    # 将原图和彩色掩码混合
    # 这里使用更高效的混合方式
    result = image.copy()
    mask_area = mask[:, :, np.newaxis]  # 增加一个维度用于广播
    result = np.where(mask_area, (alpha * colored_mask + (1 - alpha) * image).astype(np.uint8), result)

    return result

这个函数的原理是:先创建一个和原图一样大的纯色图层(colored_mask),只在掩码标记为前景的区域填充指定的颜色(比如绿色)。然后,通过 alpha 参数控制这个颜色图层的透明度,并将其与原图进行混合。np.where 是NumPy的条件赋值函数,效率极高。

现在,我们可以展示结果了:

# 9. 可视化结果
# 使用上面定义的函数生成带掩码的结果图
result_image = show_mask_on_image(image, best_mask, color=(0, 255, 0), alpha=0.6)

# 创建一个画布,同时显示原图、掩码和结果
plt.figure(figsize=(15, 5))

plt.subplot(1, 3, 1)
plt.imshow(image)
plt.title("Original Image")
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(best_mask, cmap='gray') # 掩码用灰度图显示
plt.title("Predicted Mask")
plt.axis('off')

plt.subplot(1, 3, 3)
plt.imshow(result_image)
plt.title("Segmentation Result")
plt.axis('off')

plt.tight_layout()
plt.show()

# 10. 保存结果
cv2.imwrite('segmented_cat.jpg', cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR))
print("分割结果已保存为 'segmented_cat.jpg'")

5. 实战技巧与避坑指南

理论跑通了,但在实际项目中,你肯定会遇到各种问题。这里我分享几个我踩过坑才总结出来的实战技巧。

技巧一:点的质量远胜于点的数量。 很多人觉得多点几个点会更准,其实不然。点的位置至关重要。一个精准的前景点,应该点在目标物体的中心区域,而不是边缘。一个精准的背景点,应该点在紧邻目标、但明确不属于目标的区域。比如分割杯子,前景点点在杯身中央,背景点点在紧贴杯子的桌面上,效果比胡乱点十几个点要好得多。

技巧二:善用“多掩码输出”进行微调。 当你对结果不满意时,别急着加新点。先把 multimask_output 设为 True,看看SAM提供的另外两个备选方案。有时候,你想要的恰好是分数第二高的那个掩码。选中一个最接近你想要的掩码后,你可以在这个基础上,补充一两个点进行二次预测,往往能快速得到理想结果。

技巧三:处理复杂场景与小物体。 对于非常小或者边缘复杂的物体(比如树枝、毛发),SAM有时会力不从心。这时候可以尝试使用更小的模型(vit_b)?不,恰恰相反,应该用更大的模型(vit_h),因为它细节捕捉能力更强。同时,可以适当增加前景点的密度,在物体的不同部位多给一些提示。对于特别复杂的场景,可以考虑结合“框提示”(Bounding Box Prompt),用框先圈定一个大范围,再用点去精细调整,这是SAM支持的另一种强大交互方式。

技巧四:性能优化。 如果你处理的是高分辨率图片(比如4K),set_image 那一步会非常慢,而且可能爆内存。一个实用的技巧是,在调用 set_image 之前,先将图片缩放到一个合理的尺寸(比如长边1024像素)。SAM模型本身对输入分辨率有一定鲁棒性,缩放后对分割精度影响不大,但能极大提升速度和降低内存消耗。

def resize_long_edge(image, max_length=1024):
    h, w = image.shape[:2]
    scale = max_length / max(h, w)
    new_h, new_w = int(h * scale), int(w * scale)
    return cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_LINEAR)

small_image = resize_long_edge(big_image, max_length=1024)
predictor.set_image(small_image)
# 注意:此时你提供的点坐标(input_point)也需要按相同比例缩放!

常见错误排查:

  1. KeyError: 'image_encoder.pos_embed':这几乎总是因为模型权重文件(.pth)和指定的 model_type 不匹配。检查你下载的模型文件名,sam_vit_h_4b8939.pth 对应 vit_h,sam_vit_l_0b3195.pth 对应 vit_l,别搞混了。
  2. CUDA内存不足(Out of Memory):如果使用GPU,遇到这个问题,首先尝试缩小输入图像尺寸。如果还不行,退而求其次使用CPU模式(device='cpu'),虽然慢,但肯定能跑。对于 vit_h 模型,处理大图时CPU内存也可能吃紧,缩放图片是必须的。
  3. 点坐标无效或结果全黑/全白:确认你的点坐标没有超出图片范围(坐标值应小于图片的宽和高)。确认 input_point 和 input_label 这两个数组的长度一致。如果结果全黑(mask全是False),说明SAM没找到任何对象,尝试调整点的位置,或者增加一个背景点来提供对比信息。

6. 集成到你的应用:一个简单的交互式Demo

理解了所有片段后,我们可以把它们组装成一个有点实用价值的小工具。下面这个脚本,允许你通过鼠标点击图片来添加前景点(左键)和背景点(右键),按空格键进行分割,按‘r’键重置,按‘s’键保存。这几乎就是一个简易版标注工具的雏形了。

import cv2
import numpy as np
from segment_anything import sam_model_registry, SamPredictor

class InteractiveSAM:
    def __init__(self, model_path, model_type='vit_h', device='cuda'):
        self.sam = sam_model_registry[model_type](checkpoint=model_path)
        self.sam.to(device=device)
        self.predictor = SamPredictor(self.sam)
        self.image = None
        self.points = []
        self.labels = []  # 1 for foreground, 0 for background
        self.current_mask = None

    def load_image(self, image_path):
        self.image = cv2.imread(image_path)
        self.image = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB)
        self.predictor.set_image(self.image)
        self.display_image = self.image.copy()
        self.points.clear()
        self.labels.clear()
        self.current_mask = None

    def add_point(self, x, y, is_foreground=True):
        self.points.append([x, y])
        self.labels.append(1 if is_foreground else 0)
        self._draw_points()

    def _draw_points(self):
        self.display_image = self.image.copy()
        for (x, y), label in zip(self.points, self.labels):
            color = (0, 255, 0) if label == 1 else (255, 0, 0)  # 绿前景,红背景
            cv2.circle(self.display_image, (x, y), 8, color, -1)

    def predict(self):
        if len(self.points) == 0:
            print("请至少添加一个点!")
            return
        input_point = np.array(self.points)
        input_label = np.array(self.labels)
        masks, scores, _ = self.predictor.predict(
            point_coords=input_point,
            point_labels=input_label,
            multimask_output=False
        )
        self.current_mask = masks[0]
        self._apply_mask_to_display()

    def _apply_mask_to_display(self):
        if self.current_mask is not None:
            # 创建一个半透明绿色掩码层
            mask_layer = np.zeros_like(self.display_image)
            mask_layer[self.current_mask] = [0, 255, 0]
            self.display_image = cv2.addWeighted(self.display_image, 0.7, mask_layer, 0.3, 0)
        self._draw_points()

    def reset(self):
        self.points.clear()
        self.labels.clear()
        self.current_mask = None
        self.display_image = self.image.copy()

    def save_result(self, path='result.png'):
        if self.current_mask is not None:
            result = self.image.copy()
            result[self.current_mask] = [0, 255, 0]
            cv2.imwrite(path, cv2.cvtColor(result, cv2.COLOR_RGB2BGR))
            print(f"结果已保存至 {path}")

# 鼠标回调函数
clicked_points = []
def mouse_callback(event, x, y, flags, param):
    sam_tool, window_name = param
    if event == cv2.EVENT_LBUTTONDOWN:  # 左键前景点
        sam_tool.add_point(x, y, is_foreground=True)
        cv2.imshow(window_name, cv2.cvtColor(sam_tool.display_image, cv2.COLOR_RGB2BGR))
    elif event == cv2.EVENT_RBUTTONDOWN:  # 右键背景点
        sam_tool.add_point(x, y, is_foreground=False)
        cv2.imshow(window_name, cv2.cvtColor(sam_tool.display_image, cv2.COLOR_RGB2BGR))

# 主程序
def main():
    model_path = "./models/sam_vit_h_4b8939.pth"
    image_path = "your_image.jpg"  # 换成你的图片路径

    sam_tool = InteractiveSAM(model_path, device='cuda' if torch.cuda.is_available() else 'cpu')
    sam_tool.load_image(image_path)

    window_name = 'Interactive SAM'
    cv2.namedWindow(window_name)
    cv2.setMouseCallback(window_name, mouse_callback, (sam_tool, window_name))

    print("操作说明:左键点击添加前景点(绿色),右键点击添加背景点(红色)。")
    print("按空格键进行分割,按 'r' 键重置,按 's' 键保存结果,按 'q' 键退出。")

    while True:
        cv2.imshow(window_name, cv2.cvtColor(sam_tool.display_image, cv2.COLOR_RGB2BGR))
        key = cv2.waitKey(1) & 0xFF

        if key == ord(' '):  # 空格键预测
            sam_tool.predict()
        elif key == ord('r'):  # 重置
            sam_tool.reset()
        elif key == ord('s'):  # 保存
            sam_tool.save_result()
        elif key == ord('q'):  # 退出
            break

    cv2.destroyAllWindows()

if __name__ == "__main__":
    import torch
    main()

这个Demo虽然界面简陋,但它完整展示了SAM交互式分割的核心流程。你可以用它来快速体验点提示的强大,也能以此为脚手架,开发更复杂的图像标注或编辑工具。在实际使用中,我发现对于边界模糊或纹理相似的对象,通过交替添加前景点和背景点(比如在对象内部点绿色,在对象外部紧贴边缘点红色),SAM能像“抠图大师”一样,精准地勾勒出边界,这种体验非常直观。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐