ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

表格OCR与普通OCR的核心差异:从结构识别到单元格重建的实战解析

表格OCR与普通OCR的核心差异:从结构识别到单元格重建的实战解析

我最早意识到表格OCR和普通OCR不是一回事,是在一次处理发票扫描件的项目里。当时同一个OCR引擎,应付普通文章段落识别得挺好,可一旦丢来一张报销明细表,输出的就是一大堆上下错位的文本,金额、日期、项目名全挤在一起。后来翻了开源代码和论文才确认:表格识别必须走专用的结构和算法,单纯把普通OCR的结果按坐标排序,是救不回来的。

这篇内容我会从原理层讲到落地层,把表格OCR和普通OCR的核心差异拆开揉碎,覆盖表格检测、结构解析、单元格重建、无边框表识别等关键点,也会给出可直接参考的流水线设计和问题排查经验。比较适合正在做票据结构化、文档解析、数据抽取的同学,以及想搞清楚“为什么我直接调普通OCR识别表格总是乱”的开发者。

1. 普通OCR到底哪里不够用

1.1 它只能回答“字是什么”,回答不了“字在哪儿”

普通OCR的完整链路大致是:图像预处理、文字区域检测、字符或单词识别、语言模型纠错、输出纯文本。这个过程本质上是“把图像里的文字符号转成字符串”。它关心的是字形、语义、词序,不关心这些文字在版面里属于第几行第几列,也不关心哪些文字属于同一个单元格。

你可以把普通OCR理解成一个视力很好但完全不懂排版的人。他拿到一张表格,能一个一个把字念出来,甚至念得很快,但你问他“金额这一列第三行的数字是哪个?”,他就懵了。因为他看到的是连续的文字流,不是“第2行第3列”的位置坐标。

所以当表格图片丢给普通OCR时,最典型的输出是:表格内容按从上到下、从左到右的顺序被拉成一长串。看似每个字都认识,但行与行之间的对应关系、字段与值之间的匹配关系,全部丢光。比如“商品名称:苹果 数量:3 单价:5 金额:15”这行内容,在没有单元格边界约束的情况下,下一行也可能同样被摊平,连在一起以后根本分不清哪个数值属于哪个字段。

1.2 光靠“按坐标排序”整理表格,为什么行不通

有人会说,那普通OCR不是能返回每个文本框的坐标吗,我按横坐标和纵坐标排序,把相近的行归到一起,不就能重构表格了?这个思路听起来可行,但实际操作中坑很多。

一方面,普通OCR的文字框检测是按文本块划分的,同一行里多个单元格的词可能被合并成一个大的检测框,也可能一个单元格被拆成好几个框。检测框一旦与真实单元格边界不对齐,后面的按坐标聚类就全乱了。另一方面,表格里经常有跨列单元格、合并单元格、空单元格,这些结构在坐标系里没有一个稳定的规律可循。比如某行只有一个“备注”字段,它横向跨了三列,如果只按坐标排序,这一行的“列对齐”就会和其他行完全对不上。

我在实际项目里见过最典型的错误是:普通OCR把表头识别出来以后,再按坐标分列,结果把“日期”这一列的内容和“金额”这一列的内容错位拼接到了一起。文字全对,但表格语义彻底错了。这种错比识别出几个错字更严重,因为错字还能靠人工校对发现,行列错位后整张表的所有数据都不可信了。

2. 表格OCR的三个技术分水岭

2.1 从“识别文本”变成“识别结构”

表格专用算法和普通OCR最本质的区别,是前者把“结构”当成了核心目标。普通OCR的目标函数是字符正确率,而表格识别模型的目标是:先搞清楚表有多少行、多少列、哪些单元格被合并、哪些单元格跨行跨列,然后再去识别每个单元格里的文字。

这一步在行业里常被称为表格结构识别。它输出的是一个描述表格拓扑关系的中间结果,而不是一串文本。这个中间结果可以是一张行列划分图,也可以是一个带有合并关系的二维网格,还可以是类似HTML Table结构的分层描述。只有拿到了这个中间结果,后续的文字识别才有意义——因为每个文字块都被归属到了具体的单元格坐标里。

我习惯用一个比喻:普通OCR是把墙上的字抄下来,表格OCR是把户型图画出来再往每个房间里放家具。家具放得对不对,前提是户型图得正确。

2.2 从“线性文本流”变成“树形层级”

普通OCR输出的是一维的、线性的文本流。即使带坐标,本质上仍然是一堆平面散点。表格则天然是二维结构,而且不一定是规则的二维网格。比如会计台账里常出现三层表头:

