Skip to content

Officia.OCR — 图片与扫描件识别

能力摘要(AI 友好)OfficiaOcr 把图片识别成文字(recognize 返回纯文本、analyze 返回逐行文本 + 坐标 + 置信度);ScannedPdfConverter 把扫描件 PDF 转成可编辑 DOCX。中英两档权重随包内置(int8,合计约 11.7 MB),纯 JDK 自研推理内核,不依赖 Tesseract / ONNX Runtime / OpenCV

把位图变成文字:发票号、证件字段、整页扫描书稿。识别内核与权重都是自研的, 引入依赖即可用——不需要在服务器上装任何 OCR 软件,也不需要联网调用外部识别服务

引入

xml
<dependency>
  <groupId>plus.ruoyi</groupId>
  <artifactId>officia-all</artifactId>
  <version>1.0.0</version>
</dependency>

这是唯一需要引的依赖:Maven Central 上只发布 officia-all 这一个构件, 它是含全部能力模块与授权客户端的单一 jar,且无任何第三方传递依赖。

图片 → 文字

java
import plus.ruoyi.officia.ocr.OfficiaOcr;
import plus.ruoyi.officia.ocr.OcrOptions;
import plus.ruoyi.officia.ocr.recog.BuiltinModels.Language;

// 纯文本
String text = OfficiaOcr.recognize(imageBytes,
        OcrOptions.defaults().setLanguage(Language.CHINESE));

// 也接受 InputStream / File
String t2 = OfficiaOcr.recognize(new File("scan.png"), opts);

富结果:逐行文本 + 坐标 + 置信度

要定位「哪一行识别得不好」、或者要按位置做后续处理时用 analyze

java
import plus.ruoyi.officia.ocr.result.OcrResult;
import plus.ruoyi.officia.ocr.result.OcrLine;

OcrResult r = OfficiaOcr.analyze(imageBytes,
        OcrOptions.defaults().setLanguage(Language.CHINESE));

r.text();            // 整页文本(行间以 \n 连接)
r.lineCount();       // 行数
r.confidence();      // 平均置信度
r.skewAngleDeg();    // 检测到的倾斜角(绝对值太小时不会真旋转——旋转有损)

for (OcrLine line : r.lines()) {
    line.text();        // 该行文本
    line.confidence();  // 该行置信度
    line.box();         // 该行在原图中的位置(x / y / width / height)
}

// 滤掉低置信度的行(对纯噪声区域有效,见下方警告)
OcrResult clean = r.filterByConfidence(0.5);

票据号 / 编号:给字符白名单

已知内容只可能是数字、或只可能是某几个字符时,给白名单是提准最划算的一招

java
// 只认数字
String no = OfficiaOcr.recognize(png, OcrOptions.digitsOnly());

// 自定义字符集
String code = OfficiaOcr.recognize(png,
        OcrOptions.defaults().setCharWhitelist("0123456789ABCDEF-"));

白名单会切换识别路径

给了 charWhitelist(或显式指定字体)时,门面会改走模板匹配而不是神经网络: 候选被塌缩到白名单内,票据/编号场景更准。反过来,通用中文文档不要给白名单, 那会让它退到需要字体同源的模板匹配路径上,反而更差。

扫描件 PDF → 可编辑 Word

整本扫描书稿的入口。注意与 OfficiaPdf.toWord 的区别:后者要求 PDF 有文字层, 扫描件没有文字层,必须走这里。

java
import plus.ruoyi.officia.ocr.scan.ScannedPdfConverter;

byte[] docx = new ScannedPdfConverter()
        .language(Language.CHINESE)   // 必填,见下
        .toWord(pdfBytes);

// 老书常见版面:扫描时页面横放、两页并排印在一张上
byte[] docx2 = new ScannedPdfConverter()
        .language(Language.CHINESE)
        .rotate(ScannedPdfConverter.Rotation.CLOCKWISE_90)
        .splitFacingPages(true)   // 按最大空白带定位中缝,不是对半切
        .minConfidence(0.01)      // 滤掉插图/印章被误当文本行的噪声
        .toWord(pdfBytes);

🔴 三条必读的使用约定

OCR 与套件里其它能力有个根本区别:它配错了不会报错。识别器在固定字符集上 永远给出某个类别,没有「认不出」这一档。以下三条都是实测踩出来的。

① 语种必须显式指定

java
new ScannedPdfConverter().toWord(pdf);   // ❌ 直接抛异常

OcrOptions 的默认语种是英文。中文扫描件走英文模型不会报错, 只会吐出满篇拉丁噪声,而且平均置信度反而更高——按置信度过滤会被它骗过去。

实测:一本 183 页的中文书就这么白转出 25 万字垃圾,8.6 分钟跑完、零报错。 所以 ScannedPdfConverter 宁可在你不表态时响亮地失败

② 版面参数配错同样不报错

rotate / splitFacingPages 不对时照样返回正常结果,只是内容全是怪字。 同一本对开扫描书、同一份权重,只差这两个参数:

