把 OvisOCR2 用在表单、合同这类业务上之前,要先回答的问题是:混排页面的输出差异到底有多大;识别得准不准并非首要问题。同一页上,印刷正文与手写批注在解析结果里是分成不同块,还是被合并成一段文本;手写内容有没有被直接丢字;有没有一部分手写被拼进了本该属于印刷正文的字段。差异范围不清,后面的字段清洗、人工复核规则就没法设计,上线只能是碰运气。
手写与印刷混排的主要风险是两类:手写被吞掉,以及手写串入印刷字段。建议先用「无手写、少量批注、大面积手写」三档样张跑出对照,逐块标出输出中手写与印刷的归属,再按业务字段决定自动采用还是人工确认。样张没覆盖的版式(手写压线、页边批注、覆盖印刷字)不要默认安全,需要结合具体模板和扫描质量单独确认。
准备手写占比不同的样张
样张要有梯度,才有比较基准。全都拿手写很多的页面去试,看不出「从哪一档开始出问题」;全都拿干净页面去试,又会高估稳定性。建议按模板各取一批,覆盖同一类表单的不同版式:
| 档位 | 页面特征 | 建议张数 | 需要记录的内容 |
|---|---|---|---|
| 无手写 | 纯印刷的表格与条款正文 | 每模板 3-5 张 | 基准输出,用作对照底稿 |
| 少量批注 | 个别字段手写填写、勾选、单处签名 | 每模板 5-8 张 | 手写位置、相邻的印刷字段名 |
| 大面积手写 | 半页以上手写意见、划改、盖在正文上的签章 | 每模板 3-5 张 | 手写覆盖范围、与印刷文字的叠压关系 |
手写位置建议用归一化坐标登记,跨分辨率、跨扫描批次的样张才能放在一起比对:
{
"sample_id": "contract_a_p2",
"tier": "none | sparse | dense",
"template": "contract_v3",
"scan_dpi": 300,
"handwriting_regions": [
{
"page": 2,
"bbox_norm": [0.62, 0.18, 0.94, 0.26],
"kind": "annotation",
"overlaps_print": false
}
]
}
overlaps_print 这一项是给后面判断串行风险用的:标记为 true 的区域,输出里出现混同的概率通常更高,需要单独盯。
分别看手写内容与印刷正文的落点
这一节只做一件事:确认两类内容有没有被分开。做法是把模型每次返回的文本按块拆开,逐块与样张上手写区域的坐标做位置对照,每块归到三类之一——印刷正文、手写批注、混同。归不到前两类的块,就是要继续排查的位置。
# 归属比对骨架,仅示意结构,坐标与字段名按实际返回调整
for block in ocr_blocks:
tag = "unknown"
if inside(block.bbox, handwriting_regions):
tag = "handwriting"
elif disjoint(block.bbox, handwriting_regions):
tag = "print"
elif overlaps(block.bbox, handwriting_regions):
tag = "mixed" # 串行基本都在这一类里
log(block.id, tag, block.text, block.bbox)
跑完后看两件事:mixed 块的绝对数量,以及 mixed 块里的具体形态——是整段合并(印刷正文和批注连成一句),还是字段内拼接(某个印刷字段的值后面跟了手写内容)。前者影响后续分词和段落判断,后者直接影响字段取值,两者处理方式不同。纯手写区域如果完全没出现对应块,就是丢字,同样要记进差异清单。
记录手写串行的典型形态
不需要把所有版式都试一遍,但要把最容易出错的几类抓出来。下面三张情况建议各找几张样张实际跑,逐张记录输出表现;下表列的是常见观察点,不是结论,实际结果受模板排版、扫描清晰度和笔迹影响,需结合环境确认。
| 情况 | 常见输出表现(逐张确认) | 重点看什么 |
|---|---|---|
| 手写压线 | 常被拆到相邻两行的字段里,或整段归到上一行或下一行 | 行归属是否稳定,有没有半行文字消失 |
| 写在页边 | 可能整段丢字,也可能被追加到页面开头或末尾成为独立段落 | 输出里能否搜到该段文字,落点在不在原位 |
| 覆盖印刷字 | 印刷字与手写笔画混合识别,出现错字,或同一字段出现两条值 | 同名字段是否重复,异常字符是否属于原印刷内容 |
记录时最好把原始输出块和样张截图放在同一条记录里,后面做字段策略时能直接回溯。三张情况里,只要有一种在样张上稳定出问题,就不能按「解析结果可以直接入库」来设计流程。
按业务字段定使用范围
结论不要落在「OCR 能不能用」这种层面,而要落成字段级的使用边界:哪些字段可以直接采用解析值,哪些必须人工确认,依据是上一步的块归属标签和串行记录。
| 字段类别 | 建议使用方式 | 判断依据 |
|---|---|---|
| 印刷正文中的条款、编号、模板固定项 | 可直接采用解析值 | 样张中该块稳定归为印刷正文,没有 mixed 块覆盖该区域 |
| 印刷字段旁的少量手写填写值(金额、日期、数量) | 采用但做字段级校验,校验不通过转人工 | 该字段出现过 mixed 块或同字段双值 |
| 整段手写批注、意见、划改说明 | 只作为文本存档,不写入结构化字段 | 样张中出现过丢字或跨行错位 |
| 签名、盖章区域 | 不做文字取值,只判断区域是否存在 | 手写覆盖印刷字时识别结果不可靠 |
把上面的判断写成可执行的字段策略,比写在文档里更有用,也方便回归时直接比对:
field_policy:
contract_no: {mode: auto, require_tag: print, on_mixed: reject}
amount: {mode: auto_with_check, check: [numeric, range], on_mixed: manual}
sign_date: {mode: auto_with_check, check: [date_format], on_mixed: manual}
handwritten_note: {mode: archive_only, target: attachment_text}
signature_area: {mode: region_only}
上线前的准入判断也基于这份策略:拿新样张重跑,看落到 manual 的字段是否都在预期范围内、reject 是否只发生在混同块上。样张没覆盖到的模板和版式,建议先按人工确认处理,等积累到对应的样张再放开。