要点:
1 预测部署简介与总览
本章主要介绍PP-OCRv2系统的高性能推理方法、服务化部署方法以及端侧部署方法。通过本章的学习,您可以学习到:
在前面几个章节中,我们通过模型训练的方法,得到了训练好的模型,在使用它去预测的时候,我们首先需要定义好模型,然后加载训练好的模型,再将预处理之后的数据送进网络中进行预测、后处理,得到最终的结果。使用这种方法去进行预测,调试方便,但是预测效率比较低下。
针对上面的问题,对于训练得到的模型,一般有下面2种离线预测的方式。
具体地,PaddleOCR 针对不同应用场景,提供了三种预测部署方案。
本章基于PP-OCRv2,介绍文本检测、识别以及系统串联预测推理与部署过程。
体验本章节内容需要首先下载PaddleOCR代码,安装相关依赖,具体命令如下
import os
os.chdir("/home/aistudio")
# 下载代码
!git clone https://gitee.com/paddlepaddle/PaddleOCR.git
os.chdir("/home/aistudio/PaddleOCR")
# 安装运行所需要的whl包
!pip install -U pip
!pip install -r requirements.txt
# VQA任务中需要用到该库
!pip install paddlenlp==2.2.1
# 导入一些库
import cv2
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import os
在项目中,模型的推理性能直接影响项目成本,因此我们期望一个训练好的模型的模型可以拥有更快的推理速度。直接基于训练引擎进行预测,模型中包含与训练相关的算子,因此效率一般较低;而且需要定义模型,难以与训练代码解耦。Paddle Inference应运而生。它是飞桨的原生推理库,作用于服务器端和云端,提供高性能的推理能力。由于能力直接基于飞桨的训练算子,因此Paddle Inference 可以通用支持飞桨训练出的所有模型。
考虑到大家的使用场景差异很大,Paddle Inference针对不同平台不同的应用场景进行了深度的适配优化,做到高吞吐、低时延,保证了飞桨模型在服务器端即训即用,快速部署。
本章主要介绍基于Paddle Inference的PP-OCRv2预测推理过程,更多关于Paddle Inference的介绍可以参考:Paddle Inference 介绍。
在基于Paddle Inference进行模型推理时,一般有以下几个步骤。
PP-OCRv2系统包含文字检测、方向分类器和文字识别3个模型,下面分别介绍这3个模型基于Paddle Inference的推理过程。
PaddleOCR中,在基于文字检测模型进行推理时,需要通过参数image_dir
指定单张图像或者图像集合的路径、参数det_model_dir
, 指定检测的 inference
模型路径。
下面进行最新的超轻量文本检测模型推理实战,更多的模型和使用方法请参考文本检测预测教程。
更多等算法超参数的介绍可以参考PaddleOCR Inference推理相关参数介绍。
在最开始已经安装好了Paddle以及相应的依赖,这里环境已经准备好了。
检测示例数据在doc/imgs
文件夹下面,部分数据如下所示。
# 切换目录
os.chdir("/home/aistudio/PaddleOCR")
# 查看数据
!ls doc/imgs/
# 选择2张图像可视化
img1 = cv2.imread("doc/imgs/00006737.jpg")
img2 = cv2.imread("doc/imgs/00056221.jpg")
plt.figure(figsize=(15, 6))
plt.subplot(1, 2, 1)
plt.imshow(img1[:,:,::-1])
plt.subplot(1, 2, 2)
plt.imshow(img2[:,:,::-1])
plt.show()
下载推理模型并解压,放在inference
目录下面。
# 下载模型
!mkdir inference
!cd inference && wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar -O ch_PP-OCRv2_det_infer.tar && tar -xf ch_PP-OCRv2_det_infer.tar
!tree -h inference/ch_PP-OCRv2_det_infer
# 参考代码
# https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/tools/export_model.py
# 下载预训练模型
!wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar && tar -xf ch_PP-OCRv2_det_distill_train.tar && rm ch_PP-OCRv2_det_distill_train.tar
# 导出推理模型
!python tools/export_model.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml \
-o Global.pretrained_model="ch_PP-OCRv2_det_distill_train/best_accuracy" \
Global.save_inference_dir="./my_model"
# PP-OCRv2检测模型包含3个子网络:教师、学生、学生2,因此导出时,包含3个子文件,实际推理时,使用其中1个学生网络进行推理即可
!tree -h my_model
我们先来看看加载加载推理模型预测得到的结果。
# 参考代码
# https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/tools/infer/predict_det.py
# 预测
!python tools/infer/predict_det.py --image_dir="./doc/imgs/00018069.jpg" --det_model_dir="./inference/ch_PP-OCRv2_det_infer" --use_gpu=False
# 读取图像并显示出来,显示结果
plt.figure(figsize=(20, 8))
img_ori = cv2.imread("./doc/imgs/00018069.jpg")
img_out = cv2.imread("./inference_results/det_res_00018069.jpg")
plt.subplot(1, 2, 1)
plt.imshow(img_ori[:,:,::-1])
plt.subplot(1, 2, 2)
plt.imshow(img_out[:,:,::-1])
plt.show()
首先需要定义参数设置如下所示。更多参数的介绍可以参考:PaddleOCR推理过程参数介绍。
# 参考代码
# https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/tools/infer/utility.py
import argparse
import os
import sys
import cv2
import numpy as np
import paddle
from PIL import Image, ImageDraw, ImageFont
import math
from paddle import inference
import time
from ppocr.utils.logging import get_logger
def str2bool(v):
return v.lower() in ("true", "t", "1")
def init_args():
parser = argparse.ArgumentParser()
# params for prediction engine
parser.add_argument("--use_gpu", type=str2bool, default=True)
parser.add_argument("--ir_optim", type=str2bool, default=True)
parser.add_argument("--use_tensorrt", type=str2bool, default=False)
parser.add_argument("--min_subgraph_size", type=int, default=15)
parser.add_argument("--precision", type=str, default="fp32")
parser.add_argument("--gpu_mem", type=int, default=500)
# params for text detector
parser.add_argument("--image_dir", type=str)
parser.add_argument("--det_algorithm", type=str, default='DB')
parser.add_argument("--det_model_dir", type=str)
parser.add_argument("--det_limit_side_len", type=float, default=960)
parser.add_argument("--det_limit_type", type=str, default='max')
# DB parmas
parser.add_argument("--det_db_thresh", type=float, default=0.3)
parser.add_argument("--det_db_box_thresh", type=float, default=0.6)
parser.add_argument("--det_db_unclip_ratio", type=float, default=1.5)
parser.add_argument("--max_batch_size", type=int, default=10)
parser.add_argument("--use_dilation", type=str2bool, default=False)
parser.add_argument("--det_db_score_mode", type=str, default="fast")
# EAST parmas
parser.add_argument("--det_east_score_thresh", type=float, default=0.8)
parser.add_argument("--det_east_cover_thresh", type=float, default=0.1)
parser.add_argument("--det_east_nms_thresh", type=float, default=0.2)
# SAST parmas
parser.add_argument("--det_sast_score_thresh", type=float, default=0.5)
parser.add_argument("--det_sast_nms_thresh", type=float, default=0.2)
parser.add_argument("--det_sast_polygon", type=str2bool, default=False)
# PSE parmas
parser.add_argument("--det_pse_thresh", type=float, default=0)
parser.add_argument("--det_pse_box_thresh", type=float, default=0.85)
parser.add_argument("--det_pse_min_area", type=float, default=16)
parser.add_argument("--det_pse_box_type", type=str, default='box')
parser.add_argument("--det_pse_scale", type=int, default=1)
# params for text recognizer
parser.add_argument("--rec_algorithm", type=str, default='CRNN')
parser.add_argument("--rec_model_dir", type=str)
parser.add_argument("--rec_image_shape", type=str, default="3, 32, 320")
parser.add_argument("--rec_batch_num", type=int, default=6)
parser.add_argument("--max_text_length", type=int, default=25)
parser.add_argument(
"--rec_char_dict_path",
type=str,
default="./ppocr/utils/ppocr_keys_v1.txt")
parser.add_argument("--use_space_char", type=str2bool, default=True)
parser.add_argument(
"--vis_font_path", type=str, default="./doc/fonts/simfang.ttf")
parser.add_argument("--drop_score", type=float, default=0.5)
# params for e2e
parser.add_argument("--e2e_algorithm", type=str, default='PGNet')
parser.add_argument("--e2e_model_dir", type=str)
parser.add_argument("--e2e_limit_side_len", type=float, default=768)
parser.add_argument("--e2e_limit_type", type=str, default='max')
# PGNet parmas
parser.add_argument("--e2e_pgnet_score_thresh", type=float, default=0.5)
parser.add_argument(
"--e2e_char_dict_path", type=str, default="./ppocr/utils/ic15_dict.txt")
parser.add_argument("--e2e_pgnet_valid_set", type=str, default='totaltext')
parser.add_argument("--e2e_pgnet_mode", type=str, default='fast')
# params for text classifier
parser.add_argument("--use_angle_cls", type=str2bool, default=False)
parser.add_argument("--cls_model_dir", type=str)
parser.add_argument("--cls_image_shape", type=str, default="3, 48, 192")
parser.add_argument("--label_list", type=list, default=['0', '180'])
parser.add_argument("--cls_batch_num", type=int, default=6)
parser.add_argument("--cls_thresh", type=float, default=0.9)
parser.add_argument("--enable_mkldnn", type=str2bool, default=False)
parser.add_argument("--cpu_threads", type=int, default=10)
parser.add_argument("--use_pdserving", type=str2bool, default=False)
parser.add_argument("--warmup", type=str2bool, default=False)
#
parser.add_argument(
"--draw_img_save_dir", type=str, default="./inference_results")
parser.add_argument("--save_crop_res", type=str2bool, default=False)
parser.add_argument("--crop_res_save_dir", type=str, default="./output")
# multi-process
parser.add_argument("--use_mp", type=str2bool, default=False)
parser.add_argument("--total_process_num", type=int, default=1)
parser.add_argument("--process_id", type=int, default=0)
parser.add_argument("--benchmark", type=str2bool, default=False)
parser.add_argument("--save_log_path", type=str, default="./log_output/")
parser.add_argument("--show_log", type=str2bool, default=True)
parser.add_argument("--use_onnx", type=str2bool, default=False)
# 这里需要注意,添加这个是因为直接在notebook中解析的话,sys.argv会在后面添加下面的内容,导致解析失败
# '-f', '/home/aistudio/.local/share/jupyter/runtime/kernel-e1221262-c656-4129-896f-1b197b6b782c.json'
parser.add_argument("-f", type=str, default=None)
return parser
def parse_args():
parser = init_args()
return parser.parse_args()
下面和大家具体看下文字检测的具体代码。
# 参考代码
# https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/tools/infer/predict_det.py
import os
import sys
import cv2
import numpy as np
import time
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
import json
logger = get_logger()
# 文字检测类
class TextDetector(object):
def __init__(self, args):
self.args = args
self.det_algorithm = args.det_algorithm
pre_process_list = [{
'DetResizeForTest': {
'limit_side_len': args.det_limit_side_len,
'limit_type': args.det_limit_type,
}
}, {
'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.det_algorithm == "DB":
postprocess_params['name'] = 'DBPostProcess'
postprocess_params["thresh"] = args.det_db_thresh
postprocess_params["box_thresh"] = args.det_db_box_thresh
postprocess_params["max_candidates"] = 1000
postprocess_params["unclip_ratio"] = args.det_db_unclip_ratio
postprocess_params["use_dilation"] = args.use_dilation
postprocess_params["score_mode"] = args.det_db_score_mode
else:
logger.info("unknown det_algorithm:{}".format(self.det_algorithm))
sys.exit(0)
# 初始化预测引擎
self.predictor, self.input_tensor, self.output_tensors, self.config = utility.create_predictor(
args, 'det', logger)
# 构建预处理算子
self.preprocess_op = create_operators(pre_process_list)
# 构建后处理算子
self.postprocess_op = build_post_process(postprocess_params)
def order_points_clockwise(self, pts):
"""
参考: https://github.com/jrosebr1/imutils/blob/master/imutils/perspective.py
对检测出来的点进行按照顺时针排序
"""
xSorted = pts[np.argsort(pts[:, 0]), :]
leftMost = xSorted[:2, :]
rightMost = xSorted[2:, :]
leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
(tl, bl) = leftMost
rightMost = rightMost[np.argsort(rightMost[:, 1]), :]
(tr, br) = rightMost
rect = np.array([tl, tr, br, bl], dtype="float32")
return rect
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(self, dt_boxes, image_shape):
# 去除小于特定尺寸的检测结果
img_height, img_width = image_shape[0:2]
dt_boxes_new = []
for box in dt_boxes:
box = self.order_points_clockwise(box)
box = self.clip_det_res(box, img_height, img_width)
rect_width = int(np.linalg.norm(box[0] - box[1]))
rect_height = int(np.linalg.norm(box[0] - box[3]))
if rect_width <= 3 or rect_height <= 3:
continue
dt_boxes_new.append(box)
dt_boxes = np.array(dt_boxes_new)
return dt_boxes
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}
st = time.time()
# 数据预处理
data = transform(data, self.preprocess_op)
img, shape_list = data
if img is None:
return None, 0
# 扩展bs维度:CHW -> NCHW
img = np.expand_dims(img, axis=0)
shape_list = np.expand_dims(shape_list, axis=0)
img = img.copy()
# 将数据拷贝到预测引擎中
self.input_tensor.copy_from_cpu(img)
# 自动推理
self.predictor.run()
outputs = []
# 将返回结果从预测引擎中拷贝回CPU
for output_tensor in self.output_tensors:
output = output_tensor.copy_to_cpu()
outputs.append(output)
preds = {}
if self.det_algorithm in ['DB', 'PSE']:
preds['maps'] = outputs[0]
else:
raise NotImplementedError
# 后处理
post_result = self.postprocess_op(preds, shape_list)
dt_boxes = post_result[0]['points']
dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im.shape)
et = time.time()
return dt_boxes, et - st
# 设置参数
args = parse_args()
args.det_model_dir = "./inference/ch_PP-OCRv2_det_infer"
args.image_dir = "./doc/imgs/00018069.jpg"
# 获取图片列表
image_file_list = get_image_file_list(args.image_dir)
# 创建文本检测器对象
text_detector = TextDetector(args)
count = 0
total_time = 0
draw_img_save = "./inference_results"
if not os.path.exists(draw_img_save):
os.makedirs(draw_img_save)
save_results = []
for image_file in image_file_list:
img = cv2.imread(image_file)
if img is None:
logger.info("error in loading image:{}".format(image_file))
continue
st = time.time()
dt_boxes, _ = text_detector(img)
elapse = time.time() - st
if count > 0:
total_time += elapse
count += 1
save_pred = os.path.basename(image_file) + "\t" + str(
json.dumps(np.array(dt_boxes).astype(np.int32).tolist())) + "\n"
save_results.append(save_pred)
logger.info(save_pred)
logger.info("The predict time of {}: {}".format(image_file, elapse))
src_im = utility.draw_text_det_res(dt_boxes, image_file)
img_name_pure = os.path.split(image_file)[-1]
img_path = os.path.join(draw_img_save,
"det_res_{}".format(img_name_pure))
cv2.imwrite(img_path, src_im)
logger.info("The visualized image saved in {}".format(img_path))
break
with open(os.path.join(draw_img_save, "det_results.txt"), 'w') as f:
f.writelines(save_results)
f.close()
plt.figure(figsize=(10, 10))
plt.imshow(src_im[:, :, ::-1])
plt.show()
上面就完成了完整的文本检测流程。
同样地,对于方向分类器模型,我们也可以使用下面的命令快速体验其功能。
# 下载模型
!cd inference && wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar -O ch_ppocr_mobile_v2.0_cls_infer.tar && tar -xf ch_ppocr_mobile_v2.0_cls_infer.tar
# 预测
!python tools/infer/predict_cls.py \
--image_dir="./doc/imgs_words/ch/word_1.jpg" \
--cls_model_dir="./inference/ch_ppocr_mobile_v2.0_cls_infer" \
--use_gpu=False
# 画图
img = cv2.imread("./doc/imgs_words/ch/word_1.jpg")
plt.imshow(img[:,:,::-1])
plt.show()
图片的方向是正向水平文本,预测结果正确。
具体地,方向分类器的具体实现代码如下所示。
# 参考代码
# https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/tools/infer/predict_cls.py
import copy
# 方向分类器实现的类
class TextClassifier(object):
def __init__(self, args):
self.cls_image_shape = [int(v) for v in args.cls_image_shape.split(",")]
self.cls_batch_num = args.cls_batch_num
self.cls_thresh = args.cls_thresh
postprocess_params = {
'name': 'ClsPostProcess',
"label_list": args.label_list,
}
# 后处理算子
self.postprocess_op = build_post_process(postprocess_params)
# 初始化预测引擎
self.predictor, self.input_tensor, self.output_tensors, _ = \
utility.create_predictor(args, 'cls', logger)
# 对图像进行resize并且normalize
def resize_norm_img(self, img):
imgC, imgH, imgW = self.cls_image_shape
h = img.shape[0]
w = img.shape[1]
ratio = w / float(h)
if math.ceil(imgH * ratio) > imgW:
resized_w = imgW
else:
resized_w = int(math.ceil(imgH * ratio))
resized_image = cv2.resize(img, (resized_w, imgH))
resized_image = resized_image.astype('float32')
if self.cls_image_shape[0] == 1:
resized_image = resized_image / 255
resized_image = resized_image[np.newaxis, :]
else:
resized_image = resized_image.transpose((2, 0, 1)) / 255
resized_image -= 0.5
resized_image /= 0.5
padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)
padding_im[:, :, 0:resized_w] = resized_image
return padding_im
def __call__(self, img_list):
img_list = copy.deepcopy(img_list)
img_num = len(img_list)
# 记录宽高比
width_list = []
for img in img_list:
width_list.append(img.shape[1] / float(img.shape[0]))
# 排序,加速后续的预处理过程
indices = np.argsort(np.array(width_list))
cls_res = [['', 0.0]] * img_num
batch_num = self.cls_batch_num
elapse = 0
for beg_img_no in range(0, img_num, batch_num):
end_img_no = min(img_num, beg_img_no + batch_num)
norm_img_batch = []
max_wh_ratio = 0
starttime = time.time()
# 预处理数据,组batch
for ino in range(beg_img_no, end_img_no):
h, w = img_list[indices[ino]].shape[0:2]
wh_ratio = w * 1.0 / h
max_wh_ratio = max(max_wh_ratio, wh_ratio)
for ino in range(beg_img_no, end_img_no):
norm_img = self.resize_norm_img(img_list[indices[ino]])
norm_img = norm_img[np.newaxis, :]
norm_img_batch.append(norm_img)
norm_img_batch = np.concatenate(norm_img_batch)
norm_img_batch = norm_img_batch.copy()
# 将数据拷贝到预测引擎
self.input_tensor.copy_from_cpu(norm_img_batch)
# 自动推理过程
self.predictor.run()
# 将数据拷贝回CPU
prob_out = self.output_tensors[0].copy_to_cpu()
# 后处理
cls_result = self.postprocess_op(prob_out)
elapse += time.time() - starttime
for rno in range(len(cls_result)):
label, score = cls_result[rno]
cls_res[indices[beg_img_no + rno]] = [label, score]
if '180' in label and score > self.cls_thresh:
img_list[indices[beg_img_no + rno]] = cv2.rotate(
img_list[indices[beg_img_no + rno]], 1)
return img_list, cls_res, elapse
args = parse_args()
args.cls_model_dir = "./inference/ch_ppocr_mobile_v2.0_cls_infer"
args.image_dir = "./doc/imgs_words/ch/word_4.jpg"
image_file_list = get_image_file_list(args.image_dir)
text_classifier = TextClassifier(args)
valid_image_file_list = []
img_list = []
for image_file in image_file_list:
img = cv2.imread(image_file)
# 预测之前对图像旋转180度
# img = cv2.rotate(img, cv2.ROTATE_180)
if img is None:
logger.info("error in loading image:{}".format(image_file))
continue
valid_image_file_list.append(image_file)
img_list.append(img)
img_list, cls_res, predict_time = text_classifier(img_list)
for ino in range(len(img_list)):
logger.info("Predicts of {}:{}".format(valid_image_file_list[ino],
cls_res[ino]))
plt.imshow(img[:,:,::-1])
plt.show()
这里我们也可以将图像旋转180度之后,看下方向分类器的分类效果。
上面就完成了完整的方向分类器的推理过程。
对于文字识别模型,我们也可以使用下面的命令快速体验其功能。
# 下载模型
!cd inference && wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar -O ch_PP-OCRv2_rec_infer.tar && tar -xf ch_PP-OCRv2_rec_infer.tar
# 预测
!python tools/infer/predict_rec.py \
--image_dir="./doc/imgs_words/ch/word_4.jpg" \
--rec_model_dir="./inference/ch_PP-OCRv2_rec_infer" \
--use_gpu=False
# 读取图像并显示
img = cv2.imread("./doc/imgs_words/ch/word_4.jpg")
plt.imshow(img[:,:,::-1])
plt.show()
文字识别的具体代码如下所示。
# 参考代码
# https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/tools/infer/predict_rec.py
class TextRecognizer(object):
def __init__(self, args):
self.rec_image_shape = [int(v) for v in args.rec_image_shape.split(",")]
self.rec_batch_num = args.rec_batch_num
self.rec_algorithm = args.rec_algorithm
postprocess_params = {
'name': 'CTCLabelDecode',
"character_dict_path": args.rec_char_dict_path,
"use_space_char": args.use_space_char
}
# 初始化预测引擎
self.predictor, self.input_tensor, self.output_tensors, self.config = \
utility.create_predictor(args, 'rec', logger)
# 初始化后处理过程
self.postprocess_op = build_post_process(postprocess_params)
# 预处理核心逻辑
def resize_norm_img(self, img, max_wh_ratio):
imgC, imgH, imgW = self.rec_image_shape
assert imgC == img.shape[2]
imgW = int((32 * max_wh_ratio))
h, w = img.shape[:2]
ratio = w / float(h)
if math.ceil(imgH * ratio) > imgW:
resized_w = imgW
else:
resized_w = int(math.ceil(imgH * ratio))
resized_image = cv2.resize(img, (resized_w, imgH))
resized_image = resized_image.astype('float32')
# [0, 255] -> [0, 1]
resized_image = resized_image.transpose((2, 0, 1)) / 255
# [0, 1] -> [-0.5, 0.5]
resized_image -= 0.5
# [-0.5, 0.5] -> [-1, 1]
resized_image /= 0.5
padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)
padding_im[:, :, 0:resized_w] = resized_image
return padding_im
# 对图像列表进行处理
def __call__(self, img_list):
img_num = len(img_list)
# 记录宽高比
width_list = []
for img in img_list:
width_list.append(img.shape[1] / float(img.shape[0]))
# 排序,加速处理过程
indices = np.argsort(np.array(width_list))
rec_res = [['', 0.0]] * img_num
batch_num = self.rec_batch_num
st = time.time()
for beg_img_no in range(0, img_num, batch_num):
end_img_no = min(img_num, beg_img_no + batch_num)
norm_img_batch = []
max_wh_ratio = 0
for ino in range(beg_img_no, end_img_no):
h, w = img_list[indices[ino]].shape[0:2]
wh_ratio = w * 1.0 / h
max_wh_ratio = max(max_wh_ratio, wh_ratio)
# 调用预处理方法并组batch
for ino in range(beg_img_no, end_img_no):
norm_img = self.resize_norm_img(img_list[indices[ino]],
max_wh_ratio)
norm_img = norm_img[np.newaxis, :]
norm_img_batch.append(norm_img)
norm_img_batch = np.concatenate(norm_img_batch)
norm_img_batch = norm_img_batch.copy()
# 将数据拷贝到预测引擎中
self.input_tensor.copy_from_cpu(norm_img_batch)
# 自动化推理过程
self.predictor.run()
outputs = []
# 将数据拷贝到CPU
for output_tensor in self.output_tensors:
output = output_tensor.copy_to_cpu()
outputs.append(output)
if len(outputs) != 1:
preds = outputs
else:
preds = outputs[0]
# 后处理
rec_result = self.postprocess_op(preds)
for rno in range(len(rec_result)):
rec_res[indices[beg_img_no + rno]] = rec_result[rno]
return rec_res, time.time() - st
# 定义参数
args = parse_args()
args.rec_model_dir = "./inference/ch_PP-OCRv2_rec_infer"
args.image_dir = "./doc/imgs_words/ch/word_4.jpg"
img_list = []
image_file_list = get_image_file_list(args.image_dir)
text_recognizer = TextRecognizer(args)
valid_image_file_list = []
for image_file in image_file_list:
img = cv2.imread(image_file)
if img is None:
logger.info("error in loading image:{}".format(image_file))
continue
valid_image_file_list.append(image_file)
img_list.append(img)
rec_res, _ = text_recognizer(img_list)
for ino in range(len(img_list)):
logger.info("Predicts of {}:{}".format(valid_image_file_list[ino],
rec_res[ino]))
前面的内容给大家介绍了PP-OCRv2系统中,检测、方向分类器、识别模型的单独推理过程。为了方便大家端到端地使用,我们将这三个模块串联起来,组成了PP-OCRv2系统,并提供了相应的预测脚本。
在执行PP-OCRv2的系统推理时,需要通过参数image_dir
指定单张图像或者图像集合的路径、参数det_model_dir
, cls_model_dir
和 rec_model_dir
分别指定检测、方向分类和识别的 inference
模型路径。参数 use_angle_cls
用于控制是否启用方向分类模型。use_mp
表示是否使用多进程。total_process_num
表示在使用多进程时的进程数。
以图像文件./doc/imgs/00018069.jpg
为例,预测的原始图像如下。
如果串联预测时使用方向分类器,则可以使用下面的命令进行预测。
# 使用方向分类器,运行PP-OCRv2系统
!python tools/infer/predict_system.py \
--image_dir="./doc/imgs/00018069.jpg" \
--det_model_dir="./inference/ch_PP-OCRv2_det_infer/" \
--cls_model_dir="./inference/ch_ppocr_mobile_v2.0_cls_infer/" \
--rec_model_dir="./inference/ch_PP-OCRv2_rec_infer/" \
--use_angle_cls=True
# 可视化
img = cv2.imread("./inference_results/00018069.jpg")
plt.figure(figsize=(20, 8))
plt.imshow(img[..., ::-1])
plt.show()
可视化识别结果默认保存到 ./inference_results
文件夹里面。
在图象中可视化出了检测框和识别结果,在上面的notebook中也打印出了具体的识别文件以及文件读取路径信息。
如果希望保存裁剪后的识别结果,可以将save_crop_res
参数设置为True,最终结果保存在output
目录下,其中部分裁剪后图像如下所示。保存的结果可以用于后续的识别模型标注与训练。
# 裁剪文字检测的结果图像并保存
!python tools/infer/predict_system.py \
--image_dir="./doc/imgs/00018069.jpg" \
--det_model_dir="./inference/ch_PP-OCRv2_det_infer/" \
--cls_model_dir="./inference/ch_ppocr_mobile_v2.0_cls_infer/" \
--rec_model_dir="./inference/ch_PP-OCRv2_rec_infer/" \
--use_angle_cls=True \
--save_crop_res=True
!ls output
plt.figure(figsize=(8, 8))
plt.imshow(cv2.imread("./doc/imgs/00018069.jpg")[:, :, ::-1])
plt.show()
plt.figure(figsize=(14, 4))
plt.subplot(1,3,1)
plt.imshow(cv2.imread("output/mg_crop_0.jpg")[:, :, ::-1])
plt.subplot(1,3,2)
plt.imshow(cv2.imread("output/mg_crop_1.jpg")[:, :, ::-1])
plt.subplot(1,3,3)
plt.imshow(cv2.imread("output/mg_crop_2.jpg")[:, :, ::-1])
plt.show()
串联预测通过TextSystem
类进行实现,其具体实现过程与函数定义如下。
# 参考代码:https://github.com/PaddlePaddle/PaddleOCR/blob/release%2F2.4/tools/infer/predict_system.py
from tools.infer.utility import draw_ocr_box_txt, get_rotate_crop_image
from ppocr.utils.utility import get_image_file_list
class TextSystem(object):
# 初始化函数
def __init__(self, args):
self.args = args
# 如果不希望显示log,可以将show_log设置为False
if not args.show_log:
logger.setLevel(logging.INFO)
# 定义文本检测模型预测引擎
self.text_detector = TextDetector(args)
# 定义文本识别模型预测引擎
self.text_recognizer = TextRecognizer(args)
# 是否使用方向分类器
self.use_angle_cls = args.use_angle_cls
# 得分阈值,根据该阈值判断检测与识别结果是否需要进行可视化或者返回
self.drop_score = args.drop_score
# 定义方向分类器预测引擎
if self.use_angle_cls:
self.text_classifier = TextClassifier(args)
# 保存文本检测结果图像
def draw_crop_rec_res(self, output_dir, img_crop_list, rec_res):
os.makedirs(output_dir, exist_ok=True)
bbox_num = len(img_crop_list)
for bno in range(bbox_num):
cv2.imwrite(
os.path.join(output_dir,
f"mg_crop_{bno+self.crop_image_res_index}.jpg"),
img_crop_list[bno])
logger.debug(f"{bno}, {rec_res[bno]}")
self.crop_image_res_index += bbox_num
# 核心预测函数
def __call__(self, img, cls=True):
ori_im = img.copy()
# 获取检测文本检测结果
dt_boxes, elapse = self.text_detector(img)
logger.debug("dt_boxes num : {}, elapse : {}".format(
len(dt_boxes), elapse))
if dt_boxes is None:
return None, None
img_crop_list = []
# 对检测框进行排序,顺序为:优先从上到下,其次从左到右
dt_boxes = sorted_boxes(dt_boxes)
# 对检测结果进行透视变换与校正
for bno in range(len(dt_boxes)):
tmp_box = copy.deepcopy(dt_boxes[bno])
img_crop = get_rotate_crop_image(ori_im, tmp_box)
img_crop_list.append(img_crop)
# 使用方向分类器对检测结果进行转正
if self.use_angle_cls and cls:
img_crop_list, angle_list, elapse = self.text_classifier(
img_crop_list)
logger.debug("cls num : {}, elapse : {}".format(
len(img_crop_list), elapse))
# 获取文本识别结果
rec_res, elapse = self.text_recognizer(img_crop_list)
logger.debug("rec_res num : {}, elapse : {}".format(
len(rec_res), elapse))
# 保存经过校正之后的文本检测图像
if self.args.save_crop_res:
self.draw_crop_rec_res(self.args.crop_res_save_dir, img_crop_list,
rec_res)
filter_boxes, filter_rec_res = [], []
# 根据识别得分的阈值对结果进行过滤,如果得分小于阈值,就过滤掉
for box, rec_result in zip(dt_boxes, rec_res):
text, score = rec_result
if score >= self.drop_score:
filter_boxes.append(box)
filter_rec_res.append(rec_result)
return filter_boxes, filter_rec_res
def sorted_boxes(dt_boxes):
# 对检测框进行排序:优先从上到下,其次从左到右
num_boxes = dt_boxes.shape[0]
sorted_boxes = sorted(dt_boxes, key=lambda x: (x[0][1], x[0][0]))
_boxes = list(sorted_boxes)
for i in range(num_boxes - 1):
if abs(_boxes[i + 1][0][1] - _boxes[i][0][1]) < 10 and \
(_boxes[i + 1][0][0] < _boxes[i][0][0]):
tmp = _boxes[i]
_boxes[i] = _boxes[i + 1]
_boxes[i + 1] = tmp
return _boxes
args = parse_args()
args.cls_model_dir = "./inference/ch_ppocr_mobile_v2.0_cls_infer"
args.det_model_dir="./inference/ch_PP-OCRv2_det_infer/"
args.rec_model_dir="./inference/ch_PP-OCRv2_rec_infer/"
args.image_dir = "./doc/imgs/00018069.jpg"
args.use_angle_cls=True
args.use_gpu=True
image_file_list = get_image_file_list(args.image_dir)
image_file_list = image_file_list[args.process_id::args.total_process_num]
text_sys = TextSystem(args)
is_visualize = True
font_path = args.vis_font_path
drop_score = args.drop_score
total_time = 0
cpu_mem, gpu_mem, gpu_util = 0, 0, 0
_st = time.time()
count = 0
for idx, image_file in enumerate(image_file_list):
img = cv2.imread(image_file)
if img is None:
logger.debug("error in loading image:{}".format(image_file))
continue
starttime = time.time()
dt_boxes, rec_res = text_sys(img)
elapse = time.time() - starttime
total_time += elapse
logger.debug(
str(idx) + " Predict time of %s: %.3fs" % (image_file, elapse))
for text, score in rec_res:
logger.debug("{}, {:.3f}".format(text, score))
if is_visualize:
image = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
boxes = dt_boxes
txts = [rec_res[i][0] for i in range(len(rec_res))]
scores = [rec_res[i][1] for i in range(len(rec_res))]
draw_img = draw_ocr_box_txt(
image,
boxes,
txts,
scores,
drop_score=drop_score,
font_path=font_path)
draw_img_save_dir = args.draw_img_save_dir
os.makedirs(draw_img_save_dir, exist_ok=True)
cv2.imwrite(
os.path.join(draw_img_save_dir, os.path.basename(image_file)),
draw_img[:, :, ::-1])
logger.debug("The visualized image saved in {}".format(
os.path.join(draw_img_save_dir, os.path.basename(image_file))))
logger.info("The predict total time is {}".format(time.time() - _st))
plt.figure(figsize=(8, 8))
plt.imshow(image)
plt.show()
plt.figure(figsize=(16, 8))
plt.imshow(draw_img)
plt.show()
为了更加方便快速体验OCR文本检测与识别模型,PaddleOCR提供了基于Paddle Inference预测引擎的whl包,方便您一键安装,体验PaddleOCR。
使用pip安装paddleocr的whl包,命令如下。
!pip install "paddleocr==2.3.0.2"
# 如果希望获取最新特性,可以基于源码编译安装
# python3 setup.py bdist_wheel
# pip3 install dist/paddleocr-x.x.x-py3-none-any.whl # x.x.x是paddleocr的版本号
paddleocr whl包会自动下载PP-OCRv2超轻量模型作为默认模型,也支持自定义模型路径、预测配置等参数,参数名称与基于Paddle Inference的python预测中参数相同。
运行下面的代码,可快速体验文本检测模型的预测与效果。
from paddleocr import PaddleOCR, draw_ocr
ocr = PaddleOCR(use_gpu=False) # need to run only once to download and load model into memory
img_path = '/home/aistudio/PaddleOCR/doc/imgs/11.jpg'
result = ocr.ocr(img_path, rec=False)
for line in result:
print(line)
# 显示结果
from PIL import Image
image = Image.open(img_path).convert('RGB')
im_show = draw_ocr(image, result, txts=None, scores=None, font_path='/home/aistudio/PaddleOCR/doc/fonts/simfang.ttf')
plt.figure(figsize=(15, 8))
plt.imshow(im_show)
plt.show()
可以指定det=False
,仅运行单独的识别模块。
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_gpu=False) # need to run only once to download and load model into memory
img_path = '/home/aistudio/PaddleOCR/doc/imgs_words/ch/word_1.jpg'
result = ocr.ocr(img_path, det=False)
for line in result:
print(line)
可以指定det=False, rec=False, cls=True
,仅运行方向分类器。
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, use_gpu=False) # need to run only once to download and load model into memory
img_path = '/home/aistudio/PaddleOCR/doc/imgs_words/ch/word_1.jpg'
result = ocr.ocr(img_path, det=False, rec=False, cls=True)
for line in result:
print(line)
img = cv2.imread(img_path)
plt.imshow(img[...,::-1])
plt.show()
检测+方向分类器+识别
全流程体验from paddleocr import PaddleOCR, draw_ocr
import matplotlib.pyplot as plt
%matplotlib inline
# PaddleOCR目前支持中英文、英文、法语、德语、韩语、日语,可以通过修改lang参数进行切换
# 参数依次为`ch`, `en`, `french`, `german`, `korean`, `japan`。
ocr = PaddleOCR(use_angle_cls=True, lang="ch", use_gpu=False) # need to run only once to download and load model into memory
img_path = '/home/aistudio/PaddleOCR/doc/imgs/11.jpg'
result = ocr.ocr(img_path, cls=True)
for line in result:
print(line)
# 显示结果
from PIL import Image
image = Image.open(img_path).convert('RGB')
boxes = [line[0] for line in result]
txts = [line[1][0] for line in result]
scores = [line[1][1] for line in result]
im_show = draw_ocr(image, boxes, txts, scores, font_path='/home/aistudio/PaddleOCR/doc/fonts/simfang.ttf')
plt.figure(figsize=(15, 8))
plt.imshow(im_show)
plt.show()
结果是一个list,每个item包含了文本框,文字和识别置信度。
在推理部署过程中,相比于Python,C++的性能一般会更好一些,因此很多实际推理场景中会考虑使用C++作为开发语言进行推理。
上一小节给大家介绍的Paddle Inference也支持C++的推理过程,本节主要介绍C++的PP-OCRv2推理过程。
在基于Paddle Inference,对PP-OCRv2系统使用C++推理时,有以下几个步骤。
(1)准备模型
(2)编译opencv库
(3)获取Paddle Inference预测库
(4)编译PaddleOCR C++推理代码
(5)运行PP-OCRv2系统
由于AiStudio上版本限制,这里不做具体演示过程,仅给大家介绍具体的流程。建议您在本地体验PP-OCRv2的C++推理过程。
关于本小节更加详细的内容可以参考:PP-OCRv2 C++推理教程。
使用下面的命令,准备PP-OCRv2的推理模型。
cd deploy/cpp_infer
wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar -O ch_PP-OCRv2_det_infer.tar && tar -xf ch_PP-OCRv2_det_infer.tar
wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar -O ch_PP-OCRv2_rec_infer.tar && tar -xf ch_PP-OCRv2_rec_infer.tar
wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar -O ch_ppocr_mobile_v2.0_cls_infer.tar && tar -xf ch_ppocr_mobile_v2.0_cls_infer.tar
wget https://paddleocr.bj.bcebos.com/libs/opencv/opencv-3.4.7.tar.gz
tar -xf opencv-3.4.7.tar.gz
最终可以在当前目录下看到opencv-3.4.7/
的文件夹。
root_path
)以及安装路径(install_path
)。进入opencv源码路径下,按照下面的方式进行编译。root_path="your_opencv_root_path"
install_path=${root_path}/opencv3
build_dir=${root_path}/build
rm -rf ${build_dir}
mkdir ${build_dir}
cd ${build_dir}
cmake .. \
-DCMAKE_INSTALL_PREFIX=${install_path} \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DWITH_IPP=OFF \
-DBUILD_IPP_IW=OFF \
-DWITH_LAPACK=OFF \
-DWITH_EIGEN=OFF \
-DCMAKE_INSTALL_LIBDIR=lib64 \
-DWITH_ZLIB=ON \
-DBUILD_ZLIB=ON \
-DWITH_JPEG=ON \
-DBUILD_JPEG=ON \
-DWITH_PNG=ON \
-DBUILD_PNG=ON \
-DWITH_TIFF=ON \
-DBUILD_TIFF=ON
make -j
make install
也可以直接修改tools/build_opencv.sh
的内容,然后直接运行下面的命令进行编译。
sh tools/build_opencv.sh
其中root_path
为下载的opencv源码路径,install_path
为opencv的安装路径,make install
完成之后,会在该文件夹下生成opencv头文件和库文件,用于后面的OCR代码编译。
最终在安装路径下的文件结构如下所示。
opencv3/
|-- bin
|-- include
|-- lib
|-- lib64
|-- share
Paddle预测库官网 上提供了不同cuda版本的Linux预测库,可以在官网根据自己的环境选择合适的预测库版本。
下载之后使用下面的方法解压。
wget https://paddle-inference-lib.bj.bcebos.com/2.2.1/cxx_c/Linux/GPU/x86-64_gcc8.2_avx_mkl_cuda10.2_cudnn8.1.1_trt7.2.3.4/paddle_inference.tgz -O paddle_inference.tgz
tar -xf paddle_inference.tgz
最终会在当前的文件夹中生成paddle_inference/
的子文件夹。
编译命令如下,其中Paddle C++预测库、opencv等其他依赖库的地址需要换成自己机器上的实际地址。
sh tools/build.sh
tools/build.sh
中环境路径,相关内容如下:OPENCV_DIR=your_opencv_dir
LIB_DIR=your_paddle_inference_dir
CUDA_LIB_DIR=your_cuda_lib_dir
CUDNN_LIB_DIR=/your_cudnn_lib_dir
其中,OPENCV_DIR
为opencv编译安装的地址;LIB_DIR
为下载(paddle_inference
文件夹)或者编译生成的Paddle预测库地址(build/paddle_inference_install_dir
文件夹);CUDA_LIB_DIR
为cuda库文件地址,在docker中为/usr/local/cuda/lib64
;CUDNN_LIB_DIR
为cudnn库文件地址,在docker中为/usr/lib/x86_64-linux-gnu/
。注意:以上路径都写绝对路径,不要写相对路径。
build
文件夹下生成一个名为ppocr
的可执行文件。运行方式:
./build/ppocr [--param1] [--param2] [...]
其中,mode
为必选参数,表示选择的功能,取值范围['det', 'rec', 'system'],分别表示调用检测、识别、检测识别串联(包括方向分类器)。具体命令如下:
./build/ppocr det \
--det_model_dir=./ch_PP-OCRv2_det_infer/ \
--image_dir=../../doc/imgs/12.jpg
./build/ppocr rec \
--rec_model_dir=./ch_PP-OCRv2_rec_infer/ \
--image_dir=../../doc/imgs_words/ch/
# 不使用方向分类器
./build/ppocr system \
--det_model_dir=./ch_PP-OCRv2_det_infer/ \
--rec_model_dir=./ch_PP-OCRv2_rec_infer/ \
--image_dir=../../doc/imgs/12.jpg
# 使用方向分类器
./build/ppocr system \
--det_model_dir=./ch_PP-OCRv2_det_infer/ \
--rec_model_dir=./ch_PP-OCRv2_rec_infer/ \
--use_angle_cls=true \
--cls_model_dir=./ch_ppocr_mobile_v2.0_cls_infer \
--image_dir=../../doc/imgs/12.jpg
在第2和第3节内容中,我们详细介绍了基于Paddle Inference的PP-OCRv2系统推理,它属于离线推理,即在特定机器上部署的代码只能在这台机器上使用,无法通过其他机器进行访问。因此模型服务化部署的需求也就衍生出来。
服务化部署指的是,将模型以服务的形式进行部署,其他的设备可以通过发送请求的形式去访问服务,从而获取模型服务的推理结果。服务化部署示意图如下所示。
在模型部署成功后,不同用户都可以通过客户端,以发送网络请求的方式获得推理服务。
Paddle Serving是飞桨为方便开发者进行服务化部署而打造的工具,本节主要介绍基于Paddle Serving的PP-OCRv2系统服务化部署过程。
Paddle Serving作为飞桨(PaddlePaddle)开源的服务化部署框架,长期目标就是围绕着人工智能落地的最后一公里提供越来越专业、可靠、易用的服务。Paddle Serving目前提供了两套框架C++ Serving和Python Pipeline。Python Pipeline框架倾向于二次开发的便捷性,C++ Serving框架更倾向于追求极致性能。
基于Paddle Serving进行PP-OCRv2模型的服务化部署时,流程如下所示。
数据与模型推理所用数据一致。
运行Paddle Serving,需要安装Paddle Serving三个安装包:paddle-serving-server、paddle-serving-client 和 paddle-serving-app,命令如下。
!wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_server_gpu-0.7.0.post102-py3-none-any.whl
!pip install paddle_serving_server_gpu-0.7.0.post102-py3-none-any.whl
!wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_client-0.7.0-cp37-none-any.whl
!pip install paddle_serving_client-0.7.0-cp37-none-any.whl
!wget https://paddle-serving.bj.bcebos.com/test-dev/whl/paddle_serving_app-0.7.0-py3-none-any.whl
!pip install paddle_serving_app-0.7.0-py3-none-any.whl
!rm ./*.whl
在进行模型服务化部署时,首先需要将推理模型转为用户服务化部署的模型。
首先运行下面的命令下载推理模型。
os.chdir("/home/aistudio/PaddleOCR/deploy/pdserving/")
# 下载并解压 OCR 文本检测模型
!wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar -O ch_PP-OCRv2_det_infer.tar && tar -xf ch_PP-OCRv2_det_infer.tar && rm ch_PP-OCRv2_det_infer.tar
# 下载并解压 OCR 文本识别模型
!wget https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar -O ch_PP-OCRv2_rec_infer.tar && tar -xf ch_PP-OCRv2_rec_infer.tar && rm ch_PP-OCRv2_rec_infer.tar
运行下面的命令进行模型转换。
# 转换检测模型
!python -m paddle_serving_client.convert --dirname ./ch_PP-OCRv2_det_infer/ \
--model_filename inference.pdmodel \
--params_filename inference.pdiparams \
--serving_server ./ppocrv2_det_serving/ \
--serving_client ./ppocrv2_det_client/
# 转换识别模型
!python -m paddle_serving_client.convert --dirname ./ch_PP-OCRv2_rec_infer/ \
--model_filename inference.pdmodel \
--params_filename inference.pdiparams \
--serving_server ./ppocrv2_rec_serving/ \
--serving_client ./ppocrv2_rec_client/
# 查看文件夹
!tree -h *_client *_serving
检测模型转换完成后,会在当前文件夹多出ppocrv2_det_mobile_serving
和ppocrv2_det_mobile_client
的文件夹,具备如下格式:
|- ppocrv2_det_mobile_serving/
|- __model__
|- __params__
|- serving_server_conf.prototxt
|- serving_server_conf.stream.prototxt
|- ppocrv2_det_mobile_client
|- serving_client_conf.prototxt
|- serving_client_conf.stream.prototxt
识别模型同理。
注意: 将PaddleOCR/deploy/pdserving/config.yml文件中的两个model_config
字段分别修改为ppocrv2_det_mobile_serving、ppocrv2_rec_mobile_serving,对应模型转换的文件夹。
pdserving目录包含启动pipeline服务和发送预测请求的代码,包括:
```
__init__.py
config.yml # 启动服务的配置文件
ocr_reader.py # OCR模型预处理和后处理的代码实现
pipeline_http_client.py # 发送预测请求的脚本
web_service.py # 启动服务端的脚本
```
运行如下命令启动服务:
开启新的终端运行下列启动服务的命令
```
# 启动服务,运行日志保存在web_serving_log.txt
cd PaddleOCR/deploy/pdserving/
nohup python web_service.py &>web_serving_log.txt &
```
成功启动服务后,web_serving_log.txt中会打印类似如下日志
!python pipeline_http_client.py