不用 ONNX Runtime,也不用 OpenCV:我用纯 C 做了一个 PP-OCR 专用推理 Runtime

项目:lw.PPOCR.C

GitHub:github.com/lxw112190/l...

一个面向 PP-OCR 的轻量级纯 C 推理运行时:开发阶段把 ONNX 转换成自定义 LWM 模型,部署端只保留 C11 Runtime,完成 DET → CLS → REC 全链路 OCR。


做 OCR 推理,今天已经不算什么新鲜事了。

Python、PaddleOCR、ONNX Runtime、OpenVINO、TensorRT......任选一套成熟框架,跑起来并不困难。

真正麻烦的,往往是部署

尤其当目标环境不是一台配置充足的服务器,而是工业现场的一台 Windows 工控机、一套需要长期维护的桌面软件、一个嵌入式 Linux 设备,甚至还要兼顾 32 位程序时,问题很快就会从:

"模型能不能识别?"

变成:

"为了识别几行字,我到底要带多少东西?"

这也是我做 lw.PPOCR.C 的出发点。

我想尝试一件看起来有点"反潮流"的事情:

不在部署端加载 ONNX,不依赖 ONNX Runtime,不依赖 OpenCV,不依赖 OpenVINO,也不依赖 TensorRT,自己用纯 C 写一个只服务于 PP-OCR 的专用推理 Runtime。

它不追求成为下一个通用 AI 框架。

恰恰相反,它从一开始就主动收缩边界:

只解决 PP-OCR 的部署问题,并尽可能把这件事做小、做稳、做清楚。

于是有了:

lw.PPOCR.C


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

先说结论:

如果你的部署环境完全不在意依赖体积,也不需要兼顾旧系统,更不关心运行时内部到底发生了什么,那么成熟框架通常仍然是更省事的选择。

lw.PPOCR.C 并不是为了证明"自己造轮子一定比成熟框架好"。

它解决的是另一类问题。

比如:

  • 一个 C/C++ 桌面软件,只想嵌入 OCR,不想再附带一整套推理框架;
  • 一个 C# 工业软件,希望通过稳定的 C ABI 直接调用 OCR;
  • 一个老项目仍然需要兼顾 32 位程序;
  • 一台离线设备需要尽可能简单的部署目录;
  • 工业现场更在意可控、可审计、可长期维护,而不是模型框架功能有多丰富;
  • 嵌入式 Linux 上,希望将来可以只针对 CPU/NEON 做优化;
  • 业务模型非常明确,就是 PP-OCR,并不需要执行任意 ONNX 网络。

在这些场景里,一个通用 Runtime 的"强大",有时候也意味着你必须一起承担它的复杂度。

lw.PPOCR.C 选择了另外一条路:

objectivec 复制代码
PP-OCR ONNX
      │
      ▼
开发期 Converter
      │
      ▼
平台无关 LWM 模型
      │
      ▼
纯 C Runtime
      │
      ├── DET 文字检测
      ├── CLS 方向分类
      └── REC 文字识别

一句话概括:

把复杂工作尽可能放到开发阶段,把部署阶段压缩成一个专用执行器。

这也是整个项目最核心的设计思想。


二、最关键的一步:部署端干脆不解析 ONNX

这是 lw.PPOCR.C 和很多"C/C++ OCR 封装项目"最大的区别。

常见方案大致是:

复制代码
应用程序
    ↓
ONNX Runtime / OpenVINO / TensorRT / OpenCV DNN
    ↓
ONNX 模型

lw.PPOCR.C 是:

markdown 复制代码
开发环境:

PP-OCR.onnx
    ↓
Model Converter
    ↓
*.lwm


部署环境:

*.lwm
    ↓
lw.PPOCR.C
    ↓
Inference

为什么这么做?

因为 ONNX 首先是一个优秀的模型交换格式,但它不一定是一个"极简专用 Runtime"最合适的直接执行格式。

如果部署端直接解析 ONNX,就意味着 Runtime 还需要面对很多原本可以提前完成的工作,例如:

复制代码
protobuf
ModelProto / GraphProto
NodeProto / TensorProto
AttributeProto
opset 兼容
Shape 推导
常量处理
图结构解析
算子属性归一化
模型合法性检查
内存规划

