From 5bc6fee14bc11b171ee9a4d57b633ec6dbef2fd7 Mon Sep 17 00:00:00 2001 From: SWHL Date: Wed, 29 Jun 2022 22:21:29 +0800 Subject: [PATCH] Add use_cuda parameter --- python/config.yaml | 25 +++++++++++ .../ch_ppocr_v2_cls/config.yaml | 9 ++++ .../ch_ppocr_v2_cls/text_cls.py | 15 +++---- .../ch_ppocr_v2_cls/utils.py | 40 ++++++++++++++++++ .../ch_ppocr_v2_det/config.yaml | 2 +- .../ch_ppocr_v2_det/text_detect.py | 5 +-- .../ch_ppocr_v2_det/utils.py | 8 ++-- .../ch_ppocr_v2_rec/config.yaml | 9 ++++ .../ch_ppocr_v2_rec/text_recognize.py | 15 +++---- .../ch_ppocr_v2_rec/utils.py | 40 ++++++++++++++++++ .../ch_ppocr_v2_rec/word_1.jpg | Bin 18945 -> 0 bytes .../ch_ppocr_v3_rec/config.yaml | 9 ++++ .../ch_ppocr_v3_rec/text_recognize.py | 13 +++--- .../ch_ppocr_v3_rec/utils.py | 40 ++++++++++++++++++ python/test_demo.py | 3 +- 15 files changed, 197 insertions(+), 36 deletions(-) delete mode 100644 python/rapidocr_onnxruntime/ch_ppocr_v2_rec/word_1.jpg diff --git a/python/config.yaml b/python/config.yaml index 0c59831..6d177e7 100644 --- a/python/config.yaml +++ b/python/config.yaml @@ -8,6 +8,15 @@ Det: class_name: TextDetector model_path: resources/models/ch_PP-OCRv2_det_infer.onnx + use_cuda: false + # Details of the params: https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html + CUDAExecutionProvider: + device_id: 0 + arena_extend_strategy: kNextPowerOfTwo + gpu_mem_limit: 2 * 1024 * 1024 * 1024 + cudnn_conv_algo_search: EXHAUSTIVE + do_copy_in_default_stream: true + pre_process: DetResizeForTest: limit_side_len: 736 @@ -33,6 +42,14 @@ Cls: class_name: TextClassifier model_path: resources/models/ch_ppocr_mobile_v2.0_cls_infer.onnx + use_cuda: false + CUDAExecutionProvider: + device_id: 0 + arena_extend_strategy: kNextPowerOfTwo + gpu_mem_limit: 2 * 1024 * 1024 * 1024 + cudnn_conv_algo_search: EXHAUSTIVE + do_copy_in_default_stream: true + cls_image_shape: [3, 48, 192] cls_batch_num: 6 cls_thresh: 0.9 @@ -43,6 +60,14 @@ Rec: class_name: TextRecognizer model_path: resources/models/ch_ppocr_mobile_v2.0_rec_infer.onnx + use_cuda: false + CUDAExecutionProvider: + device_id: 0 + arena_extend_strategy: kNextPowerOfTwo + gpu_mem_limit: 2 * 1024 * 1024 * 1024 + cudnn_conv_algo_search: EXHAUSTIVE + do_copy_in_default_stream: true + rec_img_shape: [3, 32, 320] rec_batch_num: 6 keys_path: resources/rec_dict/ppocr_keys_v1.txt diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml b/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml index e6fa00e..89497ae 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml @@ -1,5 +1,14 @@ model_path: resources/models/ch_ppocr_mobile_v2.0_cls_infer.onnx +use_cuda: false +# Details of the params: https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html +CUDAExecutionProvider: + device_id: 0 + arena_extend_strategy: kNextPowerOfTwo + gpu_mem_limit: 2 * 1024 * 1024 * 1024 + cudnn_conv_algo_search: EXHAUSTIVE + do_copy_in_default_stream: true + cls_image_shape: [3, 48, 192] cls_batch_num: 6 cls_thresh: 0.9 diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py b/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py index fc06ac1..38d8549 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py @@ -22,9 +22,9 @@ import numpy as np import onnxruntime as ort try: - from .utils import ClsPostProcess, read_yaml + from .utils import ClsPostProcess, read_yaml, OrtInferSession except: - from utils import ClsPostProcess, read_yaml + from utils import ClsPostProcess, read_yaml, OrtInferSession class TextClassifier(object): @@ -34,11 +34,9 @@ class TextClassifier(object): self.cls_thresh = config['cls_thresh'] self.postprocess_op = ClsPostProcess(config['label_list']) - sess_opt = ort.SessionOptions() - sess_opt.log_severity_level = 4 - sess_opt.enable_cpu_mem_arena = False - self.session = ort.InferenceSession(config['model_path'], sess_opt) - self.input_name = self.session.get_inputs()[0].name + session_instance = OrtInferSession(config) + self.session = session_instance.session + self.input_name = session_instance.get_input_name() def resize_norm_img(self, img): img_c, img_h, img_w = self.cls_image_shape @@ -63,7 +61,6 @@ class TextClassifier(object): padding_im[:, :, :resized_w] = resized_image return padding_im - def __call__(self, img_list: List[np.ndarray]): if isinstance(img_list, np.ndarray): img_list = [img_list] @@ -123,4 +120,4 @@ if __name__ == "__main__": img = cv2.imread(args.image_path) img_list, cls_res, predict_time = text_classifier(img) for ino in range(len(img_list)): - print(f"分类结果:{cls_res[ino]}") + print(f"cls result:{cls_res[ino]}") diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py b/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py index dae9175..55b4816 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py @@ -11,7 +11,47 @@ # 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 warnings + import yaml +from onnxruntime import (get_available_providers, get_device, + SessionOptions, InferenceSession) + + +class OrtInferSession(object): + def __init__(self, config): + sess_opt = SessionOptions() + sess_opt.log_severity_level = 4 + sess_opt.enable_cpu_mem_arena = False + + cuda_ep = 'CUDAExecutionProvider' + cpu_ep = 'CPUExecutionProvider' + + EP_list = [] + if config['use_cuda'] and get_device() == 'GPU' \ + and cuda_ep in get_available_providers(): + EP_list = [(cuda_ep, config[cuda_ep])] + else: + raise RuntimeError(f'{cuda_ep} is not avaiable for --use_cuda!') + + EP_list.append(cpu_ep) + + self.session = InferenceSession(config['model_path'], + sess_options=sess_opt, + providers=EP_list) + + if config['use_cuda'] and cuda_ep not in self.session.get_providers(): + warnings.warn(f'{cuda_ep} is not avaiable for current env, the inference part is automatically shifted to be executed under {cpu_ep}.\n' + 'Please ensure the installed onnxruntime-gpu version matches your cuda and cudnn version, ' + 'you can check their relations from the offical web site: ' + 'https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html', + RuntimeWarning) + + def get_input_name(self, input_idx=0): + return self.session.get_inputs()[input_idx].name + + def get_output_name(self, output_idx=0): + return self.session.get_outputs()[output_idx].name def read_yaml(yaml_path): diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_det/config.yaml b/python/rapidocr_onnxruntime/ch_ppocr_v2_det/config.yaml index 0390513..428ccfc 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_det/config.yaml +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_det/config.yaml @@ -1,7 +1,7 @@ model_path: G:\ProgramFiles\_self\RapidOCR\python\resources\models\ch_PP-OCRv2_det_infer.onnx -# https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html use_cuda: false +# Details of the params: https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html CUDAExecutionProvider: device_id: 0 arena_extend_strategy: kNextPowerOfTwo diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_det/text_detect.py b/python/rapidocr_onnxruntime/ch_ppocr_v2_det/text_detect.py index d656616..7308253 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_det/text_detect.py +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_det/text_detect.py @@ -37,7 +37,6 @@ class TextDetector(object): session_instance = OrtInferSession(config) self.session = session_instance.session self.input_name = session_instance.get_input_name() - self.output_name = session_instance.get_output_name() def order_points_clockwise(self, pts): """ @@ -100,7 +99,7 @@ class TextDetector(object): shape_list = np.expand_dims(shape_list, axis=0) starttime = time.time() - preds = self.session.run([self.output_name], {self.input_name: img}) + preds = self.session.run(None, {self.input_name: img}) post_result = self.postprocess_op(preds[0], shape_list) @@ -124,4 +123,4 @@ if __name__ == "__main__": dt_boxes, elapse = text_detector(img) src_im = draw_text_det_res(dt_boxes, args.image_path) cv2.imwrite('det_results.jpg', src_im) - print('图像已经保存为det_results.jpg了') + print('The det_results.jpg has been saved in the current directory.') diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_det/utils.py b/python/rapidocr_onnxruntime/ch_ppocr_v2_det/utils.py index b1fa89d..e743908 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_det/utils.py +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_det/utils.py @@ -50,10 +50,12 @@ class OrtInferSession(object): self.session = InferenceSession(config['model_path'], sess_options=sess_opt, providers=EP_list) - # https://github.com/microsoft/onnxruntime/issues/11323 - # https://github.com/microsoft/onnxruntime/issues/11996 + if config['use_cuda'] and cuda_ep not in self.session.get_providers(): - warnings.warn(f'{cuda_ep} is not avaiable for current env', + warnings.warn(f'{cuda_ep} is not avaiable for current env, the inference part is automatically shifted to be executed under {cpu_ep}.\n' + 'Please ensure the installed onnxruntime-gpu version matches your cuda and cudnn version, ' + 'you can check their relations from the offical web site: ' + 'https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html', RuntimeWarning) def get_input_name(self, input_idx=0): diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/config.yaml b/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/config.yaml index 3c3c9df..ffcfc1d 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/config.yaml +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/config.yaml @@ -1,5 +1,14 @@ model_path: resources/models/ch_ppocr_server_v2.0_rec_infer.onnx +use_cuda: false +# Details of the params: https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html +CUDAExecutionProvider: + device_id: 0 + arena_extend_strategy: kNextPowerOfTwo + gpu_mem_limit: 2 * 1024 * 1024 * 1024 + cudnn_conv_algo_search: EXHAUSTIVE + do_copy_in_default_stream: true + rec_img_shape: [3, 32, 320] rec_batch_num: 6 keys_path: resources/rec_dict/ppocr_keys_v1.txt diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/text_recognize.py b/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/text_recognize.py index 2753ea7..4fd9265 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/text_recognize.py +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/text_recognize.py @@ -14,17 +14,15 @@ import argparse import math import time -from pathlib import Path from typing import List import cv2 import numpy as np -import onnxruntime try: - from .utils import CTCLabelDecode, read_yaml + from .utils import CTCLabelDecode, read_yaml, OrtInferSession except: - from utils import CTCLabelDecode, read_yaml + from utils import CTCLabelDecode, read_yaml, OrtInferSession class TextRecognizer(object): @@ -34,12 +32,9 @@ class TextRecognizer(object): self.character_dict_path = config['keys_path'] self.postprocess_op = CTCLabelDecode(self.character_dict_path) - sess_opt = onnxruntime.SessionOptions() - sess_opt.log_severity_level = 4 - sess_opt.enable_cpu_mem_arena = False - self.session = onnxruntime.InferenceSession(config['model_path'], - sess_opt) - self.input_name = self.session.get_inputs()[0].name + session_instance = OrtInferSession(config) + self.session = session_instance.session + self.input_name = session_instance.get_input_name() def resize_norm_img(self, img, max_wh_ratio): img_channel, img_height, img_width = self.rec_image_shape diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/utils.py b/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/utils.py index c6edeb6..489a04d 100644 --- a/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/utils.py +++ b/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/utils.py @@ -1,8 +1,48 @@ # -*- encoding: utf-8 -*- # @Author: SWHL # @Contact: liekkaskono@163.com +import warnings + import numpy as np import yaml +from onnxruntime import (get_available_providers, get_device, + SessionOptions, InferenceSession) + + +class OrtInferSession(object): + def __init__(self, config): + sess_opt = SessionOptions() + sess_opt.log_severity_level = 4 + sess_opt.enable_cpu_mem_arena = False + + cuda_ep = 'CUDAExecutionProvider' + cpu_ep = 'CPUExecutionProvider' + + EP_list = [] + if config['use_cuda'] and get_device() == 'GPU' \ + and cuda_ep in get_available_providers(): + EP_list = [(cuda_ep, config[cuda_ep])] + else: + raise RuntimeError(f'{cuda_ep} is not avaiable for --use_cuda!') + + EP_list.append(cpu_ep) + + self.session = InferenceSession(config['model_path'], + sess_options=sess_opt, + providers=EP_list) + + if config['use_cuda'] and cuda_ep not in self.session.get_providers(): + warnings.warn(f'{cuda_ep} is not avaiable for current env, the inference part is automatically shifted to be executed under {cpu_ep}.\n' + 'Please ensure the installed onnxruntime-gpu version matches your cuda and cudnn version, ' + 'you can check their relations from the offical web site: ' + 'https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html', + RuntimeWarning) + + def get_input_name(self, input_idx=0): + return self.session.get_inputs()[input_idx].name + + def get_output_name(self, output_idx=0): + return self.session.get_outputs()[output_idx].name def read_yaml(yaml_path): diff --git a/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/word_1.jpg b/python/rapidocr_onnxruntime/ch_ppocr_v2_rec/word_1.jpg deleted file mode 100644 index 934cc1cb41e8830c0c3d3966492be83392077227..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18945 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 ff1=