
做甲骨文目标检测这件事我最早是被一个现实问题推进来的手上攒了两千多张甲骨拓片和照片需要按字切分、归类、建索引纯靠人工看眼睛真的会瞎。于是开始折腾目标检测图像数据集一路做到这一期的第10341批数据整理和模型升级。这个过程里踩过的坑、绕过的弯路、以及最后沉淀下来的一套可复用的处理流程我觉得比模型本身更值得写出来。这篇就围绕这一期数据把从图像预处理、标注质量把控、模型选型训练到推理部署和跨期迭代的完整链路尽量讲透。先说个总体结论甲骨文识别在技术栈上和通用目标检测没有本质区别难点几乎全在数据侧。字形的残缺、拓片的噪声、类别体系的模糊、标注标准的不统一这些才是真正拉开效果差距的地方。后面每一个章节我尽量按实际处理顺序来写你照着走一遍基本能复现出可用的检测系统。1. 甲骨文目标检测真正的门槛不是模型是数据形态1.1 拓片、照片、摹本之间的分布差异我一开始犯过一个很典型的错误拿馆藏高清扫描图做训练拿手机翻拍图做测试结果mAP直接掉了二十多个点。原因不复杂——拓片是黑底白字博物馆数字化扫描光线均匀、背景干净手机翻拍往往带角度、带反光、带环境阴影纸张还有弯曲变形。模型在干净背景上学到的特征一到真实场景就失效了。所以做这一期数据时我特意把三种来源分开管理一是黑白拓片扫描件二是原色照片三是次生载体上的摹写或影印。训练集里三种都要有且比例不能太偏。从实际效果看原色照片占比至少应到三成否则模型对光照变化非常脆。更稳妥的做法是对每张图像额外记录来源标签后续分析哪些类别在哪种来源上容易漏检也方便做针对性补数据。1.2 “一个字”到底怎么定义在古文字场景里是模糊的通用目标检测里“目标”的边界通常是常识性的猫就是猫、车就是车。但甲骨文里没有这么干净。同一片甲骨上单字、合文、偏旁部件混在一起同一个字形可能存在多个异体写法有些残字只剩一半笔划专家也未必能确定该归到哪个释读。如果你直接拿现成标注工具开框会很快被“这个到底算不算一个字”的问题卡住。我参与整理的这批第10341期数据类别体系最终收敛了这样一套规则图像上只要能明确判断是一个独立书写单元的就单独标框单体字按释读分类已经学界公认的合文作为一整个框处理不拆成多个单字模糊到无法判断归属的统一进入“未定类”而不是硬塞进某个相近类别。这条规则看起来简单但极大减少了后续训练时的标签噪声。1.3 类别体系按字形分类还是按释读分类这里还有一个隐藏冲突。甲骨文研究有时候按“字形”组织有时候按“释读”组织但目标检测的类别标签只能二选一。我试过两版标注第一版按主流释读结果标注第二版尽量按字形聚类。实测下来按释读结果标注模型对异体字的泛化更好按字形聚类类内一致性高但类别数爆炸而且训练样本被切得太碎许多类别只有几十个实例。所以最后的妥协方案是以释读类别为主干把批量小但写法稳定的异体字保留为子类那些既没有释读结论、字形又孤立的样本集中归到“未定类”。这个归类逻辑要写进标注规范文档里否则标注人员每换一批就会按照自己的理解重新定义一遍。规范文字不需要多宏大能解决“这个框归哪一类”的日常疑问就够了。2. 从原始图像到干净训练集第10341期的完整加工链路2.1 一张图是否要做二值化我的实测答案是不做很多做文档识别的项目喜欢在前处理阶段做二值化或背景去除让前景文字更“干净”。我在甲骨文图像上也试过几种经典二值化算法、自适应阈值和形态学降噪。结论比较反直觉在目标检测 pipeline 里做二值化收益很小风险很大。风险主要来自拓片自身的纹理。甲骨拓片的裂纹、石花、纸纹在二值化后很容易被保留为边缘噪声和字形笔划分辨不开而温和的阈值参数又会削掉浅淡笔划导致文字残缺。相比之下让模型直接看灰度图或原图它反而能学到“哪些纹理是字形、哪些纹理是背景”的区分能力。我自己后来只保留了两步前处理统一缩放尺寸以及将彩色图转灰度图再做一次轻度的直方图均衡化。如果训练数据里原色照片多我甚至不做灰度转换。这一步省掉之后训练速度变化不大但测试集的误报明显少了。这一期的数据我在保存时同时保留原图和处理图两套文件后面重训模型也方便做前后对照。2.2 高效标注预标注加人工修正而不是从零开框标注是目标检测图像数据集构建里最费人力的环节。第10341期这批图像如果全部从零标注按每个框三十秒估算要消耗大量工时。实际做法是先用上一版模型在这批图上跑一遍预标注再把置信度在0.4以上的框导入标注工具人工负责增补漏框、删除误报、修正偏移框。这套流程能把标注效率提升不少但有一个系统性风险预标注模型本身的偏差会被人工标注“继承”。比如旧模型对某一类字形系统性漏检人工修正时如果没注意到新标注数据也会缺这部分。我用的对策是随机抽一批样本不导入任何预标注框直接从零标注拿它和“预标注加修正”的结果做增量对比确认旧模型漏掉的类别在人工阶段被补回来了。双侧校验做完这批数据的标注覆盖度才有保障。2.3 双重标注一致性校验漏标比错框更致命我让两名标注员独立标注了同一批共80张图然后计算标注一致性。一致性检验如果只看框的位置和类别很容易忽略“某些字整个没标”的情况。真正的统计口径是把所有框按类别和大致位置匹配起来统计漏标数、多标数、类别不一致数、边界偏移量。实测下来漏标率对训练影响最大因为漏标会让模型把“背景/区域”学成“无目标”在真实推断时表现为对该字形视而不见。针对漏标我的处理是定期跑“反向校验”用当前模型检测已完成的标注图凡是模型给出高置信度但标注中不存在的框一律弹出来让人工复核。这个步骤能捞回不少漏标。很多团队只关注框准不准却忽略了“有没有把所有目标都框出来”这是我在几轮数据整理之后感受最深的一点。另外标注工具有必要同时导出多份格式。这一期我保留了VOC XML和YOLO txt两种版本前者方便后续二次处理与可视化后者直接喂给训练脚本。两个格式间的转换脚本必须做字段核对不要只做字符串替换否则很容易在坐标格式上翻车。2.4 数据增强策略少即是多mosaic不宜拉满通用目标检测里mosaic增强能显著提升模型对遮挡和多尺度目标的适应力。但甲骨文和自然场景不太一样一个字的核心信息集中在细窄笔划上过强的几何增强和颜色扰动反而会破坏笔划结构。我在第10341期数据上做过一组消融对比mosaic设为0.5时mAP50有小幅提升mAP50-95基本持平mosaic设为1.0时字形断裂类误检开始增多颜色增强hsv_h拉大到0.05以上模型对浅淡残字的召回反而下降。所以最终训练配置里我保留了轻度翻转和微小尺度抖动关闭了大部分色彩扰动mosaic开到0.5而不是满值。旋转增强也控制在小角度范围因为甲骨文的文字方向虽然有倾斜但整体接近水平过大的旋转会生成现实中几乎不存在的样本等同变相引入噪声。3. 模型选型和训练参数第10341期实测出的平衡点3.1 模型规模为什么我没有选更大的骨干网络第10341期这批标注完成的数据实例总数不到8000个类别约30类。在训练目标检测模型时数据量决定了模型复杂度上限。我对比过YOLO系列里两个体量的模型轻量版本和更大规模版本。同样训练300轮大模型在训练集上拟合得更彻底但在验证集上的mAP50-95反而低1到2个点训练时间也长不少。原因就是数据量撑不起大模型的容量过拟合来得更早。轻量版本在小数据集上收敛快、灵活性高后期做量化部署也更方便。真实项目里准确率固然重要但可迭代性同样关键。我们经常需要根据新的考古发现或新的释读意见调整类别体系轻量模型一次重训只需要几十分钟大模型动辄几小时迭代效率完全不同。3.2 训练参数配置和取值理由我直接给出一份训练配置复制就能用条件是你的标注格式是YOLO格式。yolo detect train \ dataoracle.yaml \ modelyolov8n.pt \ imgsz640 \ epochs300 \ batch16 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ mosaic0.5 \ close_mosaic10 \ hsv_h0.015 \ hsv_s0.3 \ hsv_v0.3 \ fliplr0.5 \ scale0.3逐个说下关键参数的考虑imgsz640数据尺寸的默认值和预训练权重输入一致多数情况下够用。batch16在显存允许范围内尽量大。小batch加高学习率在数据类别不均衡时容易震荡。epochs300带上早停实际跑到220轮左右就停了并不是越多越好。判断标准是验证集mAP是否连续20轮不涨。optimizerAdamW收敛比SGD稳尤其在不做大幅学习率预热的情况下。close_mosaic10训练最后10轮关闭mosaic让模型在接近真实分布的数据上做最后微调。这个细节能显著降低验证集loss的抖动。hsv变化全部调低上文已经说过甲骨文识别对颜色扰动敏感。这份配置在验证集上的结果大概在mAP50为0.87、mAP50-95为0.63的水平不算惊艳但对于类别多、残缺多、噪声大的甲骨文数据属于比较合理且稳定的区间。3.3 训练曲线怎么看loss掉但mAP不动先查数据问题很多人在训练时只看训练loss还在降就认为模型在变好。实际上如果训练loss一路下降、验证mAP却停滞绝大多数时候不是训练轮数不够而是标签里有系统性错误。一个很典型的例子由于标注规范里“未定类”把大量噪声框都吸收进来了模型会把很多背景纹理当成目标学结果就是训练loss很漂亮验证集上全是误报。我后来改进了诊断方式每训练50轮就保存一次权重用固定的一批测试图跑一遍可视化推理。不要只看指标曲线直接看检测框画到图上是什么效果很多问题在图像上比在数字里暴露得更快。第10341期就靠这种方式发现了一个很隐蔽的问题同一种偏旁出现在上下不同位置模型频繁把偏旁所在区域预测成邻近的整字类别。这个属于采样偏差——训练集里该偏旁的独立样本太少模型被迫靠上下文猜而上下文又不够稳定。3.4 两次典型的跑偏经历一次是数据划分出错某一张拓片被裁剪成四个子图其中三个进了训练集、一个进了验证集。因为它们来自同一原始图像图像风格、裂纹背景、字形风格高度一致导致验证集分数虚高了大约2.5个点。这类泄漏最隐蔽因为背景特征成了模型判断类别的捷径。后来我引入了基于图像来源路径的分组划分确保来自同一张原始图像的所有子图只出现在同一个划分里。另一次是增强参数过强我给训练集开了接近默认值的mosaic和旋转结果模型对甲骨上常见边框线的误检率明显升高。原因也很直接放大旋转后裂痕和边框在形状上越来越像某些字形笔划。后来回退增强强度后误检率降回正常水平。增强不是越狠越好这个教训我是真金白银换来的。4. 推理部署中的真实表现验证集分数高不代表能直接上线4.1 认清验证集和真实使用场景的差距验证集分数高很多时候只说明你的模型和你的验证集“合拍”。真实的甲骨文检测使用场景可能是学者在电脑上放大的高清单张图也可能是考古现场用手机拍的工作照还可能是从PDF论文里截出来的低分辨率插图。三种场景差异极大。我验证过的一个现象很能说明问题模型在扫描拓片测试集上mAP50将近0.9但放到低分辨率截图测试集上直接降到0.6左右。原因不只是分辨率下降更在于低分辨率图经过压缩后细笔划周围产生了明显振铃和模糊模型提取不到原来的边缘特征。应对方法是训练时imgsz用640但推理时可以按长宽比先做超分辨率增强另一个更省事的方法是准备一批低分辨率样本加进训练集。4.2 置信度阈值和NMS的调参经验对部署来说阈值设定甚至可以算作模型效果的一部分。我测过不同置信度阈值在一批混合来源测试集上的表现置信度阈值精确率召回率框数量主观观感0.100.340.82非常多大量误报没法用0.250.550.75偏多仍有零散误报0.400.720.66适中整体舒服偶漏0.600.840.48偏少漏检较多甲骨文检测的信噪比天然比自然场景低阈值太低会崩太高会漏。综合考虑人工复核成本之后我默认选择0.35到0.45之间。学术研究场景我甚至会单独提供0.30的“高召回”版本方便专家尽可能看到候选区域宁多勿缺。NMS的IoU阈值同样值得调。甲骨文里两个相邻字形经常离得很近如果NMS阈值太高两个真实框可能被合并成一个如果太低一个字的多个重叠框又会同时保留。实测设0.5比较均衡。4.3 最常见的四类误报抛开阈值问题模型在真实图像上常见的误报大致有四种第一类是纸张裂纹和折痕被识别成字的笔划尤其是长直裂纹经常和“横”类字形撞车。第二类是拓片上的石花或砂点在低置信度区间大量出现表现为大块密集小框。第三类是扫描形成的阴影或者反光带容易在边缘产生一个或几个高置信度误报。第四类是表格线、边框线或排版痕迹被模型认成某些横笔较多的字。针对第一类和第四类我试过加负样本专门截取那些“像字但不是字”的图像块放进训练集把背景类别显式地引入。这个方法对降误报出奇有效但也可能让模型变得过于保守。所以负样本的比例要控制我一般控制在正样本量的20%以内并且持续做单测。4.4 高分辨率长图的切块推理整张拓片往往是一张超长图直接缩到640推理会丢失大量小字细节。我的做法是滑窗切块推理把长图按1024的步长切出带重叠区域的子图每个子图单独推理再把所有检测框映射回原图坐标做融合。重叠区域用0.2比较稳太少了边框处的字容易漏太多了同一位置会被多个子图重复检测虽然NMS能合并但计算量浪费不少。映射回原图时要注意坐标如果不在原尺度上需要先拉伸这里是一个典型的容易出bug的位置所有缩放比例必须先统一。我处理完切块推理后会把整图的叠加重叠框结果可视化一遍用肉眼抽查边界处有没有出现“半截框”或“双线框”。5. 跨期数据迭代第10341期不是终点是怎么接入旧模型5.1 增量训练和全量重训的选择当一批新数据标注完成后最大的诱惑是直接在旧权重上继续训练。我试过几次发现直接fine-tune会把旧数据上的能力逐渐冲掉尤其是那些旧数据里占比较高、新数据里占比低的类别。所以后来坚持每次新数据合并进总数据集重新从头训练。代价是训练时间变长但换来的是能力稳定不偏科。也有一些中间路线比如冻结部分层只训练检测头或给旧数据做回放采样。在类别体系没有大改动时这些方法也能用。但类别体系一旦有增改一定要从头训练因为检测头的输出维度变了旧权重结构对应不上。5.2 固定回归测试集是防止模型退步的好工具每期数据整理后我都会从历史数据里抽出大约两百张覆盖所有类别和典型难例的图组成一个冻结的回归测试集。只要训练脚本或数据有改动就用这份回归集跑一遍指标对比。回归测试集的意义在于它不是让你看到数字变高而是防止旧能力悄悄丢掉。比如某一期我调整了数据增强策略总体验证集mAP提升了但回归集里几个特定的残字漏检变多了。如果没有这组固定测试这个问题很可能要在上线后被用户反馈才发现。保持一份长期不动的测试集能帮你把“整体变好”和“局部变差”分开看清楚。5.3 处理重复数据同一拓片被多次收录怎么办跨期整理数据一定会遇到同一张拓片在不同期次里被再次收录。如果不去重训练集和验证集之间就可能出现同源图像轻则分数虚高重则导致“背题”假象。我用的是感知哈希粗筛加人工复核对所有图像计算感知哈希两两相似度超过阈值的自动标记然后人工判断是否是同一来源。同一来源的图只保留一个代表样本其余进候选池备用。虽然相似度阈值偶尔会误伤不同图像但漏掉的代价是近重复数据“污染”测试集。两害相权取其轻我宁可多标记一些候选人上去复核也不想漏掉任何一张。5.4 每一期数据接入之前先跑一轮“冻结基线”我的标准流程是新数据不直接进训练集。先把它单独放一边用上一版模型在上面跑一次完整推理统计指标并可视化结果。这一份结果就是“冻结基线”能告诉我们旧模型对这批新数据知道多少、缺多少。然后把新数据合并进来重训再用同一批图做测试和冻结基线对比。这个流程虽然多花一点时间但能清楚量化每一期数据带来的提升。第10341期我当时记录的对比情况是融合重训后mAP50比旧模型在该批上提升了大约11个点其中“未定类”单项召回率提升最明显。不跑基线的话这种提升只能靠感觉没法形成可靠的数据洞察。回到开头说的那个场景当我把这套流程完整跑通之后新到的数据对我而言就不再有“拆盲盒”的感觉。每一批数据进来都有明确的加工路径、质量校验和回归验证方法。做小众领域的目标检测图像数据集本质上拼的是流程设计和对领域噪声的敏感度。模型本身已经高度工具化了反而是数据侧的那些细节决定了系统最终能被信任到什么程度。如果有人也正在整理类似的小样本识别数据集我建议先把数据规范文档、回归测试集和基线对比这三件事做扎实再碰模型后面你会感谢当初没有偷懒的自己。