如果目标是"支持任意 ONNX",这些复杂度没有办法绕开。

lw.PPOCR.C 根本不准备支持任意 ONNX。

它只运行经过验证的 PP-OCR 模型。

那么一个自然的问题就出现了:

为什么不在开发机上把这些事情提前做完?

于是项目引入了自己的模型格式:

LWM ------ LightWeight Model

当前版本是实验性的 LWM v0.1

Converter 可以使用 Python、ONNX、NumPy、protobuf,因为它只存在于开发环境。

但生成 .lwm 后,部署端就不再需要这些东西。

这种思路有点像一个很小的:

Model Compiler + Runtime

而不是:

Runtime 现场解析一个复杂的模型交换格式。


三、LWM 不是"换个后缀的 ONNX"

LWM 并不是把 ONNX 内容原样塞进另外一个文件。

它有自己明确的二进制布局。

当前 LWM v0.1 使用:

  • little-endian 固定宽度整数;
  • IEEE-754 FP32;
  • 不保存原生指针;
  • 不保存 size_tlong 等平台相关类型;
  • Section 使用绝对偏移;
  • Section 按 8 字节对齐;
  • Tensor 和 Node 使用固定记录结构;
  • 模型加载前必须完成边界、结构和 checksum 校验。

核心文件结构大致可以理解为:

css 复制代码
┌──────────────────────────────┐
│ Header                       │
├──────────────────────────────┤
│ Graph Input / Output Table   │
├──────────────────────────────┤
│ Tensor Table                 │
├──────────────────────────────┤
│ Node Table                   │
├──────────────────────────────┤
│ Operator Parameters          │
├──────────────────────────────┤
│ String Section               │
├──────────────────────────────┤
│ FP32 Weight Blob             │
└──────────────────────────────┘

当前格式定义了 21 个 Runtime Operator ID,包括:

css 复制代码
Conv
Add
Mul
Div
Erf
HardSigmoid
BatchNormalization
ReduceMean
Relu
AveragePool
Squeeze
Transpose
Unsqueeze
MatMul
Softmax
Reshape
Concat
ConvTranspose
MaxPool
Resize
Sigmoid

注意,这并不意味着:

"现在已经支持所有包含这 21 个算子的 ONNX 模型。"

并不是。

项目的原则恰恰相反:

支持的是经过 Converter 和完整测试验证的具体 PP-OCR 图,而不是看到某个算子名字相同就认为任意网络都能运行。

这是一个非常重要的边界。


四、从 REC 开始,最后做到了 DET → CLS → REC

项目最初并没有一上来就做完整 OCR。

第一阶段只做了一件事:

PP-OCRv6 tiny REC。

也就是输入一张已经裁剪好的文本行图片:

scss 复制代码
text crop
   ↓
resize / normalize
   ↓
REC graph
   ↓
logits
   ↓
CTC Decode
   ↓
UTF-8 text

这个阶段真正跑通以后,才继续向外扩展:

css 复制代码
REC
 ↓
CLS
 ↓
DET
 ↓
Perspective Crop
 ↓
Full OCR

现在完整流程已经是:

scss 复制代码
BGR8 Image
   │
   ▼
DET Resize / Normalize
   │
   ▼
DET Graph
   │
   ▼
DB-style Postprocess
   │
   ▼
Reading-order Quadrilaterals
   │
   ▼
Pure-C Perspective Crop
   │
   ├── Tall text → 90° correction
   │
   ▼
Optional CLS
   │
   ├── 0°
   │
   └── 180°
   │
   ▼
REC
   │
   ▼
CTC Decode
   │
   ▼
UTF-8 Text Lines

也就是说,目前已经不是"纯 C 跑一个识别模型"的实验代码,而是把 OCR 的核心链路串了起来:

检测、方向分类、透视裁剪、文字识别、CTC 解码、坐标恢复、阅读顺序输出。


五、连透视裁剪都没有依赖 OpenCV

这一点我个人很在意。

因为如果所谓"纯 C OCR Runtime"最后在关键路径上还是需要 OpenCV 做:

css 复制代码
warpPerspective
rotate
resize

那么部署边界其实还是没有真正收干净。