一级表头是“2024年度收支”,下面分成“收入”和“支出”两列,“收入”下面又分成“工资性收入”“经营性收入”两个子列。这种嵌套关系如果用“第几行第几列”的平铺网格来描述,会很别扭;更好的表达方式是用树形层级或带合并单元格的表格模型。

这个差异直接决定了后续数据抽取的方式。从线性文本流里抽“工资性收入”的数值,需要靠字符串匹配和上下文猜;从树形表格结构里取,则可以直接沿着“表头节点→子节点→对应数据行”的路径精确定位。

2.3 从“文本正确”变成“关系正确”

第三个分水岭在于评价指标。普通OCR评价的是字符准确率,而表格识别必须评价“关系正确率”:单元格之间的行关系、列关系是否成立,表头与数据是否对应,合并单元格是否还原完整。

这个区别在实践里非常明显。我曾经拿一批财务报销单做过对比测试,普通OCR在金额数字上的识别准确率其实并不低,能到99%左右;但把整张表还原成结构化数据后,因为行列错位、字段漂移造成的“数据关联错误”占比却接近10%。换用表格专用算法后,字符准确率可能没有明显提升,但结构化后的整体可用率大幅上升。

所以如果你做的是证件照、段落文本、名片这类场景,普通OCR完全够用;一旦涉及报表、发票、单据、实验记录这些需要“按行按列读取”的场景,就必须把结构识别放在第一位,单纯追单个字符的准确率没有意义。

3. 专用表格算法是怎么工作的

3.1 表格检测:先用目标检测的思路找“表格在哪”

表格识别不能对整页图像从头到尾直接硬解,第一步通常是从版面里把表格区域找出来。这一步和普通OCR里的文字区域检测思路相近,但更关注表格的边界和整体位置。

常用的做法有两种。一种是基于传统图像处理的线检测:先用边缘检测、二值化、形态学操作把横线和竖线提取出来,再通过直线的交点和闭合区域定位表格边界。这种方式在扫描清晰的“有线表”上很快,但对拍摄角度、光照不均、表格线断裂非常敏感。

另一种是基于深度学习的表格检测模型,本质上就是目标检测或语义分割:模型输出表格区域的外接框或像素级掩码。和普通OCR的文本检测相比,表格检测模型需要更大的感受野,因为表格往往横跨大半张纸,线条和文字交织在一起,如果只看局部小块,很难判断哪些文字属于同一个表格。

在实际流程里,建议把表格检测和普通文本检测并列进行。表格区域交给结构解析模块,表格外部的标题、备注、签名则走普通OCR。很多项目一开始想省事,只用普通OCR的版面分析去框表格区域,效果往往不理想,因为通用版面分析对“表格块”的召回率不如专门的表格检测模型。

3.2 表格线提取与单元格重建

拿到表格区域之后,第二个关键步骤是提取表格线、重建单元格。对有边框的表格,经典的实现路径是这样的:

先对表格区域做二值化,让深色线条和浅色背景彻底分离;然后用形态学操作分别提取横线和竖线。具体做法是用一个横向的长条形核做开运算,把竖直部分滤掉,剩下横线;再换一个竖向的长条形核,提取竖线。把横线图和竖线图叠加,就能得到线的交叉点,再根据交叉点之间的连线把表格切分成一个个矩形单元格。

这个流程听起来简单,但实际工程里需要处理一堆边缘情况。比如表格线不是严格的直线,扫描件里会带轻微弯曲;比如表头区域有多层合并,线条不完全贯穿整行;比如有的单元格边框不是闭合的,只在两侧有线。这些问题都会让“求交点、连线段”的朴素方法失效。

更现代的做法是用深度学习直接预测单元格边界。模型对表格区域做像素级分类,输出的每个像素被标记为“水平线”“竖直线”“单元格内部”或者“背景”,然后从预测结果里重建出单元格划分关系。这种方法的优势是抗干扰能力强,能处理线断裂和部分遮挡,缺点是对训练数据要求高,遇到训练集里没见过的特殊版式时,表现也可能不稳定。

无论用哪种方法,单元格重建后的结果一定需要保留“合并关系”。简单矩形网格不能表达跨行跨列,所以中间格式里至少要有每个单元格的左上角和右下角坐标,以及它跨越的行列范围。

3.3 无边框表格:没有线也需要结构

很多实际表格根本没有线。比如从PDF导出的银行流水,视觉上有清晰的列,但没有画网格线;再比如网页截图里的表格,列与列之间全靠空白隔开。这种表格对普通OCR来说,反而比有线表更友好——因为不需要处理线干扰;但对表格专用算法来说,结构解析难度反而更大。

