PP-OCRv4模型转OM全流程操作指南

1. 环境构建

1.1 核心依赖版本说明

可以使用mindie镜像或者cann包镜像,详细见昇腾社区镜像仓库

为确保流程兼容性,需严格匹配以下依赖版本,避免因版本不兼容导致的功能异常。

依赖组件 版本要求 说明
PaddlePaddle 3.0.0 昇腾NPU适配核心框架
PaddleOCR 2.10.0 提供OCR模型推理基础能力
Paddle2ONNX 1.3.0 实现Paddle模型到ONNX格式的转换
ONNX Runtime 1.21.0 ONNX模型验证与临时推理
NumPy 1.26.4 低版本规避高版本API兼容性问题

1.2 环境安装步骤

1.2.1 基础依赖安装
# 1. 拉取PaddleOCR代码
git clone -b release/2.10 https://github.com/PaddlePaddle/PaddleOCR.git
cd PaddleOCR
vim requirements.txt
#### 按照以下配置修改
paddlepaddle==3.0.0
paddle2onnx==1.3.0
onnxruntime==1.21.0
pyyaml==6.0.2
onnx==1.17.0
opencv-contrib-python
imgaug
shapely
scikit-image
pyclipper
lmdb
tqdm
numpy
visualdl
rapidfuzz
cython
lxml
premailer
openpyxl
attrdict
sympy
####

# 2. 安装基础依赖
apt-get update -y && apt-get install -y gcc g++ cmake libnuma-dev wget git curl jq vim build-essential
# 如果是open欧拉系统需要 yum install mesa-libGL -y

# 3. 补全昇腾工具链依赖(te/schedule-search组件必需)
pip install attrs cloudpickle ml-dtypes tornado absl-py -i https://mirrors.aliyun.com/pypi/simple/

# 4. 安装指定版本PaddleOCR(确保与源码版本一致)
pip install paddleocr==2.10.0 -i https://mirrors.aliyun.com/pypi/simple/

# 5. 固定NumPy版本(规避高版本兼容性问题)
pip install numpy==1.26.4 -i https://mirrors.aliyun.com/pypi/simple/
1.2.2 CANN环境配置
# 加载CANN环境变量(路径需根据实际安装位置调整)
source /usr/local/Ascend/ascend-toolkit/set_env.sh

# 验证CANN环境(查看ATC工具版本)
atc --version
# 预期输出:ATC version: 8.0.0.xxx(与CANN版本匹配)

2. 数据处理

2.1 数据集准备

2.1.1 数据集结构

需手动创建与PP-OCRv3兼容的数据集目录,确保图片路径符合预处理脚本预期:

./data/
└── PaddleOCR/
    └── doc/
        └── imgs_words/
            └── ch/
                ├── word_1.jpg
                ├── word_2.jpg
                ├── word_3.jpg
                ├── word_4.jpg
                └── word_5.jpg
  • 图片要求:支持中英文字符、数字识别,单图文字长度不超过25个字符(匹配模型max_text_length=25配置)。
  • 获取说明:可在PaddleOCR 2.5.0版本中获得,PaddleOCR/doc/imgs_words/ch下
2.1.2 数据预处理执行
python3 ch_PP-OCRv4_rec_preprocess.py \
  -c PaddleOCR/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_hgnet_doc.yml \
  -o Global.infer_img=data/PaddleOCR/doc/imgs_words/ch/ \
  Global.bin_data=./image_npy
  • 输出说明:预处理后生成的.npy文件存储于./image_npy目录,每个文件对应一张输入图片的张量数据(格式:NCHW,数据类型:RGB_FP32)。
  • 脚本功能:自动完成图片Resize(适配模型输入尺寸)、归一化、通道转换(HWC→NCHW)等操作。

3. 模型格式转换(Paddle→ONNX→OM)

3.1 模型准备

3.1.1 下载推理模型
# 1. 创建推理模型目录并下载解压
mkdir -p ./inference
wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0rc0/PP-OCRv4_server_rec_doc_infer.tar
cd ./inference && tar xf PP-OCRv4_server_rec_doc_infer.tar && cd ..

# 2. 手动下载模型
#  https://www.paddleocr.ai/v2.10.0/ppocr/model_list.html#21  
#  下载ch_PP-OCRv4_server_rec_doc模型

3.2 Paddle模型转ONNX

# 1. 基础转换(生成初始ONNX模型)
paddle2onnx \
  --model_dir ./inference/PP-OCRv4_server_rec_doc_infer \
  --model_filename inference.pdmodel \
  --params_filename inference.pdiparams \
  --save_file ./PP-OCRv4_server_rec_doc.onnx \
  --opset_version 11 \
  --enable_onnx_checker True