参数产物
不给5.9 KB,全是乱码
rotate + splitFacingPages285.8 KB,9330 段、17.6 万字可读

每本书都要先转两三页看看输出是不是人话,再决定整本怎么转。

③ 置信度不是「对不对」

confidence 高不代表正确。实测一处生僻字把「七曜齐」认成「七隆齐」, 置信度 0.972 —— 它对错字也可以很自信。

minConfidence 能滤掉纯噪声区域(插图、印章被误当文本行,实测那类得分在 0.0001 量级), 滤不掉「认错字」。要验收质量只能对照原文,不能看分数。

授权与评估版限制

未授权的评估态下,识别结果最多输出 1000 行(按实测约 25 行/页折算约 40 页), 超出部分截断,并在末尾追加一行显式提示

【未授权评估版:识别结果已截断至 1000 行 · 授权联系QQ770492966】

为什么是限行数而不是水印

套件里其它能力的评估降级是「限 30 页 + 盖水印」,但那对 OCR 两条都失效: recognize(图片) 没有「页」的概念;而往识别文本里插水印字就是污染数据, 你拿不到干净结果,也就无从评估质量。

改用限行数后,1000 行以内的结果与授权版逐字一致——评估者能真实判断识别质量, 这正是 OCR 最需要被信任的地方。

截断是显式的:提示行会出现在 OcrResult.text() 与产出的 docx 里, 不会让你误以为是识别丢字。

识别质量(实测,非宣称)

口径数值题目
留出字体 CER0.01077 款训练时未见过的字体 × 6720 字,只考字形泛化
中文验证集 CER0.0162整行完全正确率 89.4%
英文验证集 CER0.0013
2004 年胶印书正文基本逐字准确平均置信度 0.961
1984 年扫描书召回 85.9%30 页实测,与 Tesseract 同口径比对(见下)
1990 年铅印书召回 69.9%能读懂大意,不宜直接交付

老铅印件差距主要来自字形域差(老铅字的墨色扩散、纸张透印在训练分布外)。 交付前请用你自己的样本实测,别按单一数字预期。

2026-08 的一次取舍:留出字体 CER 从 0.0071 上升到 0.0107

这是有意为之。修正训练语料的字符配比后(拉丁字母此前被数字严重挤占), 真实扫描件上的一类高频误识大幅减少——o6l1a8Ss 这四类合计从 93 次降到 21 次(-77%),两本真实书的 CER 也都下降。

代价是干净渲染的黑体系汉字字形分辨力下降(退化集中在等线、宋体、雅黑、黑体; 仿宋、楷体反而改善)。因为拉丁字母与黑体系汉字同为横平竖直的简单笔画, 低分辨率下更易混淆。

我们选择了对真实扫描件更有利的一侧——那才是 OCR 的实际输入。

与 Tesseract 的同口径对比

2026-08 拿一本 1984 年的扫描书(526 页)做过一次同口径比对: 同一份扫描图、同样的 30 页、同一套评分脚本。

口径数值
召回85.9%
CER0.1633
扣除字表缺口后的可达召回88.4%
速度31 秒/页(纯 CPU)

这个数字不是「准确率」

参照用的是 Internet Archive 提供的 Tesseract 5.3.0 输出,不是人工校对的真值—— 它自己也会认错(实测「陀阁逝」被它认成 pew)。所以上表只能读作 两个引擎在同一批页上的相对表现,不能理解成「officia 的准确率是 85.9%」。

要评估你自己的场景,唯一可靠的办法是拿你的样本实测

当前剩余误差主要在标点与拉丁字母(中西文标点互混、半角全角、l/1o/0 形近), 汉字识别本身已较稳定。

能力边界

场景状态
版面还原只做「行 → 段落」的线性还原,还原表格、多栏、图文混排
竖排文字不支持。切分按横排设计(水平投影切行),竖排页面整页只切出 1 行
两页并排⚠️ 必须显式开 splitFacingPages(true),否则静默产出垃圾
页面扫描黑边✅ 自动剥离;装订阴影呈渐变且不贯穿时仍可能残留
手写体 / 艺术字❌ 不支持
倾角 > 15°❌ 属页面朝向错误,请先旋转页面
速度纯 CPU 推理,一页约数十秒(无 GPU 依赖,也无法用 GPU 加速)

常见问题

  • 要不要在服务器上装 Tesseract? 不需要。推理内核与权重都在 jar 里, 引依赖即可用,不装任何 OCR 软件、不联网。
  • OfficiaPdf.toWord 报"没有文字层":那是扫描件的预期行为,改用 ScannedPdfConverter
  • 转出来全是怪字:先查语种,再查 rotate / splitFacingPages(见上方约定 ①②)。
  • 模型能换吗:可以,OcrOptions.setModelBytes(byte[]) 接受自定义 .ofm 权重; 传了坏模型会抛异常而不是静默退回内置模型。