把 PP-OCR 塞进一个纯 C Runtime:从 Tiny 到 Medium,再到单文件 HTML OCR

0 阅读10分钟

图片

最近一段时间,我一直在折腾一个看起来有点“逆潮流”的项目:

不用 Python,不用 OpenCV,不用 ONNX Runtime,也不用 OpenVINO、TensorRT、protobuf,直接用纯 C 跑 PP-OCR。

这个项目叫:

lw.PPOCR.C

经过一段时间的开发、重构和大量 CI 验证,现在终于发布了第一个 v0.2.0 预览版本:

v0.2.0-preview.1

这次更新不再只是原来以 PP-OCRv6 Tiny 为核心的小型运行时,而是第一次把:

PP-OCRv6 Tiny、Small、Medium

三套模型统一到了同一套纯 C Runtime 之上。

更重要的是,这次不仅仅是“代码支持了”。

Windows、Linux、WebAssembly、浏览器单文件 HTML、Android ARM64、Java/JNI、Node.js,以及三套模型包,都已经通过自动化测试,并通过 GitHub Actions 完成了真实 Release 发布。


为什么还要自己做一个 OCR Runtime?

现在部署 OCR,其实已经有很多成熟方案。

Python + PaddleOCR 很方便;

ONNX Runtime 很通用;

OpenVINO、TensorRT 性能也非常不错。

但实际做工程项目以后,会碰到另外一类问题。

比如客户给你一台工控机,只想要:

“拷几个 DLL 进去就能跑。”

或者需要部署到一个比较克制的 Linux ARM64 设备上,又或者要做成自己的 C++、C#、Java 软件,希望整个运行环境尽量简单。

这时候你会发现,真正麻烦的往往不是 OCR 模型本身,而是模型后面的整个运行时生态。

Python 环境、动态库、第三方依赖、版本兼容、模型格式、部署目录、安装包体积……

最后一个 OCR 功能,可能带进去一大堆东西。

所以我做 lw.PPOCR.C 时,一开始就定了一个比较明确的目标:

它不是一个通用 ONNX Runtime,而是一个专门针对 PP-OCR 模型优化的轻量级纯 C Runtime。

换句话说,我们主动放弃“什么模型都能跑”,换取:

更小、更简单、更容易集成,也更容易控制。


现在真的只需要纯 C

lw.PPOCR.C 当前核心采用 C11 开发。

运行 PP-OCR 时,不依赖:

Python、OpenCV、ONNX Runtime、OpenVINO、TensorRT、protobuf。

对于工程项目来说,这一点带来的价值其实比“少几个 DLL”更大。

因为 Runtime 足够小以后,我们就可以开始真正控制整个 OCR 执行链:

PP-OCR ONNX
    ↓
模型转换
    ↓
LWM
    ↓
lw.PPOCR.C
    ↓
DET
    ↓
CLS
    ↓
REC
    ↓
完整 OCR 结果

模型加载、Shape 推导、Workspace 管理、算子实现、SIMD 优化、OCR 后处理,全部掌握在自己的代码里。

目前 CPU SIMD 已经覆盖:

Scalar
SSE2
AVX2
AArch64 NEON
LoongArch LSX

这也意味着,它不再只是为了普通 Windows x64 PC 写的 Demo,而是开始具备真正做跨平台部署 Runtime 的基础。


v0.2.0 最大变化:Tiny、Small、Medium 三模型统一

之前项目主要围绕 PP-OCRv6 Tiny。

Tiny 有一个非常大的优势:

小。

它非常适合:

手机、普通桌面软件、边缘设备、浏览器以及对资源比较敏感的场景。

但实际项目中,大家很自然会问:

能不能用 Small?

能不能跑 Medium?

所以 v0.2.0-preview.1 做的一件很重要的事情,就是让:

PP-OCRv6 Tiny
PP-OCRv6 Small
PP-OCRv6 Medium

使用同一套 Runtime。

这里我特意没有在 C API 里面增加:

LW_MODEL_TINY
LW_MODEL_SMALL
LW_MODEL_MEDIUM

这样的枚举。

因为在我的设计里:

Tiny / Small / Medium 是模型包的问题,不应该变成 Runtime ABI 的问题。

原生程序只需要更换模型目录。

