在项目中,模型的推理性能直接影响项目成本,因此我们期望一个训练好的模型的模型可以拥有更快的推理速度。直接基于训练引擎进行预测,模型中包含与训练相关的算子,因此效率一般较低;而且需要定义模型,难以与训练代码解耦。Paddle Inference应运而生。它是飞桨的原生推理库,作用于服务器端和云端,提供高性能的推理能力。由于能力直接基于飞桨的训练算子,因此Paddle Inference 可以通用支持飞桨训练出的所有模型。
考虑到大家的使用场景差异很大,Paddle Inference针对不同平台不同的应用场景进行了深度的适配优化,做到高吞吐、低时延,保证了飞桨模型在服务器端即训即用,快速部署。
本章主要介绍基于Paddle Inference的PP-OCRv3预测推理过程,更多关于Paddle Inference的介绍可以参考:Paddle Inference 介绍。
在基于Paddle Inference进行模型推理时,一般有以下几个步骤。
Paddle Inference 模型推理流程
分别介绍文字检测、方向分类器和文字识别3个模型,基于Paddle Inference的推理过程。
Paddle Inference 的 Python 离线推理
离线推理,即在特定机器上部署的代码只能在这台机器上使用,无法通过其他机器进行访问
使用whl包预测推理
“WHL”是“WHeeL”的英文缩写,意思是“车轮” ,whl 格式本质上是一个压缩包,里面包含了py文件,以及经过编译的pyd文件
为了更加方便快速体验OCR文本检测与识别模型,PaddleOCR提供了基于Paddle Inference预测引擎的whl包,方便您一键安装,体验PaddleOCR。
安装whl包
pip install paddleocr -i https://pypi.tuna.tsinghua.edu.cn/simple --verbose
使用whl包预测推理
paddleocr whl包会自动下载PP-OCRv2超轻量模型作为默认模型,也支持自定义模型路径、预测配置等参数,参数名称与基于Paddle Inference的python预测中参数相同。
单独执行检测
import cv2 import matplotlib.pyplot as plt import numpy as np import os 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 = './images/006.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='./fonts/simfang.ttf') plt.figure(figsize=(15, 8)) plt.imshow(im_show) plt.show()
单独执行识别
可以指定det=False
,仅运行单独的识别模块。
import matplotlib.pyplot as plt 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 = './images/006.jpg' result = ocr.ocr(img_path, det=False) for line in result: print(line)
单独执行方向分类器
可以指定det=False, rec=False, cls=True
,仅运行方向分类器。
import cv2 import matplotlib.pyplot as plt from paddleocr import PaddleOCR, draw_ocr ocr = PaddleOCR(use_angle_cls=True, use_gpu=False) # need to run only once to download and load model into memory img_path = './images/006.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()
全流程体验(检测+方向分类器+识别)
源码地址:https://gitee.com/VipSoft/Paddle/ 里的 PaddleOCR 目录
import cv2 import os import matplotlib.pyplot as plt from paddleocr import PaddleOCR, draw_ocr # 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 save_results = [] img_path = 'images/003.jpg' save_dir = 'ocr_result' result = ocr.ocr(img_path, cls=True)[0] # 将结果写入文件 with open( os.path.join(save_dir, "003_result.txt"), 'w', encoding='utf-8') as f: for line in result: f.writelines(str(line)+'\n') 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='./fonts/simfang.ttf') cv2.imwrite(os.path.join(save_dir, "003_result.jpg"), im_show) plt.figure(figsize=(15, 8)) plt.imshow(im_show) plt.show()
使用源码推理
下载源码,并解压:https://gitee.com/paddlepaddle/PaddleOCR/tree/release/2.6
安装依赖包
https://gitee.com/paddlepaddle/PaddleOCR/blob/release/2.6/requirements.txt
将文件 requirements.txt 保存到运行目录下如 D:\OpenSource\PaddlePaddle\PaddleOCR-release-2.6
shapely scikit-image imgaug pyclipper lmdb tqdm numpy visualdl rapidfuzz opencv-python==4.6.0.66 opencv-contrib-python==4.6.0.66 cython lxml premailer openpyxl attrdict Polygon3 lanms-neo==1.0.2 PyMuPDF<1.21.0
安装运行所需要的包
D:\OpenSource\PaddlePaddle\PaddleOCR-release-2.6>pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --verbose D:\OpenSource\PaddlePaddle\PaddleOCR-release-2.6>pip install paddlenlp -i https://pypi.tuna.tsinghua.edu.cn/simple --verbose
文字检测
PaddleOCR中,在基于文字检测模型进行推理时,需要通过参数image_dir
指定单张图像或者图像集合的路径、参数det_model_dir
, 指定检测的 inference
模型路径。
百度OCR源码中提供了样例图片:https://gitee.com/paddlepaddle/PaddleOCR/tree/release/2.6/doc/imgs
准备数据和环境
import cv2 import matplotlib.pyplot as plt import numpy as np import os # 选择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) # 定义 1行2列 plt.imshow(img1[:, :, ::-1]) # 第1列 放 img1 ,::-1 => axis 3 倒序 plt.subplot(1, 2, 2) # 定义 1行2列 plt.imshow(img2[:, :, ::-1]) # 第2列 放 img1 plt.show()
准备推理模型
下载模型:https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv3_det_infer.tar
解压放至:inference
目录
inference/ch_PP-OCRv3_det_infer ├── [2.2M] inference.pdiparams # 模型参数 ├── [ 23K] inference.pdiparams.info └── [845K] inference.pdmodel # 模型结构
如果您希望导出自己训练得到的模型,使用Paddle Inference部署,那么可以使用下面的命令将预训练模型使用动转静的方法,转化为推理模型。
# 参考代码 # https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/tools/export_model.py # 下载预训练模型(V2) 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 # 导出推理模型(V2) 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"
文字检测模型推理
CMD 进到代码目录如图
使用V3模型预测
# 预测 python tools/infer/predict_det.py --image_dir="./doc/imgs/00018069.jpg" --det_model_dir="./inference/ch_PP-OCRv3_det_infer" --use_gpu=False
输出
- 定义参数设置
PaddleOCR-release-2.6\tools\infer\utility.py
更多参数说明:doc\doc_ch\inference_args.md
- 文字检测
PaddleOCR-release-2.6\tools\infer\predict_det.py
部分代码说明:https://aistudio.baidu.com/aistudio/projectdetail/6180758
方向分类器模型推理
//TODO 现在还不知道这玩意具体是用来干嘛的。
将角度不正确的文字处理成正常方向的
https://www.paddlepaddle.org.cn/modelsDetail?modelId=17
下载模型:https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar
解压放至:inference
目录
# 预测 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
方向分类器的具体实现代码: PaddleOCR-release-2.6\tools\infer\predict_cls.py
文字识别
https://www.paddlepaddle.org.cn/modelsDetail?modelId=17
下载模型:https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar
解压放至:inference
目录
# 预测 python tools/infer/predict_rec.py \ --image_dir="./doc/imgs_words/ch/word_4.jpg" \ --rec_model_dir="./inference/ch_PP-OCRv3_rec_infer" \ --use_gpu=False
文字识别的具体代码:PaddleOCR-release-2.6\tools\infer\predict_rec.py
系统串联预测推理
在执行PP-OCR的系统推理时,需要通过参数image_dir
指定单张图像或者图像集合的路径、参数det_model_dir
, cls_model_dir
和 rec_model_dir
分别指定检测、方向分类和识别的 inference
模型路径。参数 use_angle_cls
用于控制是否启用方向分类模型。use_mp
表示是否使用多进程。total_process_num
表示在使用多进程时的进程数。
以图像文件 ./doc/imgs/00018069.jpg
为例,预测的原始图像如下。
# 预测 python tools/infer/predict_system.py \ --image_dir="./doc/imgs/00018069.jpg" \ --det_model_dir="./inference/ch_PP-OCRv3_det_infer/" \ --cls_model_dir="./inference/ch_ppocr_mobile_v2.0_cls_infer/" \ --rec_model_dir="./inference/ch_PP-OCRv3_rec_infer/" \ --use_angle_cls=True
可视化识别结果默认保存到 ./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-OCRv3_det_infer/" \ --cls_model_dir="./inference/ch_ppocr_mobile_v2.0_cls_infer/" \ --rec_model_dir="./inference/ch_PP-OCRv3_rec_infer/" \ --use_angle_cls=True \ --save_crop_res=True
核心代码介绍
串联预测通过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_reuslt in zip(dt_boxes, rec_res): text, score = rec_reuslt if score >= self.drop_score: filter_boxes.append(box) filter_rec_res.append(rec_reuslt) 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()