mica-ppocr:Spring Boot 一行代码集成 PP-OCRv6 + 结构化解析

29 阅读6分钟

一、为什么是 mica-ppocr?

做业务系统时,"证件识别"几乎是绕不开的需求:行驶证核保、身份证实名、银行卡绑卡、营业执照审核、增值税发票记账……每种证件都对应一套字段抽取规则。

但市面上的 OCR 方案,多多少少有点不趁手:

  • 绑定 PaddlePaddle 这套庞大的 Python 生态,部署运维成本高
  • 只返回"散落文字框" ,离业务字段还差最后一公里 —— 每个项目都要重写一堆正则兜底
  • 跨平台结果不一致,GPU/CPU 浮点漂移让测试用例形同虚设

于是就有了 mica-ppocr,一次解决这三个问题:

  • Java 17 实现,服务端零 Python 生态依赖,Spring Boot / Solon 一键接入
  • 纯 ONNX Runtime 推理,CPU 单线程保证 bit-exact,跨平台结果一致
  • 结构化解析模块,6 类常见证件 / 票据直接吐出业务字段
  • 可视化友好,所有解析结果都带原始 OCR 框与字段坐标映射

二、能力全景

2.1 核心引擎 mica-ppocr-core

模块能力
文字检测(DB)resize + normalize + HWC→NCHW,DB 后处理 → 文本框
文字识别(CTC)透视裁剪、批处理、CTC greedy decode → 文本 + 置信度
文档方向分类PP-LCNet_x1_0_doc_ori,4 方向(0°/90°/180°/270°)自动旋转
API 入参String / File / Path / byte[] / InputStream 5 种重载,内部自动释放 native Mat

2.2 结构化解析 mica-ppocr-structured

解析器核心字段
行驶证车牌、车主、车辆类型、VIN、发证日期
身份证(正反面自动判定)姓名、性别、民族、身份证号、住址
银行卡卡号、持卡人、银行、有效期
机动车驾驶证姓名、准驾车型、有效期、证号
营业执照社会信用代码、单位名称、法定代表人、注册资本等 9 个字段
增值税发票发票代码、号码、开票日期、金额、税额、购销方

所有解析器都基于公共骨架 LabelMatcher:标签定位 + 位置匹配 + 正则兜底 + 版面布局兜底,并且提供 fieldBoxes 字段 → OCR 框坐标映射,方便前端高亮。


三、环境要求

组件版本说明
JDK17+Java 环境
ONNX Runtime1.18.0内置 Windows / Linux / macOS 原生库
OpenCV4.10.0-0内置原生库,无需手动安装
JTS1.20.0多边形偏移(pyclipper 等价物)

模型不内置在仓库中,需要单独下载,详见下文。


四、模型准备

下载 PP-OCRv6 官方 ONNX 模型(det + rec),放到 models/ppocr-v6/{tier}/ 目录:

档次detrec字符表定位
tiny1.7 MB4.3 MB~2855轻量优先,速度快
small9.4 MB20.2 MB~2855速度与精度均衡,推荐默认
medium59.2 MB73.0 MB~7180精度优先,覆盖更全字符集

medium 的 det/rec 模型为 .onnx.zip,需解压后使用。

可选:文档方向分类模型 models/ppocr-v6/doc_ori/doc_ori.onnx(6.47 MB)。

v6acc_opt.png


五、5 分钟快速上手

5.1 Spring Boot 一行接入(最推荐)

① 引入依赖

<dependency>
    <groupId>net.dreamlu</groupId>
    <artifactId>mica-ppocr-spring-boot-starter</artifactId>
    <version>${mica.ppocr.version}</version>
</dependency>

② 配置模型路径

mica:
  ai:
    ppocr:
      det-model-path:    models/ppocr-v6/tiny/det.onnx
      rec-model-path:    models/ppocr-v6/tiny/rec.onnx
      rec-char-dict-path: models/ppocr-v6/tiny/dict.txt
      # 可选:启用文档方向分类(处理倒拍/横拍)
      # use-doc-orientation-classify: true
      # doc-orientation-model-path: models/ppocr-v6/doc_ori/doc_ori.onnx
      # doc-orientation-thresh: 0.3