# 2. ONNX模型优化(调整动态Shape,适配昇腾ATC工具)可不执行,有需要使用
python3 -m paddle2onnx.optimize \
  --input_model ./PP-OCRv4_server_rec_doc.onnx \
  --output_model ./PP-OCRv4_server_rec.onnx \
  --input_shape_dict '{"x": [-1, 3, -1, -1]}'
  • 参数说明
    • --opset_version 11:指定ONNX算子集版本,昇腾310P/910B推荐使用11~13。
    • --input_shape_dict '{"x": [-1, 3, -1, -1]}':定义动态输入Shape(batch_size、通道数、高度、宽度均支持动态)。
    • 优化目的:解决原始ONNX模型动态维度标识不兼容ATC工具的问题(将DynamicDimension替换为-1)。

3.3 ONNX模型转OM模型

3.3.1 查看昇腾芯片信息
# 查看NPU设备信息,确认soc_version(如Ascend310P3/Ascend910B3)
npu-smi info
  • 示例输出:若芯片型号为310P3,则--soc_version=Ascend310P3;若为910B3,则设置为Ascend910B3
3.3.2 执行ATC转换命令
atc --framework=5 \
  --model=./PP-OCRv4_server_rec.onnx \
  --output=./PP-OCRv4_server_rec_bs1 \
  --input_format=ND \
  --input_shape="x:1,3,-1,-1" \
  --dynamic_dims="48,320;48,620" \
  --log=error \
  --soc_version=Ascend910B3
  • 关键参数解析
    参数 说明
    --framework=5 指定模型框架为ONNX(5对应ONNX,0对应Caffe,1对应TensorFlow等)
    --input_shape 固定batch_size=1,通道数=3,高度/宽度动态(-1表示动态)
    --dynamic_dims 预定义动态分辨率组合(高度,宽度),支持推理时动态切换(如48×320、48×620)
    --soc_version 昇腾芯片型号,需与实际设备严格匹配(错误会导致OM模型无法加载)
  • 转换成功标志:生成PP-OCRv4_server_rec_bs1.om文件,无报错日志。

4. 模型推理

4.1 推理工具安装(ais_bench)

# 1. 克隆ais_bench代码仓(昇腾官方推理工具)
git clone https://gitee.com/ascend/tools.git
cd tools/ais-bench_workload/tool/ais_bench/

# 2. 安装工具依赖
pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/

# 3. 验证安装(查看工具版本)
python -m ais_bench --version

4.2 执行推理

# 1. 创建推理结果目录并设置权限
mkdir ./results_bs1
chmod 750 ./results_bs1  # 确保读写权限,避免结果写入失败

# 2. 执行推理(batch_size=1,动态分辨率自动适配)
python -m ais_bench \
  --model=PP-OCRv4_server_rec_bs1.om \
  --input=./image_npy \
  --output=./ \
  --output_dirname=results_bs1 \
  --auto_set_dymdims_mode=1
  • 参数说明
    • --input=./image_npy:指定预处理后的.npy文件目录,工具自动批量读取。
    • --auto_set_dymdims_mode=1:开启动态分辨率自动适配,无需手动指定每次推理的尺寸。
    • 输出说明:推理结果(.bin格式)存储于./results_bs1,每个文件对应一张图片的模型输出张量(形状:1×D×15631,D为动态维度)。

5. 精度验证

5.1 后处理脚本优化说明(ch_PP-OCRv4_rec_postprocess.py)

5.1.1 核心功能调整
  1. 字符表路径配置:自动设置character_dict_pathPaddleOCR/ppocr/utils/dict/ppocrv4_doc_dict.txt,支持1.5万+中英文字符及生僻字识别。
  2. 动态Shape适配
    • 根据推理输出.bin文件的形状(如1203587→1×77×15631,625240→1×40×15631)自动重塑张量。
    • 新增异常捕获逻辑,避免单张图片处理失败导致整个流程中断。
  3. 结果可视化:通过tqdm显示后处理进度,终端打印每张图片的识别结果(含置信度)。

5.2 执行后处理与验证

python3 ch_PP-OCRv4_rec_postprocess.py \
  -c PaddleOCR/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_hgnet_doc.yml \
  -o Global.infer_results=./results_bs1
  • 预期输出示例
Infer Results: 
word_1.jpg: {'Student': [('韩国小馆', 0.99755859375)]}
word_2.jpg: {'Student': [('汉阳鹦鹉家居建材市场E区25-26号', 0.9877658486366272)]}
word_3.jpg: {'Student': [('电话:15952301928', 0.997314453125)]}
word_4.jpg: {'Student': [('实力活力', 0.99462890625)]}
word_5.jpg: {'Student': [('西湾监管', 0.99853515625)]}
  • 精度判断标准:识别置信度≥0.95为合格,若出现“乱码”或低置信度(<0.8),需检查:
    1. OM模型转换时--soc_version是否与设备匹配。
    2. 预处理脚本是否正确执行(.npy文件维度是否为4D)。
    3. Paddle-custom-npu版本是否适配昇腾芯片(310P需避免使用910B专用包)。

6. 数据处理与评估代码

6.1 数据处理代码

vim ch_PP-OCRv4_rec_preprocess.py

# Copyright 2022 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys

