一张照片多件文物如何单独出图:四种本地方案全对比
一张照片里的多件文物,怎么给每件单独出图?四种本地方案全对比 - 寻找无名的特质
作者用同一组展柜照片对比 Grounding DINO+SAM、YOLO11x-seg、SAM2 自动掩码和 Florence-2 四种本地文物分割出图方案。实测两样张检出分别为 7+5、9+5、27+18、5+5 个,速度约 1.8s、0.15–0.9s、7–14s、0.5–0.9s。最终方案 A 被封装为 relic_splitter 工具,提供 Gradio 与 CLI 双入口。
场景:一张展柜照片里有若干文物,希望为每个文物生成一张独立照片——
矩形裁剪图 + 透明背景抠图。全程本地模型,不调云端 API。
我们实现了四种方案:Grounding DINO + SAM、YOLO11x-seg、SAM2 自动掩码、
Florence-2,在同一组样张上完整对比。本文是这次迭代的完整记录,
包括各方案的原理取舍、transformers 5.x 的兼容性排雷,以及最终的产品化封装。
下面是拍摄的展柜照片(首都博物馆:玉米.黄金.美洲豹——玛雅与安第斯古代文明大展)

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

一、需求拆解:这不是一个单纯的"目标检测"问题
四个字面需求,各自指向不同的模型能力:
- "识别物体"——要检测,最好还懂语义(知道那是"花瓶"而不是一堆像素);
- "文物"——不是 COCO 80 类里的常规物体,鼎、玉璧、编钟统统不在列表里,
固定类别检测器天然吃亏,需要开放词汇检测(Open-Vocabulary Detection); - "独立照片"——两种形态:矩形裁剪(容易)和透明抠图(需要实例分割掩码);
- "本地"——模型下载到本地、离线可跑。
由此得出四种候选组合,正好覆盖当前本地视觉模型的四条技术路线:
| 方案 | 路线 | 类别能力 | 分割能力 |
|---|---|---|---|
| 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 近似抠图),
但这次排雷过程极具学习价值,完整故障链:
- transformers 5.19 原生类:权重大量 MISSING(微软仓库还是旧命名格式),
输出乱码("CD CD CD rites..."); - 远程代码路径:远程代码读
forced_bos_token_id,5.x 已删该属性,直接崩; - 结论:必须回退 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 轮子,旧版不认卡。
九、写在最后
四个方案是四种哲学:专用检测(快而受限)、开放词汇(灵活而慢)、
类别无关分割(精细而啰嗦)、多任务大模型(全能而不稳)。
实测的意义在于把"听说很好"变成"在我照片上到底怎样"——
检出数量、边缘质量、类别语义、速度,四个维度只有跑过才知道。
别选边站,让它们各自站到流水线里最合适的位置。
来源:博客园 · cnblogs.com