③ Controller 一行接入

@Autowired
private PPOcrTemplate ppocr;
​
@PostMapping("/ocr/vehicle")
public VehicleLicenseResult vehicle(@RequestParam MultipartFile file) throws IOException {
    return ppocr.vehicleLicense().parse(file.getBytes());
}

搞定。检测 → 识别 → 结构化解析 → 返回 VehicleLicenseResult(车牌、车主、车辆类型、VIN、发证日期),一条龙全自动。

5.2 纯 SDK 调用(任意 Java 项目)

不依赖 Spring Boot?直接用 mica-ppocr-core

public class Demo {
    public static void main(String[] args) {
        OpenCV.loadLocally();  // 首次启动加载 native 库
        PPOcrV6Config config = PPOcrV6Config.builder()
            .detModelPath("models/ppocr-v6/tiny/det.onnx")
            .recModelPath("models/ppocr-v6/tiny/rec.onnx")
            .recCharDictPath("models/ppocr-v6/tiny/dict.txt")
            .useDocOrientationClassify(true)
            .docOrientationModelPath("models/ppocr-v6/doc_ori/doc_ori.onnx")
            .docOrientationThresh(0.3f)
            .build();
​
        try (PPOcrV6Engine engine = new PPOcrV6Engine(config)) {
            List<PPOcrV6Result> results = engine.run("test_images/vehicle/vehicle1.png");
            for (PPOcrV6Result r : results) {
                System.out.printf("%s  (%.3f)%n", r.text(), r.score());
            }
        }
    }
}

5.3 Solon 项目

依赖换成 mica-ppocr-solon-plugin,API 与 Spring Boot 完全一致,零迁移成本。


六、6 类结构化解析一览

@Autowired
private PPOcrTemplate ppocr;
​
@Service
public class OcrService {
​
    // 1) Spring Boot 上传(最常用)
    public VehicleLicenseResult recognizeVehicle(MultipartFile file) throws IOException {
        return ppocr.vehicleLicense().parse(file.getBytes());
    }
​
    // 2) 网络流 / S3 下载流
    public DriverLicenseResult recognizeDriver(URL url) throws IOException {
        try (InputStream in = url.openStream()) {
            return ppocr.driverLicense().parse(in);
        }
    }
​
    // 3) 纯 OCR(不结构化,只要散落文字框)
    public List<PPOcrV6Result> recognizeRaw(byte[] imgBytes) throws IOException {
        return ppocr.run(imgBytes);
    }
​
    // 4) 身份证(正反面自动判定)
    public IdCardResult recognizeIdCard(byte[] bytes) throws IOException {
        return ppocr.idCard().parse(bytes);
    }
​
    // 5) 银行卡
    public BankCardResult recognizeBankCard(byte[] bytes) throws IOException {
        return ppocr.bankCard().parse(bytes);
    }
​
    // 6) 营业执照
    public BusinessLicenseResult recognizeBusiness(byte[] bytes) throws IOException {
        return ppocr.businessLicense().parse(bytes);
    }
​
    // 7) 增值税发票
    public InvoiceResult recognizeInvoice(byte[] bytes) throws IOException {
        return ppocr.invoice().parse(bytes);
    }
}

链式调用总览:

getter解析器结果类型
vehicleLicense()VehicleLicenseParserVehicleLicenseResult
idCard()IdCardParser(正反面自动判定)IdCardResult
bankCard()BankCardParserBankCardResult
driverLicense()DriverLicenseParserDriverLicenseResult
businessLicense()BusinessLicenseParserBusinessLicenseResult
invoice()InvoiceParserInvoiceResult

七、效果展示

test_images/vehicle/vehicle1.png 为例(tiny 档模型):