__dir__ = os.path.dirname(os.path.abspath(__file__))
sys.path.append(__dir__)
sys.path.append(os.path.abspath(os.path.join(__dir__, 'PaddleOCR')))

import shutil
import paddle
import numpy as np
import PaddleOCR.tools.program as program

from tqdm import tqdm
from PaddleOCR.ppocr.postprocess import build_post_process
from PaddleOCR.ppocr.data import create_operators, transform
from PaddleOCR.ppocr.utils.utility import get_image_file_list


def main(config, device, logger, vdl_writer, data_path):
    global_config = config['Global']

    # build post process
    post_process_class = build_post_process(config['PostProcess'], global_config)

    # create data ops
    transforms = []
    for op in config['Eval']['dataset']['transforms']:
        op_name = list(op)[0]
        if 'Label' in op_name:
            continue
        elif op_name in ['RecResizeImg']:
            op[op_name]['infer_mode'] = True
        elif op_name == 'KeepKeys':
            if config['Architecture']['algorithm'] == "SRN":
                op[op_name]['keep_keys'] = [
                    'image', 'encoder_word_pos', 'gsrm_word_pos',
                    'gsrm_slf_attn_bias1', 'gsrm_slf_attn_bias2'
                ]
            elif config['Architecture']['algorithm'] == "SAR":
                op[op_name]['keep_keys'] = ['image', 'valid_ratio']
            else:
                op[op_name]['keep_keys'] = ['image']
        transforms.append(op)

    global_config['infer_mode'] = True

    ops = create_operators(transforms, global_config)

    pbar = tqdm(
            total=len(get_image_file_list(config['Global']['infer_img'])),
            desc='Preprocessing',
            position=0,
            leave=True)

    for im_file in get_image_file_list(config['Global']['infer_img']):
        with open(im_file, 'rb') as f:
            img = f.read()
            data = {'image': img}

        batch = transform(data, ops)

        images = np.expand_dims(batch[0], axis=0)
        image_name = "{}.npy".format(os.path.basename(im_file)[:-4])
        np.save(os.path.join(data_path, image_name), images)

        pbar.update(1)

    pbar.close()


if __name__ == "__main__":
    config, device, logger, vdl_writer = program.preprocess()

    data_path = os.path.join(config['Global']['bin_data'])
    if os.path.exists(data_path):
        shutil.rmtree(data_path)
    os.makedirs(data_path)

    main(config, device, logger, vdl_writer, data_path)

6.2 评估代码

vim ch_PP-OCRv4_rec_postprocess.py

# Copyright 2022 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys

__dir__ = os.path.dirname(os.path.abspath(__file__))
sys.path.append(__dir__)
sys.path.append(os.path.abspath(os.path.join(__dir__, 'PaddleOCR')))

import paddle
import numpy as np
import PaddleOCR.tools.program as program

from tqdm import tqdm
from PaddleOCR.ppocr.postprocess import build_post_process
from PaddleOCR.ppocr.utils.utility import get_image_file_list


def main(config, device, logger, vdl_writer):
    global_config = config['Global']

    # 设置字符表路径
    if 'character_dict_path' not in global_config:
        global_config['character_dict_path'] = 'PaddleOCR/ppocr/utils/dict/ppocrv4_doc_dict.txt'

    config['PostProcess'] = {
                            'name': 'DistillationCTCLabelDecode',
                            'model_name': ['Student'],
                            'key': 'head_out',
                            'multi_head': True
                            }

    try:
        # 直接使用原始后处理器,不进行安全包装
        post_process_class = build_post_process(config['PostProcess'], global_config)
    except Exception as e:
        print(f"后处理器初始化失败: {e}")
        return

    pbar = tqdm(
            total=5,
            desc='Postprocessing',
            position=0,
            leave=True)

    post_results = {}
    for idx in range(1, 6):
        try:
            result = "{}/word_{}_0.bin".format(config['Global']['infer_results'], str(idx))
            pred = np.fromfile(result, dtype=np.float32)

            print(f"处理 word_{idx}, 形状: {pred.shape}")

            # 根据形状重塑
            if pred.shape == (1203587,):
                pred = paddle.to_tensor(pred.reshape(1, 77, 15631))
            elif pred.shape == (625240,):
                pred = paddle.to_tensor(pred.reshape(1, 40, 15631))
            else:
                print(f"未知的形状: {pred.shape}")
                continue

            preds = {'Student': {'head_out': pred}}
            # 直接使用原始后处理
            post_result = post_process_class(preds)

            img_name = "word_{}.jpg".format(str(idx))
            post_results[img_name] = post_result
            print(f"{img_name}: {post_result}")

        except Exception as e:
            print(f"处理 word_{idx} 时出错: {e}")
            post_results[f"word_{idx}.jpg"] = [f"错误: {str(e)}"]

        pbar.update(1)

    pbar.close()

    print("Infer Results: ")
    for key in post_results.keys():
        print("{}: {}".format(key, post_results[key]))


if __name__ == "__main__":
    config, device, logger, vdl_writer = program.preprocess()
    main(config, device, logger, vdl_writer)

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