logo

PP-OCR模型ONNX部署:C++实现高效OCR推理

作者:php是最好的2025.09.26 19:10浏览量:1

简介:本文详细介绍如何基于PP-OCR模型,通过ONNX格式实现C++环境下的OCR文字识别推理部署。涵盖模型转换、环境配置、代码实现及性能优化,为开发者提供完整的技术指南。

OCR文字识别—基于PP-OCR模型实现ONNX C++推理部署

一、技术背景与选型分析

OCR(光学字符识别)技术是计算机视觉领域的重要分支,广泛应用于文档数字化、票据处理、智能办公等场景。传统OCR方案存在模型体积大、推理速度慢、跨平台兼容性差等问题。PP-OCR(PaddlePaddle OCR)作为百度开源的高精度轻量化OCR工具库,通过CRNN+CTC架构实现端到端识别,在精度与速度间取得良好平衡。

选择ONNX(Open Neural Network Exchange)作为模型部署格式具有显著优势:

  1. 跨框架兼容性:支持PyTorchTensorFlow、PaddlePaddle等主流框架模型转换
  2. 硬件加速支持:可通过ONNX Runtime在CPU/GPU/NPU等多平台实现优化推理
  3. 部署灵活性:避免深度依赖特定深度学习框架,降低技术栈耦合度

C++作为系统级编程语言,在嵌入式设备、高性能服务器等场景具有不可替代性。通过ONNX Runtime的C++ API实现推理,可兼顾开发效率与运行性能。

二、PP-OCR模型准备与ONNX转换

2.1 模型获取与版本选择

推荐使用PP-OCRv3版本,该版本在中文识别场景下具有更高精度。可通过PaddleOCR官方仓库获取预训练模型:

  1. git clone https://github.com/PaddlePaddle/PaddleOCR.git
  2. cd PaddleOCR/ppocr/utils
  3. python export_model.py -c configs/rec/rec_r50_vd_none_bilstm_ctc.yml \
  4. -o Global.pretrained_model=./ch_PP-OCRv3_rec_train/best_accuracy \
  5. Global.save_inference_dir=./inference

2.2 ONNX模型转换

使用Paddle2ONNX工具完成模型格式转换:

  1. import paddle2onnx
  2. model_dir = "./inference/ch_PP-OCRv3_rec_train"
  3. onnx_model = paddle2onnx.convert(
  4. model_dir + "/inference.pdmodel",
  5. model_dir + "/inference.onnx",
  6. opset_version=11, # 推荐使用opset 11+
  7. enable_onnx_checker=True
  8. )

关键转换参数说明:

  • opset_version:建议选择11或更高版本以支持完整算子
  • input_shape_dict:需明确指定输入尺寸,如{'x': [1, 3, 32, 320]}
  • enable_onnx_checker:启用模型结构验证

转换完成后使用Netron工具可视化模型结构,检查是否存在不支持的算子。常见问题处理:

  • 不支持算子:升级ONNX Runtime版本或手动替换算子
  • 维度不匹配:在转换时通过input_shape_dict参数固定输入尺寸
  • 动态轴处理:对于可变长度输入,需在推理代码中动态构建输入张量

三、C++开发环境配置

3.1 依赖库安装

推荐使用vcpkg管理第三方依赖:

  1. vcpkg install onnxruntime:x64-windows # Windows环境
  2. vcpkg install onnxruntime:x64-linux # Linux环境

核心依赖项:

  • ONNX Runtime(1.15+版本推荐)
  • OpenCV(用于图像预处理)
  • CMake(构建系统)

3.2 项目结构规划

  1. project/
  2. ├── cmake/
  3. └── FindONNXRuntime.cmake
  4. ├── include/
  5. └── ocr_utils.h
  6. ├── src/
  7. ├── main.cpp
  8. └── ocr_utils.cpp
  9. └── models/
  10. └── ch_PP-OCRv3_rec_train.onnx

3.3 CMake构建配置

