☰
一行 curl 接入 SAM3:把图像分割封装成 RESTful API 的生产级实战
2026/10/20 2:51:05 网站建设 项目流程

一行 curl 接入 SAM3:把图像分割封装成 RESTful API 的生产级实战

【免费下载链接】sam3项目地址: https://ai.gitcode.com/hf_mirrors/facebook/sam3

从 SAM 到 SAM 2,Meta 用两代模型重新定义了"可提示分割"(Promptable Segmentation);而 2025 年末亮相、以 ICLR 2026 论文身份引发热议的 SAM 3,则把这场变革从"分割一切"推进到了"理解世界"——它不再满足于跟随你点的点、画的框,而是提出了可提示概念分割(Promptable Concept Segmentation, PCS)任务:给定一句文本短语或几张示例图,就能穷尽式地分割出图像中所有匹配该概念的实例。官方在 SA-CO 基准上收录了 27 万个独特概念,是既有评测基准的 50 倍以上,SAM 3 在这上面做到了人类表现的 75%–80%。社区里"万字硬核解读""无需画框、文本提示即分割"的教程持续刷屏,智源社区的在线演示甚至展示了 30 毫秒量级处理上百个检测对象掩码的能力。

但模型能力再强,如果只能躺在 Python Notebook 里交互,就与生产环境无缘。本文以本仓库(hf_mirrors/facebook/sam3,即 facebook/sam3 权重仓库的镜像)为蓝本,从config.json、processor_config.json到README.md逐层拆解 SAM3 的输入输出契约,手把手把它封装成一个 FastAPI 服务——最终做到一行 curl 传图、拿回掩码,并给出与多模态大模型、检测器串联的三种生产级集成方案。

一、先看清仓库里有什么:从权重到可调用的模型

仓库根目录的文件构成非常典型,是一个完整的 HF 权重仓库:

  • model.safetensors/sam3.pt:模型权重(safetensors 与 PyTorch 双格式,经 Git LFS 托管,实际体积约 3.4 GB / 3.45 GB);
  • config.json:模型结构配置,architectures声明为Sam3VideoModel;
  • processor_config.json:图像/视频预处理参数;
  • tokenizer_config.json、vocab.json、merges.txt:CLIP 风格 BPE 分词器三件套;
  • configuration.json:{"framework": "pytorch", "task": "mask-generation"},声明了推理任务类型;
  • README.md:官方用法文档,也是本文所有代码示例的事实来源。

真正决定"怎么传参、怎么拿结果"的是config.json与processor_config.json。打开 config.json 可以看到 SAM3 的"五脏六腑":

  • 视觉主干:sam3_vit_model,32 层 Transformer,patch 尺寸 14,窗口注意力window_size=24,输入分辨率 1008×1008,全局注意力层位于第 7/15/23/31 层;
  • 文本编码器:clip_text_model,24 层、hidden size 1024,词表 49408,max_position_embeddings=32——这正是文本提示能直达语义空间的根基;
  • 检测器:DETR 式 encoder-decoder,各 6 层,num_queries=200且use_presence_token=true——SAM3 用 200 个查询位点做全实例穷举检测,并显式输出"概念是否存在"的判定;
  • 几何编码器:sam3_geometry_encoder,3 层,roi_size=7,负责把点、框等空间提示编码进模型;
  • 视频追踪器:score_threshold_detection=0.5、new_det_thresh=0.7、high_conf_thresh=0.8、recondition_every_nth_frame=16等一系列阈值,支撑跨帧的检测-关联-追踪流水线。

再看 processor_config.json:图像统一 resize 到 1008×1008,掩码输出分辨率 288×288(mask_size),归一化采用 mean/std = 0.5。而 tokenizer_config.json 显示分词器是CLIPTokenizer,max_length=32——也就是说文本提示最长 32 个 token,生产环境中对超长自然语言描述要先做截断或改写。

这些文件共同定义了 SAM3 的接口契约:图像 + 文本/视觉提示 → 掩码 + 边界框 + 置信度,实例分割之外还附带单通道语义分割输出(outputs.semantic_seg)。README 中的核心调用范式如下:

