纯 C + WebAssembly,让 OCR 真正做到:一个文件、离线运行、下载即用。

项目地址:
前言:如果 OCR 的交付物只剩一个 HTML,会怎样?
传统 OCR 部署,通常意味着一整套运行环境:
- Python
- OpenCV
- ONNX Runtime
- 模型文件
- 动态库
- 配置文件
- Web 服务
- 甚至 Docker
如果只是想把一个 OCR Demo 发给别人体验,也经常免不了告诉对方:
先安装环境,再下载模型,然后运行程序。
最近在开发 lw.PPOCR.C 的过程中,我尝试了另一种方式:
把完整 OCR Runtime、检测模型、方向分类模型、文字识别模型、字典以及前端界面,全部装进一个 HTML 文件。
最终的交付物只有:
ocr-demo.html
不需要安装 Python。
不需要 OpenCV。
不需要 ONNX Runtime。
不需要启动服务器。
不需要额外放置模型目录。
只要有支持 WebAssembly 的现代浏览器,打开这个 HTML 文件,就可以选择图片并完成 OCR。
这背后的关键技术,就是:
WebAssembly
一、一个 HTML 文件里,到底装了什么?
从表面上看,它只是:
ocr-demo.html
但实际上,这个文件内部包含了完整的 OCR 运行环境:
css
ocr-demo.html
│
├── HTML / CSS / JavaScript
├── WebAssembly OCR Runtime
├── det.lwm
├── cls.lwm
├── rec.lwm
└── ppocr_keys.txt
也就是说:
界面、Runtime、模型、字典,全都被打包进了同一个文件。
浏览器打开之后,会在本地初始化 WebAssembly 运行环境,并加载内嵌的 OCR 模型。
随后执行完整的 OCR Pipeline:
objectivec
输入图片
↓
DET 文本检测
↓
DB 后处理
↓
透视裁剪
↓
可选 CLS 方向分类
↓
REC 文字识别
↓
输出文字、检测框和置信度
这不是一个只跑 REC 的简单 Demo,而是一套完整 OCR 流程。
二、为什么浏览器可以运行 C 写的 OCR?
关键就是 WebAssembly。
传统 C 程序一般会编译成:
Windows → EXE / DLL
Linux → ELF / SO
而通过 Emscripten,可以把 C/C++ 代码编译为 WebAssembly:
javascript
C Runtime
↓
Emscripten
↓
WebAssembly
↓
Browser
现代浏览器可以直接执行 WebAssembly。
因此,一套原本运行在 Windows、Linux 上的纯 C OCR Runtime,也可以进入浏览器。
对 lw.PPOCR.C 来说,这一点尤其自然,因为这个项目从一开始就尽量保持部署端足够轻量:
diff
C11
+
libc
+
少量平台适配
核心 Runtime 不依赖:
Python
OpenCV
ONNX Runtime
OpenVINO
TensorRT
protobuf
所以移植到 WebAssembly 时,不需要把一整套重量级推理框架一起搬进浏览器。
三、为什么不直接在浏览器里跑 ONNX?
当然可以。
现在已经有很多成熟方案,可以直接在 Web 端解析和执行 ONNX 模型。
但 lw.PPOCR.C 走的是另外一条路线:
javascript
开发阶段
PP-OCR ONNX
↓
Converter
↓
LWM
部署阶段
LWM
↓
Pure C Runtime
↓
WebAssembly
↓
Browser
这里有一个很重要的设计原则:
部署阶段不解析 ONNX。
ONNX 更适合作为开发阶段的模型交换格式。
真正部署时,模型先转换成 lw.PPOCR.C 自己的轻量格式 .lwm,Runtime 只需要理解这个专用格式。
这样做的好处很明显:
- 不需要 ONNX Parser;
- 不需要 protobuf;
- 不需要通用图优化器;
- 不需要为大量无关模型结构背负复杂性;
- 浏览器端 Runtime 可以保持更小、更简单。
这也是 lw.PPOCR.C 一直坚持的方向:
开发端可以复杂,部署端尽量简单。
四、为什么坚持做成"单 HTML"?
其实 WebAssembly 项目完全可以按照常规方式发布:
web/
├── index.html
├── runtime.js
├── runtime.wasm
├── det.lwm
├── cls.lwm
├── rec.lwm
└── ppocr_keys.txt
这种形式很正常,也更利于开发和调试。
但如果目标是"让别人快速体验 OCR",用户感受就完全不同。
当别人拿到一整个目录时,很容易问:
- 需要部署 Web Server 吗?
- 要不要 nginx?
.wasm文件能不能直接双击?- 模型目录能不能移动?
- 路径是不是必须固定?
而如果最后只交付:
ocr-demo.html
使用过程就变成:
下载
↓
打开
↓
选择图片
↓
OCR
这看起来只是少了几个文件,但交付体验发生了很大变化。
我更愿意把这种形式理解成:
一个可以执行 AI 模型的"可执行文档"。
五、它可以完全离线运行
这是这个 Demo 最有意思的地方之一。
OCR 过程中,图片不需要上传到服务器。
整个流程可以完全留在本机:
css
本地 JPEG / PNG
↓
浏览器读取
↓
Canvas / Pixel Buffer
↓
WASM Memory
↓
Pure C OCR Runtime
↓
OCR Result
识别得到的文字、检测框和置信度,再从 WASM 返回给 JavaScript,在页面里显示。
所以,只要 ocr-demo.html 已经下载到本地:
即使没有网络,也可以完成 OCR。
这对于一些场景非常有意义,例如:
- 内部资料识别;
- 工业现场离线工具;
- 局域网隔离环境;
- 教学演示;
- 模型效果展示;
- 不希望图片上传云端的轻量业务;
- 客户体验版;
- 快速验证算法效果。
当然,这不意味着浏览器 OCR 会取代服务器 OCR。
但它提供了一种新的选择:
OCR 不一定非得是"上传图片 → 服务端推理 → 返回结果"。
计算也可以直接跟着 HTML 文件走。
六、模型是怎么放进 HTML 的?
det.lwm、cls.lwm、rec.lwm 本质上都是二进制模型文件。
生成单文件 HTML 时,可以把这些模型编码后嵌入页面。
可以简单理解为:
css
det.lwm
↓
Base64
↓
HTML
方向分类模型、文字识别模型和字典也是同样处理。
最终这个 HTML 文件里实际包含:
diff
Web UI
+
WASM Runtime
+
DET Model
+
CLS Model
+
REC Model
+
Dictionary
所以它已经不只是一个网页。
它更像:
一个以 HTML 形式交付的完整 OCR 应用。
七、WASM 能不能更换自己的检测模型?
这个问题最近正好有使用者问到。
答案是:
可以。WASM 技术本身完全支持运行时更换模型。
目前的 ocr-demo.html 之所以没有提供"选择模型"按钮,并不是 WebAssembly 做不到,而只是当前 Demo 为了追求:
单文件、离线、开箱即用
所以默认把模型直接内嵌到了 HTML 中。
未来完全可以增加:
css
检测模型:
○ 使用内置模型
● 加载自定义 det.lwm
[选择 det.lwm]
浏览器可以通过 File API 读取用户自己的模型:
arduino
用户选择 det.lwm
↓
JavaScript File API
↓
写入 WASM 文件系统 / WASM 内存
↓
释放旧 OCR Handle
↓
重新加载模型
↓
新模型生效
换句话说:
更换兼容的
.lwm模型,通常不需要重新编译 WASM Runtime。
这也是 WASM 方案的一个优势。
八、未来还可以进一步做到"内存加载模型"
如果 Runtime 后续增加类似:
scss
lw_model_load_memory(...)
这样的内存加载接口,那么模型甚至不用先写入 WASM 虚拟文件系统。
浏览器端可以直接:
ini
<input type="file">
↓
ArrayBuffer
↓
WASM Memory
↓
lw_model_load_memory()
这样整个模型切换过程会更干净。
以后甚至可以设计成:
css
模型配置
DET
[内置 PP-OCRv6 Tiny]
[选择 det.lwm]
CLS
[内置 PP-OCRv6 Tiny]
[选择 cls.lwm]
REC
[内置 PP-OCRv6 Tiny]
[选择 rec.lwm]
Dictionary
[内置字典]
[选择字典]
不过从实现优先级上看,我更倾向于先开放 DET。
因为 REC 模型一旦更换,通常还涉及:
- 字典;
- 字符类别数量;
- CTC;
- 输入高度和宽度;
- 输出维度;
- 字符集兼容。
DET 相对更独立,更适合作为第一个可替换模型。
九、但"能换模型"不等于"任意模型都能换"
这一点必须说明。
lw.PPOCR.C 并不是一个通用深度学习框架,也不是另一个 ONNX Runtime。
它的定位一直是:
面向 PP-OCR 的专用轻量推理 Runtime。
所以可以换自己的模型,但新模型仍然需要满足当前 Runtime 的约束,例如:
- 模型算子必须已经被 Runtime 支持;
- DET 输入输出结构需要符合当前检测 Pipeline;
- 当前后处理仍然是 DB PostProcess;
- 输出需要满足概率图形式;
- 预处理方式要保持兼容;
- 模型 Shape 不能和现有 Pipeline 完全冲突。
所以更准确的说法是:
WASM 支持动态换模型,
lw.PPOCR.C支持的是"兼容的 LWM 模型",而不是任意神经网络。
这其实也是项目保持轻量的代价和优势。
十、为什么不让用户直接拖 ONNX 进去?
从技术上讲,也不是完全做不到。
但如果浏览器端直接支持 ONNX,就意味着 Runtime 需要进一步承担:
ONNX Parser
protobuf
Graph Validation
Graph Optimization
更多动态结构
更多通用算子
这会让项目越来越接近一个通用推理引擎。
而这并不是 lw.PPOCR.C 想走的方向。
所以即使以后网页支持自定义模型,我更希望用户提供:
det.lwm
而不是:
det.onnx
模型工作流仍然保持:
用户自己的 ONNX
↓
lw.PPOCR.C Converter
↓
det.lwm
↓
拖入浏览器
↓
WASM OCR
这样既能支持模型定制,又不会破坏 Runtime 的轻量定位。
十一、一个 HTML 文件,对软件交付意味着什么?
这是这次尝试中我最感兴趣的地方。
以前发布一个 AI 软件,经常是:
diff
模型
+
Python
+
环境
+
依赖
后来很多项目变成:
diff
模型
+
Docker
+
REST API
而 WebAssembly 又提供了另外一种可能:
css
一个 HTML
+
浏览器
对于中小型 AI 模型而言,这种交付形式很有吸引力。
HTML 原本是"文档"。
EXE 原本是"程序"。
而现在:
ocr-demo.html
里面已经同时包含:
diff
UI
+
程序
+
AI Runtime
+
模型
+
业务逻辑
所以它已经介于"网页"和"应用程序"之间。
把它通过:
U 盘
邮件
网盘
局域网
企业内部系统
发给别人,对方只需要一个现代浏览器就能运行。
这可能是 WebAssembly 最值得关注的一点:
它不只是让 C/C++ 进入浏览器,也是在改变 Native 软件的交付方式。
十二、这种单 HTML OCR 适合哪些场景?
它不是所有 OCR 场景的最佳方案,但在一些场景里非常方便。
1. 产品演示
给客户一个文件:
ocr-demo.html
比让客户安装 Python、DLL、Docker 或者服务端程序简单得多。
2. 算法效果验证
模型转换成 .lwm 后,可以直接用浏览器验证效果。
3. 离线 OCR 小工具
没有服务器,也可以完成基本 OCR。
4. 教学
C Runtime、WebAssembly、深度学习推理和 OCR Pipeline,可以在同一个 Demo 里展示。
5. 工业设备 Web 界面
很多工业设备本来就提供浏览器管理界面。
未来甚至可以把部分 OCR 运算直接交给客户端浏览器:
设备提供网页
↓
浏览器加载 WASM Runtime
↓
客户端 CPU 完成 OCR
这样在某些业务中,可以把部分计算压力从设备端或者服务器端转移给客户端。
十三、它不能替代 Native OCR
这一点也需要说清楚。
浏览器 WASM 很方便,但 Native C 仍然更适合:
高吞吐批处理
后台服务
工业长期运行
复杂多线程
极限 CPU 性能
大规模 OCR 服务
而 WASM 更适合:
即开即用
跨平台体验
离线工具
算法演示
快速交付
轻量部署
两种形态不是互相替代。
更合理的关系应该是:
lw.PPOCR.C
│
┌──── ───┴───────┐
│ │
Native WASM
│ │
Windows / Linux Browser
│ │
工业与服务部署 即开即用
最重要的是:
两边仍然可以共享同一套纯 C OCR Runtime。
十四、为什么我坚持做一个 PP-OCR 专用 Runtime?
lw.PPOCR.C 从一开始就没有准备做成:
"另一个 ONNX Runtime"。
这个项目更希望解决的是:
PP-OCR 能不能在不依赖重量级推理框架的情况下,用一套很小的纯 C Runtime 完成部署?
所以目前方向一直比较克制:
objectivec
Pure C
FP32 CPU
PP-OCR 专用
DET / CLS / REC
完整 OCR Pipeline
SSE2 / AVX2
Windows / Linux
WebAssembly
LWM 模型格式
部署阶段不解析 ONNX
最近还在继续围绕真实 PP-OCR Shape 做 CPU Kernel 优化。
后续仍然会重点研究:
Conv
SIMD
Weight Packing
Microkernel
CPU 多线程
Cache / Workspace
而不是无限扩大成一个通用推理框架。
十五、如何体验?
项目地址:
GitHub:
建议从项目的 GitHub Releases 页面获取正式发布的离线 HTML Demo:
下载下面的文件:
lw.PPOCR.C-*-ocr-demo.html
然后直接用现代浏览器打开即可。
十六、这个小 Demo 真正有趣的地方
如果只看技术实现:
C
↓
Emscripten
↓
WASM
↓
Browser
并不是什么神秘技术。
真正让我觉得有意思的是它最后改变了软件的交付体验。
以前交付 OCR,可能需要交一套环境。
现在可以只交:
ocr-demo.html
一个文件。
一个浏览器。
就能 OCR。
没有安装包。
没有 Python。
没有 OpenCV。
没有 ONNX Runtime。
没有服务端。
没有模型目录。
没有网络请求。
而真正完成 OCR 的,又不是浏览器自带 OCR API,而是一套由 C 编写、编译成 WebAssembly 的完整 PP-OCR Runtime。
这种反差本身就很有意思。
写在最后
我越来越觉得,WebAssembly 真正值得关注的地方,并不仅仅是:
"浏览器终于可以跑 C/C++ 了。"
更重要的是:
它让 Native Runtime 获得了一种新的交付方式。
对 lw.PPOCR.C 来说,这种交付方式最后变成了:
ocr-demo.html
把它发给别人。
对方打开。
选择图片。
OCR。
就这么简单。
这可能不是 OCR 最强大的运行方式。
但它一定是一种非常轻量、非常有趣,也非常适合分享和演示的方式。
让 AI Runtime 跟着一个 HTML 文件走。
这正是我目前在 lw.PPOCR.C 上探索的方向之一。
项目信息
项目名称: lw.PPOCR.C
项目地址: github.com/lxw112190/l...
定位: Lightweight Pure-C PP-OCR Runtime
部署方向: Windows / Linux / WebAssembly
模型格式: LWM
当前重点: FP32 CPU 性能、完整 OCR、轻量部署、跨平台运行