关键CMake配置示例:

  1. cmake_minimum_required(VERSION 3.10)
  2. project(PP-OCR-ONNX-Demo)
  3. set(CMAKE_CXX_STANDARD 17)
  4. find_package(OpenCV REQUIRED)
  5. find_package(ONNXRuntime REQUIRED)
  6. add_executable(${PROJECT_NAME}
  7. src/main.cpp
  8. src/ocr_utils.cpp
  9. )
  10. target_include_directories(${PROJECT_NAME} PRIVATE
  11. ${OpenCV_INCLUDE_DIRS}
  12. ${ONNXRUNTIME_INCLUDE_DIRS}
  13. ./include
  14. )
  15. target_link_libraries(${PROJECT_NAME} PRIVATE
  16. ${OpenCV_LIBS}
  17. ${ONNXRUNTIME_LIBRARIES}
  18. )

四、核心推理代码实现

4.1 图像预处理流程

  1. #include <opencv2/opencv.hpp>
  2. cv::Mat preprocessImage(const cv::Mat& src, int target_height = 32) {
  3. // 1. 灰度化
  4. cv::Mat gray;
  5. if (src.channels() == 3) {
  6. cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
  7. } else {
  8. gray = src.clone();
  9. }
  10. // 2. 二值化(可选)
  11. cv::Mat binary;
  12. cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
  13. // 3. 尺寸调整(保持宽高比)
  14. float ratio = target_height / static_cast<float>(binary.rows);
  15. int new_width = static_cast<int>(binary.cols * ratio);
  16. cv::Mat resized;
  17. cv::resize(binary, resized, cv::Size(new_width, target_height));
  18. // 4. 通道变换(ONNX通常需要NCHW格式)
  19. cv::Mat transposed;
  20. cv::transpose(resized, transposed);
  21. std::vector<cv::Mat> channels;
  22. channels.push_back(transposed);
  23. cv::Mat chw_img;
  24. cv::merge(channels, chw_img);
  25. // 5. 归一化处理
  26. chw_img.convertTo(chw_img, CV_32F, 1.0/255.0);
  27. return chw_img;
  28. }

4.2 ONNX推理核心代码

  1. #include <onnxruntime_cxx_api.h>
  2. class ONNXInferencer {
  3. public:
  4. ONNXInferencer(const std::string& model_path) {
  5. Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "PP-OCR-Demo");
  6. Ort::SessionOptions session_options;
  7. // 性能优化配置
  8. session_options.SetIntraOpNumThreads(4);
  9. session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
  10. session = std::make_shared<Ort::Session>(env, model_path.c_str(), session_options);
  11. // 获取输入输出信息
  12. Ort::AllocatorWithDefaultOptions allocator;
  13. auto input_name = session->GetInputName(0, allocator);
  14. auto output_name = session->GetOutputName(0, allocator);
  15. input_shape = session->GetInputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
  16. output_shape = session->GetOutputTypeInfo(0).GetTensorTypeAndShapeInfo().GetShape();
  17. }
  18. std::vector<float> infer(const cv::Mat& input_img) {
  19. // 准备输入张量
  20. std::vector<int64_t> input_dims = {1, 1, input_shape[2], input_shape[3]};
  21. size_t input_tensor_size = 1 * 1 * input_shape[2] * input_shape[3];
  22. std::vector<float> input_tensor_values(input_tensor_size);
  23. // 填充输入数据(需根据实际预处理结果调整)
  24. // 这里简化处理,实际应用中需要严格匹配预处理流程
  25. for (int i = 0; i < input_tensor_size; ++i) {
  26. input_tensor_values[i] = input_img.at<float>(i % input_shape[3], i / input_shape[3]);
  27. }
  28. auto memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
  29. Ort::Value input_tensor = Ort::Value::CreateTensor<float>(
  30. memory_info, input_tensor_values.data(), input_tensor_size,
  31. input_dims.data(), input_dims.size());
  32. // 运行推理
  33. std::vector<Ort::Value> output_tensors;
  34. auto output_names = std::vector<const char*>{"output"};
  35. session->Run(Ort::RunOptions{nullptr},
  36. &input_name, &input_tensor, 1,
  37. output_names.data(), output_tensors, 1);
  38. // 处理输出
  39. float* floatarr = output_tensors[0].GetTensorMutableData<float>();
  40. std::vector<float> output_data(floatarr, floatarr + output_tensors[0].GetTensorTypeAndShapeInfo().GetElementCount());
  41. return output_data;
  42. }
  43. private:
  44. std::shared_ptr<Ort::Session> session;
  45. std::vector<int64_t> input_shape;
  46. std::vector<int64_t> output_shape;
  47. const char* input_name;
  48. };

