简介:本资源为基于RapidOcr与Onnxruntime的离线文字识别依赖库,面向需要在本地完成OCR处理的开发者与算法学习者,尤其适合文档处理、车牌识别、屏幕截图转文本等对隐私与实时性有要求的场景。压缩包共991个文件,约192.61MB,以hpp、json、txt、o、cmake、so、bin、onnx等为主,涵盖C++头文件、模型配置、编译中间产物、动态库与ONNX模型文件,并包含Android端jar、aar、dex、kt等构件,便于跨平台集成。已有1493人学习下载。资源提供完整的依赖库与模型文件,读者可据此在本地加载RapidOcr模型,完成图像预处理、推理执行与结果后处理,理解CRNN或Transformer类OCR模型的运行机制,并掌握Onnxruntime的Session加载、硬件加速与资源释放等关键环节,为离线文字识别项目提供可直接复用的工程基础。
1. 离线 OCR 的最后一公里:为什么我最终选了 RapidOcr-Onnxruntime
上个月帮一个做档案数字化的朋友处理一批扫描件,对方明确要求:不能联网、不能调云 API、识别结果要能直接进他们的内部系统。我第一反应是 PaddleOCR,但部署环境是台内网 Windows 机器,装 Paddle 那套依赖折腾了一下午,各种 DLL 冲突。后来换到 RapidOcr-Onnxruntime,解压即用,十分钟跑通。这就是我想聊这份依赖库的原因——它把文字识别里最烦人的模型加载、推理调度、前后处理全打包好了,你只需要调一个接口。
RapidOcr 本身是个轻量级 OCR 项目,底层用 ONNX Runtime 做推理引擎,模型是 PP-OCR 系列转过来的 ONNX 格式。相比直接上 PaddleOCR,它的优势在于:依赖极简(就一个 onnxruntime 加几个 DLL),启动快,CPU 推理也能接受。适合谁?内网环境、边缘设备、桌面工具集成、对识别精度要求不是极致但要求稳定可控的场景。如果你要做的是通用文字识别,又不想被深度学习框架的依赖地狱折磨,这份依赖库值得拆开看看。
2. 拆开依赖包:模型、推理引擎与调用链
2.1 目录结构与核心文件
拿到依赖包后别急着写代码,先看清楚里面有什么。典型的 RapidOcr-Onnxruntime 依赖包结构大致如下:
RapidOcr-Onnxruntime/ ├── models/ │ ├── ch_PP-OCRv4_det_infer.onnx # 检测模型 │ ├── ch_PP-OCRv4_rec_infer.onnx # 识别模型 │ └── ch_ppocr_mobile_v2.0_cls_infer.onnx # 方向分类模型 ├── lib/ │ ├── onnxruntime.dll │ ├── opencv_world.dll │ └── RapidOcr.dll (或 .so) ├── config.yaml └── demo.exe / demo.py三个模型各司其职:检测模型负责找出文字区域(输出文本框坐标),方向分类模型判断文本是否倒置或旋转 180 度,识别模型把裁剪后的文本区域转成字符。这个三段式流程是 PP-OCR 的标准架构,RapidOcr 把它固化成了固定管线。
注意:不同版本的依赖包模型文件名可能有差异,以实际拿到的为准。如果 models 目录下只有两个模型,说明方向分类被合并或省略了。
2.2 ONNX Runtime 推理引擎的选型逻辑
为什么用 ONNX Runtime 而不是直接上 Paddle Inference?核心原因是部署便利性。ONNX Runtime 提供跨平台统一 API,Windows 上就一个 onnxruntime.dll,Linux 上装个 pip 包或放个 .so 就行。而 Paddle Inference 需要匹配特定版本的 CUDA、cuDNN、MKL,版本对不上就是一堆报错。
另一个原因是模型格式。PP-OCR 的模型可以通过 Paddle2ONNX 工具转成 ONNX 格式,转换后精度损失很小(实测在千分之一量级)。转完之后,推理就不再依赖 Paddle 框架,只依赖 ONNX Runtime。这意味着你的部署包体积能小很多,启动速度也快不少。
我一般会先确认 onnxruntime 的版本和模型 opset 是否匹配。如果加载模型时报NOT_IMPLEMENTED或Unsupported model IR version,八成是版本对不上。解决办法是用onnxruntime的 Python 包先验证模型能否加载:
import onnxruntime as ort # 指定模型路径,创建推理会话 session = ort.InferenceSession( "models/ch_PP-OCRv4_det_infer.onnx", providers=["CPUExecutionProvider"] # 内网环境用 CPU ) # 打印输入输出节点信息,确认模型结构 for inp in session.get_inputs(): print(f"输入: {inp.name}, 形状: {inp.shape}, 类型: {inp.type}") for out in session.get_outputs(): print(f"输出: {out.name}, 形状: {out.shape}")这段代码的作用是验证模型文件完整性以及 ONNX Runtime 能否正常解析。providers参数决定用 CPU 还是 GPU,内网机器一般用CPUExecutionProvider,如果有独显且装了 onnxruntime-gpu,可以换成CUDAExecutionProvider。打印出的输入形状通常是[1, 3, 640, 640]或动态尺寸,识别模型输入是[1, 3, 48, 320]这类固定高度、动态宽度的形式。
2.3 检测-分类-识别三段管线的参数含义
理解管线之后,调参才有方向。检测阶段的关键参数是二值化阈值和文本框面积阈值。阈值太低会把噪点当文字,太高会漏掉浅色文字。RapidOcr 的配置里通常有det_db_thresh和det_db_box_thresh两个参数,前者控制像素级二值化,后者控制文本框保留的最小置信度。
识别阶段的核心参数是rec_img_h和rec_img_w,即输入识别模型前把文本行 resize 到的高度和宽度。PP-OCR 默认高度 48,宽度按比例缩放但不超过 320。如果你的文字特别小或特别大,这个参数需要调整。方向分类的阈值一般是 0.9,低于这个值就不做旋转,避免误判。
# config.yaml 关键参数示例 det: det_db_thresh: 0.3 # 二值化阈值,降低可检出更多弱文字 det_db_box_thresh: 0.5 # 文本框置信度阈值 det_db_unclip_ratio: 1.6 # 文本框扩张比例,避免文字被裁切 max_candidates: 1000 # 最大候选框数量 rec: rec_img_h: 48 # 识别输入高度 rec_img_w: 320 # 识别输入最大宽度 rec_batch_num: 6 # 批处理数量,显存/内存够可加大 cls: cls_thresh: 0.9 # 方向分类置信度阈值 use_angle_cls: true # 是否启用方向分类det_db_unclip_ratio这个参数容易被忽略。检测模型输出的框往往比实际文字区域小一圈,如果不做扩张,识别时首尾字符会被切掉。1.5 到 2.0 之间是比较安全的范围。rec_batch_num影响推理速度,设太大会吃内存,CPU 推理建议 4 到 8 之间。
3. 从零跑通一次识别:代码、参数与输出解析
3.1 最小可运行示例
假设你已经拿到了依赖包,Python 环境也装好了 onnxruntime 和 opencv-python,下面这段代码可以直接跑:
import cv2 import numpy as np from rapidocr_onnxruntime import RapidOCR # 初始化引擎,指定模型目录和配置 engine = RapidOCR( det_model_path="models/ch_PP-OCRv4_det_infer.onnx", rec_model_path="models/ch_PP-OCRv4_rec_infer.onnx", cls_model_path="models/ch_ppocr_mobile_v2.0_cls_infer.onnx", use_angle_cls=True, # 启用方向分类 det_db_thresh=0.3, # 检测二值化阈值 rec_batch_num=6 # 识别批大小 ) # 读取图片 img = cv2.imread("test_scan.jpg") if img is None: raise FileNotFoundError("图片路径不对或文件损坏") # 执行识别 result, elapse = engine(img) # result 是列表,每项为 [文本框四点坐标, 识别文本, 置信度] if result: for box, text, score in result: print(f"文本: {text} 置信度: {score:.4f}") print(f"坐标: {box}") else: print("未检测到文字")这段代码的逻辑很直白:初始化引擎时把三个模型路径传进去,use_angle_cls决定是否启用方向分类。engine(img)返回两个值,第一个是识别结果列表,第二个是各阶段耗时。结果里每个元素包含文本框坐标(四个点的 x,y)、识别出的字符串、置信度分数。
3.2 参数调整与效果对比
默认参数在大多数扫描件上表现不错,但遇到特殊情况需要微调。我整理了一个参数对照表,方便快速定位:
| 参数 | 默认值 | 调大效果 | 调小效果 | 适用场景 |
|---|---|---|---|---|
| det_db_thresh | 0.3 | 减少误检,可能漏检 | 检出更多,噪点增多 | 干净文档调大,复杂背景调小 |
| det_db_box_thresh | 0.5 | 只保留高置信框 | 保留更多候选框 | 文字清晰调大,模糊调小 |
| det_db_unclip_ratio | 1.6 | 文本框更宽松 | 文本框更紧凑 | 手写体调大,印刷体可调小 |
| rec_batch_num | 6 | 推理更快,内存占用高 | 内存友好,速度慢 | 内存充足调大,嵌入式调小 |
| cls_thresh | 0.9 | 减少误旋转 | 更容易触发旋转 | 正常方向文档调大 |
实际调参时建议一次只改一个参数,用同一张测试图对比结果。我一般会准备三张图:一张标准印刷文档、一张倾斜拍摄的、一张带表格的,覆盖常见场景。
3.3 输出结果的后处理
识别出来的原始结果往往需要进一步处理才能用。比如文本框坐标是四点形式,要转成矩形框方便裁剪或标注:
def box_to_rect(box): """四点坐标转矩形 (x_min, y_min, x_max, y_max)""" xs = [p[0] for p in box] ys = [p[1] for p in box] return int(min(xs)), int(min(ys)), int(max(xs)), int(max(ys)) # 按纵向位置排序,还原阅读顺序 result_sorted = sorted(result, key=lambda x: box_to_rect(x[0])[1]) # 合并同一行的文本 lines = [] current_line = [] last_y = None for box, text, score in result_sorted: _, y_min, _, y_max = box_to_rect(box) center_y = (y_min + y_max) / 2 if last_y is None or abs(center_y - last_y) < 15: # 15 像素容差 current_line.append(text) else: lines.append("".join(current_line)) current_line = [text] last_y = center_y if current_line: lines.append("".join(current_line)) for line in lines: print(line)这段后处理的目的是把散落的识别结果按阅读顺序拼回段落。box_to_rect把四点坐标转成矩形,方便计算纵向位置。排序后按中心 y 坐标分组,容差 15 像素是个经验值,字号大的文档可以适当加大。这一步不做的话,输出就是一堆乱序的文本片段,没法直接用。
4. 避坑指南:模型加载、内存与精度问题排查
4.1 模型加载失败:版本与路径的坑
现象:初始化引擎时报Failed to load model或Invalid ONNX model。
原因:最常见的是 onnxruntime 版本和模型 opset 不匹配。比如模型是用 opset 16 导出的,但你的 onnxruntime 是 1.8 版本,只支持到 opset 13。另一个原因是模型文件下载不完整,或者路径里有中文导致读取失败。
解决:先用onnxruntime.__version__确认版本,建议 1.12 以上。然后用 2.2 节的代码单独加载模型验证。路径尽量用英文,如果必须用中文路径,在 Python 里用os.path.abspath转成绝对路径再传进去。
4.2 内存暴涨:批处理与图像尺寸的平衡
现象:处理大图或连续处理多张图时内存持续上涨,最终 OOM。
原因:rec_batch_num设太大,或者输入图像分辨率过高。检测模型会把原图 resize 到 640 或 960,但如果原图是 4000x3000,resize 后仍然很大。另外 OpenCV 的 Mat 对象如果不手动释放,在循环里会累积。
解决:把rec_batch_num降到 4 以下,检测输入尺寸限制在 960 以内。循环处理时显式调用del img并配合gc.collect()。如果还是不够,可以先把大图切分成小块再识别。
4.3 识别精度差:预处理与模型选择的取舍
现象:识别结果乱码、漏字、置信度普遍低于 0.5。
原因:可能是图像质量太差(模糊、光照不均、分辨率过低),也可能是模型选错了。PP-OCRv4 的识别模型对印刷体友好,但手写体或特殊字体效果会下降。另外如果图片是灰度图或二值图,直接送进去可能不如转成三通道效果好。
解决:先做基础预处理——转灰度、自适应二值化、去噪。如果文字太小,先放大 2 倍再识别。模型方面,可以尝试换成ch_PP-OCRv3_rec_infer对比效果,不同版本对不同场景的适配有差异。
4.4 方向分类误判:阈值与场景的匹配
现象:正常方向的文字被旋转了 180 度,识别结果完全错误。
原因:cls_thresh设得太低,方向分类模型把正常文本误判为倒置。这种情况在短文本(比如单个词或数字)上尤其常见,因为分类模型缺乏足够上下文。
解决:把cls_thresh提高到 0.95 甚至 0.99。如果文档方向统一,可以直接关掉use_angle_cls,省掉这一步推理。对于混合方向的场景,建议先做整图方向检测,再决定是否启用分类。
4.5 多线程调用崩溃:会话不是线程安全的
现象:在 Web 服务或多线程环境里调用引擎,程序随机崩溃或返回空结果。
原因:ONNX Runtime 的 InferenceSession 不是线程安全的,多个线程同时调用同一个 session 会导致内存错乱。
解决:每个线程创建独立的引擎实例,或者用锁把推理调用串行化。如果并发量不大,加个threading.Lock最简单。并发量大的话,建议用进程池而不是线程池,每个进程独立加载模型。
5. 进阶技巧:批量处理与精度验证的实操习惯
批量处理是实际项目里绕不开的需求。我一般会写一个带进度条和异常捕获的批处理脚本,核心逻辑如下:
import os import cv2 from concurrent.futures import ProcessPoolExecutor, as_completed from rapidocr_onnxruntime import RapidOCR def ocr_single(img_path): """单张图片识别,返回文件名和文本行列表""" engine = RapidOCR( det_model_path="models/ch_PP-OCRv4_det_infer.onnx", rec_model_path="models/ch_PP-OCRv4_rec_infer.onnx", use_angle_cls=True ) img = cv2.imread(img_path) if img is None: return img_path, [] result, _ = engine(img) if not result: return img_path, [] # 按 y 坐标排序后拼接 sorted_res = sorted(result, key=lambda x: min(p[1] for p in x[0])) return img_path, [text for _, text, _ in sorted_res] def batch_ocr(img_dir, max_workers=4): """批量识别目录下所有图片""" img_files = [ os.path.join(img_dir, f) for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".png", ".bmp")) ] results = {} with ProcessPoolExecutor(max_workers=max_workers) as executor: futures = {executor.submit(ocr_single, p): p for p in img_files} for future in as_completed(futures): path, lines = future.result() results[path] = lines print(f"完成: {os.path.basename(path)} 行数: {len(lines)}") return results if __name__ == "__main__": res = batch_ocr("./scans", max_workers=4) for path, lines in res.items(): print(f"\n=== {path} ===") for line in lines: print(line)这里用ProcessPoolExecutor而不是线程池,原因是每个进程独立加载 ONNX Runtime 会话,避免线程安全问题。max_workers建议设为 CPU 核心数的一半,太多反而会因为内存竞争变慢。每个子进程里重新初始化引擎是必要的,虽然有点浪费,但稳定。
精度验证方面,我习惯准备一个标注好的小测试集,至少 20 张图,覆盖不同字体、字号、背景。每次调参后跑一遍,统计字符准确率(编辑距离)和行准确率。不要只看几张图的效果就下结论,OCR 的玄学在于同一张图换个参数可能天差地别。
提示:如果项目对精度要求高,可以考虑把检测和识别模型换成更大的版本,比如
ch_PP-OCRv4_server_det和ch_PP-OCRv4_server_rec,代价是推理速度下降 2 到 3 倍。
从那以后我每次集成 OCR 依赖库,都强制走一遍「单图验证 → 批量压测 → 内存监控 → 精度抽检」的流程,不再凭感觉调参。希望帮到你。
本文还有配套的精品资源,点击获取