输入图片识别结果可视化
vehicle1.jpgvis.png
--- 行驶证结构化解析 ---
plateNo:      鲁GH9P12
owner:        盛瑞传动股份有限公司
vehicleType:  小型普通客车
vin:          LJ8F3D5H910700001
issueDate:    2018-02-24

测试图片来源于网络,如有侵权请联系删除。


八、bit-exact 是怎么做到的?

Java OCR 最常被诟病的就是"CPU 和 GPU 结果对不上"、"不同机器跑出来不一样"。mica-ppocr 默认配置:

  • intraOpNumThreads = interOpNumThreads = 1(CPU 单线程)
  • preferAccelerator = false(默认走 CPU,避免浮点漂移)
  • pyclipper → JTS BufferOp 等价实现,unclip 误差 < 1px

这套默认配置下,Java 端与 Python 参考实现 AIwork4me/ppocrv6_onnx 结果完全 bit-exact。需要 GPU 加速时,把 onnxruntime 换成 onnxruntime_gpu 并设置 preferAccelerator(true) 即可。


九、自定义解析器:5 分钟接入新证件

如果现成的 6 个解析器不够用(港澳通行证、台胞证、护照……),自定义解析器非常轻量。

公共骨架 LabelMatcher 已经把脏活都做了:

  • matchValue 标签定位 + 位置匹配
  • findLabelBox OCR 残缺标签模糊匹配(如"额" → "金额")
  • collectMultiLineRight 多行拼接(经营范围、住址跨多行)
  • WithBox 系列重载返回 LabeledMatch(value, box),便于回填 fieldBoxes

只需继承 BaseStructuredParser<R>,重写一个方法:

public class MyParser extends BaseStructuredParser<MyResult> {
    public MyParser(PPOcrV6Engine engine) { super(engine); }
​
    @Override
    protected MyResult parseResults(List<PPOcrV6Result> ocr) {
        // 用 LabelMatcher 抽取字段,装进 MyResult
        ...
    }
}
​
// 使用
MyResult r = new MyParser(engine).parse(file.getBytes());

十、生产实践小贴士

10.1 按环境切换 tiny / small / medium

通过 PPOCRPropertiesCustomizer 按环境变量切换模型档位:

@Bean
public PPOCRPropertiesCustomizer tierEnvCustomizer() {
    return builder -> {
        String tier = System.getenv("PPOCR_TIER");
        if (tier != null) {
            builder.detModelPath("models/ppocr-v6/" + tier + "/det.onnx")
                   .recModelPath("models/ppocr-v6/" + tier + "/rec.onnx")
                   .recCharDictPath("models/ppocr-v6/" + tier + "/dict.txt");
        }
    };
}

10.2 可视化高亮(前端好用)

VehicleLicenseResult r = ppocr.vehicleLicense().parse(file.getBytes());

// 画所有文字框(绿线)
for (PPOcrV6Result ocr : r.getRawResults()) {
    drawPolyline(ocr.box(), Color.GREEN);
}
// 高亮车牌字段(红线)
List<int[][]> plateBoxes = r.getFieldBoxes().get("plateNo");
if (plateBoxes != null) {
    for (int[][] box : plateBoxes) drawPolyline(box, Color.RED);
}

rawResults + fieldBoxes 两个字段是 BaseStructuredResult 的通用字段,所有解析器都带。

10.3 并发安全

  • PPOcrTemplate 是无状态 Bean,多线程共享完全安全
  • ppocr.xxxXxx() 每次返回新解析器实例,线程安全
  • PPOcrV6Engine 内部 ONNX session 是线程安全的(ORT 保证)

十一、小结

如果你正在找一款纯 Java、零 PaddlePaddle 依赖、可结构化解析、跨平台 bit-exact 的 OCR 库,不妨试试 mica-ppocr:

  • 一行依赖 + 一行配置 + 一行代码 = 完整 OCR 能力
  • 6 类证件 / 票据 已有现成解析器,字段直接到手
  • 可视化坐标 自带,前端高亮零成本
  • Spring Boot / Solon 双生态,存量项目平滑接入

仓库地址:

欢迎 Star / Issue / PR!