所以 lw.PPOCR.C 的完整 OCR 路径自己实现了透视裁剪与双线性采样。

检测得到四边形后,根据几何关系构造目标区域,再完成:

scss 复制代码
四点坐标
   ↓
Homography
   ↓
Bilinear Sampling
   ↓
Text Crop

遇到高大于宽很多的竖向文本区域,还会按策略进行 90° 旋转,再进入 CLS/REC。

那怎么确认自己写的裁剪没有悄悄写错?

测试中仍然会使用 OpenCV 作为独立参考实现

也就是说:

OpenCV 可以出现在开发测试环境中,但不进入正式 Runtime。

测试会将纯 C 透视裁剪结果与 OpenCV warpPerspective / rotate 的结果进行像素误差比较,同时完整 OCR 还要经过真实图片的 Golden Test。

这种"测试可以依赖成熟工具,部署核心不依赖"的边界,是项目一直在坚持的原则。


六、公共接口不是简单地 char* ocr(...)

既然要做 DLL、C#、Win32、Linux 甚至以后 ARM,那么 ABI 设计必须认真。

当前公开头文件只有一个核心入口:

bash 复制代码
include/lw_infer.h

分别提供:

复制代码
lw_model
lw_session

lw_detector
lw_classifier
lw_recognizer
lw_ocr

这些对象对调用者都是 opaque handle

典型调用方式类似:

ini 复制代码
lw_ocr* ocr = NULL;
lw_error error;

lw_error_init(&error);

lw_status status = lw_ocr_create(
    "det.lwm",
    "cls.lwm",
    "rec.lwm",
    "ppocr_keys.txt",
    &options,
    &ocr,
    &error
);

执行完整 OCR:

arduino 复制代码
lw_ocr_run_bgr_u8(
    ocr,
    image,
    image_bytes,
    width,
    height,
    stride,
    lines,
    line_capacity,
    text_buffer,
    text_capacity,
    &result,
    &error
);

这里有几个看起来不起眼,但对 DLL 工程非常重要的细节。

1. 不跨 ABI 释放内存

输入和输出 Buffer 都由调用方拥有。

Runtime 不会:

c 复制代码
DLL 内 malloc
EXE 外 free

也不会要求不同编译器、不同 CRT 之间互相释放对象。


2. 公共结构都有 struct_size

例如:

arduino 复制代码
typedef struct lw_error {
    uint32_t struct_size;
    int32_t code;
    char message[256];
} lw_error;

初始化函数会填写结构大小并清零保留字段。

这样做的目的,是给未来 ABI 演进留下空间。


3. 错误不是只返回一句字符串

API 有稳定的 lw_status

复制代码
OK
INVALID_ARGUMENT
IO_ERROR
OUT_OF_MEMORY
INVALID_FORMAT
UNSUPPORTED_VERSION
OUT_OF_BOUNDS
CHECKSUM_MISMATCH
UNSUPPORTED
INVALID_SHAPE
MEMORY_LIMIT

自然语言错误消息主要用于诊断,而不是让业务程序去解析字符串。


七、模型文件也按照"不可信输入"处理

一个推理 Runtime 最容易被忽略的问题之一,就是:

模型文件本身也是输入。

LWM Loader 不会因为文件扩展名叫 .lwm 就直接信任它。

加载过程会检查:

  • magic;
  • format version;
  • header size;
  • reserved fields;
  • 文件实际大小;
  • section offset;
  • section size;
  • alignment;
  • section 是否重叠;
  • tensor rank;
  • tensor dtype;
  • shape 乘法是否溢出;
  • weight 范围;
  • node input/output index;
  • operator ID;
  • parameter version;
  • checksum;
  • workspace 约束。

任何格式错误都应该返回稳定错误码,而不是:

c 复制代码
assert
abort
exit

当前 checksum 使用的是 FNV-1a 64。

项目文档也明确说明:

它只是损坏检测,不是密码学签名。

如果需要模型真实性校验,仍然应该依靠可信发布渠道和 SHA-256。

这一点我觉得很重要,因为"做一个二进制模型格式"容易,把它当作需要防御性解析的文件格式来设计,才是真正进入工程阶段。


八、现在到底支持哪些模型?

