HyOCR-1.5 先跑印刷体票据、再试手写场景并分开建验收集

文章导读
要在自家业务里判断 HyOCR-1.5 哪类图像能用,比较稳妥的做法不是先跑一份混合样本看总分,而是先把验收集按图像来源拆开:印刷体票据、手机翻拍、手写各建一套。先在印刷体这一类上把调用链路、输出字段和字段取值跑通,再拿手写和翻拍类单独看失败模式。混在一起算分,通常会出现“印刷体拉高平均分、手写素材其实完全不可用”的情况,验收结论也就没法落到具体场景。
📋 目录
  1. Ⅰ 按图像来源分成印刷体票据、手机翻拍、手写三类目录
  2. Ⅱ 先只跑印刷体这一类,确认基础链路与输出格式
  3. Ⅲ 再跑手写与翻拍类,单独记录失败模式
  4. Ⅳ 给每类图像定验收观察项,而不是一个总分
  5. Ⅴ 把两类结论合并成上线范围说明
A A

要在自家业务里判断 HyOCR-1.5 哪类图像能用,比较稳妥的做法不是先跑一份混合样本看总分,而是先把验收集按图像来源拆开:印刷体票据、手机翻拍、手写各建一套。先在印刷体这一类上把调用链路、输出字段和字段取值跑通,再拿手写和翻拍类单独看失败模式。混在一起算分,通常会出现“印刷体拉高平均分、手写素材其实完全不可用”的情况,验收结论也就没法落到具体场景。

判断方向:HyOCR-1.5 的可用性要按图像来源分开验证。印刷体票据、手机翻拍、手写三类各自建目录,先只跑印刷体确认基础链路和输出字段,再跑手写与翻拍并单独记录漏检、错字、错行。验收看每类的观察项和可接受边界,而不是一个平均分。边界在于:单类样本偏少、或没有人工复核口径时,这套结论只适合用来决定“先上哪类、哪类先不上”,不宜当成整体识别能力的定论。

按图像来源分成印刷体票据、手机翻拍、手写三类目录

目录先按来源分桶,再做二次标注。这样跑评测时可以用一个参数只跑某一类,也能在结果里按来源聚合,不会因为目录平铺而丢分类信息。

eval_sets/
  printed_invoice/          # 扫描件、PDF 导出图、电子发票截图
    images/
      {yyyy}-{source}-{doctype}-{seq}.jpg
    manifest.csv
    out/
  phone_photo/              # 手机翻拍、桌面平铺拍照
    images/
    manifest.csv
    out/
  handwriting/              # 手写单据、手写备注区
    images/
    manifest.csv
    out/
  _rejected/                # 跑不通、字段缺失、无法判读的样本

文件命名建议带上来源和单据类型,例如 2025-shopA-invoice-0001.jpg、2025-mobileA-receipt-0007.jpg,序号只用本地递增,不要用哈希值,方便人工在结果里定位原图。manifest.csv 至少包含 id、file、doctype、expected_fields 四列,expected_fields 写期望能取到的字段名,不写期望值——期望值先放人工标注表,避免标注和样本一起改坏。

入选标准按来源分别定:印刷体票据选清晰、无强反光、整页完整的图;手机翻拍选带倾斜、阴影、轻微畸变的真实拍摄图,而不是同角度补拍的“干净版”;手写类选有实际录入需求的那几栏(金额、日期、签字区),签字区如果业务上本来就不要求识别,就不要放进验收集充数。建议每类先攒到能覆盖常见单据类型为止,样本不够就先补,不要用一类样本去代表另一类。

先只跑印刷体这一类,确认基础链路与输出格式

印刷体票这一类的目的是排流程问题,不是评效果。先确认图片能传进去、结果能取回来、字段名和业务侧能对上,再去碰手写。

# run_eval.py `--set` printed_invoice `--limit` 5
import csv, json, pathlib

def call_ocr(image_path):
    # 替换为 HyOCR-1.5 的实际调用方式:本地服务、命令行封装或 SDK
    raise NotImplementedError

