大家好,我非常自豪地宣布纯 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 完全相同。
如果你想了解前情提要,可以回顾我们之前的踩坑与进化之路:
相关传送门:
- GitHub 仓库: https://github.com/sdcb/SimdPaddleOCR
- NuGet 包:
Sdcb.SimdPaddleOCR(当前 1.4.2) - v1.4 Release 日志: https://github.com/sdcb/SimdPaddleOCR/releases/tag/v1.4
- v1.4.2 CLS 补丁: https://github.com/sdcb/SimdPaddleOCR/releases/tag/v1.4.2
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% |
结论非常震撼:
- 吞吐量碾压 :在 tiny 模型上,本库的吞吐量大约是 C 引擎的 3.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。 - 方向分类满分 :本机 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 张测试集开源了!
- HuggingFace: simdpaddleocr-dataset-v1
- ModelScope: simdpaddleocr-dataset-v1
数据集采用 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 个开箱即用的脚本:imagesharp、skiasharp、opencvsharp5 和 bitmap。
免费版的 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)