OvisOCR2 处理手写与印刷混排的页面 / 先把差异范围摸清楚再谈上线

文章导读
把 OvisOCR2 用在表单、合同这类业务上之前,要先回答的问题是:混排页面的输出差异到底有多大;识别得准不准并非首要问题。同一页上,印刷正文与手写批注在解析结果里是分成不同块,还是被合并成一段文本;手写内容有没有被直接丢字;有没有一部分手写被拼进了本该属于印刷正文的字段。差异范围不清,后面的字段清洗、人工复核规则就没法设计,上线只能是碰运气。
📋 目录
  1. 一 准备手写占比不同的样张
  2. 二 分别看手写内容与印刷正文的落点
  3. 三 记录手写串行的典型形态
  4. 四 按业务字段定使用范围
A A

把 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 块里的具体形态——是整段合并(印刷正文和批注连成一句),还是字段内拼接(某个印刷字段的值后面跟了手写内容)。前者影响后续分词和段落判断,后者直接影响字段取值,两者处理方式不同。纯手写区域如果完全没出现对应块,就是丢字,同样要记进差异清单。

OvisOCR2 处理手写与印刷混排的页面 / 先把差异范围摸清楚再谈上线

记录手写串行的典型形态

不需要把所有版式都试一遍,但要把最容易出错的几类抓出来。下面三张情况建议各找几张样张实际跑,逐张记录输出表现;下表列的是常见观察点,不是结论,实际结果受模板排版、扫描清晰度和笔迹影响,需结合环境确认。

情况常见输出表现(逐张确认)重点看什么
手写压线常被拆到相邻两行的字段里,或整段归到上一行或下一行行归属是否稳定,有没有半行文字消失
写在页边可能整段丢字,也可能被追加到页面开头或末尾成为独立段落输出里能否搜到该段文字,落点在不在原位
覆盖印刷字印刷字与手写笔画混合识别,出现错字,或同一字段出现两条值同名字段是否重复,异常字符是否属于原印刷内容

记录时最好把原始输出块和样张截图放在同一条记录里,后面做字段策略时能直接回溯。三张情况里,只要有一种在样张上稳定出问题,就不能按「解析结果可以直接入库」来设计流程。

按业务字段定使用范围

结论不要落在「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 是否只发生在混同块上。样张没覆盖到的模板和版式,建议先按人工确认处理,等积累到对应的样张再放开。