扫描版 PDF 的表格错位,通常不是字符识别错,而是版面检测、阅读顺序、单元格结构还原这三层里至少有一层先出了偏差,后面的结果只是把偏差放大。比较省时间的做法是:先拿同一页样本把“错位”定位到具体一层,再决定改预处理、改版面参数还是改后处理规则;在没有确认层次之前直接调识别模型或加正则修补,多数时候只是把错误挪到别的位置。
扫描表格错位建议按“版面检测 → 阅读顺序 → 单元格结构”三层顺序核对:先用同一页原图与输出并排,标出首次出错的行列位置;再固定倾斜校正、分辨率等预处理变量,两次运行之间只改一个参数;最后把结构化单元格回填到原表逐格抽查。纯文本顺序正确而单元格坐标错乱时,问题多在表格结构还原;反过来则多在阅读顺序。具体参数名与默认值需结合所用仓库和扫描件质量确认。
用同一页表格确认错位落在表头、行还是列
“错位”这个词太笼统,先把它拆成可观察的现象。挑一页行列不多、最好带合并表头的扫描表格,把原图与解析输出并排放在一起,逐项记录:错的是表头整体丢失、还是行序被打乱、还是某一列的内容串到了相邻列、还是合并单元格被拆成多个空格。然后标出首次出错的位置,例如“第 3 行第 2 列开始偏移”,而不是笼统写“后面都乱了”。
- 表头错:常见于跨页表头、加粗反白表头,属于版面或结构还原问题。
- 行序错:多行文字被合并成一段,或顺序颠倒,先怀疑阅读顺序。
- 列序错:数值列整体平移,通常是列边界检测或单元格切分偏移。
- 合并单元格被拆散:输出里出现大量空单元格,属结构还原层。
这一步只做记录,不改任何参数。记录结果决定后面重点看哪一层。
检查扫描倾斜与分辨率是否改变了单元格边界
先排除输入图像本身的影响。同一页做两组对照:第一组开倾斜校正与不开校正各跑一次;第二组用不同分辨率(例如渲染 DPI 取两个相差明显的值)各跑一次。只描述输出差异,例如“未校正时右侧两列被合并成一列”“低分辨率下表头横线消失导致表头被当成正文”。
倾斜扫描会让表格线在检测阶段变成不规则多边形,单元格边界随之滑动;分辨率过低时细线容易断裂,过高时小字号区域的重采样行为也可能变化。这里不给具体阈值结论,扫描件质量差异很大,建议以“同一页在两组设置下的输出是否出现结构性差别”为准。如果两组输出差别明显,说明错位有相当一部分来自输入预处理,而不是模型能力。
在推理脚本里固定输入预处理参数再跑对照
把图像处理变量固定下来,避免把预处理波动误判成模型问题。下面是一个通用调用骨架,参数名以实际仓库为准,占位处替换成自己仓库的字段名:
# 通用推理骨架,参数名以实际仓库为准
ocr = UnlimitedOCR(
lang="ch",
layout_model="<版面检测权重路径>", # 版面/表格区域检测
use_table=True, # 是否启用表格结构还原
deskew=True, # 倾斜校正开关
dpi=300, # 预处理/渲染分辨率
reading_order="auto", # 阅读顺序策略
return_cells=True, # 是否输出单元格坐标
)
res = ocr.run("page_017.pdf", page=3)
res.dump_text("out/text.txt") # 纯文本流,用于核对阅读顺序
res.dump_cells("out/cells.json") # 结构化单元格,用于核对行列坐标
跑对照时严格做到两次运行之间只改一个变量:例如第一次 deskew=False,第二次 deskew=True,其余全部不动;确认这一项的结论后,再单独动 dpi 或 use_table。同时把每次运行的配置和输入文件路径写进日志,后面回看时才能对上号。如果两次运行结果一致,说明这几项不是主因,可以去第 4 节看顺序与结构。
把阅读顺序与表格结构分开核对
文本流顺序错误和单元格坐标错误是两类不同故障,混在一起看会互相掩盖。先看 out/text.txt:文字是否按“先左列后右列、先上后下”的正常阅读顺序排列,表格里的内容是否被拆到正文段落之间。再看 out/cells.json:每个单元格是否有行列坐标和跨行跨列标记,坐标是否落在原图对应的位置。
- 文本顺序乱、单元格坐标基本正确 → 优先怀疑阅读顺序策略或分栏判断。
- 文本顺序正常、单元格坐标错乱 → 优先怀疑表格结构还原与列边界检测。
- 两层都错 → 先回到第 2 节确认输入图像,再考虑版面检测。
记录“哪一层先出问题”很关键:如果阅读顺序把表格外的一行标题串进了表格内容,后面的结构还原就会被这一行带偏,此时修结构还原没有意义。
用逐格回填比对确认修复是否生效
验收不要只靠肉眼看“好像对了”。把 cells.json 按行列坐标回填到原表格结构里,生成一份可对照的结果(例如在原图上画单元格框,或输出成同尺寸 CSV),然后按下面的方式抽查:
- 从表头、表格中段、表格末尾各抽 2 到 3 行,逐格核对行列对应关系。
- 专门盯住之前首次出错的位置,确认该处是否真的改变。
- 记录仍错的格子类型,例如“跨两列的合并表头”“带单位的数字列被并入相邻列”“换行单元格只保留了第一行”。
如果剩余错误集中在同一类格子,说明还有一层没对齐,可以带着这个类型回到第 1 节重新定位;如果错误分布在随机位置,则需要结合扫描件质量再确认一次输入预处理。整个流程不改模型权重,只调整版面、顺序与结构参数,便于重复执行。