def run(set_dir, limit=None):
    rows = list(csv.DictReader(open(set_dir / 'manifest.csv', encoding='utf-8')))
    (set_dir / 'out').mkdir(exist_ok=True)
    for row in (rows[:limit] if limit else rows):
        img = set_dir / 'images' / row['file']
        try:
            out = call_ocr(str(img))
        except Exception as e:
            print('ERR', row['file'], repr(e))
            continue
        json.dump(out, open(set_dir / 'out' / (row['id'] + '.json'), 'w',
                  encoding='utf-8'), ensure_ascii=False, indent=2)

输出字段检查清单,跑完前 5 张就逐项对照,缺什么先补在适配层里,别留在评测脚本里补:

  • 有没有整页纯文本字段,以及分块/分行结果是否带 bbox
  • bbox 是像素坐标还是归一化坐标,是否与图片尺寸对应
  • 有没有行级或字段级置信度,没有的话验收就只能靠人工比对
  • 空结果用什么表示:空字符串、null 还是空列表,适配层要统一
  • 多页单据是否分页返回,页序是否稳定
  • 编码统一 UTF-8,金额里的逗号、小数点在返回里是否被改写

异常样本的收集方式:报错、超时、返回空、字段缺失这四种,直接把原图和返回内容一起复制到 _rejected/,文件名加后缀 _err 或 _empty,并在一行日志里记下时间、样本 id 和异常类型。这批样本不要删,后面判断回退条件时用得上。

再跑手写与翻拍类,单独记录失败模式

手写和翻拍的失败往往不是“认错字”,而是“根本没框到”。如果记录表只写“识别不准”,后面就没法判断该改预处理、改字段规则还是干脆不做。所以失败模式至少要分开:漏检(该出现的行或字段没有出现)、错字(框到了但内容错)、错行(内容对但串到相邻行或相邻字段)。

HyOCR-1.5 先跑印刷体票据、再试手写场景并分开建验收集
id类别模式位置意外输出期望是否阻断上线
hw-0003手写漏检金额栏空非空金额是
pp-0011翻拍错行第 2 行商品名带出单价仅商品名否

典型样本留存建议原图、返回 json、人工标注三方放同一个目录 cases/{set}/{id}/,并在记录表里写清楚这张图属于哪种失败。翻拍类还要额外记一条拍摄条件(倾斜、阴影、反光),否则复现时没法还原现场。手写类记录笔迹特征,例如连笔、数字 1 和 7 写法、是否有涂改。

给每类图像定验收观察项,而不是一个总分

三类图像的观察项不一样,硬凑一个总分意义不大。印刷体票据看关键字段能不能直接取用、表格行有没有错位、印章覆盖区是否影响金额;手机翻拍看整页可读性和边缘裁切,重点字段是否落在被裁掉的区域;手写类先看是否只要求关键几栏,连笔字段直接标成“需人工”,不要算进必须通过的观察项。

可接受边界建议写成规则而不是比例,例如:金额允许缺千分位和货币符号,不允许小数点错位;日期允许分隔符不同,不允许日月互换;单据号允许前缀缺失,不允许位数缺失。这样验收时是一条条对,不依赖某次抽样算出的数字。

需要人工复核的情形也提前写下来:置信度偏低、字段为空、同一字段出现多个候选、印章或涂改覆盖、跨页续行。这几类在灰度期一律转人工,不进自动入库,避免把边界情况当成识别正确。

把两类结论合并成上线范围说明

最后交付的不是“HyOCR-1.5 能用”这句话,而是一段范围说明,写清楚先上哪类、哪些先不上。模板可以这样写:本期仅对印刷体票据、且来源为扫描或清晰拍照的单据启用自动识别,字段限于金额、日期、单据号;手机翻拍与手写类暂不启用,仍走人工录入,待样本补齐并单独验收后再评估。灰度期按来源分开抽检,出现某类样本连续报错、空输出集中出现、或金额类字段人工改单明显集中,即回退到人工录入并保留原图和返回结果用于排查。

监控项按类看,不要合并:每类每日的空结果数、调用异常数、人工改单数、字段缺失数。这几项用日志和人工改单记录就能统计,不需要额外造指标。回退条件在灰度前写好并让业务方确认,避免出现问题时临时争论阈值。