目前项目明确聚焦于:

PP-OCRv6 tiny

包括:

模型 用途
PP-OCRv6 tiny DET 文字检测
PP-OCRv6 tiny CLS 0° / 180° 方向分类
PP-OCRv6 tiny REC 文字识别

当前精度:

复制代码
FP32

当前设备:

复制代码
CPU

当前 x86 SIMD:

objectivec 复制代码
Scalar
SSE2
AVX2

当前 LWM v0.1 转换出的模型文件大约为:

模型 LWM 大小
REC 4.46 MB
CLS 1.02 MB
DET 1.77 MB

这些数字只是当前格式和当前转换策略的结果。

LWM v0.1 尚未冻结,后续如果加入权重重排、Fusion、INT8 或新的内存计划,文件 Hash 和大小都可能变化。


九、最有意思的一段:从 570 ms 优化到了 18.865 ms

如果只看功能,自己实现一个 Runtime 其实还不够。

因为一个最直接的问题一定会出现:

"能跑是能跑,但会不会慢得没法用?"

项目里目前记录得最完整的是 REC 单行文字识别 Benchmark。

先把测试条件说清楚:

sql 复制代码
CPU:AMD Ryzen 7 7735H
OS:Windows 10 x64
Compiler:MSVC 19.40
Build:Release /O2
Precision:FP32
Thread:single-thread
测试图片:295 × 46 文本裁剪图
模型:PP-OCRv6 tiny REC

所以,下面的数据:

不是任意电脑的性能承诺,也不是整张图片的 Full OCR 延迟。

它只是同一测试环境下用来观察 Runtime 优化效果的一组可复现实验数据。

最初纯 Scalar 版本:

平台 REC 平均耗时
Windows x64 570.752 ms
Windows x86 1416.520 ms

这个速度显然还不够理想。

于是项目没有直接开始"凭感觉上 SIMD",而是先做 Profile。

第一轮优化 Scalar Conv:

yaml 复制代码
x64: 570.752 → 381.206 ms
x86: 1416.520 → 669.329 ms

接下来发现 Pointwise Conv 占据大量 Conv 时间,于是为 1×1 Conv 单独设计 cache-contiguous path:

makefile 复制代码
x64: 381.206 → 51.495 ms
x86: 669.329 → 139.297 ms

这一步非常关键。

它说明在专用 Runtime 里,最大的优势有时候并不是"我会不会写 AVX2",而是:

我知道自己只需要跑哪一张图,所以可以围绕真实模型的数据访问模式做优化。

随后又继续优化:

objectivec 复制代码
3×3 stride-2 Conv
MatMul
SSE2 MatMul
AVX2 MatMul
SIMD Pointwise Conv
Flat Binary SIMD
Broadcast SIMD
Depthwise 3×3 SIMD
Stride-2 3×3 SIMD

最终当前记录的结果是:

平台 初始 Scalar 当前结果 相对初始
Windows x64 570.752 ms 18.865 ms 30.255×
Windows x86 1416.520 ms 38.833 ms 36.477×

Windows x64 在这组测试中的吞吐达到:

复制代码
53.010 次 REC / 秒

而且优化过程中有一个要求始终没有放松:

Golden 结果不能因为性能优化而变化。

性能 Benchmark 不只是记录 mean latency,还会记录:

sql 复制代码
Median
P95
Min / Max
Throughput
Workspace
RSS after warm-up
Final RSS
RSS growth
Peak RSS
Backend

在文档记录的优化测试里,结果文本保持一致,测量阶段 RSS growth 为 0。

这里也必须再次强调:

18.865 ms 是一张 295×46 文字裁剪图的 REC 数据,不是 DET + 多行 REC 的整图 OCR 数据。

完整 OCR 的最终延迟还会受到:

  • 原图分辨率;
  • DET;
  • DB 后处理;
  • 文本框数量;
  • 每个文本框宽度;
  • 是否启用 CLS;

等因素影响。

项目后续也很有必要增加正式的 Full OCR Benchmark。


十、为什么专用 Runtime 可能有性能优化空间?

通用 Runtime 必须考虑:

vbnet 复制代码
很多模型
很多 Shape
很多 Operator
很多硬件
很多边界条件

