使用 OpenVINO™ 实现 Paddle的PGNet 推理程序
使用 OpenVINO™ 实现 Paddle的PGNet 推理程序
OpenVINO™ 简介
OpenVINO™ 工具包 2022.1 版于 2022 年 3 月 22 日正式发布,根据官宣《OpenVINO™ 迎来迄今为止最重大更新,2022.1 新特性抢先看》,OpenVINO™ 2022.1 将是迄今为止最大变化的版本,并可以直接支持读取飞桨模型。
PGNet 简介
任意形状的文本检测与识别是很有挑战性的,目前大部分解决方案(如图Fig.2)都有如下劣势:
(1)两阶段网络通常都博爱阔NMS和RoI这两种非常耗时的操作,比如RoISlide 和 BezierAlign;
(2)训练时需要字符级标注,这是非常费时费力,比如 Mask TextSpotter;
(3)使用预定义规则对非常规文本方向进行识别,导致识别失败,比如 TextDragon 和 Mask TextSpotter 假设文本方向为从左到右或者从上到下,这些都阻碍了正确识别有挑战性的文本。
论文提出实时解析(检测与识别)文本的新框架,即PGNet, 它应用了一种成为点聚集的操作(point gathering)。PGNet是一种基于多任务学习的单阶段文本解读器, 架构如图所示。
在模型推理阶段:论文中使用FCN(Fully convolutional network)模型来学习文本区域包含的各种信息,包括文本中心线(text center line, TCL, 1 通道特征图), 文本边界偏移(text border offset, TBO, 4通道特征图-TCL每一像素距离文本区域上、下边界点的偏移量),文本方向偏移(text direction offset, TDO, 2通道特征图-TCL每一像素到下一个文本阅读位置的偏移量)以及文本字符分类特征图(text character classification, TCC, n通道特征图-n为字符类别数)。基于像素的字符分类特征图由PG-CTC(Point Gathering CTC)损失函数训练得到,避免了字符标注操作。
在后处理阶段:在每一个文本实例中可以根据TCL和TBO特征图提取出带有文本方向的中心点序列;可以从TBO特征图获取文本检测的结果;应用PG-CTC解码器,论文将高级别的二维TCC特征图转换为字符分类概率向量序列,用这个概率向量序列可以解码出最终的文本识别结果。
受到 SRN 和 GTC 的启发,论文提出使用图强化模块(GRM)来进一步提升端到端识别性能。文本序列中的点可视为图(graph)中的一个节点(node),每一个节点的表现能力可通过相邻的语义上下文(semantic context)和视觉上下文信息(visual context)来提升,字符分类结果理应更加准确。
任意形状文本的识别(例如,弯曲文本)受到越来越多的研究关注,而应用也愈发广泛(例如,广告牌识别、印章识别等),如下图所示。
AAAI 2021 会议上发表的百度自研的端到端场景文本识别
PGNet[1] 算法,有效的满足了上述需求,其典型特点有:
• PGNet 是一种新颖的、端到端的、的任意形状的文本检测识
别器框架
• 不需要字符级别的标注,NMS 操作以及 ROI 操作[2]
• 提出预测文本行内的阅读顺序模块和基于图的修正模块来提
升文本识别效果
• 识别精度和运行速度堪称 SOTA
另外,飞桨版PGNet的工程化和文档化做的极好,没有任何基础的人,都可以在不到半天的时间内完成 PGNet 的开发环境搭建、模型训练、ONNX 模型导出。
关于文档,可以参考PGNet 的文档链接:https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/algorithm_e2e_pgnet.md
下面开始进入正题。
准备 PGNet 的 OpenVINO 推理程序开发环境
要完成 PGNet 的 OpenVINO 推理程序开发,需要安装:
• PaddleOCR 运行环境,参考:
- 安装 PaddleOCR 运行环境
- 克隆 PaddleOCR 到本地,并安装第三方库
• OpenVINO™ 开发工具
pip install openvino-dev[onnx]
下载 PGNet 预训练模型
PaddleOCR 已提供 PGNet 预训练模型,请自行下载并解压,如下图所示。
PGNet 预训练模型下载链接:https://paddleocr.bj.bcebos.com/dygraph_v2.0/pgnet/e2e_server_pgnetA_infer.tar
!wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/pgnet/e2e_server_pgnetA_infer.tar
--2022-12-15 11:41:05-- https://paddleocr.bj.bcebos.com/dygraph_v2.0/pgnet/e2e_server_pgnetA_infer.tar
正在解析主机 paddleocr.bj.bcebos.com (paddleocr.bj.bcebos.com)... 182.61.200.195, 182.61.200.229, 2409:8c04:1001:1002:0:ff:b001:368a
正在连接 paddleocr.bj.bcebos.com (paddleocr.bj.bcebos.com)|182.61.200.195|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度: 195768320 (187M) [application/x-tar]
正在保存至: “e2e_server_pgnetA_infer.tar.3”
e2e_server_pgnetA_i 100%[===================>] 186.70M 59.5MB/s in 3.3s
2022-12-15 11:41:08 (56.9 MB/s) - 已保存 “e2e_server_pgnetA_infer.tar.3” [195768320/195768320])
!tar xvf e2e_server_pgnetA_infer.tar
!ls
e2e_server_pgnetA_infer/
e2e_server_pgnetA_infer/inference.pdiparams
e2e_server_pgnetA_infer/inference.pdiparams.info
e2e_server_pgnetA_infer/inference.pdmodel
data ic15_dict.txt PaddleOCR-release-2.6.zip
e2e_server_pgnetA_infer main.ipynb work
e2e_server_pgnetA_infer.tar PaddleOCR
!git clone https://gitee.com/paddlepaddle/PaddleOCR.git
fatal: 目标路径 'PaddleOCR' 已经存在,并且不是一个空目录。
下载完毕后,运行 PaddleOCR 自带的预测程序 tools/infer/predict_e2e.py
%cd PaddleOCR
/home/aistudio/PaddleOCR
!ls
applications inference_results ppocr requirements.txt train.sh
benchmark __init__.py PPOCRLabel setup.py
configs LICENSE ppstructure StyleText
deploy MANIFEST.in README_ch.md test_tipc
doc paddleocr.py README.md tools
!pip install -r requirements.txt
!pip install scikit-image imgaug
# 如果想使用 CPU 进行预测,需设置 use_gpu 参数为False
!python3 tools/infer/predict_e2e.py --e2e_algorithm="PGNet" --image_dir="./doc/imgs_en/img623.jpg" --e2e_model_dir="../e2e_server_pgnetA_infer" --e2e_pgnet_valid_set="totaltext" --use_gpu=False
python3: can't open file 'tools/infer/predict_e2e.py': [Errno 2] No such file or directory
运行结果,如下图所示,说明 PaddleOCR 和 PGNet 预训练模型都准备好了。
从图中可见,运行的平均时间是 5.09s。
%matplotlib inline
def imshow_dst():
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img_path = './inference_results/e2e_res_img623.jpg'
img1 = mpimg.imread(img_path)
plt.figure(figsize=(10,10))
plt.imshow(img1)
plt.axis('off')
plt.show()
imshow_dst()
用 OpenVINO™ 读取 PGNet 预训练模型
如前所述,OpenVINO™ 支持直接读入飞桨模型。使用下面的
代码,可以快速测试 OpenVINO™ 读入 PGNet 模型的效果。
# 安装openvino-dev
!pip install openvino==2022.3.0.dev20221125 -i https://pypi.tuna.tsinghua.edu.cn/simple
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting openvino==2022.3.0.dev20221125
Downloading https://pypi.tuna.tsinghua.edu.cn/packages/54/fd/06a9503058d22f2c743800e85857e92d6dd0a30ec3e927b3d09623069e90/openvino-2022.3.0.dev20221125-8831-cp37-cp37m-manylinux_2_17_x86_64.whl (37.5 MB)
[2K [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m37.5/37.5 MB[0m [31m6.1 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m
[?25hRequirement already satisfied: numpy<=1.23.1,>=1.16.6 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from openvino==2022.3.0.dev20221125) (1.19.5)
Installing collected packages: openvino
Successfully installed openvino-2022.3.0.dev20221125
[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip available: [0m[31;49m22.1.2[0m[39;49m -> [0m[32;49m22.3.1[0m
[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpip install --upgrade pip[0m
32;49mpip install --upgrade pip[0m
!pip install openvino-dev
from openvino.runtime import Core
# 指定 PGNet 模型路径
pgnet_path = "../e2e_server_pgnetA_infer/inference.pdmodel"
# 创建 Core 对象
core = Core()
# 载入并编译 PGNet 模型
sess = core.compile_model(model=pgnet_path,
device_name="CPU")
# 输出 PGNet 模型输入&输出信息
print(sess.input)
运行效果如下图所示,说明 OpenVINO™ 直接读取飞桨版PGNet 模型成功。
使用 OpenVINO™ 开发 PGNet 的推理程序
基于上述代码,结合PGNet的数据前处理和后处理代码,即可开发出完整的PGNet推理程序。
升级 PaddleOCR 自带预测程序,支持OpenVINO 推理
由于 PaddleOCR 自带的预测程序 tools/infer/predict_e2e.py,已经实现了 PGNet 的数据前处理和后处理代码,所以,只需要
稍微修改:
• tools/infer/predict_e2e.py
• tools/infer/utility.py
添加 OpenVINO 的推理代码,即可升级 PaddleOCR 对
OpenVINO 的支持。
上述代码,可以从 https://gitee.com/ppov-nuc/pgnetopenvino-inference 下载。下载后,请并替换同名文件。
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# 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.insert(0, os.path.abspath(os.path.join(__dir__, '../..')))
os.environ["FLAGS_allocator_strategy"] = 'auto_growth'
import cv2
import numpy as np
import time
import sys
import tools.infer.utility as utility
from ppocr.utils.logging import get_logger
from ppocr.utils.utility import get_image_file_list, check_and_read_gif
from ppocr.data import create_operators, transform
from ppocr.postprocess import build_post_process
logger = get_logger()
class TextE2E(object):
def __init__(self, args):
self.args = args
self.e2e_algorithm = args.e2e_algorithm
self.use_onnx = args.use_onnx
# OpenVINO Support here
self.use_openvino = args.use_openvino
pre_process_list = [{
'E2EResizeForTest': {}
}, {
'NormalizeImage': {
'std': [0.229, 0.224, 0.225],
'mean': [0.485, 0.456, 0.406],
'scale': '1./255.',
'order': 'hwc'
}
}, {
'ToCHWImage': None
}, {
'KeepKeys': {
'keep_keys': ['image', 'shape']
}
}]
postprocess_params = {}
if self.e2e_algorithm == "PGNet":
pre_process_list[0] = {
'E2EResizeForTest': {
'max_side_len': args.e2e_limit_side_len,
'valid_set': 'totaltext'
}
}
postprocess_params['name'] = 'PGPostProcess'
postprocess_params["score_thresh"] = args.e2e_pgnet_score_thresh
postprocess_params["character_dict_path"] = args.e2e_char_dict_path
postprocess_params["valid_set"] = args.e2e_pgnet_valid_set
postprocess_params["mode"] = args.e2e_pgnet_mode
else:
logger.info("unknown e2e_algorithm:{}".format(self.e2e_algorithm))
sys.exit(0)
self.preprocess_op = create_operators(pre_process_list)
self.postprocess_op = build_post_process(postprocess_params)
self.predictor, self.input_tensor, self.output_tensors, _ = utility.create_predictor(
args, 'e2e', logger) # paddle.jit.load(args.det_model_dir)
# self.predictor.eval()
def clip_det_res(self, points, img_height, img_width):
for pno in range(points.shape[0]):
points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1))
points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1))
return points
def filter_tag_det_res_only_clip(self, dt_boxes, image_shape):
img_height, img_width = image_shape[0:2]
dt_boxes_new = []
for box in dt_boxes:
box = self.clip_det_res(box, img_height, img_width)
dt_boxes_new.append(box)
dt_boxes = np.array(dt_boxes_new)
return dt_boxes
def __call__(self, img):
ori_im = img.copy()
data = {'image': img}
data = transform(data, self.preprocess_op)
img, shape_list = data
if img is None:
return None, 0
img = np.expand_dims(img, axis=0)
shape_list = np.expand_dims(shape_list, axis=0)
img = img.copy()
starttime = time.time()
if self.use_onnx:
input_dict = {}
input_dict[self.input_tensor.name] = img
outputs = self.predictor.run(self.output_tensors, input_dict)
preds = {}
preds['f_border'] = outputs[0]
preds['f_char'] = outputs[1]
preds['f_direction'] = outputs[2]
preds['f_score'] = outputs[3]
# OpenVINO Support Here
elif self.use_openvino:
outputs = self.predictor([img])
out_layers = self.predictor.output
preds = {}
preds['f_border'] = outputs[out_layers(0)]
preds['f_char'] = outputs[out_layers(1)]
preds['f_direction'] = outputs[out_layers(2)]
preds['f_score'] = outputs[out_layers(3)]
else:
self.input_tensor.copy_from_cpu(img)
self.predictor.run()
outputs = []
for output_tensor in self.output_tensors:
output = output_tensor.copy_to_cpu()
outputs.append(output)
preds = {}
if self.e2e_algorithm == 'PGNet':
preds['f_border'] = outputs[0]
preds['f_char'] = outputs[1]
preds['f_direction'] = outputs[2]
preds['f_score'] = outputs[3]
else:
raise NotImplementedError
post_result = self.postprocess_op(preds, shape_list)
points, strs = post_result['points'], post_result['texts']
dt_boxes = self.filter_tag_det_res_only_clip(points, ori_im.shape)
elapse = time.time() - starttime
return dt_boxes, strs, elapse
if __name__ == "__main__":
args = utility.parse_args()
image_file_list = get_image_file_list(args.image_dir)
text_detector = TextE2E(args)
count = 0
total_time = 0
draw_img_save = "./inference_results"
if not os.path.exists(draw_img_save):
os.makedirs(draw_img_save)
for image_file in image_file_list:
img, flag = check_and_read_gif(image_file)
if not flag:
img = cv2.imread(image_file)
if img is None:
logger.info("error in loading image:{}".format(image_file))
continue
points, strs, elapse = text_detector(img)
if count > 0:
total_time += elapse
count += 1
logger.info("Predict time of {}: {}".format(image_file, elapse))
src_im = utility.draw_e2e_res(points, strs, image_file)
img_name_pure = os.path.split(image_file)[-1]
img_path = os.path.join(draw_img_save,
"e2e_res_{}".format(img_name_pure))
cv2.imwrite(img_path, src_im)
logger.info("The visualized image saved in {}".format(img_path))
if count > 1:
logger.info("Avg Time: {}".format(total_time / (count - 1)))
借鉴 PaddleOCR 的前处理和后处理代码,实现OpenVINO 推理程序
直接使用 ppocr 中自带的前处理和后处理代码,可以轻松实现PGNet的OpenVINO 推理程序:
def clip_det_res(points, img_height, img_width):
for pno in range(points.shape[0]):
points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1))
points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1))
return points
# 定义 filter_tag_det_res_only_clip 函数
def filter_tag_det_res_only_clip(dt_boxes, image_shape):
img_height, img_width = image_shape[0:2]
dt_boxes_new = []
for box in dt_boxes:
box = clip_det_res(box, img_height, img_width)
dt_boxes_new.append(box)
dt_boxes = np.array(dt_boxes_new)
return dt_boxes
from openvino.runtime import Core
from ppocr.data import create_operators, transform
from ppocr.postprocess import build_post_process
import cv2
import numpy as np
import time
# 指定 PGNet 模型路径
pgnet_path = "../e2e_server_pgnetA_infer/inference.pdmodel"
# 创建 Core 对象
core = Core()
# 载入并编译 PGNet 模型
pgnet = core.compile_model(model=pgnet_path, device_name="CPU")
# 创建 preprocess_op
pre_process_list = [{
'E2EResizeForTest': {
'max_side_len': 768,
'valid_set': 'totaltext'}
}, {
'NormalizeImage': {
'std': [0.229, 0.224, 0.225],
'mean': [0.485, 0.456, 0.406],
'scale': '1./255.',
'order': 'hwc'
}
}, {
'ToCHWImage': None
}, {
'KeepKeys': {
'keep_keys': ['image', 'shape']
}
}]
preprocess_op = create_operators(pre_process_list)
# 创建 postprocess_op
postprocess_params = {}
postprocess_params['name'] = 'PGPostProcess'
postprocess_params["score_thresh"] = 0.5
postprocess_params["character_dict_path"] = "../ic15_dict.txt"
postprocess_params["valid_set"] = 'totaltext'
postprocess_params["mode"] = 'fast'
postprocess_op = build_post_process(postprocess_params)
# 载入图像数据并实现预处理
image_path = './doc/imgs_en/img623.jpg'
image = cv2.imread(image_path)
data = {'image': image}
data = transform(data, preprocess_op)
img, shape_list = data
img = np.expand_dims(img, axis=0)
shape_list = np.expand_dims(shape_list, axis=0)
starttime = time.time()
# Do the inference by OpenVINO
outputs = pgnet([img])
out_layers = pgnet.output
preds = {}
preds['f_border'] = outputs[out_layers(0)]
preds['f_char'] = outputs[out_layers(1)]
preds['f_direction'] = outputs[out_layers(2)]
preds['f_score'] = outputs[out_layers(3)]
post_result = postprocess_op(preds, shape_list)
points, strs = post_result['points'], post_result['texts']
dt_boxes = filter_tag_det_res_only_clip(points, image.shape)
elapse = time.time() - starttime
#print(f"points:{points}s")
print(f"strs:{strs}s")
print(f"Predict time: {elapse}s")
imshow_dst()
运行结果,如下图所示:
总结
飞桨版的 PGNet 是一个易学易用、文档化和工程化做的非常好的[3]、端到端的、可以检测弯曲文字的模型。由于从 OpenVINO™ 2022.1 版本开始,OpenVINO™ Runtime 已经支持了直接读取飞桨模型,且 OpenVINO™ 的API 简单易用,所以,可以很容易的升级 PaddleOCR 自带的预测程序 tools/infer/predict_e2e.py 支持 OpenVINO™ 推理计算,或者从零开发 OpenVINO™ 推理程序。
经过 OpenVINO™ 优化后,PGNet 的 CPU 推理计算效率大大提升,平均运行时间从 5.09s,提升到 1.33s,效率提升非常明显。
注意:运行时间,仅供参考,在不同 CPU 上,运行的时间会不一样。
个人介绍
- 林旭 某小厂算法架构师
- AICA六期班学员
- 飞桨PFCC成员
参考资料
[1] PGNet: Real-time Arbitrarily-Shaped Text Spotting
with Point Gathering Network, https://www.aaai.org/AAAI21Papers/AAAI-2885.WangP.pdf
[2] PaddleOCR FAQ: https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/FAQ.md#13
[3] PGNet repo: https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.5/doc/doc_ch/algorithm_e2e_pgnet.md
此文章为搬运
原项目链接
所有评论(0)