前言
做工业项目的时候经常遇到一个需求——现场的压力表、温度计、流量计这些传统仪表没有数字输出接口,但产线又要求把读数采集上来做数据分析。以前的做法是装个摄像头,把图传到服务器上跑OCR,但延迟和带宽成本一直是痛点。
这次我们在高通跃龙IQ-9075平台上把PaddleOCR的PP-OCRv4模型跑通了,从拍摄到出识别结果全程在板端完成,不依赖云端。整个过程踩了不少坑,记录下来供大家参考。

本系列分三篇:

  • (一)PC端完成PP-OCRv4模型的导出和ONNX转换
  • (二)QNN编译与模型转换
  • (三)板端部署与实时识别效果验证

1. 系统整体架构

先看整体方案。整个OCR识别系统采用经典的“检测+识别”两阶段pipeline,部署架构如下:

在这里插入图片描述

简单说就是:USB工业摄像头采集仪表图像,经过预处理后先跑文字检测模型(PP-OCRv4 Det)定位文字区域,把检测到的文字区域裁剪出来,再送进文字识别模型(PP-OCRv4 Rec)得到最终的文字内容。检测和识别模型全部跑在IQ-9075的Hexagon NPU上,利用QNN框架做加速。

核心硬件参数如下:

在这里插入图片描述

2. 为什么选PP-OCRv4

选模型的时候对比了几个方案:

模型 检测模型大小 识别模型大小 中文支持 ONNX导出
PP-OCRv4 4.7MB 10MB 原生支持 官方支持
EasyOCR ~25MB ~50MB 支持 需手动转
TrOCR - ~560MB 需微调 支持

PP-OCRv4的优势很明显:模型小、中文效果好、官方直接提供ONNX导出工具。工业仪表场景下,数字和少量汉字的识别用PP-OCRv4完全够用。

3. 环境准备

3.1 PC端环境配置

模型导出在PC端(x86_64 Ubuntu 22.04)完成,需要安装以下依赖。

先创建一个干净的虚拟环境:

python3 -m venv ocr_export
source ocr_export/bin/activate

安装PaddlePaddle和PaddleOCR:

pip install paddlepaddle==3.0.0
pip install paddleocr==2.9.1
pip install paddle2onnx==1.3.1
pip install onnx==1.17.0
pip install onnxsim==0.4.36

验证安装是否成功:

python3 -c "import paddle; print(paddle.__version__)"
python3 -c "import paddleocr; print(paddleocr.__version__)"

输出应该分别是 3.0.02.9.1

3.2 下载PP-OCRv4预训练模型

PP-OCRv4包含两个核心模型:文字检测模型(Det)和文字识别模型(Rec)。从PaddleOCR的GitHub仓库下载server版本的预训练权重:

mkdir -p ~/ocr_models && cd ~/ocr_models

wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_server_infer.tar
wget https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_rec_server_infer.tar

tar xf ch_PP-OCRv4_det_server_infer.tar
tar xf ch_PP-OCRv4_rec_server_infer.tar

解压后目录结构如下:

~/ocr_models/
├── ch_PP-OCRv4_det_server_infer/
│   ├── inference.pdmodel
│   ├── inference.pdiparams
│   └── inference.pdiparams.info
└── ch_PP-OCRv4_rec_server_infer/
    ├── inference.pdmodel
    ├── inference.pdiparams
    └── inference.pdiparams.info

每个模型目录下有三个文件:.pdmodel是网络结构,.pdiparams是权重参数,.pdiparams.info是参数描述信息。

4. Paddle模型导出为ONNX

这一步是整个流程的关键。QNN工具链不直接支持Paddle格式,需要先转成ONNX中间格式。

图2 模型导出流程图

4.1 导出检测模型

检测模型的输入是一张图片,输出是文字区域的坐标。导出时需要固定输入shape,这对后续QNN编译很重要:

paddle2onnx \
    --model_dir ~/ocr_models/ch_PP-OCRv4_det_server_infer \
    --model_filename inference.pdmodel \
    --params_filename inference.pdiparams \
    --save_file ~/ocr_models/ch_ppocr_v4_det.onnx \
    --opset_version 16 \
    --enable_onnx_checker True

导出完成后用onnxsim做一次图优化,合并冗余的算子节点:

python3 -m onnxsim \
    ~/ocr_models/ch_ppocr_v4_det.onnx \
    ~/ocr_models/ch_ppocr_v4_det_sim.onnx

查看检测模型的输入输出:

python3 -c "
import onnx
model = onnx.load('/root/ocr_models/ch_ppocr_v4_det_sim.onnx')
print('输入:', [(i.name, [d.dim_value for d in i.type.tensor_type.shape.dim]) for i in model.graph.input])
print('输出:', [(o.name, [d.dim_value for d in o.type.tensor_type.shape.dim]) for o in model.graph.output])
print('节点数:', len(model.graph.node))
"

输出类似:

输入: [('x', [1, 3, 960, 960])]
输出: [('sigmoid_0.tmp_0', [1, 1, 960, 960])]
节点数: 267

检测模型的输入是 1×3×960×960 的图像张量(NCHW格式),输出是同尺寸的概率图,表示每个像素属于文字区域的概率。

4.2 导出识别模型

识别模型的输入是裁剪出的文字区域图片,输出是字符序列。PP-OCRv4的识别模型采用SVTR架构,输入高度固定为48像素,宽度按比例缩放:

paddle2onnx \
    --model_dir ~/ocr_models/ch_PP-OCRv4_rec_server_infer \
    --model_filename inference.pdmodel \
    --params_filename inference.pdiparams \
    --save_file ~/ocr_models/ch_ppocr_v4_rec.onnx \
    --opset_version 16 \
    --enable_onnx_checker True

同样做简化:

python3 -m onnxsim \
    ~/ocr_models/ch_ppocr_v4_rec.onnx \
    ~/ocr_models/ch_ppocr_v4_rec_sim.onnx

查看识别模型的输入输出:

python3 -c "
import onnx
model = onnx.load('/root/ocr_models/ch_ppocr_v4_rec_sim.onnx')
print('输入:', [(i.name, [d.dim_value for d in i.type.tensor_type.shape.dim]) for i in model.graph.input])
print('输出:', [(o.name, [d.dim_value for d in o.type.tensor_type.shape.dim]) for o in model.graph.output])
print('节点数:', len(model.graph.node))
"

输出类似:

输入: [('x', [1, 3, 48, 320])]
输出: [('softmax_0.tmp_0', [1, 40, 6625])]
节点数: 354

识别模型输入是 1×3×48×320(高48、宽320),输出是 1×40×6625,其中40是序列长度,6625是字符字典大小(包含6623个中英文字符 + blank + padding)。

4.3 固定动态维度

PP-OCRv4原始模型的宽度维度是动态的,但QNN要求所有维度在编译时确定。需要用onnx工具把动态维度固定下来。

检测模型固定为 960×960:

python3 << 'PYEOF'
import onnx
from onnx.tools import update_model_dims

model = onnx.load("/root/ocr_models/ch_ppocr_v4_det_sim.onnx")
updated = update_model_dims.update_inputs_outputs_dims(
    model,
    {"x": [1, 3, 960, 960]},
    {"sigmoid_0.tmp_0": [1, 1, 960, 960]}
)
onnx.save(updated, "/root/ocr_models/ch_ppocr_v4_det_fixed.onnx")
print("检测模型固定shape完成")
PYEOF

识别模型固定为 48×320:

python3 << 'PYEOF'
import onnx
from onnx.tools import update_model_dims

model = onnx.load("/root/ocr_models/ch_ppocr_v4_rec_sim.onnx")
updated = update_model_dims.update_inputs_outputs_dims(
    model,
    {"x": [1, 3, 48, 320]},
    {"softmax_0.tmp_0": [1, 40, 6625]}
)
onnx.save(updated, "/root/ocr_models/ch_ppocr_v4_rec_fixed.onnx")
print("识别模型固定shape完成")
PYEOF

5. PC端验证ONNX模型

在送去做QNN转换之前,先在PC端验证一下ONNX模型的推理结果是否正确。准备一张工业仪表的测试图片(比如压力表的特写照片),运行以下脚本:

python3 << 'PYEOF'
import cv2
import numpy as np
import onnxruntime as ort

