基于 Paddle Inference 3.0 的高性能 OCR 服务实现 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
sssxyd
V2EX    分享创造

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

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

    基于 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 条回复   nbsp;2025-08-15 08:59:34 +08:00
    bigtan
        1
    bigtan  
       56 天前
    我最近也做了一个类似的识别,我用的是通义的[读光系列]( 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  
       56 天前 via Android
    @bigtan @bigtan onnx 别的非 python 语言试过吗?之前用 java 他那个 onnx 返回数据解析麻烦的很就放弃了
    bigtan
        3
    bigtan  
       56 天前
    https://github.com/pykeio/ort 我用的 rust 绑定,这个项目
    sssxyd
        4
    sssxyd  
    OP
       55 天前
    @bigtan nice!
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3651 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 00:49 PVG 08:49 LAX 17:49 JFK 20:49
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86