长表格断行通常不是单一原因。切图时切片边界落在表行中间、缩放时小字号笔画糊在一起,都会让识别结果在某一列突然断开或行数变多。要判断到底是哪一个在起作用,比较稳妥的做法是:拿同一张长表格做基准,先只改切图重叠度、其余参数不动跑一轮,再只改输入长边尺寸、其余参数不动跑一轮,把两轮的断行位置逐行对比。哪一轮的断行清单变化明显,主因基本就落在那个变量上;两轮都变,说明切图和缩放叠加生效,这时优先保证切图不切断表行。
同一张长表格、固定其余参数、只改一个变量的对照,是把“断行”从主观感受变成位置清单的最短路径。建议先固定切图窗口高度,用重叠度梯度跑一轮;再固定重叠度,用长边尺寸梯度跑一轮;两轮结果按行号和字段名并排记录。判定时以断行位置是否移动、列是否重新对齐为依据,不要只看总行数或整体观感。跨页表头、合并单元格、贴近切片边界的行属于边界样例,需要回看原图确认。
固定一张长表格做基准,逐行标出断行位置
基准图要选有代表性的:包含表头、有合并单元格或跨行备注、字号偏小、行数足够多到必须切成多片、并且表中有几处横向对齐线(方便判断列错位)。建议只保留一张,避免后续对照时混入不同版面带来的干扰。选取后不要对原图做任何预处理,保留原始分辨率作为基准件。
标注时用“行号 + 字段名”定位,而不是写“中间那行断了”。行号以原图从上往下数,字段名用表头文字。判定口径可以先定三条:同一行的单元格被拆到两行输出、某一列的值串到相邻列、表头与数据行不再对齐。命中任一即记为断行,并写清楚命中的是第几条。
| 行号 | 字段名 | 断行表现 | 原图对照位置 | 备注 |
|------|--------|----------|--------------|------|
| 12 | 金额 | 拆成两行 | y≈1180 | 切片边界附近 |
| 27 | 备注 | 列错位 | y≈2650 | 跨行文本 |
| 41 | 日期 | 值串列 | y≈3900 | 窄列 |
这张表就是后续所有对照的基线,任何一轮实验的结果都要能填进同一套行号和字段名里,才具备可比性。
只改切图重叠度,其余参数保持不变
切图这一轮,窗口高度、缩放策略、识别模型和解析参数全部锁死,只动重叠度。重叠度的作用是把相邻切片的接缝挪开,让落在边界上的表行至少完整出现在其中一片里。梯度可以取 0、窗口高度的 10%、15%、20% 三到四档,够看出趋势即可,不必铺得很密。
from PIL import Image
def slide_crop(src, out_dir, win_h, overlap, tag):
img = Image.open(src)
w, h = img.size
step = win_h - overlap
y = 0
while y < h:
box = (0, y, w, min(y + win_h, h))
name = f"{tag}_y{y:05d}_win{win_h}_ov{overlap}.png"
img.crop(box).save(f"{out_dir}/{name}")
if y + win_h >= h:
break
y += step
切片输出后按 y 偏移拼回整表:每片的识别坐标加上该片的 y 起点,重叠区里的重复行以行框中心落在哪一片为主,另一片丢弃。这里不要用简单去重,重复文本在长表格里很常见,按坐标归属更稳。跑完后核对每档重叠度下断行清单的位置有没有变化。
只改输入长边尺寸,其余参数保持不变
尺寸这一轮把上一轮选定或默认的重叠度固定住,只缩放长边。梯度建议覆盖“不缩放、模型可接受的中等长边、偏大的长边”三档,具体数值要结合所用推理端的显存和预处理逻辑确认。缩得太小,小字号行容易糊;缩得太大,切片内有效行数变少,反而增加接缝次数。缩放时不要放大原图,只做缩小。
from PIL import Image
def resize_long_side(src, dst, long_side):
img = Image.open(src).convert("RGB")
w, h = img.size
scale = long_side / max(w, h)
if scale >= 1:
img.save(dst)
return
img.resize((round(w * scale), round(h * scale)), Image.LANCZOS).save(dst)
结果命名建议带上变量值和用途,例如 base_win1024_ov0.15_long2048.png 与对应 .json 结果文件同名,避免几轮实验的文件混在一起。解析输出的结构化结果也同样保留,方便后面按行号回查。
把两组结果并排记录,判断断行主要来自哪个变量
两轮实验的记录并排后,判断依据就比较直观了。可以用下面这张表,行是变量组合,列是关键指标。
| 重叠度 | 长边 | 断行条数 | 列错位条数 | 断行位置是否与基线一致 |
|--------|------|----------|------------|------------------------|
| 0 | 固定 | | | |
| 10% | 固定 | | | |
| 20% | 固定 | | | |
| 固定 | 小 | | | |
| 固定 | 大 | | | |
判定规则可以先这样用:重叠度变化时断行位置整体前移或消失、而尺寸变化时位置基本不动,说明切图是主因;尺寸变化时列错位和断行位置一起变、重叠度变化时影响有限,说明缩放是主因;两组都明显变化,则两个变量在叠加,此时优先固定切图策略,再微调尺寸。跨页表头、紧贴切片边界的行、合并单元格里换行书写的文本,属于必须回看原图才能定性的边界样例,不要只看解析结果就下判断。
按结论固定参数并重跑整批长表格
结论确定后,把窗口高度、重叠度、长边尺寸写进统一的配置文件,不要再散落在脚本参数里。建议放在类似 configs/long_table.yaml 的位置,脚本只读配置,方便复现。
crop:
win_h: 1024
overlap: 153 # 约窗口高度的 15%
resize:
long_side: 2048
keep_original_if_smaller: true
python run_ocr.py \
`--config` configs/long_table.yaml \
`--input` ./data/long_tables \
`--out` ./runs/fixed_params \
`--dump-layout`
批量重跑后做回归检查:整批里仍有断行的图,逐张对比是否集中在表头、跨行文本、贴近切片边界的行;同一字段的列对齐是否稳定;切片数量与拼接后的行数是否自洽。发现异常先回看原图,再决定是调整重叠度还是调整尺寸,避免同时改动两个变量后又回到最初说不清的状态。