lw.PPOCR.C 的思路是:

复制代码
我知道模型是谁
我知道哪些算子真的会出现
我知道热点节点在哪里
我知道哪些 Shape 模式最常见

于是可以针对真实 PP-OCR 图做一些非常明确的 fast path。

例如:

  • 1×1 Pointwise Conv;
  • 3×3 Depthwise Conv;
  • 特定 stride-2 Conv;
  • MatMul 行访问;
  • flat Add/Mul/Div;
  • 单轴 Broadcast;
  • SSE2 / AVX2 runtime dispatch。

同时仍然保留:

复制代码
Scalar fallback

CPU 检测不会简单地因为"编译了 AVX2 文件"就假设运行机器支持 AVX2。

x86/x64 下会检查:

objectivec 复制代码
CPUID
OSXSAVE
XGETBV
AVX2

如果不满足条件,会自动回落到 SSE2 或 Scalar。

这对于需要在不同年代 CPU 上部署的软件很重要。


十一、完整 OCR 不只返回文字,还保留坐标和各阶段信息

完整接口最终返回按阅读顺序排列的文本行。

每一行包含:

objectivec 复制代码
四点检测坐标
DET score
REC score
CLS score
CLS label
实际应用的旋转角度
CTC emitted count
UTF-8 text offset
UTF-8 text length

这样调用方不仅可以得到:

复制代码
"识别出了什么"

还可以得到:

复制代码
"文字在哪里"
"检测有多可信"
"识别有多可信"
"是否发生方向纠正"

这对很多实际项目非常有用,比如:

  • OCR 结果叠加显示;
  • 文档结构分析的前置处理;
  • 工业字符读取;
  • 票据字段定位;
  • 证照信息提取;
  • 图片搜索;
  • OCR 服务 API。

十二、核心库不负责 JPEG / PNG 解码,这是故意的

很多人第一次看到 API 输入是:

复制代码
BGR8 pixels

可能会问:

"为什么不直接给我一个 jpg 文件路径?"

因为图片解码也是一种依赖策略。

如果核心 Runtime 自己支持:

erlang 复制代码
JPEG
PNG
BMP
TIFF
GIF
WebP
...

那么你马上又需要选择一套图片库,并让它进入核心部署边界。

lw.PPOCR.C 的做法是:

核心只接受调用方已经解码好的 BGR8。

所以:

css 复制代码
Camera Frame
OpenCV Mat
System.Drawing Bitmap
stb_image
FFmpeg Frame
浏览器 Canvas
自研图像 SDK

都可以成为它的上游。

这让 Runtime 不必绑定某一种图片生态。

为了证明这件事确实容易集成,仓库里目前已经提供了三类使用方式。


十三、最朴素的 C Demo:不用图片库也能跑

为了证明核心库本身不依赖图片解码组件,原生 C Demo 直接使用非常简单的:

复制代码
P6 PPM

例如完整 OCR:

