V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
sssxyd
V2EX  ›  分享创造

基于 Paddle Inference 3.0 的高性能 OCR 服务实现

  •  
  •   sssxyd · 38 天前 · 1412 次点击
    这是一个创建于 38 天前的主题,其中的信息可能已经有所发展或是发生改变。

    基于 Paddle Inference 3.0 的高性能 OCR 服务实现

    一个面向 Windows 桌面应用内嵌、RPA 和自动化脚本的低延迟 OCR 解决方案

    1. 项目背景与解决的问题

    在开发 Windows 桌面项目时,我遇到了需要频繁识别界面中"小卡片"区域的需求。这些区域通常包括电商平台的订单块、工具类应用的信息条、列表行等界面元素。考虑到目标用户主要使用集成显卡的办公电脑,我对比测试了以下几种方案:

    1.1 现有方案的问题分析

    第三方云 OCR 接口的局限性:

    • 网络往返延迟 + 鉴权验证 + 请求排队,单次识别耗时普遍超过 800~1200ms
    • 批量并发请求时容易触发 API 限流
    • 依赖网络连接,无法满足离线或内网环境需求

    官方 PaddleOCR Python 版本的性能瓶颈:

    • 即使使用动转静模型和轻量化配置,单张小卡片(约 500×300 像素)识别仍需 650ms 左右
    • Python GIL (全局解释器锁)限制了真正的多线程并发
    • 随着线程数增加,性能提升边际递减,甚至出现性能抖动

    1.2 性能目标

    在桌面场景中,超过 300ms 的延迟会明显影响用户交互体验。因此,我们需要构建一套本地化、低延迟、高并发的 OCR 解决方案。

    2. 技术路线

    2.1 概述

    • 采用 C++ 封装百度 Paddle Inference 3.0 推理引擎,直接加载检测( det )/ 分类( cls ,可选)/ 识别( rec )模型,构建最小稳定工作单元 OCR Worker

      ┌───────────┐    ┌────────────┐     ┌─────────────┐
      │  Detector │ -> │ Classifier │ ->  │ Recognizer  │
      └───────────┘    └────────────┘     └─────────────┘
                       (可选 cls)          (输出文本+置信度+框)
      
    • 针对“小卡片 / 程序截图 / 规整文本密度高”这一特定分布做参数裁剪与尺寸优化,一个 Worker 的端到端耗时(以下所有数据均在一台普通办公电脑,集成显卡 + 中端多核 CPU 环境下测试,未使用独立 GPU 加速):

      • 实测稳定在 100 ~ 150ms(含检测 + 识别)
      • 相比原 Python 方案提速 ≈4~6 倍
      • 注:在更高主频或独立 GPU (如 RTX 系列)环境中还有继续提升空间
    • 通过创建多组 Worker (线程内复用各自的 Paddle Predictor ),用一个轻量线程池 / 队列调度请求,实现近线性并发扩展( Worker 数量受限于 CPU 逻辑核心 / GPU 显存)。

    2.2 服务化设计( Named Pipe IPC )

    • 为了方便被不同进程 / 语言( GO / C# / 脚本 / 其他自动化工具)复用,本项目没有嵌入到主进程,而是做成命名管道服务

      Client ──>  \\.\pipe\ocr_service  ──>  请求队列  ──>  Worker Pool ──>  JSON 结果
      
    • 支持两种输入:

      1. 本地图片路径(客户端发送路径,服务端自行加载)
      2. Base64 图片(适合截图内存中转 / 无法落盘场景)
    • 返回 JSON:

      {
      "success": true,
      "width": 480,
      "height": 300,
      "processing_time_ms": 128.7,
      "words": [
          {
          "text": "示例文字",
          "confidence": 0.984,
          "box": [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
          }
      ]
      }
      

    2.3 请求处理流程

    1. 请求接收:IPC 监听器接收客户端消息
    2. 数据解析:解析图片路径或 Base64 数据,构建 OCRRequest 对象
    3. 队列调度:将请求放入线程安全的任务队列
    4. Worker 处理:空闲 Worker 从队列取出任务执行推理
    5. 结果返回:Worker 将 JSON 结果通过 promise.set_value() 返回
    6. IPC 响应:主处理线程获取结果并写回命名管道

    2.4 并发控制策略

    1. 线程安全保障:

      • 使用线程安全队列管理请求调度
      • 每个 Worker 独立运行,避免竞态条件
      • 支持优雅的服务启动和关闭
    2. 负载均衡:

      • 空闲 Worker 自动获取新任务
      • 支持动态调整 Worker 数量
      • 实现任务公平调度,避免饥饿现象
    3. 客户端支持:

      • 同步调用:客户端阻塞等待结果返回
      • 异步模式:支持 Future 模式的非阻塞处理
      • 批量处理:支持多个请求并发提交

    2.5 关键性能优化点

    模块 默认/常规设定 本项目裁剪/调参 目的
    检测 max_side_len 640 / 960 512 减少缩放 & 卷积开销(适配卡片尺寸)
    det_db_thresh / box_thresh 0.3 / 0.5 0.2 / 0.4 保留更多小框,后面再用置信度过滤
    det_db_unclip_ratio 2.0 1.8 减少框扩张,规整文本更贴边
    rec_img_h / rec_img_w 32 / 224 28 / 192 降低特征图分辨率,保持可辨性前提下降算力
    rec_batch_num 6~12 16 批内吞吐提升(小卡片平均词块多)
    cls 阶段 默认启用 可配置关闭 卡片内文字方向稳定时直接跳过加速
    CPU Threads Paddle 默认较高 det/rec 限制为 2 + 主线程 避免多 Worker 时线程爆炸

    3 与 Python 方案对比

    python 版本测试代码请访问 Github 页面

    维度 Python PaddleOCR 本项目 C++ 版
    单张小卡片耗时(集成显卡办公机) ~650ms 100~150ms
    并发扩展 线程受 GIL 影响 多 Worker 几乎线性( CPU/GPU 上限前)
    部署 需要 Python 运行时 单一 exe + 模型目录
    启动时间 较慢(解释器+加载) 快速(直接加载预测器)
    IPC 集成 需再封装 命名管道原生支持

    4. 快速使用

    4.1 命令行调用

    • Github Release 下载编译后端 windows 程序包: cpp-paddle-ocr-win-amd64.7z

    • 服务端(示例):

      # 启动 IPC 服务
      .\ocr-service.exe --cpu-workers 3 
      
    • 客户端识别本地图片:

      # 识别图片
      .\ocr-client.exe ..\images\card-jd.jpg
      
    • 优雅关闭:

      .\ocr-client.exe --shutdown
      

    4.2 客户端集成

    • 客户端向命名管道发送:

      {
          "command":"recognize", 
          "image_path":"path/to/your/image, 与 image_data 二选一", 
          "image_data": "image base64, 与 image_path 二选一"
      }
      
    • Go 版客户端示例

      package main
      
      import (
          "encoding/json"
          "fmt"
          "syscall"
      
          "golang.org/x/sys/windows"
      )
      
      type OcrRequest struct {
          Command   string `json:"command"`    // OCR 命令
          ImageData string `json:"image_data"` // 图像数据,Base64 编码
          ImagePath string `json:"image_path"` // 图像文件路径
      }
      
      func main() {
          // 使用默认的 Named Pipe 名称
          pipeName, err := syscall.UTF16PtrFromString(`\\.\pipe\ocr_service`)
          if err != nil {
              panic(err)
          }
      
          // 打开 Named Pipe
          handle, err := windows.CreateFile(
              pipeName,
              windows.GENERIC_READ|windows.GENERIC_WRITE,
              0,
              nil,
              windows.OPEN_EXISTING,
              0,
              0,
          )
          if err != nil {
              fmt.Printf("打开 Named Pipe 失败: %v\n", err)
          }
      
          defer windows.CloseHandle(handle)
      
          fmt.Println("成功打开 Named Pipe:", pipeName)
      
          // 构建请求, 这里改为你自己的图片地址
          image_path := "E:\\1755074161639.jpg"
          req := &OcrRequest{
              Command:   "recognize",
              ImagePath: image_path,
          }
      
          // 序列化请求
          reqData, err := json.Marshal(req)
          if err != nil {
              fmt.Printf("序列化请求失败: %v\n", err)
              return
          }
      
          // 发送请求
          var bytesWritten uint32
          err = windows.WriteFile(handle, reqData, &bytesWritten, nil)
          if err != nil {
              fmt.Printf("发送请求失败: %v\n", err)
              return
          }
      
          // 读取响应
          buffer := make([]byte, 64*1024) // 64K 缓冲区
          var bytesRead uint32
          err = windows.ReadFile(handle, buffer, &bytesRead, nil)
          if err != nil {
              fmt.Printf("读取响应失败: %v\n", err)
              return
          }
      
          fmt.Printf("接收到响应:\n %s\n", string(buffer[:bytesRead]))
      
      }
      
      

    5. 致谢

    • 本项目是百度 paddle 推理引擎及 OCR 模型的一个应用
    • 非常感谢 飞浆 提供完备的文档让我们学习 AI 知识
    4 条回复    2025-08-15 08:59:34 +08:00
    bigtan
        1
    bigtan  
       38 天前
    我最近也做了一个类似的识别,我用的是通义的[读光系列]( https://modelscope.cn/search?page=1&search=%E8%AF%BB%E5%85%89&type=model)。

    感觉这个比较贴近开源生态,有 onnx 格式的预训练模型,用[onnxruntime]( https://github.com/microsoft/onnxruntime)可以在 c++,Python ,rust 等等语言中集成,调用 GPU 也方便,推荐楼主也可以试试。
    soap0X
        2
    soap0X  
       37 天前 via Android
    @bigtan @bigtan onnx 别的非 python 语言试过吗?之前用 java 他那个 onnx 返回数据解析麻烦的很就放弃了
    bigtan
        3
    bigtan  
       37 天前
    https://github.com/pykeio/ort 我用的 rust 绑定,这个项目
    sssxyd
        4
    sssxyd  
    OP
       37 天前
    @bigtan nice!
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2671 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 07:12 · PVG 15:12 · LAX 00:12 · JFK 03:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.