def preprocess_det(img, target_size=960):
    h, w = img.shape[:2]
    ratio = target_size / max(h, w)
    new_h, new_w = int(h * ratio), int(w * ratio)
    resized = cv2.resize(img, (new_w, new_h))
    padded = np.zeros((target_size, target_size, 3), dtype=np.float32)
    padded[:new_h, :new_w, :] = resized
    padded = padded / 255.0
    padded = (padded - np.array([0.485,0.456,0.406])) / np.array([0.229,0.224,0.225])
    padded = padded.transpose(2,0,1)[np.newaxis,:].astype(np.float32)
    return padded, ratio

img = cv2.imread("test_meter.jpg")
input_data, ratio = preprocess_det(img)

sess = ort.InferenceSession("/root/ocr_models/ch_ppocr_v4_det_fixed.onnx")
output = sess.run(None, {"x": input_data})

prob_map = output[0][0,0]
binary = (prob_map > 0.3).astype(np.uint8)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

print(f"检测到{len(contours)}个文字区域")
for i, cnt in enumerate(contours):
    x,y,w,h = cv2.boundingRect(cnt)
    print(f"区域{i+1}: x={int(x/ratio)}, y={int(y/ratio)}, w={int(w/ratio)}, h={int(h/ratio)}")
PYEOF

如果能正确检测到仪表上的数字区域,说明ONNX模型导出没有问题。

6. 准备校准数据集

QNN量化需要校准数据集来确定激活值的动态范围。收集20-30张工业仪表的实拍图片,包含不同光照条件和不同仪表型号。

创建校准数据目录:

mkdir -p ~/ocr_models/calibration/det_input
mkdir -p ~/ocr_models/calibration/rec_input

用下面的脚本批量预处理校准图片:

python3 << 'PYEOF'
import cv2
import numpy as np
import glob
import os

det_out = os.path.expanduser("~/ocr_models/calibration/det_input")
raw_dir = os.path.expanduser("~/ocr_models/calibration/raw_images")

for idx, path in enumerate(sorted(glob.glob(f"{raw_dir}/*.jpg"))):
    img = cv2.imread(path)
    h, w = img.shape[:2]
    ratio = 960 / max(h, w)
    new_h, new_w = int(h * ratio), int(w * ratio)
    resized = cv2.resize(img, (new_w, new_h))
    padded = np.zeros((960, 960, 3), dtype=np.float32)
    padded[:new_h, :new_w, :] = resized
    padded = padded / 255.0
    padded = (padded - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225])
    padded = padded.transpose(2, 0, 1).astype(np.float32)
    padded.tofile(f"{det_out}/input_{idx:04d}.raw")
    print(f"已处理: {os.path.basename(path)}")

print(f"共生成 {idx+1} 个检测模型校准文件")
PYEOF

识别模型的校准数据同理,从检测结果中裁剪出文字区域,resize到 48×320 后保存为raw文件。这部分具体操作放到下篇文章里讲。

7. 导出产物清单

到这一步,PC端的工作基本完成了。最终在 ~/ocr_models/ 下应该有这些文件:

~/ocr_models/
├── ch_ppocr_v4_det_fixed.onnx   # 检测模型(固定shape,约4.7MB)
├── ch_ppocr_v4_rec_fixed.onnx   # 识别模型(固定shape,约10MB)
├── calibration/
│   ├── det_input/               # 检测模型校准数据(.raw文件)
│   └── rec_input/               # 识别模型校准数据(.raw文件)
└── ppocr_keys_v1.txt            # 字符串字典文件(6623个字符)

字符串字典文件 ppocr_keys_v1.txt 从PaddleOCR仓库下载:

wget -O ~/ocr_models/ppocr_keys_v1.txt \
    https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.9/ppocr/utils/ppocr_keys_v1.txt

8. 小结

本篇完成了PP-OCRv4模型从PaddlePaddle格式到ONNX格式的全部转换工作,包括:

  1. 搭建了PC端的PaddlePaddle + paddle2onnx环境
  2. 下载并解压了PP-OCRv4 server版检测和识别预训练模型
  3. 使用paddle2onnx工具将Paddle模型转为ONNX格式
  4. 用onnxsim做了图优化,并固定了动态维度
  5. 在PC端验证了ONNX模型的推理正确性
  6. 准备了QNN量化所需的校准数据集

下一篇将进入QNN编译环节——使用QNN SDK把ONNX模型编译为IQ-9075 NPU可执行的格式。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