甲骨碎片缀合实战:断口图像检索与深度缀合模型 1. 项目缘起与整体思路拆解1.1 从人眼拼图到模型找邻居甲骨碎片缀合这件事说穿了就是把一堆断掉的骨片重新拼回它原来的整版。传统做法是靠研究者肉眼比对断口形状、兆纹走向、钻凿位置、字形风格和辞例内容一片一片试。一个熟练的研究者盯着一批拓片看上一整天能确认两三组可靠缀合就已经很不错了。这里的关键瓶颈不是研究者不专业而是候选组合的数量级太吓人一个库房里几千片碎片两两配对就是几百万种可能人眼根本筛不过来。我做的这个项目核心目标很朴素——用图像检索的方式把值得人看一眼的候选对从几百万压到几十个。注意这个定位很重要模型不负责下最终结论它只负责排序和推荐。缀合的最后一步永远要人来拍板因为断口对得上不代表同一版兆序、字体、厚度、色泽、出土层位这些信息模型吃不全。把模型当成高级筛子比把它当成自动拼图机要靠谱得多也更符合实际工作流。适合读这篇内容的人有三类做文化遗产数字化的工程同学、想入门细粒度图像检索的算法同学、以及手上有碎片数据但不知道怎么组织成训练集的研究者。文中涉及甲骨碎片图像的采集规范、深度缀合模型的架构取舍、数据集构建的样本对设计都是可以直接照着改的。1.2 三个子任务拆解与选型考量一上来就端出一个端到端大模型是新手最容易踩的坑。我把整个问题拆成三层每层单独可控层级任务输出常用方案L1碎片实例分割每片碎片的掩膜U-Net 微调 / 通用分割模型微调L2断口轮廓提取与特征编码轮廓序列 定长特征向量边缘检测 轮廓采样 CNN/ViT 编码L3候选配对与全局一致性校验排序列表 缀合组合度量学习 向量检索 图优化为什么这么拆因为这三层的错误性质完全不同。L1 错了是分割边界糊了可以直接看掩膜图发现L2 错了是特征不 discriminative表现为检索结果全是同类不同片的干扰项L3 错了是局部对得上但全局冲突比如三片两两都能对但拼在一起几何上闭合不了。分层之后每一层的失败模式都能单独定位调起来才有方向。另一个关键取舍是要不要端到端训练。我试过把分割和匹配联合训练理论上更优雅实际很难收敛——分割的损失和度量学习的损失尺度差太多梯度互相打架而且标注成本极高要像素级的配对标注。最后退回到分阶段训练 冻结前段的工程做法虽然不够漂亮但复现性好了不止一个档次。1.3 为什么先做数据集再做模型这句话我要加重说在这个项目里数据集的质量上限决定了模型的效果上限而且这个上限压得很低。图像检索领域的公开数据集动辄百万级但甲骨碎片的正样本对是稀缺资源——每一对可靠缀合背后都是一个研究者的判断标注成本极高。我手上第一批数据只有几百对确认缀合剩下的全是疑似但未确认。所以我的策略是不追求正样本数量转而把负样本挖深、把特征学得更稳。具体做法后面第2、3节会展开。这里先给一个结论性的经验在正样本只有几百对的量级下用 Triplet 类度量学习配合大量难负样本效果明显好过直接训一个分类头。因为分类头在类别数远大于样本数时基本学不动而度量学习只需要相对距离正确。还有一点必须提前说清楚拓片和实物照片不要混着当同一种模态处理。拓片是墨拓纹理被强化、颜色信息丢失、背景干净实物照片有光照、有阴影、有土色。同一个碎片的两种图在特征空间里距离可能比不同碎片的两个拓片还远。这一点我在早期吃过亏第5节会详细讲怎么处理。2. 数据集构建从碎片照片到可训练样本2.1 图像采集与规范化处理采集环节决定了后面所有的上限这里省下的时间后面要加倍还。我给团队定的规范是碎片平放正上方俯拍光源用双侧柔光箱 45 度打光避免单侧硬光在断口上产生假阴影——假阴影会让轮廓检测把阴影边界当成断口这是最隐蔽也最常见的脏数据来源。每片至少拍三张正面、背面、以及一个倾斜 30 度的侧视图侧视图用来估厚度和断面倾角。分辨率上碎片普遍只有几厘米见方我要求长边至少 2000 像素等效约 500 dpi 以上。低于这个值断口的微观锯齿就糊成一团轮廓特征基本失效。文件统一存无损格式命名用批次号_盒号_片号_视角的规则别用中文和空格后面写脚本批量处理时你会感谢自己。规范化处理的顺序我固定为去背景用固定色卡背景板色差阈值分割足够→ 白平衡校正用色卡做参考→ 尺度归一化按物理尺寸换算统一到每毫米固定像素数→ 存双份一份原分辨率存档一份 1024 边长的工作副本。尺度归一化这一步千万别省不同批次拍摄距离不同同一片碎片在两个批次里尺寸差 20%轮廓相似度计算直接崩掉。注意不要用自动增强类工具做一键美化锐化和对比度拉伸会改变断口的灰度分布让边缘检测的阈值失去可移植性。所有增强操作必须是确定性的、可复现的、参数写进配置文件的。2.2 碎片实例分割与掩膜质量把控一张图里往往不止一片碎片尤其是库房整盒拍摄的照片。所以第一步是实例分割。我用的方案是基于 U-Net 结构微调输入 512×512输出单通道概率图阈值 0.5 二值化后再做连通域分析面积小于阈值的碎屑直接丢弃。这里有个细节值得说掩膜的边界精度比掩膜的完整性更重要。断口附近的掩膜如果往外扩了两个像素轮廓就会带上一圈背景噪声往里缩两个像素断口的尖锐特征就被磨平了。我的做法是在训练掩膜时给边界像素更高的损失权重边界带宽度设 5 像素权重 3 倍同时在推理后用 Guided Filter 做一次边缘贴合。实测下来轮廓定位误差从平均 3.1 像素降到 1.4 像素别小看这一点检索 Recall10 直接涨了 6 个百分点。质量控制上我设了三道关第一道是自动检查掩膜面积占图像比例在 0.5% 到 60% 之外的一律打回第二道是轮廓平滑度检查计算轮廓的周长面积比异常值人工看第三道是抽样人工复核每批抽 10%。这三道关加起来的返工率大概 8%但不做的话脏数据会以 15% 以上的比例渗进训练集后面根本查不出来。2.3 边缘轮廓提取与几何特征掩膜有了接下来是把断口轮廓从整体轮廓里分离出来。这一步是整个流程里最需要领域知识的地方——一片碎片的轮廓包含两类原始边缘甲骨原来的边通常光滑、有弧度和断口新断裂面锯齿状、不规则。只有断口才参与缀合匹配。我的分离方法是几何统计结合先对轮廓做曲率分析用滑动窗口窗口长度取轮廓点数的 3%算局部曲率序列断口段的曲率方差显著高于原始边缘段再用一个简单的规则过滤——原始边缘往往接近直线或大半径圆弧用 RANSAC 拟合直线/圆内点比例超过 70% 的段判为原始边缘。这套规则在大部分碎片上能work复杂形状的还是要人工修一下。轮廓提取之后做重采样按等弧长间隔取 256 个点同时记录每点的法向量。为什么要等弧长而不是等参数因为不同碎片轮廓长度差异大等参数采样会让长轮廓的点间距很稀短轮廓很密特征尺度不一致。等弧长采样之后所有轮廓都是 256 个点、间距与物理尺寸成正比后续做条带裁剪时窗口大小才有可比性。除了轮廓我还提取了一组手工几何特征做兜底断口段的长度、平均曲率、曲率符号变化次数、端点夹角、以及断面倾角从侧视图估。这些特征在深度特征失效时比如碎片太碎、轮廓太短可以作为辅助排序依据实测在极小碎片轮廓长度小于 200 像素上纯手工特征的 Top-5 命中率比深度特征还高。2.4 正负样本对构造与标注策略样本对构造是数据集构建的核心。正样本对好理解——确认缀合的两片。但这里有个坑确认缀合的两片接触的那一段断口才是真正的匹配区域其他部分不是。如果直接拿整片碎片做正样本对模型会学到这两片整体长得像而不是这两个断口能对上。所以我在构造样本时是从两片的接触断口段各裁一条带沿轮廓法向内外各扩 32 像素沿切向取接触段长度组成条带对。负样本分三档难度递增易负样本随机两片断口长度差异大、曲率分布完全不同。这类样本占 60%作用是稳定训练初期。中负样本随机两片但断口长度相近比值在 0.8~1.25 之间。这类占 30%迫使模型关注形状细节而非长度统计量。难负样本同坑位、同批次、甚至同版不同位置的碎片对或者模型当前排序靠前但确认不缀合的。这类占 10%是提升效果的关键但需要人工确认成本高。标注上我定了一条铁律疑似一律不进正样本。早期为了凑数量把疑似对当正样本模型学得很欢验证集指标也漂亮但实际推荐出来的候选对人工复核通过率只有 12%基本没法用。后来严格只用确认缀合正样本数量掉了一半但复核通过率涨到 40% 以上。宁可少不能脏。3. 深度缀合模型的架构与关键实现3.1 双分支特征提取与度量学习模型主体是双分支 Siamese 结构两个分支共享权重各自吃一条断口条带输出 512 维的 L2 归一化特征。主干试过三种ResNet-50、EfficientNet-B3、ViT-B/16。结论是ResNet-50 在数据量小于一万对时最稳ViT 需要更多数据才能发挥EfficientNet 精度接近但推理速度快 30%最后线上用的是 EfficientNet-B3离线实验用 ResNet-50 做对照。为什么选度量学习而不选分类前面提过类别数远大于样本数。具体说如果按碎片ID分类每类只有一两张图分类头根本训不动如果按缀合组分类组数几百、每组样本数不均长尾严重。而度量学习只要求同组近、异组远天然适配这种稀疏监督。损失函数我用的是 Triplet Loss 加 Circle Loss 的组合前者收敛快后者在难样本上梯度更合理。三元组构造用 PK 采样每个 batch 取 P 个缀合组、每组 K 条样本P16、K4batch size 64。这个配置在 24GB 显存的卡上大约占 14GB留了余量给数据加载和梯度累积。3.2 轮廓-纹理双通道融合只用轮廓信息会丢东西。很多甲骨碎片的断口被磨蚀过轮廓形状已经不能反映原始断面但断面上的纹理、颜色、钻凿痕迹仍然有信息量。所以我做了双通道轮廓通道吃的是沿法向展开的条带图把弯曲的断口拉平成矩形宽 64、高 接触段长度纹理通道吃的是断口附近 128×128 的原始图像块。两个通道各自过一个轻量编码器输出拼接后过一个融合模块两层 MLP LayerNorm再投影到 512 维。融合权重我加了可学习的门控实测下来模型自己学到的规律很有意思轮廓清晰的样本门控偏向轮廓通道磨蚀严重的样本偏向纹理通道这个行为不是设计的是数据驱动的但很符合直觉。这里有个工程细节条带图的构造需要沿法向重采样如果法向估计不准条带会扭曲。我的做法是对法向序列做一次滑动平均平滑窗口 9并在轮廓两端各留 16 个点不做采样端点处法向不稳定。这个处理让条带图的质量明显提升可视化检查时能直接看出来。3.3 图神经网络做全局一致性校验L2 输出的只是两两之间的相似度但缀合是个组合问题。三片碎片 A-B 相似度 0.9、B-C 0.85、A-C 0.88不代表三片能拼成一个闭合结构——可能几何上根本闭合不了。这层校验我用了一个轻量的图网络。具体做法取检索出来的 Top-K 候选对K20把涉及的碎片作为节点候选对作为边边特征包括相似度、断口长度匹配度、端点夹角差、断面倾角差。用一个两层的 EdgeConv 更新节点和边特征输出每条边可缀合的概率。然后用一个带约束的最大权匹配每片碎片最多参与一次缀合或者允许链式缀合时用路径搜索选出最终组合。这一层的收益不是精度提升而是大幅降低误报。没有这层的时候Top-20 候选里人工复核通过率 40%加了图校验之后通过率提到 58%代价是召回掉了 3 个百分点。这个交换在实际工作流里非常值因为研究者的时间是最贵的资源。3.4 损失函数与难负样本挖掘难负样本挖掘我用的是在线离线混合策略。在线部分每个 batch 内选距离最近但标签不同的样本作为三元组的负样本这是标准做法。离线部分每训练 2 个 epoch 跑一次全库检索把相似度高但确认不缀合的样本对加入难负样本池池子容量控制在正样本数的 3 倍以内。为什么要控容量因为难负样本太多会让模型过拟合到那几个特定的干扰项上。我踩过一次坑难负样本池扩到正样本 10 倍训练损失降得很漂亮但验证集 Recall 反而掉了 5 个点典型的分布偏移。后来降到 3 倍并且每轮从池子里随机采样 70% 使用才稳定下来。还有一个技巧是标签平滑式的相似度目标。正样本对的目标相似度不设成 1.0而是设成 0.85 到 0.95 之间的随机值理由是不是所有确认缀合的断口接触都同样紧密——有些是严丝合缝有些中间有缺损。这个随机化让模型学到的相似度分布更接近真实情况排序时的区分度反而更好。4. 训练、检索与推理的完整实操流程4.1 环境与显存预算环境就是标准的 PyTorch 那一套CUDA 版本跟驱动对齐就行没必要追新。我把关键依赖锁死版本因为图像处理库的插值实现在版本间会变会导致数据增强不可复现——这个坑很隐蔽同一份代码两天跑出两个结果查了一整天才发现问题出在某个库的小版本升级。显存预算我实测了一组数据给做同类项目的同学参考配置输入尺寸batch size显存占用单 epoch 耗时约 8000 对ResNet-50 单通道256×25664约 11 GB18 分钟EfficientNet-B3 双通道条带 64×256 块 128×12864约 14 GB26 分钟ViT-B/16384×38432约 22 GB41 分钟如果卡只有 12GB建议用梯度累积把等效 batch 拉到 64别硬降 batch size——度量学习对 batch 内的负样本数量很敏感batch 太小三元组根本组不起来。4.2 训练配置与参数计算优化器用 AdamW权重衰减 1e-4。学习率这块我做了 warmup cosine前 500 步线性 warmup 到 3e-4然后余弦退火到 1e-6总步数按数据量算。为什么初始学习率是 3e-4 而不是常见的 1e-3因为 Siamese 双分支共享权重梯度是两路叠加的等效学习率会翻倍用 1e-3 直接发散过两次。训练轮数上正样本几百对的量级我一般训 40 到 60 个 epoch配合早停验证集 Recall10 连续 5 轮不涨就停。实测 30 轮左右基本收敛后面主要是难负样本挖掘在起作用。一个具体建议第一个 epoch 先用易负样本热身别一上来就上难负样本否则模型在特征还很随机的时候就被推着去区分细微差异容易塌缩。我的做法是前 3 个 epoch 只用易负样本第 4 轮开始按比例混入中负样本第 8 轮才引入难负样本。数据增强方面旋转±15 度、缩放0.9~1.1、亮度扰动±10%、高斯噪声是安全的。水平翻转要慎用——甲骨上的字形和兆纹是有方向性的翻转之后语义就变了虽然对纯轮廓匹配影响不大但会让纹理通道学到错误的不变性。我的做法是轮廓通道允许翻转纹理通道禁止翻转两个通道用不同的增强管线。4.3 向量索引与大规模候选检索训练完之后把所有碎片的断口段编码成向量建索引做近似最近邻检索。碎片数量在几千到几万这个量级FAISS 的 IVF-PQ 足够用nlist 取 4 倍根号 N比如 N50000 时取 900 左右PQ 分段数取 32压缩后单条向量 64 字节内存占用可以忽略。检索流程是这样的每片碎片取特征在索引里查 Top-50然后过滤掉自身、过滤掉同一片碎片的其他断口段一片碎片可能有多个断口段这是合法的但要单独标记、过滤掉长度差异超过 40% 的候选剩下的大概 20 到 30 个进图校验层。整个流程在单机上跑几千片碎片端到端大约 3 分钟。注意索引必须用只包含训练集之外碎片的底库来评估否则就是自己查自己。我第一次做评估时忘了这个Recall10 报了 0.92兴高采烈地写进报告后来发现底库里混了训练样本实际只有 0.61。这个错误在检索类项目里极其常见务必用碎片ID做严格隔离不是用图像ID——同一片碎片的不同视角图必须整体分到同一侧。4.4 人工复核界面与反馈闭环模型输出的候选对最终要给人看所以界面的设计直接影响整个系统的实用价值。我的界面很简单左边是待缀合碎片的放大图右边是 Top-20 候选的缩略图列表点开某个候选后两片的断口轮廓用不同颜色高亮并且做一个自动对齐按轮廓端点匹配做刚体变换让人能一眼看出断口是否吻合。反馈闭环是重点复核结果分三档——确认缀合、确认不缀合、待定。确认缀合的进正样本池确认不缀合的进难负样本池待定的不参与训练但会在下次检索时置顶提示。每积累 50 条新标注就增量微调一次用低学习率1e-5只训最后两层避免破坏已学到的特征。这个闭环跑起来之后系统的复核通过率是逐月上升的比一次性训练完再也不动的效果好很多。5. 常见问题与排查技巧实录5.1 数据侧问题速查数据侧的问题占了我在这个项目里处理问题的七成以上而且症状往往伪装成模型不行。下面这张表是我整理的速查清单都是从实际踩坑里提炼的症状可能原因排查方法处理验证集指标虚高线上很差训练/测试集碎片泄漏按碎片ID统计交集严格按碎片ID划分同片多视角必须同侧模型对某些碎片完全不响应掩膜外扩轮廓带背景噪声可视化掩膜叠加图重训掩膜加边界权重相似度普遍偏高区分度差尺度未归一化统计轮廓长度分布按物理尺寸重采样拓片与照片互查失败模态差异分模态统计相似度分别建索引或加模态对齐损失极短轮廓检索全错特征不充分按轮廓长度分桶看指标手工几何特征兜底特别说一下模态差异这个问题。早期我把拓片和实物照片混在一个特征空间里结果就是拓片查拓片还行拓片查照片基本全错。根因是拓片是二值的、背景纯黑模型很容易学到背景全黑这个捷径特征。解决办法有两个一是训练时随机做背景替换增强把背景换成随机噪声或纯色逼模型看断口二是分别建索引检索时按模态路由。我两个都做了效果叠加。5.2 模型侧问题速查模型侧最常见的是训练不收敛和特征塌缩。特征塌缩的表现很有辨识度所有样本的特征向量两两余弦相似度都在 0.9 以上检索出来的候选基本是随机的。原因通常是负样本太简单或者学习率太大。排查方法是画特征相似度分布直方图正样本对和负样本对的两个峰应该是分开的如果重叠成一个大峰说明特征没学开。另一个隐蔽问题是难负样本过拟合。表现是训练损失持续下降但验证集指标在第 15 轮左右达到峰值后开始缓慢下滑。我的处理是把难负样本池做成轮换制每个 epoch 只采样其中一部分并且定期淘汰那些已经被模型学会的样本相似度降到阈值以下的移出池子。还有个小技巧值得分享定期做一次消融式检索就是把轮廓通道和纹理通道分别单独拿去检索看两个通道的 Recall 差距。如果某个通道的指标突然暴跌说明那一侧的数据管线出问题了比盯着总指标更容易定位。我靠这个方法发现过一次条带图生成脚本的法向符号错误——一半的条带是上下颠倒的总指标只掉了 2 个点但单独看轮廓通道掉了 15 个点一查就查出来了。5.3 评估泄漏与指标陷阱评估环节的坑不比训练少。第一个陷阱是前面说的碎片级泄漏第二个是断口段级泄漏同一片碎片的不同断口段在训练集和测试集分到了两侧模型记住了这片碎片的整体特征指标虚高。第三个是缀合组的传递泄漏A-B 缀合、B-C 缀合那 A-C 大概率也相关如果 A-B 在训练集、B-C 在测试集评估就不可信了。我的做法是做连通分量级划分把所有确认缀合关系建成图求连通分量整个分量一起分到训练或测试。这样虽然会让训练集变小一个大连通分量可能包含几十片但评估结果是真的可信。划分比例大概 8:2如果连通分量太大导致测试集碎片数不足就接受这个结果不要为了凑数去拆分量。指标上我盯三个Recall10候选里有几成是真的、人工复核通过率Top-20 里有多少被确认、以及首次命中排名正确缀合排在第几。第三个最能反映实际体验——如果正确结果平均排在第 3 位研究者点开前三个就能找到如果平均排在第 15 位那前 14 个都是干扰体验完全不同。6. 参数微调与效果再提升的几个实战方向6.1 从相似度排序到缀合链搜索单对检索做扎实之后最有价值的扩展是链式缀合搜索。实际工作中经常出现两片能拼、但只拼出一个小块需要继续往外找的情况。我的做法是把检索结果建成图用带约束的路径搜索找长度为 3 到 5 的缀合链约束条件包括相邻两片的断口长度必须匹配、链上所有断口的走向不能自交、链的总跨度不能超过同类甲骨的常见尺寸。实现上不需要多复杂深度优先搜索加剪枝就够。关键是剪枝条件要设计好我的剪枝规则是当前链的总夹角累积超过 270 度就剪掉说明绕回来了、任意两片碎片的外接框重叠面积超过阈值就剪掉几何冲突、链末端断口的曲率与待匹配碎片差异超过阈值就剪掉。加上这三条搜索空间从指数级降到可接受的范围几千片碎片跑一遍大概十几分钟。链式搜索的收益很直接单对检索的复核通过率 58%链式搜索出来的组合通过率能到 45% 左右但每条链包含 3 到 4 对缀合单位人工投入的产出反而更高。6.2 主动学习选样把标注预算花在刀刃上标注资源永远是稀缺的所以标哪些比标多少更重要。我用的是不确定性采样加多样性采样的混合策略每轮从候选池里选 100 对送人工其中 60 对选模型相似度落在 0.5 到 0.7 这个决策边界区间的最不确定40 对用特征聚类选每簇的代表保证覆盖不同形态的碎片。这个策略的效果我做过对比随机选 100 对标注模型 Recall10 提升约 1.2 个点用主动学习选 100 对提升约 3.5 个点。差了将近三倍说明在标注预算有限的情况下选样策略的投入产出比远高于模型结构上的微调。实操上要注意一点不确定性采样容易陷入总是选同一类碎片因为模型对某几类样本持续不确定。所以要加一个去重约束同一连通分量每轮最多选 2 对避免标注预算被少数几个难例吃掉。6.3 我个人在长期迭代中的几点体会做了这么久最大的体会是这个项目的瓶颈从来不在模型结构上。我换过主干网络、加过注意力、试过对比学习的各种变体每次带来的提升都在 1 到 3 个百分点之间。而把掩膜的边界精度提上去、把样本对的构造从整片对整片改成断口段对断口段、把评估的泄漏堵住这三次改动每一次都是 5 个百分点以上的跃升。第二个体会是可视化比指标更能发现问题。我养成了一个习惯每周随机抽 20 个检索结果做人工检视看排序、看对齐、看特征热力图。很多问题指标上看不出来但一眼图就能看出来比如模型把厚度差异当成了重要特征因为某批数据厚度分布和缀合关系有相关性这种伪相关只能靠看图发现。最后分享一个小心得把每次实验的配置、数据版本、指标、以及当时的可视化截图都存进一个固定目录命名带上日期和一句话说明。这个习惯看起来笨但在做了上百次实验之后你会发现自己能随时回去复现任何一个当时效果特别好但忘了怎么配的结果省下的时间远超记录的成本。这个领域的数据太特殊、标注太贵任何一次实验都值得被完整记录下来。