Fable 5.1操刀,内存腰斩,外星科技——SimdPaddleOCR 1.4 发布!

大家好,我非常自豪地宣布纯 C# 写的 SimdPaddleOCR 今天发布 1.4 版本,这个版本有许多重大改进,其中有一些甚至说得上是"外星科技"!

对于刚认识这个项目的朋友,先交代一下我们的硬核定位 :这是一个纯 C# 实现的 PP-OCRv6 引擎。它不需要 Paddle Inference,不需要 ONNX Runtime,也完全没有挂载 OpenCV 这种庞大的原生库。我想尽办法榨干了 C# 的 SIMD 潜力,只为在 .NET 生态中提供极致的 OCR 体验。做这个项目只为回答一个问题------就是如果以我能达到的最高标准,打造出最强的 C# PaddleOCR 引擎,它能达到什么效果?1.4 版本就是我给出的答案!

1.4 这一代的骨架没变:内存腰斩、图级 NHWC 铺到全路径、原生 RGB/RGBA。对外 NuGet 请装 1.4.2 ------补丁把 CLS 评测集方向分类拉到了 100%,倒长行先转正再进 REC,tiny / small / medium 的 CER 又掉了一截。耗时和峰值内存与 1.4 完全相同。

如果你想了解前情提要,可以回顾我们之前的踩坑与进化之路:

相关传送门:


benchmark实测:性能霸榜,内存腰斩,碾压 C 引擎

为了用数据说话,我在自己的开发主力机上做了一重完整的对比测试。测试环境为 Ryzen 7 5800X(16 逻辑核,支持 AVX2,无 AVX-512),64 GB 内存,运行在 .NET 10.0.11 上。

测试流程非常严谨:使用同一个测试集,先用 25 张 tiny 图进行"烤机"预热,随后各模型跑 100 张(--warmup 1)。本次评测 1.4 系列不再跳过首张,按满勤 100 张图、总计 1036 行评测时认定的标准答案来统计。另外,我们还拉来了 C 语言版引擎(lw.PPOCR.C,提交号 20d0de6)作为对照标杆。

本库 1.3 与 1.4.2 性能与内存对比(耗时为 n=99 的单图均值 ms):

模型 路径 1.3 mean 1.4.2 mean 相对耗时 CER (字符错误率) Peak WS (峰值工作集)
tiny net10 AVX2 86.0 63.1 0.73× 2.78% → 2.37% 804 → 515 MB
tiny ns2 203.1 96.5 0.48× 同上 766 → 522 MB
small net10 AVX2 222.1 200.0 0.90× 0.60% → 0.41% 1195 → 674 MB
small ns2 432.1 303.0 0.70× 同上 1277 → 684 MB
medium net10 AVX2 628 585 0.93× 0.67% → 0.14% 2489 → 1206 MB
medium ns2 1606 874 0.54× 同上 2663 → 1218 MB

细节补充:在 tiny net10 场景下,除了峰值工作集暴降外,已加载内存 (loaded) 从 415 MB 降至 401 MB,工作集增量 (Δ WS) 更是从 389 MB 骤降至仅 113 MB!同时,在 1.4 系列中,ns2 与 net10 在本机的性能比值稳定在 1.50--1.53× 左右。

接下来,看看与 C 引擎 (lw.PPOCR.C 4w) 的同尺子对决:

模型 mean (ms) Peak WS Exact (完全一致行) CER
tiny 201.9 541 MB 752/1036 4.09%
small 541.7 760 MB 936/1036 1.08%
medium 2319.5 1479 MB 1014/1036 0.24%

结论非常震撼:

  1. 吞吐量碾压 :在 tiny 模型上,本库的吞吐量大约是 C 引擎的 3.2 倍
  2. 准确率反超 :medium 的 CER 从 1.3 的 0.67% 先被 1.4 的 ClsResizeImg 打到 0.26%,再被 1.4.2 的左 1:4 CLS 打到 0.14%,已经低于 C 引擎的 0.24%。行精确仍是 1004/1036,C 是 1014/1036。
  3. 方向分类满分 :本机 tiny CLS 1022/1022 (DET 配对到的行全对);CI 是 1020/1020

核心进化揭秘:为何 1.4 能有如此神效?

1. 内存管理"大瘦身"

我们移除了输入 LayoutConvert 阶段的第二份缓冲,并为 NHWC 工作集引入了别名机制。此外,F32 常量池不再使用"原始字节 + 解码拷贝"的笨重模式。这一系列组合拳直接让各路模型的峰值内存需求"腰斩"。