Runtime 并不需要知道:

“你现在跑的是 Tiny,还是 Medium。”

我觉得这个边界很重要。

它意味着以后如果继续增加新的 PP-OCR 模型,不需要不停修改公共 C API。


三套独立 Runtime Model Pack

这次 Release 直接提供三套模型包:

PP-OCRv6 Tiny Runtime Pack
PP-OCRv6 Small Runtime Pack
PP-OCRv6 Medium Runtime Pack

实际发布大小大约为:

Tiny      7.2 MB
Small    32.1 MB
Medium  139.6 MB

每个模型包都包含自己的模型、字典、manifest 和 SHA256 信息,并且使用独立命名空间,避免不同模型解压以后互相覆盖。

所以原生程序最终可以形成类似这样的目录:

models/
├── ppocrv6-tiny/
├── ppocrv6-small/
└── ppocrv6-medium/

程序选择哪个目录,就运行哪个模型。

我个人比较喜欢这种方式。

它比在 Runtime 里面加入越来越多的“模型类型判断”干净得多。


一个 HTML,就是一个完整 OCR

这次还有一个我自己非常喜欢的功能:

Standalone HTML。

也就是:

一个 HTML 文件里面,直接包含 Runtime + WASM + 模型 + OCR 页面。

不用启动服务器。

不用安装 Python。

不用 npm install。

甚至不需要单独准备模型目录。

下载以后,直接打开 HTML 就可以做 OCR。

而且这次不是做一个 HTML 再让用户选择三个模型,而是分别提供三个独立文件:

Tiny HTML
Small HTML
Medium HTML

这样每个文件本身就是一个完整、确定的 OCR 产品。

实际发布后的大小也很有意思:

Tiny HTML      ≈ 13.7 MB
Small HTML     ≈ 46.9 MB
Medium HTML   ≈ 190.3 MB

这三个数字其实也很好地说明了它们各自的定位。

Tiny 很适合手机和普通使用。

Small 我认为是非常值得尝试的增强版,大小仍然比较合理。

Medium 已经明显属于桌面优先的重量级方案。

所以 Medium 虽然也能做成单文件 HTML,但我并不打算把它宣传成移动端首选。


不是“HTML 能生成”,而是真的跑了 OCR

做 WebAssembly 项目很容易出现一种情况:

编译成功了。

文件也生成了。

然后就算支持浏览器了。

这次我不太想这样做。

所以 Tiny、Small、Medium 的浏览器版本,都真正通过 Chromium 执行 OCR。

Small 和 Medium 还额外做了:

创建 OCR Runtime
    ↓
执行真实 OCR
    ↓
校验行数
    ↓
校验完整识别文本 SHA256
    ↓
再次 OCR
    ↓
destroy
    ↓
重新 create
    ↓
再次 OCR

并且 Standalone HTML 也在浏览器里真正加载图片、执行 OCR、读取结果。

最终这些测试又在正式 v0.2.0-preview.1 的 Release Workflow 中重新执行了一遍,并全部通过。

所以这次说:

Small / Medium Browser 支持

不是指“理论上应该可以”,而是已经作为 Release Gate 实际跑过。


除了 C,还把 Java、Android、Node 都接上了

虽然核心坚持纯 C,但 Runtime 最终还是要被各种业务程序调用。

目前这个 Preview 已经提供了一套比较完整的分发方式,包括:

  • Windows x64 原生开发包、Linux x86_64 原生开发包;
  • Android ARM64 AAR 和 Demo APK;
  • Java/JNI:Windows x64、Linux x64、macOS ARM64;
  • Node.js 18 / 20 / 22 WASM;
  • Tiny / Small / Medium 浏览器 SDK;
  • Tiny / Small / Medium 单文件离线 HTML;
  • Tiny / Small / Medium Runtime Model Pack。

其中 macOS ARM64 Java/JNI 这次也真正通过了:

构建
→ 打包
→ 校验
→ Java 示例编译
→ macOS 上实际 OCR
→ Release

完整链路。


Release 本身也做成了一个“测试”

这次其实还有一个变化,没有那么容易从页面上直接看出来。

以前很多项目的发布流程是:

编译
↓
压缩
↓
上传

但随着平台越来越多,这种方式很容易发布错文件。

