一个面向 Windows 桌面应用内嵌、RPA 和自动化脚本的低延迟 OCR 解决方案
在开发 Windows 桌面项目时,我遇到了需要频繁识别界面中"小卡片"区域的需求。这些区域通常包括电商平台的订单块、工具类应用的信息条、列表行等界面元素。考虑到目标用户主要使用集成显卡的办公电脑,我对比测试了以下几种方案:
第三方云 OCR 接口的局限性:
官方 PaddleOCR Python 版本的性能瓶颈:
在桌面场景中,超过 300ms 的延迟会明显影响用户交互体验。因此,我们需要构建一套本地化、低延迟、高并发的 OCR 解决方案。
采用 C++ 封装百度 Paddle Inference 3.0 推理引擎,直接加载检测( det )/ 分类( cls ,可选)/ 识别( rec )模型,构建最小稳定工作单元 OCR Worker:
┌───────────┐ ┌────────────┐ ┌─────────────┐
│ Detector │ -> │ Classifier │ -> │ Recognizer │
└───────────┘ └────────────┘ └─────────────┘
(可选 cls) (输出文本+置信度+框)
针对“小卡片 / 程序截图 / 规整文本密度高”这一特定分布做参数裁剪与尺寸优化,一个 Worker 的端到端耗时(以下所有数据均在一台普通办公电脑,集成显卡 + 中端多核 CPU 环境下测试,未使用独立 GPU 加速):
通过创建多组 Worker (线程内复用各自的 Paddle Predictor ),用一个轻量线程池 / 队列调度请求,实现近线性并发扩展( Worker 数量受限于 CPU 逻辑核心 / GPU 显存)。
为了方便被不同进程 / 语言( GO / C# / 脚本 / 其他自动化工具)复用,本项目没有嵌入到主进程,而是做成命名管道服务:
Client ──> \\.\pipe\ocr_service ──> 请求队列 ──> Worker Pool ──> JSON 结果
支持两种输入:
返回 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]]
}
]
}
线程安全保障:
负载均衡:
客户端支持:
模块 | 默认/常规设定 | 本项目裁剪/调参 | 目的 |
---|---|---|---|
检测 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 时线程爆炸 |
python 版本测试代码请访问 Github 页面
维度 | Python PaddleOCR | 本项目 C++ 版 |
---|---|---|
单张小卡片耗时(集成显卡办公机) | ~650ms | 100~150ms |
并发扩展 | 线程受 GIL 影响 | 多 Worker 几乎线性( CPU/GPU 上限前) |
部署 | 需要 Python 运行时 | 单一 exe + 模型目录 |
启动时间 | 较慢(解释器+加载) | 快速(直接加载预测器) |
IPC 集成 | 需再封装 | 命名管道原生支持 |
从 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
客户端向命名管道发送:
{
"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]))
}
![]() |
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 也方便,推荐楼主也可以试试。 |
![]() |
3
bigtan 37 天前
https://github.com/pykeio/ort 我用的 rust 绑定,这个项目
|