r 复制代码
.\bin\lw-ocr-ppm.exe `
  .\models\det.lwm `
  .\models\cls.lwm `
  .\models\rec.lwm `
  .\models\ppocr_keys.txt `
  .\models\sample.ppm

它会输出:

  • UTF-8 文字;
  • 检测框四点坐标;
  • DET / CLS / REC 分数;
  • 方向分类;
  • 应用旋转。

PPM 当然不是为了让用户以后都改用 PPM。

它的意义是:

用一个几乎不需要额外解码库的格式,证明纯 C API 从模型加载到最终 OCR 是完整可运行的。


十四、C# WinForms Demo:直接 P/Invoke

如果你写的是 Windows 工业软件,那么 C# 可能比 C/C++ 更常见。

项目提供了一个:

复制代码
.NET Framework 3.5
WinForms

Demo。

调用链是:

css 复制代码
System.Drawing
    ↓
BGR pixels
    ↓
P/Invoke
    ↓
lw_ocr_run_bgr_u8

Demo 可以:

  • 打开常见图片;
  • 后台执行 OCR;
  • 在原图绘制四边形;
  • 显示逐行文字;
  • 显示各阶段 score;
  • 显示旋转信息;
  • 输出完整 JSON。

这里选择 .NET Framework 3.5 也不是因为"越老越好"。

它主要是为了验证:

公共 C ABI 可以被较老的 Windows/.NET 应用以非常传统、稳定的 P/Invoke 方式调用。

但需要注意:

这不等价于已经完成 Windows 7 实机认证。

当前 Win7 x86 仍然是兼容目标,正式声明之前还需要实体机验证。


十五、还有一个原生 HTTP / Web Demo

另一种很直观的体验方式,是直接启动:

复制代码
lw.PPOCR.C.HttpServer

默认监听:

arduino 复制代码
http://127.0.0.1:8787/

然后用浏览器选择图片。

浏览器通过 Canvas 完成常见图片解码,再转成 P6 PPM 上传。

服务端:

javascript 复制代码
HTTP Request
    ↓
PPM validation
    ↓
RGB → BGR
    ↓
lw_ocr_run_bgr_u8
    ↓
JSON

页面会把 OCR 文字和四边形框重新绘制出来。

接口提供:

bash 复制代码
GET /health

以及:

bash 复制代码
POST /api/ocr

支持二进制 P6 PPM,也支持 JSON + Base64 PPM。

需要特别说明:

这个 HTTP Server 是测试和本地集成 Demo

它默认没有:

复制代码
TLS
Authentication
Rate Limit
CORS Policy
生产级 Access Log

所以项目文档明确建议:

如果真的暴露到公网,应放在带 HTTPS、认证、限流和请求大小控制的反向代理后面。

一个 Demo 如果愿意把"不适合直接上公网"写清楚,我反而更愿意相信它的工程边界。


十六、现在的跨平台状态到底怎么样?

目前项目的主目标平台包括:

复制代码
Windows x64
Linux x64

兼容目标包括:

复制代码
Windows 7 SP1 x86

后续计划:

复制代码
Linux ARM64

最近 GitHub Actions 已经完成:

yaml 复制代码
Windows Server 2022 + Visual Studio 2022 x64
Ubuntu 22.04 + GCC/Ninja

两套环境的完整 CI。

CI 不只是编译 Runtime,而是继续执行:

objectivec 复制代码
Converter dependencies
Model conversion
Runtime build
Model analysis regeneration
CTest
CPack
Generated-report consistency check
Artifact upload

最终分别生成:

go 复制代码
Windows x64 development package
Linux x64 development package

这意味着 Linux x64 已经不是"代码理论上应该能编译"的状态,而是实际进入远程 CI 构建、测试和打包链路。

不过仍然需要区分:

CI 验证 ≠ 所有机器实机验证

例如:

arduino 复制代码
Windows Server 2022 CI success

并不能自动推导出:

复制代码
所有 Windows 10 / 11 机器都已经验证

更不能推导出:

复制代码
Windows 7 x86 已经验证

项目当前仍然把:

复制代码
源码目标
CI 验证
实体机验证

作为不同层级。

这种表述可能没有一句"全平台支持"那么吸睛,但更接近真实的软件工程。


十七、32 位为什么还值得保留?

2026 年再讨论 x86 32 位,听起来似乎有点"考古"。

但工业软件和普通消费软件的生命周期并不一样。

现实中仍然有一些设备:

  • Windows 7 32 位;
  • 老工控机;
  • 已经冻结开发环境的行业软件;
  • 无法随意升级 OS 的现场系统;
  • 第三方 SDK 只能提供 Win32 DLL 的旧项目。

因此 lw.PPOCR.C 从设计阶段就尽量避免:

arduino 复制代码
把 64 位指针写进模型
默认 size_t 永远是 64 位
默认地址空间无限
公共 ABI 暴露平台原生结构

Windows x86 已经进行过本机构建、ABI、Runtime 和 Demo 测试。

但 Windows 7 SP1 x86 仍然只是兼容目标。

后面真正值得做的是:

用最终发布包在实体 Windows 7 SP1 32 位机器上完成完整 OCR、DLL、C Demo、C# Demo 和依赖检查。

如果这一步最终跑通,它会成为这个项目一个非常有辨识度的特点。


十八、它适合什么场景?

我认为 lw.PPOCR.C 比较适合下面几类用户。

1. 桌面软件嵌入 OCR

如果你的程序本来就是:

bash 复制代码
C
C++
C#

只需要一个稳定 C API 完成 OCR,不想把业务代码绑定到某个大型推理框架,专用 Runtime 会比较舒服。


2. 工业现场和离线设备

这类项目往往更重视:

复制代码
少依赖
可复制部署
稳定 ABI
长期维护
明确资源上限

而不是需要支持几十种 AI 网络。


3. 嵌入式 Linux

目前 Linux x64 已经进入 CI。

Linux ARM64 / NEON 仍在规划中。

如果后续完成 ARM64 + NEON,这套"同一种 LWM + 不同 CPU Kernel Backend"的架构会更有价值。


4. 学习一个小型神经网络 Runtime 是怎么工作的

如果你一直想知道:

复制代码
模型文件怎么设计?
ONNX 怎么转换?
Shape 怎么传播?
Tensor 生命周期怎么算?
Workspace 怎么规划?
Graph Executor 怎么调 Kernel?
Conv 为什么慢?
SIMD 到底应该优化哪里?
C ABI 怎么设计?

这个项目也可以作为一个相对聚焦的研究对象。

因为它没有试图把所有 AI 问题都塞进来。

模型范围小,反而更容易沿着一条完整链路看到:

ONNX → Converter → Model Format → Loader → Shape → Memory → Executor → Kernel → OCR Pipeline → API


十九、它不适合什么场景?

这一部分也必须说清楚。

如果你的目标是:

复制代码
任意 ONNX
YOLO
LLM
语音模型
多模型混跑
GPU 高吞吐
CUDA
TensorRT
大 Batch
自动图优化
训练

那么 lw.PPOCR.C 不是正确工具。

当前它明确不是:

通用 ONNX Runtime。

也不是:

Paddle Inference 的替代品。

更不是:

TensorRT 的 CPU 版竞争对手。

它现在的范围就是:

objectivec 复制代码
PP-OCRv6 tiny
FP32
CPU
DET / CLS / REC

这样的边界虽然让项目"看起来没那么万能",却也是它能够保持 Runtime 简单的前提。


二十、当前仍然存在的限制

项目现在已经可以跑完整 OCR,但距离"成熟稳定版"还有不少工作。

目前比较明确的边界包括:

LWM v0.1 还没有冻结

也就是说:

复制代码
文件格式
模型 Hash
ABI

在 1.0 之前仍可能调整。


当前主要是 FP32 CPU

还没有:

复制代码
INT8
FP16
GPU
NPU

ARM64 / NEON 还没有完成

Linux ARM64 是明确的后续方向,但不能提前写成"已经支持"。


Windows 7 还需要实体机验证

设计目标和真正兼容验证是两回事。


一个 OCR Handle 不设计为并发调用

单个 Handle 应由调用方串行使用。

如果应用需要并发,可以创建多个 Handle,由上层做 Worker 调度。


还需要更大的真实图片回归集

现有测试已经包含真实图片 Golden、REC corpus、图输出对比和完整 OCR 检查。

但更多字体、语言、拍摄角度、低对比度、模糊、复杂背景等真实场景仍然需要继续扩充。


二十一、我觉得下一步最值得做什么?

现在项目已经跨过了"证明可行"的阶段。

后续我反而不建议马上扩成通用 AI Runtime。

更值得做的是把现有范围继续做硬。

我个人比较关注这些方向:

markdown 复制代码
1. Full OCR Benchmark
2. 更大的 Golden Corpus
3. ASan / UBSan
4. Windows x86 持续构建
5. Windows 7 SP1 x86 实机验证
6. Linux ARM64
7. ARM NEON
8. INT8

尤其是:

Full OCR Benchmark

现在 REC Benchmark 已经可以清楚地看到每一次优化带来的变化。

下一步应该把整图 OCR 拆开统计:

objectivec 复制代码
DET
DB Postprocess
Crop
CLS
REC
Total

这样使用者才可以真正回答:

"我的一张 1280×720 图片、有 N 行文字时,大概需要多少时间?"

而不是只知道单行 REC 的速度。


二十二、为什么我仍然想把这个项目开源出来?

因为这个项目真正有意思的地方,可能不只是"多了一个 OCR 库"。

它其实是在尝试回答一个更底层的问题:

当模型范围足够明确时,我们到底需不需要把一个通用 AI Runtime 一起带到每一台终端设备上?

我的答案不是:

"永远不需要。"

而是:

有些场景里,确实可以有另外一种选择。

把复杂度放到开发阶段:

复制代码
ONNX
 ↓
Converter
 ↓
LWM

把部署端控制在:

复制代码
Loader
Shape / Memory
Executor
CPU Kernels
PP-OCR Pipeline
C ABI

然后只为自己真正需要的网络做测试和优化。

这条路肯定没有成熟通用 Runtime 省开发时间。

但它换回来的是:

复制代码
更明确的运行边界
更少的大型部署依赖
更容易审计的执行路径
更直接的 C ABI
更自由的旧系统 / 嵌入式适配空间

对一部分软件来说,这些东西可能比"支持任意 ONNX"更重要。


二十三、如果你想试一下

项目地址:

GitHub:

github.com/lxw112190/l...

当前仓库已经包含:

objectivec 复制代码
纯 C Runtime
ONNX → LWM Converter
DET
CLS
REC
Full OCR
Scalar / SSE2 / AVX2
C API
C Command-line Demo
C# WinForms Demo
Native HTTP/Web Demo
Tests
Benchmark
CPack Development Package
CI

如果你对下面任何一个方向感兴趣:

  • PP-OCR;
  • C/C++;
  • C# P/Invoke;
  • 小型 AI Runtime;
  • ONNX 模型转换;
  • 自定义模型格式;
  • SIMD;
  • Windows 32 位兼容;
  • 嵌入式 Linux;
  • 工业 OCR;

都欢迎看看代码、跑跑 Demo、提 Issue,或者一起讨论。


结语

这几年 AI Runtime 越来越强。

一个成熟框架可以替我们解决模型解析、图优化、硬件调度、算子实现、量化、GPU 等大量复杂问题。

所以自己写 Runtime,并不是一件"显然更先进"的事情。

但有时候,工程也可以反过来问:

如果我只需要 PP-OCR,我到底最少需要什么?

lw.PPOCR.C 目前给出的答案是:

复制代码
开发端:

ONNX + Converter


部署端:

LWM + Pure C Runtime

从最初一个速度并不理想的 Scalar REC,到现在 DET / CLS / REC 全链路跑通;从单个 Kernel,到 Graph Executor、LWM Loader、Memory Planner、C ABI、SIMD、C# 和 HTTP Demo;再到 Windows / Linux CI 真正跑起来,这个项目现在已经不只是一个想法。

它仍然年轻。

LWM 还没有冻结,ARM64 还没有完成,Win7 还需要实体机验证,INT8 和 Full OCR Benchmark 也还在后面。

但至少有一件事情已经得到了验证:

为一个明确的 OCR 模型族,做一个小而专用的纯 C 推理 Runtime,是可以走通的。

接下来要做的,不是把它变得"什么都能跑"。

而是继续把它做得:

更稳、更快、更容易部署。

如果你也喜欢这种"小而硬"的工程路线,欢迎来 GitHub 看看:

github.com/lxw112190/l...

相关推荐
u13013043 分钟前
AI 日报(2026年08月25日)
人工智能
2601_957879331 小时前
Seedance排队太久怎么办?2026免排队AI视频工具与替代方案怎么选
大数据·人工智能·深度学习
IvanCodes1 小时前
RAG 实战教程(四):GraphRAG 查询实战,本地检索、全局检索与 DRIFT Search
人工智能·agent
能源革命1 小时前
AI+能源前沿20260825
人工智能·能源
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(50):PACE——按下一步预测价值动态分配历史记忆粒度
论文阅读·人工智能·学习·开源·github
陈涛谈云计算1 小时前
陈涛谈做课(3):知识萃取三步法,告别“正确的废话“
人工智能
zandy10111 小时前
百沐生物发布生命科学智能体平台“百沐一下”,并与旭燃生物达成“AI+多组学”战略合作
人工智能
代码方舟1 小时前
零信任架构实战:基于天远普通维保查询构建自动化汽车后市场供应链网关
人工智能·架构·自动化·汽车