+
25
-

回答

SoM (Set-of-Mark) 是一种视觉提示(Visual Prompting)技术,通常用于增强多模态大模型(如 GPT-4V, LLaVA)的细粒度空间理解能力。

它的核心逻辑非常直接:

分割(Segmentation):使用 SAM (Segment Anything Model) 等模型将图像分割成不同区域。

标记(Marking):在这些区域上覆盖半透明掩码,并打上数字标签(ID)。

提示(Prompting):将处理后的图发给 GPT-4V,问它:“标签 [5] 的物体是什么?”

下面是一个基于 Python 的完整示例。

准备工作

你需要安装 segment-anything 库以及 PyTorch。

# 1. 安装 PyTorch (根据你的环境选择合适的 CUDA 版本)
pip install torch torchvision

# 2. 安装 SAM 和 OpenCV
pip install opencv-python matplotlib segment-anything

重要提示:你需要下载 SAM 的权重文件(Model Checkpoint)。请下载 sam_vit_h_4b8939.pth (2.4GB) 并放在代码同级目录下。下载地址: Meta AI SAM Repository

1. 完整 SoM 生成代码

这段代码会自动分割图片,并为每个分割区域生成“彩色掩码 + 数字标签”。

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

class SoMGenerator:
    def __init__(self, checkpoint_path, model_type="vit_h", device="cuda"):
        """
        初始化 SAM 模型
        """
        print(f"正在加载 SAM 模型 ({device})...")
        self.device = device
        self.sam = sam_model_registry[model_type](checkpoint=checkpoint_path)
        self.sam.to(device=device)

        # 初始化自动掩码生成器
        # points_per_side: 采样点数,越低速度越快但精度降低
        # pred_iou_thresh: 过滤低质量掩码的阈值
        self.mask_generator = SamAutomaticMaskGenerator(
            model=self.sam,
            points_per_side=32,
            pred_iou_thresh=0.86,
            stability_score_thresh=0.92,
            crop_n_layers=0,
            crop_n_points_downscale_factor=1,
            min_mask_region_area=100,  # 忽略太小的区域
        )

    def generate_som_image(self, image_path, output_path=None):
        """
        核心流程:读取图片 -> 分割 -> 绘制标记
        """
        # 1. 读取图片
        image = cv2.imread(image_path)
        if image is None:
            raise ValueError("无法读取图片,请检查路径")
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        # 2. 生成掩码
        print("正在生成分割掩码 (这可能需要几秒钟)...")
        masks = self.mask_generator.generate(image)
        print(f"检测到 {len(masks)} 个区域")

        # 按面积从大到小排序,避免小标签被大标签覆盖
        masks = sorted(masks, key=(lambda x: x['area']), reverse=True)

        # 3. 绘制 SoM 效果 (Overlay)
        som_image = image.copy()

        # 创建一个带有透明度的图层
        overlay = image.copy()

        font_scale = 0.5
        font_thickness = 1

        for i, ann in enumerate(masks):
            # 获取掩码的布尔矩阵
            m = ann['segmentation']

            # 生成随机颜色
            color = np.random.randint(0, 255, (3,)).tolist()

            # --- A. 绘制半透明掩码 ---
            # 将掩码区域涂上颜色
            overlay[m] = color

            # --- B. 计算标签位置 (重心) ---
            # 使用 OpenCV 寻找轮廓来确定中心点,比简单的 bbox 中心更准确
            contours, _ = cv2.findContours(m.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            if contours:
                # 获取最大的轮廓
                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

                # --- C. 绘制数字标签 (The Mark) ---
                label_text = str(i + 1)

                # 为了让文字清晰,先画一个黑色背景框或描边
                (text_w, text_h), _ = cv2.getTextSize(label_text, cv2.FONT_HERSHEY_SIMPLEX, font_scale, font_thickness)

                # 画一个小黑块背景
                cv2.rectangle(som_image, (cX - 2, cY - text_h - 2), (cX + text_w + 2, cY + 2), (0, 0, 0), -1)
                # 画白色文字
                cv2.putText(som_image, label_text, (cX, cY), 
                            cv2.FONT_HERSHEY_SIMPLEX, font_scale, (255, 255, 255), font_thickness, cv2.LINE_AA)

        # 混合原图和彩色掩码 (alpha blending)
        alpha = 0.5  # 透明度
        cv2.addWeighted(overlay, alpha, som_image, 1 - alpha, 0, som_image)

        # 4. 显示或保存结果
        plt.figure(figsize=(12, 8))
        plt.imshow(som_image)
        plt.axis('off')
        plt.title(f"SoM Result: {len(masks)} Regions")
        plt.show()

        if output_path:
            # 转回 BGR 保存
            cv2.imwrite(output_path, cv2.cvtColor(som_image, cv2.COLOR_RGB2BGR))
            print(f"结果已保存至: {output_path}")

        return som_image, masks

# --- 运行示例 ---
if __name__ == "__main__":
    # 检查是否有 GPU,没有的话用 cpu (会很慢)
    device = "cuda" if torch.cuda.is_available() else "cpu"

    # 请确保路径正确
    CHECKPOINT_PATH = "./sam_vit_h_4b8939.pth" 
    IMAGE_PATH = "./test.jpg"  # 换成你的图片路径

    try:
        som = SoMGenerator(CHECKPOINT_PATH, device=device)
        # 运行
        som.generate_som_image(IMAGE_PATH, "som_output.jpg")

        print("\n--- 构建 Prompt 示例 ---")
        print("现在你可以把生成的 'som_output.jpg' 发给 GPT-4V 并提问:")
        print("User: 'Look at the image. What object is located at mark [3]?'")
        print("User: 'Describe the relationship between object [1] and object [5].'")

    except FileNotFoundError:
        print("错误:未找到模型权重文件或图片。")
        print("请下载 'sam_vit_h_4b8939.pth' 并准备一张 'test.jpg'。")
    except Exception as e:
        print(f"发生错误: {e}")

2. 代码核心逻辑解析

加载 SAM:sam_model_registry 用于加载 Meta 预训练的 ViT-H 模型。这是目前效果最好的分割模型之一。

SamAutomaticMaskGenerator:这是 SAM 的全自动模式。它不需要你点选,而是会在整个图像上生成网格点,并在每个点上运行推理,最后合并重复的掩码。

min_mask_region_area=100:这行代码很重要,它过滤掉了那些只有几个像素的噪点,保证生成的 Mark 清晰。

Visual Marking (打标):SoM 的精髓在于如何把数字画上去。

颜色叠加:代码使用了 cv2.addWeighted 实现 50% 的透明度,这样即使盖住了物体,模型依然能看到物体原本的纹理。

重心计算:使用 cv2.moments 计算不规则形状的重心(Centroid),确保数字标签位于物体的视觉中心,而不是简单的边界框中心(那样如果是“甜甜圈”形状,数字会打在空心处)。

对比度增强:在白色文字下面画了一个黑色小矩形背景,确保无论掩码颜色多亮,数字都清晰可见。

3. 如何配合大模型使用 (Prompting)

生成完这种带数字的图片后,你的 Prompt 结构应该是这样的:

User:

(上传生成的 som_output.jpg)I have overlaid a Set-of-Mark on this image. Please answer the following questions:Identify the object labeled [12].What action is the person in [5] doing?Is the object [3] closer to the camera than object [8]?GPT-4V / LLaVA Response:The object labeled [12] is a red apple.The person in [5] is holding a cup of coffee. ...

这种技术极大地解决了多模态模型容易产生的幻觉(Hallucination)空间定位不准的问题。

网友回复

我知道答案,我要回答