无边框表格的结构重建,主要靠两种线索。第一种是文字对齐线索:同一列的文字通常会在垂直方向上呈现平齐的边,数字类内容往往右对齐,文本类内容往往左对齐,根据这些对齐关系可以推断出列边界。第二种是空白间隙线索:同一行的不同单元格之间,会有相对明显的水平空白带;不同行之间则有垂直空白带。把这些空白带叠加起来,就能得到一个大致的网格划分。

我处理无边框表时常用的技巧是:把OCR识别出的每个文本行先按纵向聚类,形成“伪行”;再在每个伪行内部,根据字符间距和左边界对齐关系切分“伪列”。整个过程有点像用空白区域反过来画格子。这个方法在干净的无边框表上效果不错,但遇到内容长短差异极大、单元格内容换行、数字和单位混排的情况,就需要加入启发式规则反复调整。

4. 一套可落地的表格识别流水线

4.1 预处理阶段:先让表格线“显形”

表格识别里最容易被低估的就是预处理。很多人把图片直接丢进识别模型,结果表格线断的断、歪的歪,输出结果一团糟。预处理做得好,后面的结构解析能省一半力气。

我的建议流程是:第一步做透视校正。拍照得到的表格经常有轻微透视变形,用图像边缘检测找到最外层的四条表格边界,然后做透视变换,把表格拉正为一个矩形。第二步做倾斜校正。表格线方向会和水平方向有微小夹角,倾斜角度哪怕只有两三度,也会让后面提取横线时出现大量短线段,导致结构重建失败。第三步做线增强。用形态学闭运算把表格线的断口接起来,再用自适应阈值二值化把线从背景里分离出来。

这里有一个优先级判断:如果整个表格区域已经比较清晰,就不要过度中值滤波或高斯模糊,否则会把细表格线也抹掉。我见过不少项目因为预处理过度,把表格线条连续性和文字边缘都破坏了,识别率反而断崖式下降。预处理的目标是“适度增强”,不是“让画面变干净”。

4.2 结构解析:单元格划分是核心一步

当预处理结束,表格区域被定位之后,接下来就是把整个表格区域切割成单元格集合。这个阶段输出的不是图像,而是一个结构描述,包括每个单元格的位置、行列号、是否跨行跨列。

在实现上,我比较推荐把结构解析和文字识别分开做,而不是用一个端到端模型同时输出文字和结构。原因是:分开做可以分别调参,结构错了就修结构,文字错了就修文字;端到端模型虽然看起来省事,但一旦两种错误耦合在一起,排查起来极痛苦。

结构解析的结果建议输出成两层表示:底层是每个单元格的精确边界框,可能用(x1, y1, x2, y2)表示;上层是一个行列索引网格,标明第几行第几列,并带有rowspan和colspan信息。底层的边界框用来给文字识别模型裁剪图像,上层的索引网格用来最后拼装输出表格。

4.3 文字识别与后处理:把文字填回格子

单元格划分完之后,每个单元格里的文字识别其实是可以复用普通OCR能力的。这一步的本质,是用结构解析得到的坐标信息把大表格切成许多小块,再对每个小块单独识别。因为切块之后背景干净、内容单一,普通OCR的准确率也会明显提升。

后处理阶段需要注意三点:第一,表头文字容易出现“斜体”“加粗”“带下划线”等样式变化,某些OCR模型在识别艺术字和装饰性文字时准确率会下降,需要额外增强样本;第二,数字内容要做规则校验,比如金额字段必须保留两位小数,日期字段必须符合日期格式,如果有明显格式异常,就要标记成可疑字段;第三,空白单元格不能直接丢弃,要保留单元格的位置,否则输出表格的列对齐会被破坏。

最终输出格式我倾向于CSV和JSON各做一份。CSV方便人直接看和处理,JSON方便程序读取并保留合并单元格等复杂结构。这里有个容易被忽略的点:CSV本身不支持表达跨行跨列,所以如果表格里有大量合并单元格,导出CSV时要在对应位置重复填充内容或留空占位,这需要在导出逻辑里单独处理。

5. 常见问题与排查心得

5.1 表格线断裂、太浅,结构总是重建失败

这是有线表格识别最普遍的故障。出现这种情况,先不要急着换算法,先检查预处理环节。扫描件里的表格线常见问题是断断续续,尤其是用手机拍摄时,反光和阴影会让部分线段变成浅灰色甚至消失。此时可以先做一个形态学闭运算,把断口接上;如果线条太细,可以先用膨胀操作把线变粗,再做闭运算。

