一张照片里的多件文物,怎么给每件单独出图?四种本地方案全对比

场景:一张展柜照片里有若干文物,希望为每个文物生成一张独立照片------

矩形裁剪图 + 透明背景抠图。全程本地模型,不调云端 API。

我们实现了四种方案:Grounding DINO + SAM、YOLO11x-seg、SAM2 自动掩码、

Florence-2,在同一组样张上完整对比。本文是这次迭代的完整记录,

包括各方案的原理取舍、transformers 5.x 的兼容性排雷,以及最终的产品化封装。

下面是拍摄的展柜照片(首都博物馆:玉米.黄金.美洲豹------玛雅与安第斯古代文明大展)

我们希望为每个文物单独出图:

一、需求拆解:这不是一个单纯的"目标检测"问题

四个字面需求,各自指向不同的模型能力:

  1. "识别物体"------要检测,最好还懂语义(知道那是"花瓶"而不是一堆像素);
  2. "文物" ------不是 COCO 80 类里的常规物体,鼎、玉璧、编钟统统不在列表里,
    固定类别检测器天然吃亏,需要开放词汇检测(Open-Vocabulary Detection);
  3. "独立照片"------两种形态:矩形裁剪(容易)和透明抠图(需要实例分割掩码);
  4. "本地"------模型下载到本地、离线可跑。

由此得出四种候选组合,正好覆盖当前本地视觉模型的四条技术路线:

方案 路线 类别能力 分割能力
A: Grounding DINO + SAM 开放词汇检测 + 可提示分割 文本提示任意词 SAM 掩码,精确
B: YOLO11x-seg 固定类别检测 + 实例分割 仅 COCO 80 类 原生分割,快
C: SAM2 自动掩码 类别无关自动分割 无语义 最精细
D: Florence-2 多模态大模型多任务 开放词汇 无原生分割(GrabCut 近似)

二、方案A:Grounding DINO + SAM------开放词汇的两段式流水线

Grounding DINO 的关键在于"文本编码参与检测":图像 query 在检测过程中

同时做图像和文本的跨模态注意力,因此可以用任意文本提示(如

"cultural relic . vase . bronze ware")驱动检测,不需要训练。

提示词用 " . " 分隔帮助模型切分短语边界,这是最实用的调参旋钮。

SAM 负责把矩形框变成精细掩码。它的架构设计非常适合本场景:

重的图像编码器每张图只跑一次,换提示(框)只重跑轻量 decoder------

一张展柜照片编码一次,N 个文物框各跑一次 decoder,就得到 N 个抠图掩码。

实测:样张检出 7 + 5 个物体,检测 ~1.3s + SAM 分割 ~0.5s(RTX 5090),

透明抠图边缘干净。综合质量最优,也是我们最终产品化采用的方案。

工程细节:transformers 5.x 的 API 与旧教程差异很大------

box_threshold 改名 threshold,labels 改 text_labels,

点提示必须 4 维张量,boxes 传给 processor 前不能上 GPU(内部要 .numpy())。

写新代码请直接对照新版文档。

三、方案B:YOLO11x-seg------速度之王的天花板

单张 3648×2736 只要 0.15--0.9s,比其他方案快 1--2 个数量级。

"-seg" 后缀的实例分割用 proto 原型掩码 设计:不回归整幅掩码,

而是预测 32 个共享原型 + 每实例 32 维系数线性组合,分割几乎不增加推理成本。

代价是类别天花板:COCO 里和文物相关的只有 vase/bowl/bottle/cup 一小撮,

我们只能靠类别白名单过滤(剔除误检的人、椅子)。

实测样张检出 9 + 5 个(召回最高),但要接受"鼎被叫成瓶子"的现实,

不规则文物有漏检风险。适合海量照片批量初切。

一个必踩的坑:r.masks.data 的尺寸是 letterbox 后的模型输入尺寸,
不是原图
,掩码必须先 resize 回原图再用。

四、方案C:SAM2 自动掩码------最精细,也最啰嗦

没有类别提示怎么办?SAM 系列的经典策略是撒点网格 :

16×16 = 256 个网格点逐个作为前景提示推理,得到 256 个候选掩码,

再过三重过滤------IoU 自评分(模型自己评价切得好不好)、掩码 NMS 去重、