from transformers import Sam3Processor, Sam3Model model = Sam3Model.from_pretrained("facebook/sam3").to(device) processor = Sam3Processor.from_pretrained("facebook/sam3") inputs = processor(images=image, text="ear", return_tensors="pt").to(device) outputs = model(**inputs) results = processor.post_process_instance_segmentation( outputs, threshold=0.5, mask_threshold=0.5, target_sizes=inputs.get("original_sizes").tolist(), )[0] # results 包含: masks / boxes(xyxy 绝对像素坐标) / scores

二、SAM3 服务化架构与接口设计

模型本身无法响应 HTTP,一行 curl 直接打过来更不可能——中间必须有一层服务化封装。生产级的封装要考虑四件事:

  1. 模型常驻:3.4 GB 的权重加载一次、常驻显存,避免每次请求重新from_pretrained(这通常需要数十秒,还会反复吃显存);
  2. 预热(warm-up):服务启动后先跑一次空推理,把 CUDA kernel 与 cuDNN autotune 的缓存激活,否则第一个真实请求会包含冷启动开销;
  3. 输入输出契约化:把 PIL Image、torch.Tensor 这类内部类型收敛为 HTTP 友好形态——输入走multipart/form-data,输出走 JSON;
  4. 后处理统一:post_process_instance_segmentation负责把模型输出的低分辨率掩码上采样回原图尺寸并二值化,这段逻辑必须收敛在服务端,客户端只负责传图与收结果。

下面是一个可直接落地的 FastAPI 服务骨架(完整代码见 README.md 中 API 用法对应的部分,此处按生产形态重排):

import base64 import io import numpy as np import torch from fastapi import FastAPI, File, Form, UploadFile from PIL import Image from transformers import Sam3Model, Sam3Processor app = FastAPI(title="SAM3 Segment API", version="1.0.0") device = "cuda" if torch.cuda.is_available() else "cpu" model = Sam3Model.from_pretrained("facebook/sam3").to(device).eval() processor = Sam3Processor.from_pretrained("facebook/sam3") def mask_to_png(mask: np.ndarray) -> str: """把二值掩码转成 PNG 并做 base64,便于 JSON 传输""" buf = io.BytesIO() Image.fromarray(mask.astype(np.uint8) * 255).save(buf, format="PNG") return base64.b64encode(buf.getvalue()).decode() @app.post("/segment") async def segment( image: UploadFile = File(...), text: str = Form(...), threshold: float = Form(0.5), mask_threshold: float = Form(0.5), ): raw = Image.open(io.BytesIO(await image.read())).convert("RGB") inputs = processor(images=raw, text=text, return_tensors="pt").to(device) with torch.no_grad(): outputs = model(**inputs) results = processor.post_process_instance_segmentation( outputs, threshold=threshold, mask_threshold=mask_threshold, target_sizes=inputs.get("original_sizes").tolist(), )[0] masks = results["masks"].numpy() # [N, H, W] 二值掩码,已还原到原图尺寸 boxes = results["boxes"].numpy().tolist() # xyxy 绝对像素坐标 scores = results["scores"].numpy().tolist() return { "num_objects": len(scores), "boxes": boxes, "scores": scores, "masks_png_base64": [mask_to_png(m) for m in masks], }

接口设计上有一个容易被忽略的细节:掩码的序列化方式。二进制掩码直接塞 JSON 会爆炸(一张 1080p 图的掩码就是 200 万字节),这里采用 PNG 压缩 + base64,单张掩码通常可压缩到几 KB 到几十 KB,是图像分割服务最实用的折中方案。

如果需要框提示(视觉 prompt),可以再暴露一个/segment_box端点,直接把 README 里的input_boxes/input_boxes_labels参数化:

@app.post("/segment_box") async def segment_box( image: UploadFile = File(...), box: str = Form(...), # "x1,y1,x2,y2" positive: int = Form(1), # 1=正框(要分割), 0=负框(要排除) ): raw = Image.open(io.BytesIO(await image.read())).convert("RGB") x1, y1, x2, y2 = map(int, box.split(",")) inputs = processor( images=raw, input_boxes=[[[x1, y1, x2, y2]]], input_boxes_labels=[[positive]], return_tensors="pt", ).to(device) with torch.no_grad(): outputs = model(**inputs) results = processor.post_process_instance_segmentation( outputs, threshold=0.5, mask_threshold=0.5, target_sizes=inputs.get("original_sizes").tolist(), )[0] return {"num_objects": len(results["scores"]), "boxes": results["boxes"].tolist(), "scores": results["scores"].tolist()}

对应视频场景,README 展示了另一套会话化(session)API:Sam3VideoProcessor.init_video_session建会话 →add_text_prompt注入文本概念 →Sam3VideoModel.propagate_in_video_iterator逐帧传播。服务化时需要用字典维护 session 句柄(视频特征缓存在 session 里),并将上传视频转成帧序列。一个最小实现如下:

sessions: dict[str, object] = {} @app.post("/video/segment") async def video_segment( video: UploadFile = File(...), text: str = Form(...), max_frames: int = Form(50), ): video_bytes = await video.read() frames, _ = load_video_from_bytes(video_bytes) # 将 MP4 解码为帧列表 session = processor.init_video_session( video=frames, inference_device=device, processing_device="cpu", video_storage_device="cpu", dtype=torch.bfloat16, ) session = processor.add_text_prompt(inference_session=session, text=text) outputs_per_frame = {} for model_outputs in model.propagate_in_video_iterator( inference_session=session, max_frame_num_to_track=max_frames ): outputs_per_frame[model_outputs.frame_idx] = processor.postprocess_outputs( session, model_outputs ) return {"frames": sorted(outputs_per_frame.keys()), "objects_frame_0": len(outputs_per_frame[0]["object_ids"])}

注意视频分支用dtype=torch.bfloat16加载——半精度推理是官方示例的标准姿势,服务端应默认开启。

三、一行 curl:从传图到拿掩码

服务启动后,客户端的接入成本被压缩到极致。启动与调用:

# 1. 启动服务(模型加载 + 预热在启动阶段完成) uvicorn main:app --host 0.0.0.0 --port 8000 # 2. 一行 curl:传图 + 文本概念,拿回掩码 curl -s -X POST http://127.0.0.1:8000/segment \ -F "image=@street.jpg" \ -F "text=car" \ -F "threshold=0.5" | jq

返回示例:

{ "num_objects": 3, "boxes": [[12, 38, 210, 140], [260, 90, 402, 188], [455, 300, 520, 340]], "scores": [0.96, 0.91, 0.87], "masks_png_base64": ["iVBORw0KGgo...", "iVBORw0KGgo...", "iVBORw0KGgo..."] }

一次请求同时拿到三样东西:掩码(PNG base64,可解码后叠加到原图)、边界框(xyxy 绝对像素坐标)、置信度(scores)。这正是 SAM3 与 SAM/SAM 2 最本质的差异在 API 层的体现——text=car一句提示,返回的是图上所有汽车实例,而不是一个待你多点几次的交互式分割结果。这也让"全实例分割"真正变得可编程、可批处理。

框提示同样是一行的事:

# 用正框锁定区域,分割框内目标 curl -s -X POST http://127.0.0.1:8000/segment_box \ -F "image=@kitchen.jpg" \ -F "box=59,144,76,163" \ -F "positive=1" | jq # 文本 + 负框:分割 handle,但排除烤箱把手所在区域 curl -s -X POST http://127.0.0.1:8000/segment_box \ -F "image=@kitchen.jpg" \ -F "text=handle" \ -F "box=40,183,318,204" \ -F "positive=0" | jq

第二个例子对应 README 中的 "Combined Prompts (Text + Negative Box)" 模式:文本提示负责定义概念,负框负责在概念内排除干扰区域——这是视觉 prompt 与文本 prompt 联合服务化的最典型场景,也是 SAM3 多模态提示能力的直接体现。

四、与多模态大模型串联的三种集成方案

服务化之后,SAM3 就从一个模型变成了一个可编排的视觉能力单元。社区实践中被反复验证的三条串联路线如下:

方案一:VLM 生成概念词 → SAM3 文本分割

SAM3 的文本提示接口天然可编程,与视觉语言模型(VLM)是绝配。用 VLM 对画面做开放式理解,把输出的物体概念喂给分割服务,形成"理解-分割"两级流水线:

# VLM 输出开放式概念清单(伪代码) concepts = vlm.generate_concepts(frame, question="画面里有哪些值得分割的物体?") # ["car", "traffic light", "pedestrian"] import requests for concept in concepts: resp = requests.post( "http://127.0.0.1:8000/segment", files={"image": open("frame.jpg", "rb")}, data={"text": concept}, ) masks = resp.json()["masks_png_base64"] # 逐概念取掩码

这条路线的价值在于:SAM3 的开放词汇能力把"分割什么"的决定权完全交给了上游语言模型,无需任何类别清单或标注。

方案二:检测器出框 → SAM3 框提示精分割

工业落地中最常见的是"检测 + 分割"的组合拳(社区部署实践里 YOLOv8 与 SAM3 的多模型集成正是这一路线)。检测器负责快速定位,SAM3 负责像素级精分割:

# 步骤 1:YOLO 类检测器给出候选框 boxes = detector.predict(frame) # [[x1, y1, x2, y2], ...] # 步骤 2:逐框调用 SAM3 框提示端点 for box in boxes: resp = requests.post( "http://127.0.0.1:8000/segment_box", files={"image": open("frame.jpg", "rb")}, data={"box": ",".join(map(str, box)), "positive": 1}, ) # 掩码比检测框精细得多,可用于计数、抠图、质检等下游任务

对比"纯检测",这条路线用可控的推理成本换来了像素级边界;而 SAM3 的负框能力还能在框内排除误检区域,进一步净化分割结果。

方案三:文本概念 → SAM3 Video 全实例追踪

对视频场景,直接用文本提示启动全实例追踪,让 SAM3 Video 的检测-关联-追踪流水线接管时序维度:

# 对视频帧序列只做一次文本提示,所有匹配实例跨帧追踪 session = processor.init_video_session(video=frames, ...) session = processor.add_text_prompt(inference_session=session, text="person") for model_outputs in model.propagate_in_video_iterator(inference_session=session): outputs_per_frame[model_outputs.frame_idx] = \ processor.postprocess_outputs(session, model_outputs) # 每个 frame 的 object_ids / boxes / masks 即为追踪结果

配合大模型做上层编排(例如 LLM 根据用户指令选择提示词、汇总各帧的追踪轨迹),就可以把"一句话描述目标 → 视频中全程追踪该目标"做成标准能力。README 同时提示:流式推理会关闭 hotstart 启发式去重,可能引入更多误检与重复轨迹——全帧可得的场景优先用预加载推理,这个工程权衡值得在服务化时写进接口文档。

五、生产化清单:从能跑到大流量

最后,把社区部署实践中反复出现的坑收敛成一份清单,供服务上线前逐项核对:

  1. 半精度与显存:图像分支用 fp32 约 3.4 GB,视频分支务必torch.bfloat16加载(README 官方示例即为 bf16);显存紧张时可考虑分块推理与更小的 batch;
  2. 预热与冷启动:启动后执行一次空推理,否则首请求会叠加 CUDA kernel 编译延迟;
  3. 输入约束:tokenizer_config.json规定文本提示最长 32 token,超长需截断;上传图片需限制尺寸与格式,防止恶意大图拖垮预处理;
  4. 掩码传输:优先 RLE 或 PNG base64 压缩,避免原始数组直传;
  5. 并发模型:Transformer 类模型的推理天然支持 batch,/segment端点可接收多图、多文本(README 的 "Batched Inference" 示例展示了images=[...], text=[...]一次前向处理多图),服务端按 GPU 余量动态组 batch 是提升吞吐的关键手段;
  6. 加速探索:社区实践普遍指向 ONNX 导出、TensorRT 与 8bit 量化三个方向,配合 CUDA 内存优化与 Guided Filter 后处理,可在精度可接受的范围内换取明显延迟收益;
  7. 追踪阈值调参:config.json中的score_threshold_detection、new_det_thresh、high_conf_thresh等阈值直接影响视频场景的检出率与轨迹稳定性,生产环境应按数据分布单独标定。

结语

SAM3 的核心贡献,是把分割从"点一点、画一画"的交互式工具,升级成了"说一句就全给你"的概念级理解能力。而本文要传递的工程判断是:当模型接口变成可编程的输入输出契约时,它的价值才真正开始指数级放大。从config.json里的 200 个查询位点,到processor_config.json里 1008×1008 的输入契约,再到一行 curl 拿回掩码——这条路线的每一步都不需要魔法,只需要把官方 API 老老实实地服务化。至于接下来 SAM3 的视频追踪、Tracker 的 SAM2 兼容替换(README 明确它是 SAM2 工作流的 drop-in replacement),都是同一套思路在不同任务上的自然延伸。

【免费下载链接】sam3项目地址: https://ai.gitcode.com/hf_mirrors/facebook/sam3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询