一、为什么是 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 框坐标映射,方便前端高亮。
三、环境要求
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | 17+ | Java 环境 |
| ONNX Runtime | 1.18.0 | 内置 Windows / Linux / macOS 原生库 |
| OpenCV | 4.10.0-0 | 内置原生库,无需手动安装 |
| JTS | 1.20.0 | 多边形偏移(pyclipper 等价物) |
模型不内置在仓库中,需要单独下载,详见下文。
四、模型准备
下载 PP-OCRv6 官方 ONNX 模型(det + rec),放到 models/ppocr-v6/{tier}/ 目录:
| 档次 | det | rec | 字符表 | 定位 |
|---|---|---|---|---|
tiny | 1.7 MB | 4.3 MB | ~2855 | 轻量优先,速度快 |
small | 9.4 MB | 20.2 MB | ~2855 | 速度与精度均衡,推荐默认 |
medium | 59.2 MB | 73.0 MB | ~7180 | 精度优先,覆盖更全字符集 |
medium的 det/rec 模型为.onnx.zip,需解压后使用。
可选:文档方向分类模型 models/ppocr-v6/doc_ori/doc_ori.onnx(6.47 MB)。
五、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() | VehicleLicenseParser | VehicleLicenseResult |
idCard() | IdCardParser(正反面自动判定) | IdCardResult |
bankCard() | BankCardParser | BankCardResult |
driverLicense() | DriverLicenseParser | DriverLicenseResult |
businessLicense() | BusinessLicenseParser | BusinessLicenseResult |
invoice() | InvoiceParser | InvoiceResult |
七、效果展示
以 test_images/vehicle/vehicle1.png 为例(tiny 档模型):
| 输入图片 | 识别结果可视化 |
|---|---|
--- 行驶证结构化解析 ---
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标签定位 + 位置匹配findLabelBoxOCR 残缺标签模糊匹配(如"额" → "金额")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!