简介:这份离线文字识别依赖库基于 RapidOcr 与 Onnxruntime 实现,面向需要在本地完成 OCR 的开发者,解决云端识别依赖网络、隐私易泄露等问题。包内包含 991 个文件、约 192.61MB,涵盖 C++/Python 头文件(hpp/h)、ONNX 模型文件、动态库(so)、静态库(a)及 CMake 构建配置,还附有 OpenCV 相关静态库,可支撑 Android、Linux 等多平台编译与移植。目前已吸引 1481 人学习查看。通过学习,读者能掌握 Onnxruntime 加载与推理流程,理解图像预处理、模型执行及结果后处理的完整链路;同时,包内丰富的 cmake、ninja 等构建文件能帮助开发者快速集成 OCR 能力到自有项目,压缩包内的 txt/json 文件也可作为参数与配置参考,降低离线识别方案落地门槛。适合对 OCR 技术感兴趣的算法人员与嵌入式开发者。 近几年我在本地部署文字识别(OCR)时,被 RPA 抓取、票据翻拍、车牌号提取这些真实项目反复折腾。联网接口一断就崩,数据出境更是碰都不敢碰。后来我把目光锁定在 RapidOcr 和 Onnxruntime 上,搭了一套完全离线的文字识别管线。效果很稳。今天把整个集成思路、依赖库选型、踩过的坑和报错复盘,原原本本写出来。
这套方案最核心的价值就是“离线可用”:模型和推理运行时全部落在本地,没有网络请求,也没有数据外传。我手头有一批内部表格图片,每天要自动提取关键字段,敏感度又高,RapidOcr 配合 onnxruntime 的动态库就成了最简单的自托管解。这套东西在 Windows 和 Linux 下都能跑,单张 CPU 推理耗时在 300-600ms 区间(看图片分辨率),对绝大多数内部工具完全够。
如果你正打算做离线 OCR,又不想引入繁重的 PaddleOCR 全家桶或者调云接口,这篇文章应该能帮你少走很多弯路。
1. 整体设计与思路拆解
1.1 为什么选择 RapidOcr 而非 PaddleOCR 或 Tesseract
三套方案我都实际用过,差别不在准不准,而在“落地成本”。
Tesseract 是老牌开源方案,识别印刷体英文和数字效果不错,但中文长文本、表格混排、低分辨率截图上的表现比较吃力。PaddleOCR 识别精度高,模型也多,但框架依赖非常重——PaddlePaddle 全家桶自带一大堆动态库,部署包动辄几百 MB,有些精简环境根本塞不进去。RapidOcr 的定位恰好补齐了这两者的短板:模型来自 PaddleOCR 的 PP-OCR 系列,但推理引擎换成了 onnxruntime,依赖更干净、体积更小、跨平台性更好,而且有 C++ 动态库,也有 Python 封装,能快速嵌入到现有服务里。
实际用下来我对 RapidOcr 的判断是:如果你想在“精度不错”和“部署轻量”之间取一个平衡点,它是最理想的选择。
1.2 Onnxruntime 在链路中的角色
Onnxruntime 是微软开源的推理引擎,它把训练好的模型(比如 PaddleOCR 导出的 onnx 格式模型)加载进来,然后跑在 CPU、GPU 或者 NPU 上。很多人容易把“OCR”和“推理引擎”混为一谈,其实这是两层东西。
我打个比方:RapidOcr 是餐厅的菜单和厨师,onnxruntime 是后厨的灶台和炒锅。模型决定识别的“手艺”,推理引擎决定“上菜速度”。RapidOcr 只是把 PaddleOCR 的模型导成了 onnx 格式,然后用 onnxruntime 来做底层的算子计算,两者的关系是协作而不是绑定。这意味着你可以自己控制 onnxruntime 的版本,甚至针对 CPU 指令集做定制编译,获得更好的性能。
1.3 依赖库架构与调用流程
RapidOcr 在推理时的完整数据流是这样的:
图片输入 -> 预处理(缩放/归一化) -> 文本检测(DBNet) -> 方向分类(可选) -> 文本识别(CRNN) -> 后处理(解码) -> 文本输出每个环节在 RapidOcr 目录里都有对应模型文件。我建议你用 Onnxruntime 的 Python API 或者 C API 分别加载这三个模型,而不是混在同一个 Session 里。原因有三个:
- 检测、分类、识别模型输入尺寸不同,分开 Session 可以单独控制内存分配。
- 如果某一步计算失败,可以单独排查,不至于整个链路崩掉。
- 后续如果识别模型升级了,可以只替换对应 onnx 文件,不用动其他模块。
2. 依赖库选型与关键版本约定
2.1 全套依赖清单与版本建议
我目前稳定跑在生产环境的一套组合是这样:
| 组件 | 版本 | 说明 |
|---|---|---|
| rapidocr_onnxruntime | 1.3.24 | 核心库,内置模型与推理封装 |
| onnxruntime | 1.15.1 | CPU 版,自带 OpenMP 多线程支持 |
| opencv-python | 4.8.0.76 | 图像预处理与可视化 |
| numpy | 1.24.3 | 数组操作,注意与 onnxruntime 兼容 |
| Pillow | 10.0.0 | 可选,处理大图转码 |
别小看版本号,这里每个版本都是踩过坑之后定下来的。
onnxruntime 1.15.x 算是一个分水岭。1.13 之前对高分辨率图片偶尔会有算子兼容问题,1.16 之后 C++ API 变化了一些,但 Python 端还好。我测试过 1.14.1 和 1.15.1,后者在纯 CPU 环境下单线程延迟低了不少,推荐优先这个版本。
Python 版本建议 3.9 或 3.10。3.11 以上我在某些 Linux 环境下遇到过 onnxruntime 预编译包加载异常,排查成本很高,除非必要,不要冒险。
2.2 到底要不要自己编译 onnxruntime
我见过很多帖子劝人“源码编译 onnxruntime 以获得极致性能”。我的意见是:绝大多数场景下不要编。理由很直白:
- 预编译包已经启用了 CPU 指令集优化(AVX、AVX2),直接 pip 安装即可吃满。
- 源码编译需要安装 cmake、CUDA(如果用 GPU)、protobuf 等一堆工具链,一搞就是半天,出错的概率极高。
- RapidOcr 的模型是全卷积网络,没有特别冷门的算子,预编译包完全能撑起来。
唯一的例外:如果你的目标机器是 ARM 架构(比如树莓派或某些国产开发板),或者你确实需要裁剪 so 体积,那么自己编译才有意义。否则老老实实用 pip 装最高效。
2.3 动态库文件的位置与获取方式
RapidOcr 的下载包解压后,模型文件位于models目录下,一般包含:
models/ det.onnx cls.onnx rec.onnx dict_chi_sim.txt dict_en.txt需要特别说清楚:dict_*.txt是字典文件,也就是模型输出索引到中文字符的映射表。如果没有这个文件,识别出来全是乱码。很多人只下载了三个 onnx 模型就跑去加载,缺失字典文件导致输出错乱,这个不建议。
onnxruntime 的动态库(onnxruntime.dll / libonnxruntime.so)在 pip 安装后有对应的文件位置。如果你是纯 Python 调用,不需要手动指定;如果是 C++ 集成,需要把这个动态库路径加入系统的库搜索路径,然后把头文件目录也加进来。
3. 核心集成实操与参数调优
3.1 快速跑通 Python 版识别
先用一段最简单的代码验证整个链路是否通畅:
from rapidocr_onnxruntime import RapidOCR engine = RapidOCR() result, elapse = engine("test.png") print(result) print(elapse)result的结构是每个文本框的坐标、置信度、文本内容和得分。第一次初始化会比较慢,因为要加载三个模型并分配内存,后续单张识别就快很多。
如果这一步正常输出,说明你环境装对了。但如果在from rapidocr_onnxruntime import RapidOCR就报 DLL 加载失败,通常是 onnxruntime 的动态库依赖缺失,比如 VC++ 运行库没装,或者 Python 架构和包架构不一致(32 位/64 位混用)。
3.2 解析单张图片的完整输出结构
识别结果里嵌套的信息很关键。result的原始返回结构是这样的:
[[box, text, score], ...]box是四个点坐标,格式为[[x1, y1], [x2, y2], [x3, y3], [x4, y4]],表示文本框的四边形定位。text是识别出的字符串。score是置信度,范围 0~1。
实际项目中我用得最多的是box坐标:当需要把识别结果按位置排序、或者把人名和金额字段一一对应时,坐标信息比单纯文本有价值得多。比如发票识别,我按 y 坐标排序后,再按 x 坐标从左到右归组,就能准确把“商品名称”和“金额”列对齐。
3.3 参数调节:哪些值得动,哪些别乱动
RapidOCR 的初始化参数里,有几个我用下来对识别效果影响最大:
| 参数 | 默认值 | 我的建议 |
|---|---|---|
| det_use_dnn | False | 保持 False,ONNX 原生算子和 DNN 模块的速度在这里没有优势 |
| cls_use_dnn | False | 同上 |
| rec_batch_num | 6 | 调大可以提升批量识别吞吐,但要小心显存/内存占用 |
| det_limit_side_len | 736 | 对长图有帮助,但图片分辨率越大耗时越高,需要平衡 |
det_limit_side_len这个参数尤其值得注意。它控制检测阶段输入图片的边长上限。如果原图是 4000 像素宽的长截图,直接丢进去会被严重压缩,导致小字漏检。我会根据实际场景把上限抬到 960 或者 1280,但代价是 CPU 耗时显著上升。实测一张 1920x1080 的截图,736 上限时约 450ms,1280 上限时约 900ms,翻了一倍。所以如果没有小字密集场景,保持默认即可。
3.4 C++ 调用 onnxruntime 动态库的示例
如果你的主程序是 C++,需要通过 onnxruntime 的 C API 加载 RapidOcr 模型。简化后的核心步骤大概如下:
#include <onnxruntime/core/session/onnxruntime_cxx_api.h> Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "rapidocr"); Ort::SessionOptions options; options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL); options.SetIntraOpNumThreads(4); Ort::Session det_session(env, L"models/det.onnx", options);这里SetIntraOpNumThreads(4)是控制单算子内部的线程数。实测在 8 核 CPU 上,4 线程比 8 线程吞吐更高,因为线程上下文切换的开销超过了算子并行收益。另外,ORT_ENABLE_ALL开启后会做图优化,能小幅提速,但首次加载时间会变长,如果内存吃紧可以降到ORT_ENABLE_EXTENDED。
C++ 路线的启动流程比 Python 长,你需要自己写图像预处理(缩放、归一化、letterbox)、三个 Session 的数据传递、后处理解码和字典映射。我在实际项目中用 C++ 主要图一个部署干净,没有 Python 解释器依赖,但代码量会翻几倍。如果业务并发不高,推荐先用 Python 快速验证算法效果,再用 C++ 重写性能敏感部分。
4. 常见问题与排错过程全记录
4.1 chromadb backend init failed 报错的真相
有一个报错我觉得有必要单独拿出来讲,因为很多人在装 RapidOcr 时莫名其妙碰到它,其实跟 RapidOcr 本身没关系。
报错全文大概是:
chromadb backend init failed, falling back: the onnxruntime python package is not installed这个报错的逻辑是:chromadb这个向量数据库在初始化时,内部尝试导入一个叫onnxruntime的 Python 包来跑 embedding 模型。如果你的环境里已经装了 RapidOcr 使用的 onnxruntime,理论上应该能直接导入成功;但如果两个包存在版本冲突,或者系统里存在一个损坏的 onnxruntime pip 包装了一半,chromadb 就会把异常抓走,然后打出“falling back”的提示。
排查思路:
- 先看
pip show onnxruntime,确认版本是否正常。 - 单独执行
python -c "import onnxruntime; print(onnxruntime.__version__)",如果这一步也报错,说明 onnxruntime 本体就没装好。 - 如果这个 import 正常,那就检查 chromadb 版本的兼容性,尝试升级或降级
chromadb。
我当时是在一个同时跑 RAG 服务的机器上部署,机器里既有 chromadb 又有 RapidOcr,Python 环境比较杂。后来检查发现是onnxruntime的安装位置不对——另一个虚拟环境里的包串到当前环境导致符号冲突。我的处理方式是:用虚拟环境隔离两个服务,互不干扰。如果你也需要在同一环境里同时用 chromadb 和 RapidOcr,建议锁定 onnxruntime==1.15.1 并升级 chromadb 到最新版,我实测这样能消除报错。
4.2 onnxruntime 加载失败(错误码 5060)的处理
onnxruntime 5060几乎每个深入用过的人都会撞到一次。错误码本质上是 Windows 下LoadLibrary失败,常见原因是 DLL 依赖缺失,尤其是 msvcp140.dll、vcruntime140.dll、vcomp140.dll 这些 VC++ 运行库。
解决方案并不复杂:
- 去微软官网安装最新的 VC++ Redistributable(x64 版本)。
- 确认有且只有一个 onnxruntime.dll 在搜索路径中,不要同时混装 CPU 版和 GPU 版。
- 如果是 Python 环境,直接卸载重装:
pip uninstall onnxruntime -y pip install onnxruntime==1.15.1如果这些都不行,用 Dependency Walker 或 PE-bear 打开 onnxruntime.dll 查看具体缺失的依赖项。我遇到过最隐蔽的情况是:系统里有旧版openmp.dll,和 onnxruntime 自带的开源 OpenMP 实现冲突,导致启动即崩。解决方法是把 onnxruntime 安装目录下的libomp.dll复制到 exe 同目录,强制优先加载它。
4.3 有时能识别有时完全空白,怎么定位
这种情况我在跑一批倾斜字体的截图时经常遇到。如果结果为空,第一步不是调参,而是先检查图片的预处理结果:
img = cv2.imread("test.png") cv2.imwrite("debug.png", img)导入失败或者通道顺序不对都会让检测器直接失明。RapidOcr 内部用的是 BGR 还是 RGB 有一定讲究,我的经验是先用 OpenCV 读图然后走默认流程最稳。如果图片本身没问题,再检查det_limit_side_len,比如很宽的横幅文字会被压到无法检测。
还有一种常见问题:图片背景复杂、文字与背景对比度低。RapidOcr 的检测模型对低对比度场景会漏检,这时候最简单的做法是先用 OpenCV 做一次对比度增强或灰度化,比调任何参数都有效。
4.4 内存占用持续增长的问题
我长期跑服务时发现内存会缓慢上涨,初步怀疑是图片预处理时 OpenCV 的矩阵没有释放。RapidOcr 内部每次推理都会创建新的 OrtValue,正常情况下 Python 的引用计数会自动回收缓存,但如果你用executor或线程池反复调用同一引擎,一定要确认有没有保存推理结果的引用。
如果发现问题不好定位,直接用下面这行代码加上强制回收:
import gc result, elapse = engine("test.png") gc.collect()当然这只是治标。真正的治本方案是:避免在循环内重复创建RapidOCR实例,整个进程只初始化一次,复用同一个引擎。我实测复用实例比反复创建新实例的内存占用低大约 40%,这点在长驻服务里体现得很明显。
5. 实际场景适配与性能观察
5.1 身份证和票据类高分辨率图的适配
票据图片通常分辨率高、字段密集,而且有多行、有表格。我在处理 300dpi 扫描件时,先做一次等比缩放,把长边控制在 2000 像素左右,然后交给 RapidOcr。太高的分辨率不会提升识别率,反而让检测模型把无关边缘噪声也框进来,产生大量低置信度结果。
对于字段对齐,我的后处理方案是:
results.sort(key=lambda item: (item[0][0][1], item[0][0][0]))也就是先按 y 坐标排序,再按 x 坐标排。这一步对表格类的字段提取非常关键,因为 OCR 模型的输出顺序并不保证跟视觉阅读顺序一致。
5.2 CPU 推理性能实测数据
我拿一台普通办公机(Intel i5-10400,16GB 内存)跑了一批 1280x960 的截图,统计结果如下:
| 操作 | 平均耗时 |
|---|---|
| 文本检测 | 220ms |
| 方向分类 | 30ms |
| 文本识别 | 150ms |
| 整体单张 | 400ms 左右 |
这个速度虽然是“毫秒级”,但和 GPU 动辄几十毫秒的体验还是差很多。如果只是做定时批处理,完全够用;如果是线上实时 OCR,建议考虑 GPU 版 onnxruntime 或者提升硬件规格。
5.3 批量识别的并发与线程配置
如果一次要处理几百张图片,不要简单地开线程池到处调用同一个 RapidOCR 实例。onnxruntime 的 Session 本身是线程安全的,但 CPU 资源有限,多线程并发反而会因为资源竞争导致单张耗时暴涨。
我的做法是:先量好机器的核心数,然后按照线程数 = 核心数 / 2的规则开进程池。每个进程里独立创建 RapidOCR 实例,再切分图片列表分发给各进程。实测 8 核机器上开 4 个进程,总体吞吐是单进程的 2.8 倍,效果明显。继续往上加进程收益变小,因为内存带宽成了瓶颈。
6. 长期运行后的体会
调完这套离线识别之后,我最大的感受是“可控性”带来的安心感。云端 OCR 接口虽然快,但要么有 QPS 限制,要么对图片大小有要求,批量任务一旦跑起来就像在悬崖边走钢丝。RapidOcr 加 onnxruntime 的组合虽然需要自己处理一些细节,但一切都在掌控内,模型想换就换,线程数想调就调。
有几点再啰嗦一下:
- 模型文件一旦下载就不要再从临时目录读取,放到固定路径,方便后续版本升级时直接覆盖。
- 如果识别质量严重下滑,优先检查 dict 文件和模型是否来自同一个版本,混用不同版本容易出诡异问题。
- 不要盲目追新版本,稳定性远比“看起来更强”重要。1.15.1 这个版本我在生产环境跑了快一年,没有任何问题。
后续如果你想继续深入,可以把这个引擎封装成 HTTP 服务,或者用 pybind11 包一层给 C# 调用。方向很多,但底座已经稳了,后面的事情都是水到渠成。
本文还有配套的精品资源,点击获取