所以 lw.PPOCR.C 现在把 Release Asset 本身也定义成了一个 Contract。

每一个正式应该发布的文件:

Windows
Linux
Web
Java
Android
Node
Runtime Model Pack
Checksum

都会进入 Release Manifest。

真正发布以前,CI 会检查:

文件是否缺失
文件名是否正确
版本号是否正确
是否出现多余文件
SHA256 是否正确
checksum 是否覆盖正确

通过以后才允许执行 GitHub Release。

这次 v0.2.0-preview.1 已经第一次完整跑通了这条真实发布链。

对我来说,这件事其实比“又增加了一个接口”更重要。

因为从这里开始,项目逐渐从:

“代码仓库”

变成:

“可以重复构建和发布的软件产品”。


为什么现在还是 Preview?

虽然这次功能已经比较完整,我还是把版本标成了:

v0.2.0-preview.1

而不是直接发布 v0.2.0

原因也很简单。

现在还希望实际观察几个问题。

首先是 Small 和 Medium 在真实项目里的价值。

尤其是 Medium。

它的浏览器单文件已经接近 190 MB,我们技术上能够跑,并不代表每个场景都值得使用。

我反而更想看看:

Small 是否已经是性能、效果、体积之间更好的平衡点?

其次是不同设备上的实际表现。

尤其:

普通办公电脑
低功耗 x86
ARM64
国产平台
浏览器
工控环境

这些场景里的体验,比继续在 CI 里面多加几个测试更有价值。

还有一个原因就是:

目前公共 C ABI 和 LWM v0.1 格式仍然没有宣布冻结。

所以 Preview 阶段如果你拿去做正式项目,建议把 Runtime 和模型作为一套一起管理,不要混用不同 Release 的二进制、模型和字典。这个限制也已经明确写进发布文档。


谁可能会对这个项目感兴趣?

如果你平时使用 PaddleOCR,而且属于下面这些场景,我觉得可以试试看 lw.PPOCR.C

你在做 C/C++ 工程,希望尽可能少带第三方运行库;

你需要在 Windows / Linux / ARM64 边缘设备部署 OCR;

你在做 C#、Java 等桌面软件,希望底层 OCR Runtime 足够克制;

你想把 OCR 直接打进一个离线 HTML;

你比较在意安装包大小、部署复杂度和运行时依赖;

或者,你单纯对:

“从模型格式、算子、SIMD 到完整 OCR,自己实现一套小型推理 Runtime”

这件事情感兴趣。

这个项目应该都会有一些值得看的地方。


接下来准备做什么?

v0.2.0-preview.1 发布以后,我暂时不准备马上塞入大量新功能。

反而希望这一版先跑一跑。

看看真实用户到底更喜欢:

Tiny
Small
Medium

哪一个。

看看大家主要把它用在:

Windows
Linux
ARM64
Web
Java
Android

哪一种环境。

以及有没有一些我们在 CI 环境里看不到的兼容性问题。

后续比较大的 Runtime 工作,我依然对:

更系统的 Microkernel Architecture、ARM64 进一步优化以及新的 SIMD Kernel

很感兴趣。

但这些不会赶在 v0.2.0 发布前强行塞进去。

现阶段更重要的是先把这一版打磨稳定。


最后

lw.PPOCR.C 从最开始其实只是一个很简单的想法:

PP-OCR 能不能不用一整套通用推理框架,直接做成一个足够小、足够容易集成的纯 C Runtime?

做到现在,已经逐渐有了:

模型转换
纯 C Runtime
DET / CLS / REC
完整 OCR
SIMD
多线程
Windows / Linux
ARM64
Java
Android
Node
WASM
单 HTML OCR
Tiny / Small / Medium
自动化 Release

这一整套东西。

它当然还远没有做到“万能”。

事实上我也不希望它变成另外一个万能 Runtime。

它更希望专注做好一件事情:

用尽可能简单、透明、可控的方式,把 PP-OCR 放进真实工程。

如果你也在做 OCR 工程化部署,欢迎试试,也欢迎提 Issue、PR,或者分享你实际跑起来的数据和场景。

项目地址:

GitHub:lxw112190/lw.PPOCR.C

当前版本:

v0.2.0-preview.1

这一版,终于可以放心让大家真正下载试用了。