2. CLS 正确率两连跳:先回归PPOCR,再干到100%

在 1.3 版本中,我们的分类器 (CLS) 预处理使用了 PaddleX 的拉伸逻辑(强行拉成 160×80,并使用 ImageNet RGB)。这导致了一个致命问题:细长的拉丁文字行在被极度拉伸后,经常出现 0 度与 180 度的翻面误判。

1.4 先改回 PaddleOCR 原汁原味的 ClsResizeImg:高度固定 80,宽度按比例拉伸(封顶 160),右侧用 -1 进行 pad,并配合 REC 归一化。这一步直接让 medium 的 CER 从 0.67% 降到 0.26%。

总结走 PPOCR 的预处理路线正确率总体优于走 PaddleX 路线的 1.3 版本,但实测发现正确率并不是单纯的上升,而是有升有降。

预处理方式 正确行数 准确率 误翻 漏检180度
PPOCR (1.4) 840/851 98.71% 5 6
PaddleX (1.3) 842/851 98.94% 6 3

我仔细研究后,发现一条如果有很长的 DET 行:

走老的PPOCR/PaddleX的做法,都会被挤进 160 像素里面,这样一来字形就会糊成噪声,导致 CLS 模型根本猜不出来是什么东西:

1.4.2 解决这个问题的方法很简单,只是将原图的宽度按高度的 4 倍进行裁剪,保留左侧部分,再等比例拉伸到 160×80。

值得一提的是,1.4.2 为了解决这个问题并没有引入额外的内存分配和性能开销,转换仍然是一气呵成的。另外我还试过保持保持原图的 2:1 的比例,但发现准确率比不上 4:1。

3. 图级 NHWC 全路径制霸,ARM 端起飞

1.3 只有 AVX2+FMA 吃到了图级 NHWC 的红利。在 1.4 中,我们将这一优势扩充到了所有路径:

  • netstandard2.0 (Vector): 从 NCHW 跃升为 NHWC。
  • x64 scalar: 从 NCHW 加软件模拟,演进为专用的 NHWC 标量 tile。
  • net10 AdvSIMD: 从 NCHW 换成了手写的 NHWC NEON tile。
    同时,现在的预处理可以直接将数据写入 NHWC。
  • avx512: 从 NCHW 换成了手写的 NHWC AVX512 tile,我没有实测,但根据社区使用AMD锐龙7950X的测试反馈,性能提升45%。

这使得 1.4 在非 AVX 架构下获得了巨大收益。以 CI 跑在 linux-arm64 (Neoverse N2) 为例,tiny-4w 耗时从 241ms 降至 180ms (0.75×);同机 ns2 374→295(0.79×),scalar 984→856(0.87×)。win-x64 7763 上 ns2 则是 343→228(0.66×)。

另外为了减少代码复杂性,最新版 CI 中我也不再跟 OpenVINO.NET 这个老引擎作比较(1.3 时它的 tiny peak 高达 2.6 GB)。

4. 社区力量的加持

在此特别感谢社区开发者 yuebo119 :他将 DET 预处理改为按输出行并行,并为 crop 分配了独立线程预算,硬生生把 det_preprocess 的耗时从 ~8ms 压缩到了 ~1ms!同时也感谢 magicly 在 AVX-512/NEON/.NET Standard 2.0 相关方向上的贡献。


告别繁琐的 BGR 转换:原生支持 RGB/RGBA

1.4 虽然保持默认以 Bgr24 运行,但现在 Run 方法已经可以直接吞下 Rgb24、Bgra32 甚至是 Rgba32 格式的数据了!通道交换会在 resize 或 warp 时"就地完成",彻底告别了申请中间整图进行 BGR 转换的尴尬。

值得重复强调的是,新版本对像素格式的转换能力是 原生支持 的,并不是大家想象中的 RGB/RGBA/BGRA 先分配内存,再转换为 BGR,再传入 OCR,而是再次通过手写SIMD Kernel算子,直接将图片像素转成 NHWC 的数据,这个过程是高效且无额外内存开销的。

代码样例(拿走即用):

csharp 复制代码
using Image<Rgba32> image = await Image.LoadAsync<Rgba32>("sample.jpg");
if (!image.DangerousTryGetSinglePixelMemory(out Memory<Rgba32> memory))
    throw new InvalidDataException("图片像素不是连续内存");