如果形态学操作之后仍然断线,就要考虑是线检测算法的阈值选得不对。二值化的阈值如果太高,浅色线条会被当成背景;阈值太低,阴影和脏点会被当成线条。我的习惯是先统计表格区域的像素灰度分布,再用自适应阈值;同时保留多组二值化结果,让结构解析模型自己选择最可信的组合。

5.2 合并单元格、斜线表头怎么处理

合并单元格会让行列索引变得不规则,斜线表头更是几乎所有表格识别算法的噩梦。普通的有线表检测算法会在线条交叉点处直接切格,遇到斜线根本处理不来;遇到跨行跨列时,简单切分又会把应该属于同一个单元格的内容拆到多个格子。

我的处理办法是:结构解析环节不要只输出均匀网格,要允许相邻单元格合并。具体实现里,可以先把所有线段交点找出来,生成基础网格;再用一个分类模型判断每对相邻网格之间是否存在实际分隔线;如果没有分隔线,就判定为合并单元格。斜线表头则单独做特殊规则检测,检测到斜线时把表头区域单独截出来,用普通OCR识别斜线两侧的文字,再手工映射到对应的行列位置。

5.3 印章、手写和噪点干扰严重

业务单据最麻烦的干扰源是红色印章、手写签字和二维码。印章会盖住表格线和文字,手写笔迹会和打印体混在一起,二维码区域则可能被误识别成表格内容的一部分。

针对印章,可以在彩色图像里用颜色通道过滤,把红色分量单独提取出来,在结构解析时把红色区域视为背景。针对手写,如果业务上需要保留手写内容,就不要做抑制,而是把单元格裁剪后单独训练手写识别模型;如果不需要,就在预处理时按颜色或纹理过滤掉。针对二维码,建议在表格检测之前先做一次二维码定位,把检测到的码区域从表格区域内挖掉,避免后续单元格分割被干扰。

6. 从扫描件到结构化数据:一个最小可复现的流程

6.1 流程设计

如果你现在手头有一张表格图片,想快速搭一个可用的识别流程,我建议按以下顺序走:

  1. 图像输入后先做透视校正和倾斜校正,目标是把表格区域的四条边拉成横平竖直。
  2. 用表格检测模型定位表格区域的边界框。如果项目预算和时间有限,也可以用传统图像处理方式找外围线框,但鲁棒性会差一些。
  3. 对表格区域做线提取和单元格重建,输出带边界框的单元格列表。
  4. 裁剪每个单元格,依次送入普通OCR识别文字。
  5. 根据表格结构描述把单元格文字填回到行列索引网格里,导出CSV和JSON。

这套流程最耗时的地方在于第一步到第三步的调试。表格线检测的参数、单元格合并的判断逻辑、无边框表的列切分规则,都需要针对你手头数据的版式做迭代。不要在前期追求一个万能的模型,先把具体场景跑通,再逐步扩大版式覆盖范围。

6.2 几个容易忽略的细节

在真正落地时,有几个细节不处理会让你后面很痛苦。第一,输出表格前一定要保留原始图像的坐标映射关系,因为后续如果要把结构化结果与原始表格可视化对照,坐标信息能帮你快速定位问题格子。第二,逻辑空行要保留。有些表格中间有空行,OCR和结构重建时如果直接把空行删掉,后续行号全部错位。第三,长表格分页问题。表格跨页时,每页可能会有不同的表头,合并输出时需要用规则把各页的“表头区”识别出来,并在最终结果里统一表头结构。

我还建议在流程里加入一个前置校验模块:如果识别出的是CSV结构,先检查每一行列数是否一致,列数不一致的行直接标红。这个简单的规则能筛掉大量结构重建错误,比任何模型调优都划算。

7. 最后说点实操体会

我踩过最大的坑,是早期总想用普通OCR加坐标排序来解决表格问题,觉得可以省掉一个模型。结果每次换一批扫描件,排版一变,规则就要跟着改,最后维护成本比直接上专用表格识别算法还高。现在我的原则很简单:只要是表格类文档,结构解析就必须独立出来做,不能依赖普通OCR顺带输出。

另外一个经验是,表格识别的质量评估一定要看最终结构化结果,而不是看单字准确率。我会抽样一批图片,跑完整个流程后逐行检查行列对应关系、合并单元格还原情况和空单元格保留情况,用这些指标来衡量模型和参数改动的效果。单字准确率再高,结构化后数据不能用,等于零。

如果你正准备做表格识别,建议先把“有边框表格”这一个场景做扎实,再考虑无边框和多页合并。先把最难的流程跑通一次,后面加版式就是增量工作,不要一上来就追求全场景覆盖。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进