面积窗口(太小是碎屑,太大是桌面背景)。

直觉上:一个物体只要有一个网格点落在它身上,就能被完整切出来。

实测:196 个候选 → 过滤后 27 + 18 个,掩码边缘质量四方案最优。

但"类别无关"是双刃剑------桌沿、展签、反光斑都会成为独立"物体",

输出数量是其他方案的 3--4 倍。定位是"先切全、再挑拣"流水线的第一步,

或者与方案A组合:SAM2 切全 → Grounding DINO 命名筛选。

工程上值得一记:transformers 5.x 没有 内置 SAM2 的自动掩码生成器

(Sam2AutomaticMaskGenerator 不存在),我们按上述流程手写了约 40 行实现------

这反而是理解 AMG 原理的最好教材。

五、方案D:Florence-2------轻量选手

Florence-2(微软,0.23B)把所有视觉任务统一为 seq2seq:

图像 + 任务指令 → 文本。<OD> 任务一次输出所有物体的类别和坐标,

检测结果被编码成 <loc_x> 特殊 token 序列(坐标 0--999 分箱)。

它的分割是短板(无原生分割,我们用 OpenCV GrabCut 近似抠图),

但这次排雷过程极具学习价值,完整故障链:

  1. transformers 5.19 原生类 :权重大量 MISSING(微软仓库还是旧命名格式),
    输出乱码("CD CD CD rites...");
  2. 远程代码路径 :远程代码读 forced_bos_token_id,5.x 已删该属性,直接崩;
  3. 结论:必须回退 transformers 4.51。但主环境不能降级(方案 A/C 依赖 5.x)。

解法是兼容层 + 子进程 :pip install --target compat_florence --no-deps transformers==4.51.3 tokenizers==0.21.1 huggingface-hub==0.34.4,

方案 D 以 PYTHONPATH=compat_florence 的子进程运行。

为什么必须子进程?同进程已加载 5.x,Python 无法卸载重载两套 transformers

(C 扩展尤其不行)。版本隔离三档:独立 venv(重)→ --target 目录 +
PYTHONPATH(中)→ 容器(最重)
,本次取中间档,非常典型。

六、四方案对比与流水线建议

A: GDINO+SAM B: YOLO11x-seg C: SAM2 AMG D: Florence-2
检出(两样张) 7+5 9+5 27+18 5+5
速度/张 ~1.8s 0.15--0.9s 7--14s 0.5--0.9s
语义标签 ✅ 可定制提示 ⚠ 仅COCO ❌ ✅
抠图质量 好 好 最好 差(GrabCut)
模型体积 ~1GB 125MB ~320MB ~1.1GB
  • 单件文物出图、档案级抠图 → A;
  • 海量批量 → B;
  • 极致边缘、后续筛选 → C;
  • 组合流水线 → C 切全 + A 命名确认 + A 的 SAM 掩码抠图。

七、产品化:从实验到工具

对比结束后,方案 A 被封装为独立工具 relic_splitter:核心类懒加载模型、

Web/CLI 双入口(Gradio 上传照片 → 调提示词与阈值 → 画廊预览裁剪/抠图 → ZIP 下载)。

一个值得记录的隐蔽 bug:必须先 import 核心模块、后 import gradio ------

gradio 内部的 huggingface_hub 会在导入时读取环境变量,

顺序错了离线配置失效,推理卡死在联网重试上。本地化项目里,

"谁先初始化"往往和"用什么"一样重要。

八、附:本地模型下载经验

  • huggingface.co 直连不可达 → HF_ENDPOINT=https://hf-mirror.com;
  • 镜像 + 新版 Xet 存储 = 401 错误 → HF_HUB_DISABLE_XET=1;
  • 模型落盘后 HF_HUB_OFFLINE=1,运行时不碰网;
  • RTX 5090(Blackwell, sm_120)需要 PyTorch 2.7+ 的 cu128 轮子,旧版不认卡。

九、写在最后

四个方案是四种哲学:专用检测(快而受限)、开放词汇(灵活而慢)、

类别无关分割(精细而啰嗦)、多任务大模型(全能而不稳)。

实测的意义在于把"听说很好"变成"在我照片上到底怎样"------

检出数量、边缘质量、类别语义、速度,四个维度只有跑过才知道。

别选边站,让它们各自站到流水线里最合适的位置。