PaddleOcrResult result = ocr.Run(
    MemoryMarshal.AsBytes(memory.Span), 
    image.Width, 
    image.Height,
    format: ImagePixelFormat.Rgba32
);

(避坑指南:如果使用 ImageSharp 处理超大图,记得克隆 Configuration.Default 并设置 PreferContiguousImageBuffers=true。如果依旧抛异常,可以通过 CopyPixelDataTo 兜底,具体写法可参考源码中的 examples/ImageSharp.AspNetCore 示例。)

我们还在示例工程中新增了"就地贴字"的展示(覆盖 WinForms、WPF、Avalonia 和 Web 版),画图的算法逻辑全在示例代码里,保持了核心库的极致纯净。


数据集开源,欢迎打擂

很多时候,大家跑出来的分不一样,是因为尺子不同。为了方便大家使用自己的引擎(不管是 Paddle 原生、ORT 还是 C)进行对比回归,我把本次的 100 张测试集开源了!

数据集采用 Apache-2.0 协议发布(不含字体二进制)。它由仓库内置的生成器(种子 20260830)构建,包含 100 张 JPEG,约 26456 个中英文混排字符,20% 竖排,水平行中有 25% 是 180 度倒转的。

官方指标现在三项:Exact lines (预测与真值无序字符串全匹配)、Exact CLS (DET 纠偏后的 0/180 对上 cls_degrees;有框用 IoU≥0.3,否则按精确文本配对,漏检不进分母)、CER(非 Exact 行取最小编辑距离除以总字符数)。该数据集专用于回归与引擎对照,由于规模太小,不适合拿来训通用 OCR。

有了这个数据集,社区也可以方便地对比不同 OCR 引擎的性能和准确率,推动算法优化和模型改进,这是我在这个数据集测出来的与 PaddleOCRSharp 6.2.0 的免费版本(不代表高性能付费版本)的对比结果。同机 Ryzen 7 5800X、4 worker;墙钟都是去掉首张后的 n=99。本库是 1.4.2

模型 引擎 平均值(中位数/P95) 相对C# 匹配行数 字符错误率 内存峰值
tiny SimdPaddleOCR 66.5 (61.1 / 108) 1.00× 742/1036 2.37% 515 MB
tiny PaddleOCRSharp 270.6 (246.7 / 494) 4.07× 698/1026 3.09% 993 MB
small SimdPaddleOCR 195.4 (198.1 / 261) 1.00× 950/1036 0.41% 674 MB
small PaddleOCRSharp 505.9 (494.7 / 637) 2.59× 885/1026 1.49% 1098 MB
medium SimdPaddleOCR 585.9 (591.2 / 770) 1.00× 1004/1036 0.14% 1207 MB
medium PaddleOCRSharp 1284.0 (1282.0 / 1636) 2.19× 973/1026 0.85% 1387 MB

本库准确率按 1.4.2 满勤 100 张 / 1036 行;PaddleOCRSharp 那轮仍是跳过首张的 99 张 / 1026 行。环境相同。相对 C# = 对方平均数 / 本库平均数。


有没有LINQPad粉丝?

为了让大家更容易上手,从 v1.4 起,我们的 NuGet 包自带了 linqpad-samples 标签与示例脚本。只要你在 LINQPad 的 Samples 树中点开,就能看到 4 个开箱即用的脚本:imagesharpskiasharpopencvsharp5bitmap

免费版的 LINQPad 也能下载并运行。脚本演示了轻量化、省内存的运行流程:从下载测试图 sample.jpg,到调用 tiny 模型识别,再到 Console 输出文字,一气呵成。无论是古老的 .NET Framework 4.8 还是最新的 .NET 10,甚至是 Mac 下的 LINQPad(配合 opencvsharp5 针对 macOS 的 runtime),都能完美丝滑运行。


升级与交流

怎么升级?

老用户只需将核心 NuGet 包升级到 1.4.2 即可。公开的 Run 方法默认依然是 BGR 格式,所以你现有的调用代码完全不需要做任何修改。项目依然维持 Apache-2.0 开源协议。如果你对底层的极致压榨感兴趣,强烈推荐阅读我们的 性能细节文档

加入社区探讨:

欢迎关注微信公众号 .NET骚操作 ,或者加入 SimdPaddleOCR 微信交流群:

(如果微信群二维码过期,请 .NET骚操作交流QQ群495782587)