PaddleOCR-v3模型ONNXRuntime部署实战:C++/Python跨平台推理优化
简介:ONNX(Open Neural Network Exchange)作为一种开放的模型格式,实现了不同深度学习框架间模型的互操作性。其核心原理在于定义了一套通用的计算图表示标准,使得训练好的模型可以脱离原生框架,在统一的运行时环境中进行高效推理。这一特性为模型部署带来了巨大的技术价值,尤其在资源受限的边缘计算和需要高性能C++集成的生产环境中,能够显著降低依赖复杂性并提升执行效率。ONNXRuntime作为微软推出的高性能推理引擎,对ONNX模型提供了优异的支持,并具备多语言API和跨平台能力,成为工业级部署的理想选择。在实际应用场景中,例如光学字符识别(OCR),将业界知名的PaddleOCR-v3模型转换为ONNX格式,再利用ONNXRuntime进行部署,可以兼顾识别精度与推理速度,有效解决Python方案在边缘设备或C++主程序中集成困难的问题。本文即围绕这一实践,详细阐述了从模型导出、环境配置到核心代码实现与性能优化的完整流程。
1. 项目背景与核心价值
最近在做一个需要离线OCR识别的项目,要求是部署在边缘设备上,对速度和稳定性都有比较高的要求。网上找了一圈,PaddleOCR的识别精度和速度口碑一直不错,但官方提供的Python推理方案在资源受限的环境下,尤其是需要多线程并发或者集成到C++主程序里时,就显得有点力不从心了。直接调用Paddle Inference的C++ API虽然可行,但依赖库的编译和部署又是一大堆麻烦事,而且模型转换、前后处理都得自己从头搞,非常折腾。
这时候,ONNXRuntime就进入了我的视野。它是一个高性能的推理引擎,对ONNX格式的模型支持得非常好,而且提供了C++、Python、C#、Java等多种语言的API,部署起来非常灵活。最关键的是,它的C++接口非常干净,依赖少,性能高,特别适合集成到产品里。所以,我的目标就很明确了:把训练好的PaddleOCR-v3模型转换成ONNX格式,然后用ONNXRuntime的C++和Python接口分别进行部署,实现一套代码、两种语言、随处可跑的效果。
这个“ONNXRuntime部署PaddleOCR-v3”的项目包,就是我这个折腾过程的完整总结。里面不仅包含了转换好的ONNX模型文件,还有写好的C++和Python推理源码,以及详细的部署说明。无论你是想快速在Python环境里验证效果,还是需要把OCR能力无缝嵌入到现有的C++桌面应用或服务器后端,这个项目都能给你提供一个扎实的起点,省去你从零开始摸索模型转换、前后处理对齐、性能优化这些坑的时间。
2. 环境准备与工具链搭建
工欲善其事,必先利其器。在开始代码之前,我们需要把整个工具链搭建好。这个过程虽然有点繁琐,但每一步都关系到后面能否顺利跑通。
2.1 Python环境与PaddleOCR模型导出
首先,我们需要一个Python环境来运行PaddlePaddle,以便导出原始模型。我强烈建议使用Anaconda或者Miniconda来管理环境,这样可以避免包冲突。
# 创建一个新的conda环境,Python版本建议3.8或3.9,兼容性最好 conda create -n paddle_onnx python=3.9 conda activate paddle_onnx # 安装PaddlePaddle GPU版本(如果你有NVIDIA显卡且配置了CUDA) # 请根据你的CUDA版本去Paddle官网选择对应的安装命令,例如CUDA 11.2: python -m pip install paddlepaddle-gpu==2.5.2.post112 -f https://www.paddlepaddle.org.cn/whl/linux/mkl/avx/stable.html # 或者安装CPU版本 python -m pip install paddlepaddle==2.5.2 -i https://mirror.baidu.com/pypi/simple # 安装PaddleOCR和paddle2onnx工具 pip install paddleocr==2.7.3 pip install paddle2onnx==1.0.8 pip install onnxruntime==1.16.3 # 这是Python版的ONNXRuntime安装完成后,我们可以先验证一下PaddleOCR是否能正常工作,并用它提供的预训练模型进行导出。PaddleOCR-v3是一个统称,它通常包含三个子模型:文本检测(Detection)、方向分类(Classification)和文本识别(Recognition)。我们需要分别导出它们。
这里有个关键点:PaddleOCR的推理过程是串联的,先检测出文本框,再对每个框判断方向(是否需要旋转),最后进行识别。在部署时,我们可以选择部署这三个独立的模型,然后在代码里组织调用逻辑;也可以尝试将它们组合成一个图。为了灵活性和可调试性,我选择分别导出三个独立的ONNX模型。
假设我们已经下载好了PaddleOCR的预训练模型文件(.pdmodel和.pdiparams),或者使用PaddleOCR代码中自动下载的模型。模型通常保存在~/.paddleocr/whl/目录下。导出脚本大致如下:
import paddle2onnx from paddle2onnx.command import program2onnx # 以文本检测模型(ch_PP-OCRv3_det)为例 # 你需要指定模型的模型文件、参数文件路径,以及输入输出的名称和形状 det_model_dir = “/path/to/ch_PP-OCRv3_det_infer” # 使用paddle2onnx命令行工具转换 # 在终端执行: # paddle2onnx --model_dir {det_model_dir} --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file det_model.onnx --opset_version 12 --enable_onnx_checker True对于识别和分类模型,操作类似。opset_version我选择了12,这是一个在兼容性和性能上比较平衡的版本。导出的ONNX模型文件(det_model.onnx,cls_model.onnx,rec_model.onnx)就是后续推理的核心。
2.2 C++开发环境配置(Visual Studio / VSCode)
对于C++部署,我们需要准备ONNXRuntime的C++开发库。这里以Windows平台配合Visual Studio 2019/2022为例,Linux下的CMake流程也类似。
下载ONNXRuntime:去ONNXRuntime的GitHub Release页面,下载对应你平台的预编译包。对于Windows,我们通常下载
onnxruntime-win-x64-1.16.3.zip(CPU版本)或带gpu标签的版本。解压到一个合适的目录,例如D:\Libs\onnxruntime。配置Visual Studio项目:
- 创建一个新的C++控制台项目。
- 右键项目 -> 属性。
- C/C++ -> 常规 -> 附加包含目录:添加ONNXRuntime解压目录下的
include文件夹路径,如D:\Libs\onnxruntime\include。 - 链接器 -> 常规 -> 附加库目录:添加ONNXRuntime的
lib文件夹路径,如D:\Libs\onnxruntime\lib。 - 链接器 -> 输入 -> 附加依赖项:添加
onnxruntime.lib。 - 调试环境:为了运行时能找到DLL,你需要将ONNXRuntime的
bin目录(包含onnxruntime.dll)添加到系统的PATH环境变量,或者更简单的方法是在Visual Studio的项目属性中,“调试 -> 环境”里添加一行,例如PATH=D:\Libs\onnxruntime\bin;%PATH%。
如果你更喜欢使用VSCode配合CMake,那么你需要编写一个CMakeLists.txt文件来定位ONNXRuntime的库。网络上有很多关于“vscode 配置c++”和“vscode配置c/c++环境”的教程,核心就是配置好c_cpp_properties.json,tasks.json,launch.json这三个文件,并在CMake中通过find_package或直接指定路径来链接ONNXRuntime。
注意:ONNXRuntime有多个构建版本,如
onnxruntime(默认)、onnxruntime_providers_cuda(GPU支持)等。如果你需要GPU推理,务必下载并链接对应的版本,并在代码中指定CUDA执行提供器。否则,默认使用CPU执行提供器。
2.3 Python推理环境
Python环境就简单多了,我们在第一步已经安装了onnxruntime包。如果你想用GPU加速,需要安装onnxruntime-gpu。注意:onnxruntime和onnxruntime-gpu不能同时安装,且onnxruntime-gpu的版本必须与你系统的CUDA版本严格匹配。
# 卸载CPU版本,安装对应CUDA 11.8的GPU版本 pip uninstall onnxruntime pip install onnxruntime-gpu==1.16.3安装后,在Python代码中通过onnxruntime.InferenceSession加载模型时,可以通过providers参数指定使用‘CUDAExecutionProvider’来启用GPU。
3. 模型推理核心流程与代码实现
有了模型和环境,接下来就是最核心的部分:编写推理代码。无论是C++还是Python,其核心逻辑都是一致的:加载模型 -> 预处理输入数据 -> 运行会话(Session) -> 后处理输出结果。PaddleOCR的三个模型需要按顺序调用。
3.1 图像预处理:与PaddleOCR对齐的关键
这是最容易出错的一步。PaddleOCR在训练和原始推理时,对输入图像有一系列特定的预处理操作,包括归一化(Normalize)、通道顺序转换(BGR->RGB)、尺寸缩放(Resize)等。如果我们直接用原始图片喂给ONNX模型,结果肯定不对。我们必须完全复现PaddleOCR推理脚本中的预处理逻辑。
以检测模型为例,PaddleOCR的输入通常是一个经过Normalize(减均值、除方差)、ToCHW(HWC转CHW)和Scale(可能缩放)处理的float32数组,形状为[1, 3, H, W],其中H和W是固定的,或者是动态的。
我们需要仔细查看PaddleOCR源码中(通常是tools/infer/utility.py和ppocr/data/imaug/operators.py)关于DetResizeForTest,NormalizeImage,ToCHWImage等操作的具体参数。例如,均值可能是[123.675, 116.28, 103.53],方差是[58.395, 57.12, 57.375]。
在我的项目代码中,我分别用C++(使用OpenCV)和Python(使用NumPy和cv2)实现了完全一致的预处理函数。例如,C++的预处理片段:
cv::Mat preprocess_det(const cv::Mat& src_img, int target_height, int target_width) { cv::Mat img_resized; cv::resize(src_img, img_resized, cv::Size(target_width, target_height)); // 转换颜色空间 BGR -> RGB cv::cvtColor(img_resized, img_resized, cv::COLOR_BGR2RGB); // 转换为float并归一化 img_resized.convertTo(img_resized, CV_32FC3); // 手动进行减均值除方差 std::vector<float> mean = {123.675f, 116.28f, 103.53f}; std::vector<float> std = {58.395f, 57.12f, 57.375f}; // ... 对每个通道每个像素进行计算 ... // 转换为 CHW 格式: [H, W, C] -> [C, H, W] std::vector<cv::Mat> split_channels; cv::split(img_resized, split_channels); // 将三个通道的数据合并到一个一维vector中,准备输入到模型 std::vector<float> input_tensor_values; for (int c = 0; c < 3; ++c) { input_tensor_values.insert(input_tensor_values.end(), (float*)split_channels[c].data, (float*)split_channels[c].data + target_height * target_width); } return input_tensor_values; // 实际上返回的是vector<float> }3.2 C++接口封装与推理
在C++中,我们使用ONNXRuntime的C++ API。主要步骤是创建Ort::Env(环境)、Ort::Session(会话),然后准备输入输出。
#include <onnxruntime_cxx_api.h> class PaddleOCROnnx { public: PaddleOCROnnx(const std::string& det_model_path, const std::string& rec_model_path, const std::string& cls_model_path = “”) { // 1. 初始化环境 env_ = Ort::Env(ORT_LOGGING_LEVEL_WARNING, “PaddleOCR”); Ort::SessionOptions session_options; session_options.SetIntraOpNumThreads(1); // 设置线程数 // 如果需要GPU,在这里添加CUDA提供器 // Ort::ThrowOnError(OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0)); // 2. 创建会话 det_session_ = Ort::Session(env_, det_model_path.c_str(), session_options); rec_session_ = Ort::Session(env_, rec_model_path.c_str(), session_options); if (!cls_model_path.empty()) { cls_session_ = Ort::Session(env_, cls_model_path.c_str(), session_options); } // 3. 获取输入输出信息(名称、形状等),这部分代码略长,主要是调用session_.GetInputName/GetOutputName等 } std::vector<OCRRect> detect(const cv::Mat& image) { // 预处理图像,得到符合检测模型输入的float数组 auto input_tensor_values = preprocess_det(image); // 创建Ort::Value输入张量 std::vector<int64_t> input_shape = {1, 3, det_input_height_, det_input_width_}; auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault); Ort::Value input_tensor = Ort::Value::CreateTensor<float>( memory_info, input_tensor_values.data(), input_tensor_values.size(), input_shape.data(), input_shape.size()); // 运行推理 auto output_tensors = det_session_.Run( Ort::RunOptions{nullptr}, det_input_names_.data(), // 输入节点名数组 &input_tensor, // 输入张量指针数组 1, det_output_names_.data(), // 输出节点名数组 det_output_names_.size()); // 后处理:从output_tensors[0](可能是score map)和output_tensors[1](可能是geometry)中解析出文本框坐标 // 这里涉及到OpenCV的findContours、阈值化、多边形逼近等操作,是检测模型后处理的核心,代码较长。 return postprocess_det(output_tensors, image.size()); } // 识别和分类函数类似... private: Ort::Env env_; Ort::Session det_session_; Ort::Session rec_session_; Ort::Session cls_session_; // ... 其他成员变量,如输入输出名称、尺寸等 };C++代码的关键在于内存管理和数据转换。ONNXRuntime的Ort::Value封装了张量数据,我们需要确保输入数据的形状、类型和内存布局完全正确。后处理部分,特别是检测模型的后处理(从热力图和几何图中提取文本框),是算法核心,也是性能瓶颈之一,需要仔细优化。
3.3 Python接口封装与推理
Python接口就友好得多,代码也更简洁。我们可以利用onnxruntime和numpy的便利性。
import onnxruntime as ort import cv2 import numpy as np class PaddleOCROnnxPy: def __init__(self, det_model_path, rec_model_path, cls_model_path=None): # 创建会话,可以指定使用CPU或GPU providers = [‘CPUExecutionProvider’] # 如果想用GPU,改为: # providers = [‘CUDAExecutionProvider’, ‘CPUExecutionProvider’] sess_options = ort.SessionOptions() sess_options.intra_op_num_threads = 4 # 设置线程数 self.det_session = ort.InferenceSession(det_model_path, sess_options=sess_options, providers=providers) self.rec_session = ort.InferenceSession(rec_model_path, sess_options=sess_options, providers=providers) if cls_model_path: self.cls_session = ort.InferenceSession(cls_model_path, sess_options=sess_options, providers=providers) # 获取输入输出名 self.det_input_name = self.det_session.get_inputs()[0].name # ... def detect(self, image): # 预处理 img_preprocessed, (ratio_h, ratio_w) = self.preprocess_det(image) # 将numpy数组转换为模型输入需要的格式 det_inputs = {self.det_input_name: img_preprocessed} # 运行推理 det_outputs = self.det_session.run(None, det_inputs) # 后处理 boxes = self.postprocess_det(det_outputs, (ratio_h, ratio_w), image.shape) return boxes def recognize(self, image_crop_list): """对裁剪出的每个文本框图像进行识别""" rec_results = [] for img_crop in image_crop_list: # 对每个小图进行预处理(归一化、resize到rec模型输入尺寸等) rec_input = self.preprocess_rec(img_crop) rec_outputs = self.rec_session.run(None, {self.rec_input_name: rec_input}) # 后处理:将输出向量解码为文本字符串,这里通常需要一个CTC解码器和一个字典文件 text = self.decode_rec_output(rec_outputs[0]) rec_results.append(text) return rec_resultsPython版本的后处理逻辑与C++一致,但实现起来更快捷。识别模型的后处理通常需要一个CTC解码算法(如贪婪解码或束搜索)和一个包含所有字符的字典文件(ppocr_keys_v1.txt),将模型输出的概率序列转换成最终的文本字符串。
3.4 串联调用与结果整合
最后,我们需要将三个模型串联起来,形成一个完整的OCR流水线。
// C++ 示例 std::vector<OCRRect> run_ocr(const cv::Mat& img) { // 1. 文本检测 auto text_boxes = ocr_engine.detect(img); std::vector<OCRRect> results; for (const auto& box : text_boxes) { // 2. 裁剪文本框图像 cv::Mat crop = crop_rotate_image(img, box); // 3. 方向分类(可选) float cls_score; bool need_rotate = false; if (use_cls) { need_rotate = ocr_engine.classify(crop, cls_score); if (need_rotate) { cv::rotate(crop, crop, cv::ROTATE_180); } } // 4. 文本识别 std::string text = ocr_engine.recognize(crop); // 5. 保存结果 results.push_back({box, text, cls_score}); } // 6. 按位置排序(可选,例如从上到下,从左到右) sort_text_boxes(results); return results; }Python版本的流程完全一样。这里的一个优化点是,识别模型通常一次只能处理一张小图。为了提高吞吐量,可以对多个裁剪出的文本框进行批处理(Batch Inference)。这需要我们将多个小图预处理后,在批次维度(batch dimension)上堆叠成一个张量,然后一次性输入给识别模型。这要求识别模型的ONNX导出时支持动态的批次维度(-1),并且在代码中做好批次的组装和结果的拆分。
4. 性能优化与实战踩坑记录
把流程跑通只是第一步,要让它在实际项目中可用,性能优化和避坑是必不可少的环节。下面分享几个我在实战中遇到的关键问题和解决方案。
4.1 模型优化与动态输入
问题:最初导出的检测模型输入尺寸是固定的(例如[1, 3, 960, 960]),这意味着无论原图多大,都会被强制缩放到这个尺寸。对于长宽比悬殊的图片,缩放会导致变形,影响检测精度。
解决方案:使用Paddle2ONNX导出时,可以指定输入为动态形状。例如,将高度和宽度维度设置为-1。
paddle2onnx ... --input_shape_dict="{‘x’:[-1,3,-1,-1]}"这样导出的ONNX模型输入形状为[batch_size, 3, height, width]。在推理时,我们可以根据原图尺寸动态计算预处理后的尺寸(保持原比例缩放,短边缩放到某个值,长边按比例缩放,并填充到32的倍数)。这要求我们的预处理和后处理代码能适应可变的输入尺寸,计算复杂度会稍微增加,但精度提升是值得的。
另一个优化点:ONNXRuntime支持模型优化。我们可以使用onnxruntime_tools中的optimizer或者onnxruntime自带的GraphOptimizationLevel进行图优化。在创建Session时设置:
// C++ session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);# Python sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_EXTENDED这可以融合一些操作符,提高推理速度。
4.2 内存管理与多线程安全
问题:在C++服务端部署时,需要处理高并发请求。如果每个请求都创建和销毁ONNXRuntime Session,开销巨大。同时,多个线程同时调用同一个Session的Run方法是否安全?
解决方案:
- Session复用:在程序初始化时创建好OCR引擎(包含所有Session),并将其作为全局或单例对象。所有推理请求共享这个引擎。
- 多线程安全:根据ONNXRuntime官方文档,一个
Ort::Session对象的Run方法不是线程安全的。如果多个线程同时调用同一个Session的Run,会导致未定义行为。- 方案A(线程独占):为每个处理线程创建独立的Session实例。这适用于线程数量固定且不多的场景(如线程池)。内存消耗会随线程数增加。
- 方案B(Session池):维护一个Session对象池。当线程需要推理时,从池中借用一个Session,用完后归还。这需要自己实现池化和锁机制。
- 方案C(请求队列):采用生产者-消费者模式。所有OCR请求放入一个队列,由单个或少数几个专用的“推理线程”从队列中取出任务并执行,然后将结果返回。这是最稳妥的方案,能保证最高的吞吐量和稳定性,我在生产环境中采用了这种模式。
4.3 前后处理性能瓶颈
问题: profiling(性能分析)后发现,整个OCR流程中,模型推理时间只占一小部分,大量的时间花在了图像的预处理(resize, normalize, color convert)和后处理(检测框的解析、NMS、多边形点排序、图像裁剪、识别结果解码)上。
解决方案:
- 使用高性能库:确保使用OpenCV的优化版本(如带IPP、OpenCL支持的)。在C++中,避免在循环中频繁创建临时
cv::Mat,尽量复用内存。 - 向量化操作:将归一化等操作从逐像素循环改为对整个
cv::Mat进行矩阵运算,或者使用cv::convertTo配合cv::Scalar进行减均值除方差。 - 优化后处理算法:
- 检测后处理中的
findContours和approxPolyDP是瓶颈。可以尝试调整轮廓发现的模式(如RETR_LIST)和方法(如CHAIN_APPROX_SIMPLE)。 - 非极大值抑制(NMS)可以尝试使用更快的实现,或者调整阈值,在精度和速度间权衡。
- 识别解码部分,如果使用束搜索(Beam Search),束宽(beam width)是影响速度的关键参数,在保证准确率的前提下尽量调小。
- 检测后处理中的
- 批处理:如前所述,对多个文本框的识别进行批处理,能极大减少模型启动开销和GPU利用率低的问题。
4.4 版本兼容性与依赖地狱
问题:项目在Windows上编译运行良好,移植到Linux(如CentOS 7)或嵌入式设备(如Jetson Nano)上,出现各种链接错误或运行时崩溃。
解决方案:
- 静态链接:对于C++部署,可以考虑将ONNXRuntime库静态链接到你的程序中。ONNXRuntime提供了静态库的构建方式。这样可以避免目标机器上缺少特定版本的动态库(如
libonnxruntime.so)的问题。缺点是最终可执行文件体积会变大。 - 交叉编译与依赖打包:对于嵌入式设备,最好在设备本身或相同架构的虚拟机上编译。将所有依赖的共享库(如OpenCV, ONNXRuntime, protobuf等)打包,并通过
LD_LIBRARY_PATH指定路径。 - Docker容器化:这是解决环境问题最彻底的方法。创建一个包含所有依赖(指定版本的系统库、OpenCV、ONNXRuntime)的Docker镜像。无论在什么宿主机上,只要运行这个容器,环境就是一致的。这对于服务端部署尤其友好。
- 谨慎选择第三方库版本:OpenCV、Protobuf等库的版本与ONNXRuntime可能有兼容性要求。最好参考ONNXRuntime官方构建文档中推荐的依赖版本。例如,某个版本的ONNXRuntime可能要求Protobuf的版本不低于3.xx。
4.5 精度对齐与调试技巧
问题:用ONNXRuntime推理的结果,与直接使用PaddlePaddle原版推理的结果有细微差异,导致最终识别文本不同。
解决方案:
- 确保预处理一致:这是99%的问题来源。务必使用相同的归一化均值、方差,相同的插值算法(如
cv::INTER_LINEAR),相同的填充(padding)策略。将预处理后的第一个像素值打印出来,和Paddle版本对比。 - 检查模型导出:使用
netron工具打开导出的ONNX模型,检查输入输出节点名称、数据类型(float32)、形状是否正确。确保导出时没有启用某些可能改变计算图的优化选项。 - 中间结果对比:分别用Paddle和ONNXRuntime运行同一张图片,不仅对比最终文本,更要比对三个模型各自的原始输出张量(det的score map, rec的output)。如果原始输出就不同,那问题出在模型转换或推理引擎上。如果原始输出相同但后处理结果不同,问题出在后处理代码。
- 使用ONNXRuntime的Python API进行桥接调试:先用Python版的ONNXRuntime跑通整个流程,并和Paddle原版结果对齐。因为Python环境调试更方便。对齐后,再将完全相同的预处理、后处理逻辑移植到C++版本。这样可以隔离语言环境带来的问题。
5. 项目源码结构与应用示例
我提供的项目包ONNXRuntime部署PaddleOCR-v3包含C++和Python源码+模型+说明.zip,就是按照上述思路组织的。解压后,你可能会看到类似如下的结构:
PaddleOCR-ONNX-Deploy/ ├── models/ # 存放ONNX模型文件 │ ├── ch_PP-OCRv3_det_infer.onnx │ ├── ch_PP-OCRv3_rec_infer.onnx │ └── ch_PP-OCRv3_cls_infer.onnx ├── cpp/ # C++部署源码 │ ├── CMakeLists.txt │ ├── src/ │ │ ├── main.cpp # 示例主程序 │ │ ├── ocr_engine.cpp/.h # OCR引擎核心封装类 │ │ ├── preprocess.cpp/.h # 预处理函数 │ │ ├── postprocess.cpp/.h # 后处理函数 │ │ └── utils.cpp/.h │ └── third_party/ # 可能包含必要的第三方头文件 ├── python/ # Python部署源码 │ ├── paddleocr_onnx.py # 封装的OCR类 │ ├── demo.py # 使用示例 │ ├── tools/ │ │ └── model_export.py # 模型导出脚本 │ └── requirements.txt ├── docs/ # 详细说明文档 │ ├── build_windows.md │ ├── build_linux.md │ └── faq.md └── test_images/ # 测试图片C++示例:编译后,运行程序,指定图片路径即可。
./ocr_system_demo --image ./test.jpg --det ./models/det.onnx --rec ./models/rec.onnx --cls ./models/cls.onnxPython示例:更加简单直接。
from paddleocr_onnx import PaddleOCROnnxPy ocr = PaddleOCROnnxPy(det_model_path=‘./models/det.onnx’, rec_model_path=‘./models/rec.onnx’, cls_model_path=‘./models/cls.onnx’) image = cv2.imread(‘test.jpg’) results = ocr.ocr(image) for box, text, confidence in results: print(f“Text: {text}, Box: {box}”) # 也可以在图片上画框 cv2.polylines(image, [box.astype(np.int32)], True, (0, 255, 0), 2)这个项目包的价值在于,它提供了一个端到端、可编译、可运行的参考实现。你不需要再从零开始研究PaddleOCR的模型结构、前后处理细节,也不需要手动去摸索ONNXRuntime的C++ API调用。你可以直接基于我的代码进行修改,适配你的业务逻辑,比如修改后处理的阈值、调整识别字典、增加批量处理逻辑,或者将其集成到你的Qt、MFC桌面应用,或者HTTP服务后端中。
部署这条路,坑多且杂,但一旦走通,收益也是巨大的。它意味着你的应用摆脱了对庞大Python环境和PaddlePaddle框架的依赖,获得了更快的启动速度、更低的内存占用,以及更灵活的集成能力。希望这个项目和你正在读的这篇总结,能帮你少走些弯路。
本文还有配套的精品资源,点击获取
