尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

古籍OCR方案详解:检测、识别与版面分析实战

古籍OCR方案详解:检测、识别与版面分析实战 简介本资源为粤港澳大湾区黄埔国际算法算例大赛中‘古籍文档图像识别与分析’赛题的Alphx队完整参赛源码包面向AI算法竞赛选手、计算机视觉与古籍数字化方向的研究者及高校相关专业高年级学生。项目聚焦古籍图像OCR、版面分析、文本清洗与古文语义理解等核心任务覆盖从图像预处理、深度学习模型训练基于PyTorch/TensorFlow、PSE/CTC文本检测到jieba分词与BERT微调的全链路技术实践。压缩包共119个文件含81个Python主程序与工具脚本含pse.cpp、pa.cpp等加速模块、10个Shell自动化训练/评估脚本、8个配置与说明文本、4个Markdown文档含README结构化说明、3张示例古籍图像及1个YAML模型配置文件整体26.42MB目录组织清晰模块职责分明。目前已有161人学习下载可直接复现比赛方案、调试关键模型、参考古籍专用数据增强策略与噪声处理逻辑是文化遗产AI保护领域难得的工程级开源实践样本。 最近把“粤港澳大湾区黄埔国际算法算例大赛”古籍文档图像识别与分析算法比赛那套源码重新翻出来梳理了一遍发现很多当时踩坑的细节如果不记录下来过段时间再看自己都会懵。这个比赛聚焦的是古籍数字化里最难啃的一块——文档图像识别与分析通俗点说就是让算法去“读”扫描出来的古籍页面把繁体竖排、带批注、有污渍的文字转成可检索的文本同时还要弄清楚版面上的阅读顺序。Alphx队源码这个包里有完整的训练、推理和调参脚本我基于这份源码并结合自己在类似比赛里的实践把整套方案从头到尾拆一遍重点讲清楚为什么这样做、怎么做、中途踩过哪些坑。这套东西适合谁看一个是做OCR或者文档分析方向的算法工程师比如你要处理古文、竖排、扫描件这类非标准文档场景另一个是想参加算法比赛找参考范式的同学尤其是“从赛题到落地方案”这条逻辑链比单纯拿一份“满分代码”更有价值。我会按任务拆解、方案选型、核心模块实现、训练调优、问题排查、工程化建议这几个部分展开所有步骤都可以在你的数据集上直接复现或改造。1. 这场比赛到底要解决什么问题——古籍OCR与分析任务拆解1.1 古籍文档图像识别为什么比普通OCR更难很多人以为古籍识别就是把现代OCR模型拿过来换个字体就能跑。真上手之后会发现完全不是这么回事。普通印刷体文档通常满足几个隐含假设文字是横排的、字体相对标准、页面干净、阅读顺序从左到右从上到下。古籍文档几乎把这些假设全部打破。从图像层面看比赛给的是扫描或拍摄的古籍页面常见问题包括纸张泛黄导致背景不均匀存在水渍、霉斑、墨迹污染边缘畸变、倾斜角度大甚至有些页面是左右两栏或带眉批、夹注的复杂版面。从文字层面看古籍以繁体字为主还夹杂大量异体字、俗字、避讳字和手写批注很多字在现代字库里根本找不到。从排版层面看竖排是从右往左读的遇到正文、批注、表格混排时阅读顺序的恢复是个独立难题。这些因素叠加在一起如果还用“检测横排文字框→按坐标从左到右排序”的朴素思路结果基本没法看。所以这个比赛本质上考的不是单一模型的精度而是整套流水线的鲁棒性。1.2 赛题任务的完整解读源码包对应的赛题严格来说不是一个单任务比赛而是“识别”和“分析”两件事组合在一起。所谓识别就是把图像里的文字区域找出来并转成文本对应到技术栈是文本检测 文本识别所谓分析是对文档版面做结构化理解包括区分标题、正文、批注、页眉页脚以及给所有文本块恢复正确的阅读顺序。从源码的目录结构也能看出来主流程是两段式的先有一个检测模块负责输出文本框再有一个识别模块负责把文本框内的图像转成字符串最后有一个后处理模块把文本框按版面规则聚成段落并按阅读顺序排序。中间还有一批数据增强、字典生成、rule-based纠错的脚本。也就是说最终提交的结果不是单纯的一堆字符串而是带坐标、带段落归属、带顺序的结构化数据。很多参赛队在线下只关注字准确率忽略顺序恢复的评估结果线上分数和预期差很多就是因为赛题把版面结构和阅读顺序也纳入了评测范围。1.3 评测指标与基线选择源码里的评测脚本主要看两个维度一个是文字识别的准确率通常是整句匹配率或编辑距离的变体另一个是端到端结构化指标比如文本行是否被正确检出、段落归属是否正确、阅读顺序是否一致。后者在实际评估时常用“顺序不一致惩罚”或者“分组F1”来度量。这就带来一个很重要的策略选择如果你的检测漏了文本行后面的识别再好也救不回来如果你的识别把字认错了但检测和顺序都对还有的救如果顺序恢复错了哪怕每个字都认识整段句子也是乱序的扣分最狠。所以我在搭建方案时把优先级定为检测召回 顺序恢复 识别精度这个排序支撑了后面绝大多数模型和策略的取舍。2. 整体方案设计与技术选型思路2.1 为什么选择“检测识别”两阶段流程而非端到端方案现在OCR领域有两大类方案一类是检测识别分开的pipeline另一类是端到端模型比如把检测和识别合并成一个网络直接输出结构化文本。从论文看端到端很省事古籍场景下我却坚决选择了两阶段。原因有几个。第一古籍数据集规模小且标注成本极高端到端模型通常需要海量page-level标注来训练内部对齐模块两阶段可以分开利用预训练权重检测在合成数据上就能训得不错识别则可以用字典约束来弥补样本不足。第二两阶段便于插入规则和人工干预古籍场景里有大量不确定情况比如某页识别置信度低你可以单独把检测框和识别结果导出来分析定位是哪一级出错端到端模型的错误回溯成本高得多。第三推理阶段两阶段可以缓存中间结果调试时不用整条链路重跑这对比赛节奏很重要。从源码看Alphx队也是走的两阶段pipeline检测模型输出四边形框识别模型对每个框做裁剪和识别后处理再做版面分析。这个架构不高大上但是每一步都可控、可分析、可优化是比赛里性价比最高的选择。2.2 文字检测模型选型DBNet还是PAN检测模型是整个pipeline的地基。如果检测框不准确后续识别输入就是歪的、带多余背景的精度立刻受限。比赛数据集里的文本特点是竖排多、长行多、存在少量弯曲的批注文本检测模型需要能处理长文本。我在对比了DBNet、PAN、FCENet之后最终保留了DBNet作为主要检测器源码里也保留了对应的推理配置和权重文件。DBNet的核心思路是通过可学习的二值化把概率图转成检测结果相比传统分割后处理少了好几个超参数稳定性和速度都不错。它对长文本的召回比较好因为分割天然适合任意形状的文本区域。PAN的优势是轻量但精度略低适合做模型融合时的第二检测器。FCENet这种做任意形状很强的模型在规则印刷体古籍上反而容易把竖排的相邻列粘连我在试验后放弃了。结合源码来看DBNet的backbone用的是ResNet50FPN输入尺寸设置成960×960对古籍这种普遍长宽比不固定的页面来说等比缩放比直接拉伸更合适。具体做法是计算最长边不超过960、最短边不小于480的缩放比例然后做padding到960×960避免失真。2.3 文字识别模型选型SVTR还是CRNN识别模型的选型直接关系到繁体字和异体字能不能认准。传统CRNN的CNN部分对序列特征提取能力有限如果训练数据不足长文本、形近字容易出现识别错误。我实验后发现SVTR在这类场景下的表现更稳它用Transformer结构替代了部分RNN递归对长序列建模能力更强同时保留了轻量化的优势。源码里识别模型是基于SVTR-Tiny结构改的输出端接了一个包含字表大小的分类层。字表构建那一步花了很大功夫除了GBK里能覆盖的常用繁体字还自己整理了一批古籍常见异体字和俗字的Unicode码点最终字表有8000多类。这个字表不需要覆盖所有古文用字但一定要覆盖验证集和训练集里出现过的字否则模型结构上就“不认识”某个字再训练也白搭。另外识别模型输入高度设为32宽度动态变化最大1920像素。这是SVTR训练时的一种常见配置固定高度、宽度随文本长度变化相比统一缩放到固定尺寸能减少长文本的信息丢失。实际测试里这个细节对竖排长句的识别精度提升很明显。2.4 版面分析与阅读顺序恢复的设计版面分析这个模块源码里没有单独用深度学习模型而是用了一个轻量级的分类器加规则后处理。具体做法是先根据检测框的宽度、高度、面积、位置等特征用简单规则把文本块分为“正文”“批注”“页眉”“标题”等类别。然后按列进行聚类把同一列的文本框归为一组最后按“从右到左、从上到下”的阅读顺序输出。这里有一个很关键的细节竖排古籍的阅读顺序是“先右列再左列”同一列内再从上到下。所以排序不能简单按x坐标从小到大或者y坐标从小到大而是应该先按列中心x坐标从大到小排再在每列内部按y坐标从小到大排。源码里的order_detect.py做了两次排序并用文本块之间的重叠度来决定是否属于同一列这个逻辑虽然简单但很实用。如果检测框本身有漏检或者多框粘连规则排序容易乱。所以源码里还加了校验步骤如果某个文本框和前一个文本框的水平距离过大而且重叠度很小就判定是新的一列重新开始排序。这个“断列”逻辑对处理页面中间有装订线或大块污渍割裂文本的情况特别有效算是很实用的工程技巧。3. 核心模块的实操实现细节3.1 预处理与图像校正的最佳实践古籍图像的预处理不能只做简单的resize。我的实践是先做一次灰度化和自适应阈值处理但注意这个结果不要直接输入模型而是作为辅助信息来判断图像是否需要倾斜校正。如果文本行的角度偏移超过15度直接用仿射变换把图像转正到接近水平或垂直这样可以显著提升检测的稳定性。源码里的preprocess.py包含了几步关键操作一是删除边缘黑框扫描件经常有扫描仪边框会干扰检测二是用中值滤波去椒盐噪声同时保留笔画边缘三是把图像增强成多个尺度比如0.8x、1.0x、1.2x分别计算检测结果再合并。这里多尺度增强结合了类似测试时增强的思路对低分辨率小字场景非常有用。这里有个我自己踩过的坑对古籍扫描件做全局直方图均衡化要非常小心。古籍纸张泛黄是自然老化产生的均衡化会放大水渍和霉斑让文本和背景的对比度反而变差。正确的做法是用限制对比度自适应直方图均衡化而且clipLimit不要设太大我一般设成2.0窗口大小16×16这样能在增强笔画的同时不乱拉背景噪声。3.2 文字检测模块从配置到推理全解读检测模块的推理流程源码里封装成了infer_det.py核心逻辑并不复杂加载DBNet模型读入图像做预处理前向推理得到概率图和阈值图再通过可微二值化近似得到二值图最后用轮廓查找提取文本框并做后处理。# 关键推理步骤概念性示例 for img in batch: prob_map, thresh_map model(img) binary_map (prob_map 0.3).float() contours find_contours(binary_map) boxes [unclip(contour, scale1.5) for contour in contours] boxes filter_boxes(boxes, min_area50, min_height8)这里面有两个参数值得注意一个是二值化阈值我默认设0.3但在古籍场景下如果页面偏暗或者对比度低可以适当降低到0.25以提高召回另一个是unclip的scale参数决定了检测框外扩多少设太大容易把相邻文本粘在一起设太小又可能截掉笔画。源码里设的是1.5我在竖排密集的古籍页面上试过1.5偏保守可以尝试1.8但需要配套做非极大值抑制否则框会重叠很多。另外检测模型输出的是带角度的四边形框源码里统一转成四个点的坐标格式。这个转换要做归一化否则不同尺度图像下坐标会乱掉。很多新手在拼接检测框和识别结果时会在这里翻车因为训练时图像做了resize推理坐标如果不是在原始图上直接计算结果就会全部偏移。3.3 文字识别模块如何构建字典与微调模型识别模块是全文错误率的主要来源也是最需要针对古籍数据定制的地方。源码里train_rec.py中可以看到日志里字表是单独从训练集和验证集的标注文件里扫描生成的然后合并进一个固定的基础字典。这样做的好处是保证字表一定覆盖当前数据中出现的字避免“未知字符”直接拉低指标。微调时有一个容易忽略的操作要把所有文本统一转成简体还是保留繁体根据我实验的结果保留繁体训练效果更好因为转换到简体本身就有转换误差而且简体字库和古籍字形差异太大。源码里的字典是原始繁体字表只在评估输出时做一次可视化转换并不会改训练目标。识别模型的训练loss用的是CTC loss而不是attention-based的交叉熵。CTC在长序列、词典较大时收敛更稳定也更容易和SVTR这类序列特征提取模型配合。如果你准备换成Attention解码器要特别关注对齐问题古籍的长文本经常超过20个字符attention的解码长度如果设得不够长尾巴就会被截掉。3.4 后处理置信度过滤、聚类与纠错规则后处理的重要性在这个比赛里被放得非常大。识别结果出来后不是直接把字符串拼在一起就完事源码里的postprocess.py至少做了三层处理。第一层是置信度过滤。检测框的置信度低于0.5时直接丢弃避免把噪声块当成文本识别置信度低于0.6时保留结果但标记为“低置信”在后续排序中靠后。这个设计是为了防止某一段文本因为识别很差而打乱整段的读取顺序。第二层是文本框聚类成段落。对于横排文本按x方向和y方向的距离阈值把邻近框聚在一起对于竖排文本则按列中心x坐标的距离阈值聚类。源码里有一个关键参数叫line_gap_ratio默认为0.6意思是两个文本框之间的距离小于当前框高度或宽度的0.6倍时才认为是同一段。这个阈值对不同字号页面要调整古籍比赛里不同页面的字号差异很大我在提交前用了一个小验证集来搜索这个参数的最优值。第三层是规则纠错。源码里内置了几个典型的古籍OCR纠错规则比如把识别结果中连续的重复标点删除把全角半角符号归一化以及把“丨”和“1”、繁体“裡”和“裏”这类形近易混字做一次基于上下文的替换。这些规则不追求全对但能明显降低编辑距离。4. 训练与调优的实战经验4.1 训练策略与超参数设定检测模型和识别模型的训练策略我分别定了两套不同的思路。检测模型用ImageNet预训练权重做初始化采用多尺度训练输入尺寸在960到1280之间随机采样训练轮次控制在120个epoch前20个epoch用warmup学习率从1e-4升到1e-3然后用余弦退火降回1e-5。识别模型这边因为SVTR本身结构较轻而且数据量不大我采用了更保守的配置图片高度32底部padding到32的倍数批量大小64输入宽度动态。优化器用AdamW初始学习率2e-4配合EMA指数滑动平均这里EMA的decay系数我设为0.999能让验证集的指标更平滑、更稳定。源码里还有一个很值得一提的点混合精度训练。我用Apex或者PyTorch自带的AMP把训练显存占用降低了一半训练速度提升了大概40%。这对比赛场景来说非常关键因为本地显卡往往有限更快迭代意味着能试更多实验。混合精度有个小坑如果loss出现NaN多半是梯度溢出最简单的办法是在AMP配置里把初始scale_factor设小一点或者关掉动态损失缩放改成固定的1.0虽然慢一点但不会崩。4.2 测试时增强与模型融合测试时增强是提升公共榜单分数性价比最高的技巧。我的标准配置是三尺度测试原图、0.9倍缩放、1.1倍缩放分别跑检测和识别然后根据检测框的置信度做加权融合。具体来说检测框用“并集后按IoU聚合”的方式合并两个框IoU大于0.5就认为是同一个框置信度取两者最大值坐标取两者的加权平均识别结果则对所有候选框做识别置信度最高的结果作为最终输出。模型融合方面源码里保留了三个副本权重DBNet两个不同迭代轮次的checkpoint加一个PAN检测器识别模型则是SVTR两个不同seed训练的结果。融合方式很简单检测结果全部丢在一起做非极大值抑制识别结果按置信度投票票数相同时选编辑距离更小的那个。这样做有一个明显的收益线上评测的稳定性大幅提升。单模型偶尔会在某几页上出现大面积崩溃性错误而融合后的结果相对均衡不会因为某一页的极端情况拉低整体分数。当然代价是推理时间翻倍但比赛对推理时长的限制通常比较宽松这个时间花得值。4.3 线上评测与线下验证的差距分析很多参赛队的痛点是线下指标高线上分数却一塌糊涂。这个问题在古籍赛题上尤其严重因为线上测试集的采集来源可能跟训练集差异很大。我复盘源码时发现Alphx队线下验证做了很多额外处理来减少这种落差。首先是验证集的划分不是简单地随机切分而是按照书的页码顺序做留出。古籍同一本书内页面风格比较接近不同书之间差异很大随机切分会导致模型“见过”同风格页面高估泛化能力。按页面顺序留出能模拟“看到半本书预测另外半本”的真实场景。其次是做一个“风格采样”验证把训练集按图像亮度、对比度、是否带批注这几个特征聚类确保验证集包含各种风格的极端样本。这样即使线上数据偏移至少能暴露模型最薄弱的部分。源码里有个脚本可以输出这批“难度最高”的样本我强烈建议比赛里也这么做因为线下有针对性的补数据或者加增强往往比换模型更有效。5. 常见问题与排查技巧实录5.1 小字号古文识别效果差怎么办古籍里小字号很常见尤其是批注、夹注字体比正文小一半甚至更多。检测模型对大字号文本学习得很好但小字区域容易漏检或检测框位置不精准。排查时先别急着换模型先可视化一下检测框看看是“没检测到”还是“检测到但识别错”。如果没检测到优先检查多尺度增强是否覆盖了小字对应的尺度可以增加更小缩放倍数的TTA。如果检测到了但识别错则可能是识别模型输入分辨率不够源码里设置的是高度32对小字号来说实际文本高度可能只有10像素被拉伸到32后笔画模糊。这时候可以适当降低识别模型的输入高度要求或者用更大倍数的超分模型对低分辨率文本框做增强然后再识别。我实际试下来用Real-ESRGAN对低分辨率文本框做一次增强识别准确率能提升约3个点但耗时也显著增加需要根据推理时限权衡。5.2 繁体字和异体字误识的排查思路繁体字误识最典型的就是“己、已、巳”这类形近字以及“裡、裏”这种异体关系。如果误识比例很高首先要确认字典里是否包含正确字形。很多预训练模型默认字典只有简体常用字对繁体支持有限如果不做字典扩展模型结构上就无法输出正确字符。其次可以考虑对识别模型做领域自适应微调。不要只在标注文本上微调还要收集一批未标注的古籍图像用模型自己预测把高置信度的伪标签作为训练数据加入。这个半监督策略在古籍场景里非常有效因为训练集标注量通常太小伪标签能把大量无标注页面利用起来。源码和训练脚本里预留了伪标签生成的入口实际操作时要注意设一个比较高的置信度阈值比如0.9否则噪声伪标签会污染模型。另外字形增强也值得一提对训练图像随机做轻微的仿射扰动、笔画腐蚀、膨胀、局部遮挡模拟古籍残损字。这些增强在标准OCR数据上可能多余但在古籍断笔画的情况下经常是提升识别稳定性的关键。5.3 竖排文本顺序错乱的修复方法竖排阅读顺序错乱是最容易扣分也最难排查的问题之一。症状表现是每个字都识别对了但输出句子读不通逻辑混乱。从源码调试经验来看主要原因有三个检测框没有按列聚类正确排序时没有考虑从右到左或者识别模型把竖排文本按横排顺序给出识别序列。检测框聚类的问题可以通过可视化聚类结果直接定位如果同一列文本被分成了多个组需要调大line_gap_ratio如果不同列被粘在一起则需要调小。从右到左排序的问题则要看排序代码是否真的按列中心x降序很多默认排序函数会按升序排古籍竖排必须手动反转。还有一种更隐蔽的情况是识别模型内部把竖排文本顺序读反了。解决办法是在识别模型训练时增加竖排旋转增强把部分训练样本旋转90度模拟竖排输入让模型学会竖排文本的时序方向。或者在后处理阶段对竖排文本框内的图像先旋转90度再送识别模型识别完再把字符串顺序反转。5.4 表格和批注干扰检测结果的处理古籍页面经常有大量批注小字环绕在正文周围表格类页面则会出现栏线、边框。这些结构会让检测模型输出很多“半截框”或者把栏线当作文本区域。我的处理策略是把版面元素识别放到检测之后、排序之前用规则或者一个小分类模型区分“普通文本”“批注”“表格区域”然后分别走不同的后续流程。比如批注区域它的字号通常小、位置贴近页边或版心上缘可以按位置特征识别并单独聚类不参与正文顺序。表格区域则要先做表格线检测把表格结构解析出来再对每个单元格单独做文本识别最后按表格顺序输出。源码里对表格支持不够完善如果赛题重点考察表格页面可以直接使用开源的表格结构识别模型比如TableTransformer再结合自己的流程做适配。这里有个重要的细节检测模型输出的区域如果包含表格线最好在后处理里用一个“线段检测”把横线和竖线剔除掉因为它们不包含语义文本硬识别只会输出乱码拉低整体准确率。5.5 显存不足与推理速度慢的优化比赛中常见的硬件问题是显存不够、推理时间超限。显存不足时优先检查是否开了混合精度开了混合精度还不足就要考虑把推理切块。切块的思路是把原始大图切成几个有重叠的块分别送入模型然后把检测框坐标加回偏移量再合并。重叠区域一般设100像素左右避免文本行刚好被切开导致漏检。推理速度慢则有两个优化方向一个是使用TensorRT或者ONNX Runtime进行加速DBNet和SVTR这类模型结构比较规整转ONNX很简单源码里也提供了导出脚本。另一个是减少TTA的尺度数从三个尺度减到两个速度提升三分之一精度损失通常只有零点几个点。如果比赛有严格的推理时长限制优先去掉0.9倍尺度保留1.0和1.1倍。还有一个容易忽略的点大批量推理时把图像padding到固定尺寸会让很多空白区域也参与计算浪费算力。可以按图像实际长宽比分桶同一个batch内尽量放长宽比相近的图像这样padding的空白最少推理速度能提升20%左右。6. 源码工程化与团队协作的心得6.1 代码组织与实验管理比赛到了中后期最大的敌人不是模型效果差而是实验结果不可复现、代码逻辑混乱。我翻看Alphx队源码时注意到配置文件、训练脚本、推理脚本、评估脚本是严格分离的每个实验都有独立的config文件记录了包括学习率、数据路径、模型结构参数在内的所有信息。这样做的好处非常直接你可以随时回到某一个实验知道它当时的准确参数是什么对比实验之间也能准确找出差异变量。否则比赛后期一天跑上百个实验很容易出现“感觉上次那个模型效果更好但忘了它用了什么配置”的情况。我在其他比赛里也有过这种教训后来统一用YAML记录所有配置并给每个实验生成一个带时间戳的日志文件这样的习惯建议每个做算法比赛的人都养成。6.2 版本管理与多人协作如果参赛队伍不是单人版本管理必须重视。源码仓库里应该把权重文件排除在Git之外用单独的网盘或共享目录存放避免仓库体积膨胀。代码中不要出现硬编码的绝对路径统一用相对路径或者环境变量指定数据目录这样队员之间同步代码时不会互相破坏。多人协作最容易出现的冲突是数据增强策略和配置文件各改各的。建议约定主分支main保持可运行状态每个人在新分支上做实验稳定后再合并。源码里还保留了一个README文件记录了跑通一条完整pipeline的步骤这个文档看起来不起眼但在你离开项目两周后再回来时会帮你省下大量回忆时间。最后再分享一个我在比赛里验证过的小技巧每天结束前把当天的实验结果整理成一张表格记录模型、数据增强、验证集指标、线上指标和备注。这张表最终会成为你写比赛总结和复盘的最重要素材比任何代码都值钱。古籍OCR这个方向短时间不会过时数字化、检索、修复的需求一直在增长这套检测识别版面分析的pipeline换成其他文档类型同样适用持续迭代的价值很大。本文还有配套的精品资源点击获取
返回列表