4.3 后处理与结果解析

  1. std::string postprocessOutput(const std::vector<float>& output, const std::string& charset) {
  2. // PP-OCR输出为CTC格式,需要解码
  3. // 这里简化处理,实际应用中需要实现完整的CTC解码逻辑
  4. // 1. 找到概率最大的字符
  5. auto max_it = std::max_element(output.begin(), output.end());
  6. int max_idx = std::distance(output.begin(), max_it);
  7. // 2. 映射到字符集(示例)
  8. if (max_idx < charset.size()) {
  9. return std::string(1, charset[max_idx]);
  10. }
  11. return "";
  12. }

五、性能优化与部署建议

5.1 推理性能优化

  1. 模型量化:使用ONNX Runtime的量化工具将FP32模型转为INT8

    1. python -m onnxruntime.quantization.quantize \
    2. --input model.onnx \
    3. --output quantized_model.onnx \
    4. --quant_format QDQ \
    5. --op_types Conv,MatMul
  2. 多线程配置:在SessionOptions中设置SetIntraOpNumThreadsSetInterOpNumThreads

  3. 硬件加速

    • GPU部署:安装CUDA版本的ONNX Runtime
    • NPU部署:使用特定厂商的ONNX Runtime分支

5.2 跨平台部署要点

  1. Windows平台

    • 使用MSVC编译时需注意运行时库兼容性
    • 推荐静态链接ONNX Runtime
  2. Linux平台

    • 注意glibc版本兼容性
    • 可使用Docker容器封装依赖
  3. 嵌入式平台

    • 选择ARM架构的ONNX Runtime版本
    • 考虑使用TensorRT加速(需转换为ONNX-TensorRT格式)

5.3 常见问题解决方案

  1. 模型加载失败

    • 检查ONNX Runtime版本与模型opset版本兼容性
    • 使用ort_tool验证模型有效性
  2. 输入尺寸不匹配

    • 在预处理阶段严格保证输入尺寸与模型期望一致
    • 可通过session->GetInputTypeInfo()获取期望尺寸
  3. 内存泄漏

    • 确保Ort::Value和Ort::Session对象正确释放
    • 使用智能指针管理资源

六、完整应用示例

  1. #include <iostream>
  2. #include "ocr_utils.h"
  3. int main() {
  4. // 1. 初始化推理器
  5. ONNXInferencer inferencer("models/ch_PP-OCRv3_rec_train.onnx");
  6. // 2. 加载并预处理图像
  7. cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
  8. cv::Mat processed = preprocessImage(image);
  9. // 3. 执行推理
  10. auto output = inferencer.infer(processed);
  11. // 4. 后处理(简化版)
  12. std::string charset = "0123456789abcdefghijklmnopqrstuvwxyz";
  13. std::string result = postprocessOutput(output, charset);
  14. std::cout << "识别结果: " << result << std::endl;
  15. return 0;
  16. }

七、总结与展望

本文详细阐述了基于PP-OCR模型通过ONNX格式实现C++推理部署的完整流程。通过模型转换、环境配置、代码实现和性能优化四个关键环节,开发者可以构建出高效、跨平台的OCR识别系统。实际部署中需特别注意:

  1. 预处理流程与模型训练时保持一致
  2. 根据目标平台选择合适的ONNX Runtime版本
  3. 通过量化、并行计算等手段优化推理性能

未来发展方向包括:

  • 支持更复杂的文档布局分析
  • 集成到边缘计算设备实现实时识别
  • 结合NLP技术实现端到端文档理解

通过掌握本文介绍的技术方案,开发者能够快速构建满足业务需求的OCR系统,为智能文档处理、工业检测等场景提供基础技术支撑。

相关文章推荐

发表评论

活动