From a109090f415afdbabf2f39cbe8fddde2547cbf19 Mon Sep 17 00:00:00 2001 From: SWHL Date: Wed, 14 Dec 2022 17:18:38 +0800 Subject: [PATCH] Optim ocrweb --- README.md | 2 +- docs/README_en.md | 2 +- docs/doc_whl_layout.md | 2 +- docs/doc_whl_rapidocr_ort.md | 2 +- docs/doc_whl_rapidocr_vino.md | 2 +- ocrweb/README.md | 35 +- ocrweb/rapidocr_onnxruntime/__init__.py | 4 - .../ch_ppocr_v2_cls/__init__.py | 4 - .../ch_ppocr_v2_cls/config.yaml | 14 - .../ch_ppocr_v2_cls/text_cls.py | 117 ----- .../ch_ppocr_v2_cls/utils.py | 92 ---- .../ch_ppocr_v3_det/__init__.py | 4 - .../ch_ppocr_v3_det/config.yaml | 29 -- .../ch_ppocr_v3_det/text_detect.py | 127 ----- .../ch_ppocr_v3_det/utils.py | 464 ------------------ .../ch_ppocr_v3_rec/__init__.py | 4 - .../ch_ppocr_v3_rec/config.yaml | 12 - .../ch_ppocr_v3_rec/text_recognize.py | 119 ----- .../ch_ppocr_v3_rec/utils.py | 145 ------ ocrweb/rapidocr_onnxruntime/config.yaml | 73 --- ocrweb/rapidocr_onnxruntime/models/.gitkeep | 0 ocrweb/rapidocr_onnxruntime/rapid_ocr_api.py | 174 ------- python/README.md | 2 +- python/rapid_structure/README.md | 2 +- 24 files changed, 14 insertions(+), 1417 deletions(-) delete mode 100644 ocrweb/rapidocr_onnxruntime/__init__.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/__init__.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/__init__.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/config.yaml delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/text_detect.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/utils.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/__init__.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/config.yaml delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/text_recognize.py delete mode 100644 ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/utils.py delete mode 100644 ocrweb/rapidocr_onnxruntime/config.yaml delete mode 100644 ocrweb/rapidocr_onnxruntime/models/.gitkeep delete mode 100644 ocrweb/rapidocr_onnxruntime/rapid_ocr_api.py diff --git a/README.md b/README.md index ee32b4b..28e987e 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Open in Colab
- + diff --git a/docs/README_en.md b/docs/README_en.md index 04bc897..ecb90cd 100644 --- a/docs/README_en.md +++ b/docs/README_en.md @@ -12,7 +12,7 @@ Open in Colab
- + diff --git a/docs/doc_whl_layout.md b/docs/doc_whl_layout.md index 1b7e49c..4d47481 100644 --- a/docs/doc_whl_layout.md +++ b/docs/doc_whl_layout.md @@ -1,6 +1,6 @@ ## rapid-layout Package

- +

diff --git a/docs/doc_whl_rapidocr_ort.md b/docs/doc_whl_rapidocr_ort.md index d9d730c..1de6be4 100644 --- a/docs/doc_whl_rapidocr_ort.md +++ b/docs/doc_whl_rapidocr_ort.md @@ -1,6 +1,6 @@ ## rapidocr-onnxruntime Package

- +

diff --git a/docs/doc_whl_rapidocr_vino.md b/docs/doc_whl_rapidocr_vino.md index aae98f1..dc49045 100644 --- a/docs/doc_whl_rapidocr_vino.md +++ b/docs/doc_whl_rapidocr_vino.md @@ -1,6 +1,6 @@ ## rapidocr-openvino Package

- +

diff --git a/ocrweb/README.md b/ocrweb/README.md index e931007..9ad476d 100755 --- a/ocrweb/README.md +++ b/ocrweb/README.md @@ -19,36 +19,15 @@ ### 以Web方式运行 -1. 安装`requirements.txt`下相关包 +1. 安装`rapidocr_onnxruntime` + ```bash + $ pip install rapidocr_onnxruntime + ``` + +2. 安装`ocrweb`目录下的`requirements.txt`下相关包 ```shell - pip install -r requirements.txt -i https://pypi.douban.com/simple/ + pip install -r requirements.txt ``` -2. 下载`models`目录 - - 下载链接:[百度网盘](https://pan.baidu.com/s/1PTcgXG2zEgQU6A_A3kGJ3Q?pwd=jhai) | [Google Drive](https://drive.google.com/drive/folders/1x_a9KpCo_1blxH1xFOfgKVkw1HYRVywY?usp=sharing) - - 最终目录结构如下: - ```text - ocrweb - ├── README.md - ├── main.py - ├── requirements.txt - ├── task.py - ├── rapidocr_onnxruntime - │   ├── __init__.py - │   ├── ch_ppocr_v3_det - │   ├── ch_ppocr_v2_cls - │   ├── ch_ppocr_v3_rec - │   ├── config.yaml - │   ├── rapid_ocr_api.py - │   └── models - │    ├── ch_PP-OCRv3_det_infer.onnx - │    ├── ch_ppocr_mobile_v2.0_cls_infer.onnx - │    └── ch_PP-OCRv3_rec_infer.onnx - ├── static - │   ├── css - │   └── js - └── templates - └── index.html - ``` 3. 运行`main.py` ```shell diff --git a/ocrweb/rapidocr_onnxruntime/__init__.py b/ocrweb/rapidocr_onnxruntime/__init__.py deleted file mode 100644 index 6522633..0000000 --- a/ocrweb/rapidocr_onnxruntime/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -from .rapid_ocr_api import RapidOCR diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/__init__.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/__init__.py deleted file mode 100644 index c5a5e1f..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -from .text_cls import TextClassifier \ No newline at end of file diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml deleted file mode 100644 index 0ec669e..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/config.yaml +++ /dev/null @@ -1,14 +0,0 @@ -model_path: 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 - 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 -label_list: ['0', '180'] \ No newline at end of file diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py deleted file mode 100644 index 6ea2e4b..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/text_cls.py +++ /dev/null @@ -1,117 +0,0 @@ -# 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 argparse -import copy -import math -import time -from typing import List - -import cv2 -import numpy as np - -try: - from .utils import ClsPostProcess, read_yaml, OrtInferSession -except: - from utils import ClsPostProcess, read_yaml, OrtInferSession - - -class TextClassifier(object): - def __init__(self, config): - self.cls_image_shape = config['cls_image_shape'] - self.cls_batch_num = config['cls_batch_num'] - self.cls_thresh = config['cls_thresh'] - self.postprocess_op = ClsPostProcess(config['label_list']) - - session_instance = OrtInferSession(config) - self.session = session_instance.session - self.input_name = session_instance.get_input_name() - - def __call__(self, img_list: List[np.ndarray]): - if isinstance(img_list, np.ndarray): - img_list = [img_list] - - img_list = copy.deepcopy(img_list) - - # Calculate the aspect ratio of all text bars - width_list = [img.shape[1] / float(img.shape[0]) for img in img_list] - - # Sorting can speed up the cls process - indices = np.argsort(np.array(width_list)) - - img_num = len(img_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 = [] - 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).astype(np.float32) - - starttime = time.time() - onnx_inputs = {self.input_name: norm_img_batch} - prob_out = self.session.run(None, onnx_inputs)[0] - 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 - - def resize_norm_img(self, img): - img_c, img_h, img_w = self.cls_image_shape - h, w = img.shape[:2] - ratio = w / float(h) - if math.ceil(img_h * ratio) > img_w: - resized_w = img_w - else: - resized_w = int(math.ceil(img_h * ratio)) - - resized_image = cv2.resize(img, (resized_w, img_h)) - resized_image = resized_image.astype('float32') - if img_c == 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((img_c, img_h, img_w), dtype=np.float32) - padding_im[:, :, :resized_w] = resized_image - return padding_im - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--image_path', type=str, help='image_dir|image_path') - parser.add_argument('--config_path', type=str, default='config.yaml') - args = parser.parse_args() - - config = read_yaml(args.config_path) - - text_classifier = TextClassifier(config) - - 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 result:{cls_res[ino]}") diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py deleted file mode 100644 index b37a8be..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v2_cls/utils.py +++ /dev/null @@ -1,92 +0,0 @@ -# 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. -from pathlib import Path -import warnings - -import yaml -from onnxruntime import (get_available_providers, get_device, - SessionOptions, InferenceSession, - GraphOptimizationLevel) -root_dir = Path(__file__).resolve().parent.parent - - -class OrtInferSession(object): - def __init__(self, config): - sess_opt = SessionOptions() - sess_opt.log_severity_level = 4 - sess_opt.enable_cpu_mem_arena = False - sess_opt.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL - - cuda_ep = 'CUDAExecutionProvider' - cpu_ep = 'CPUExecutionProvider' - cpu_provider_options = { - "arena_extend_strategy": "kSameAsRequested", - } - - EP_list = [] - if config['use_cuda'] and get_device() == 'GPU' \ - and cuda_ep in get_available_providers(): - EP_list = [(cuda_ep, config[cuda_ep])] - EP_list.append((cpu_ep, cpu_provider_options)) - - config['model_path'] = str(root_dir / config['model_path']) - self._verify_model(config['model_path']) - 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 - - @staticmethod - def _verify_model(model_path): - model_path = Path(model_path) - if not model_path.exists(): - raise FileNotFoundError(f'{model_path} does not exists.') - if not model_path.is_file(): - raise FileExistsError(f'{model_path} is not a file.') - - -def read_yaml(yaml_path): - with open(yaml_path, 'rb') as f: - data = yaml.load(f, Loader=yaml.Loader) - return data - - -class ClsPostProcess(object): - """ Convert between text-label and text-index """ - - def __init__(self, label_list): - super(ClsPostProcess, self).__init__() - self.label_list = label_list - - def __call__(self, preds, label=None): - pred_idxs = preds.argmax(axis=1) - decode_out = [(self.label_list[idx], preds[i, idx]) - for i, idx in enumerate(pred_idxs)] - if label is None: - return decode_out - - label = [(self.label_list[idx], 1.0) for idx in label] - return decode_out, label \ No newline at end of file diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/__init__.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/__init__.py deleted file mode 100644 index 502f4a3..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -from .text_detect import TextDetector diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/config.yaml b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/config.yaml deleted file mode 100644 index 9bac869..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/config.yaml +++ /dev/null @@ -1,29 +0,0 @@ -model_path: models/ch_PP-OCRv3_det_infer.onnx - -use_cuda: false -CUDAExecutionProvider: - device_id: 0 - arena_extend_strategy: kNextPowerOfTwo - cudnn_conv_algo_search: EXHAUSTIVE - do_copy_in_default_stream: true - -pre_process: - DetResizeForTest: - limit_side_len: 736 - limit_type: min - NormalizeImage: - std: [0.229, 0.224, 0.225] - mean: [0.485, 0.456, 0.406] - scale: 1./255. - order: hwc - ToCHWImage: - KeepKeys: - keep_keys: ['image', 'shape'] - -post_process: - thresh: 0.3 - box_thresh: 0.5 - max_candidates: 1000 - unclip_ratio: 1.6 - use_dilation: true - score_mode: "fast" diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/text_detect.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/text_detect.py deleted file mode 100644 index c3ebf6e..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/text_detect.py +++ /dev/null @@ -1,127 +0,0 @@ -# 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. -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -import argparse -import time - -import cv2 -import numpy as np - -try: - from .utils import (DBPostProcess, create_operators, - transform, read_yaml, OrtInferSession) -except: - from utils import (DBPostProcess, create_operators, - transform, read_yaml, OrtInferSession) - - -class TextDetector(): - def __init__(self, config): - self.preprocess_op = create_operators(config['pre_process']) - self.postprocess_op = DBPostProcess(**config['post_process']) - - session_instance = OrtInferSession(config) - self.session = session_instance.session - self.input_name = session_instance.get_input_name() - - def __call__(self, img): - if img is None: - raise ValueError('img is None') - - ori_im_shape = img.shape[:2] - - 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).astype(np.float32) - shape_list = np.expand_dims(shape_list, axis=0) - - starttime = time.time() - preds = self.session.run(None, {self.input_name: img}) - - post_result = self.postprocess_op(preds[0], shape_list) - - dt_boxes = post_result[0]['points'] - dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im_shape) - elapse = time.time() - starttime - return dt_boxes, elapse - - def order_points_clockwise(self, pts): - """ - reference from: - https://github.com/jrosebr1/imutils/blob/master/imutils/perspective.py - sort the points based on their x-coordinates - """ - xSorted = pts[np.argsort(pts[:, 0]), :] - - # grab the left-most and right-most points from the sorted - # x-roodinate points - leftMost = xSorted[:2, :] - rightMost = xSorted[2:, :] - - # now, sort the left-most coordinates according to their - # y-coordinates so we can grab the top-left and bottom-left - # points, respectively - 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[: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 - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--config_path', type=str, default='config.yaml') - parser.add_argument('--image_path', type=str, default=None) - args = parser.parse_args() - - config = read_yaml(args.config_path) - - text_detector = TextDetector(config) - - img = cv2.imread(args.image_path) - dt_boxes, elapse = text_detector(img) - - from utils import draw_text_det_res - src_im = draw_text_det_res(dt_boxes, args.image_path) - cv2.imwrite('det_results.jpg', src_im) - print('The det_results.jpg has been saved in the current directory.') diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/utils.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/utils.py deleted file mode 100644 index d6511b1..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_det/utils.py +++ /dev/null @@ -1,464 +0,0 @@ -""" -# 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. -""" -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -import sys -import warnings -from pathlib import Path - -import cv2 -import numpy as np -import pyclipper -import six -import yaml -from onnxruntime import (GraphOptimizationLevel, InferenceSession, - SessionOptions, get_available_providers, get_device) -from shapely.geometry import Polygon - -root_dir = Path(__file__).resolve().parent.parent - - -class OrtInferSession(object): - def __init__(self, config): - sess_opt = SessionOptions() - sess_opt.log_severity_level = 4 - sess_opt.enable_cpu_mem_arena = False - sess_opt.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL - - cuda_ep = 'CUDAExecutionProvider' - cpu_ep = 'CPUExecutionProvider' - cpu_provider_options = { - "arena_extend_strategy": "kSameAsRequested", - } - - EP_list = [] - if config['use_cuda'] and get_device() == 'GPU' \ - and cuda_ep in get_available_providers(): - EP_list = [(cuda_ep, config[cuda_ep])] - EP_list.append((cpu_ep, cpu_provider_options)) - - config['model_path'] = str(root_dir / config['model_path']) - self._verify_model(config['model_path']) - 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 - - @staticmethod - def _verify_model(model_path): - model_path = Path(model_path) - if not model_path.exists(): - raise FileNotFoundError(f'{model_path} does not exists.') - if not model_path.is_file(): - raise FileExistsError(f'{model_path} is not a file.') - - -def read_yaml(yaml_path): - with open(yaml_path, 'rb') as f: - data = yaml.load(f, Loader=yaml.Loader) - return data - - -class DecodeImage(object): - """ decode image """ - - def __init__(self, img_mode='RGB', channel_first=False): - self.img_mode = img_mode - self.channel_first = channel_first - - def __call__(self, data): - img = data['image'] - if six.PY2: - assert type(img) is str and len(img) > 0, "invalid input 'img' in DecodeImage" - else: - assert type(img) is bytes and len(img) > 0, "invalid input 'img' in DecodeImage" - - img = np.frombuffer(img, dtype='uint8') - img = cv2.imdecode(img, 1) - if img is None: - return None - - if self.img_mode == 'GRAY': - img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - elif self.img_mode == 'RGB': - assert img.shape[2] == 3, f'invalid shape of image[{img.shape}]' - img = img[:, :, ::-1] - - if self.channel_first: - img = img.transpose((2, 0, 1)) - data['image'] = img - return data - - -class NormalizeImage(object): - """ normalize image such as substract mean, divide std""" - - def __init__(self, scale=None, mean=None, std=None, order='chw'): - if isinstance(scale, str): - scale = eval(scale) - self.scale = np.float32(scale if scale is not None else 1.0 / 255.0) - mean = mean if mean is not None else [0.485, 0.456, 0.406] - std = std if std is not None else [0.229, 0.224, 0.225] - - shape = (3, 1, 1) if order == 'chw' else (1, 1, 3) - self.mean = np.array(mean).reshape(shape).astype('float32') - self.std = np.array(std).reshape(shape).astype('float32') - - def __call__(self, data): - img = np.array(data['image']).astype(np.float32) - data['image'] = (img * self.scale - self.mean) / self.std - return data - - -class ToCHWImage(object): - """ convert hwc image to chw image""" - def __init__(self): - pass - - def __call__(self, data): - img = data['image'] - from PIL import Image - if isinstance(img, Image.Image): - img = np.array(img) - data['image'] = img.transpose((2, 0, 1)) - return data - - -class KeepKeys(object): - def __init__(self, keep_keys): - self.keep_keys = keep_keys - - def __call__(self, data): - data_list = [] - for key in self.keep_keys: - data_list.append(data[key]) - return data_list - - -class DetResizeForTest(object): - def __init__(self, **kwargs): - super(DetResizeForTest, self).__init__() - self.resize_type = 0 - if 'image_shape' in kwargs: - self.image_shape = kwargs['image_shape'] - self.resize_type = 1 - elif 'limit_side_len' in kwargs: - self.limit_side_len = kwargs.get('limit_side_len', 736) - self.limit_type = kwargs.get('limit_type', 'min') - - if 'resize_long' in kwargs: - self.resize_type = 2 - self.resize_long = kwargs.get('resize_long', 960) - else: - self.limit_side_len = kwargs.get('limit_side_len', 736) - self.limit_type = kwargs.get('limit_type', 'min') - - def __call__(self, data): - img = data['image'] - src_h, src_w = img.shape[:2] - - if self.resize_type == 0: - # img, shape = self.resize_image_type0(img) - img, [ratio_h, ratio_w] = self.resize_image_type0(img) - elif self.resize_type == 2: - img, [ratio_h, ratio_w] = self.resize_image_type2(img) - else: - # img, shape = self.resize_image_type1(img) - img, [ratio_h, ratio_w] = self.resize_image_type1(img) - data['image'] = img - data['shape'] = np.array([src_h, src_w, ratio_h, ratio_w]) - return data - - def resize_image_type1(self, img): - resize_h, resize_w = self.image_shape - ori_h, ori_w = img.shape[:2] # (h, w, c) - ratio_h = float(resize_h) / ori_h - ratio_w = float(resize_w) / ori_w - img = cv2.resize(img, (int(resize_w), int(resize_h))) - # return img, np.array([ori_h, ori_w]) - return img, [ratio_h, ratio_w] - - def resize_image_type0(self, img): - """ - resize image to a size multiple of 32 which is required by the network - args: - img(array): array with shape [h, w, c] - return(tuple): - img, (ratio_h, ratio_w) - """ - limit_side_len = self.limit_side_len - h, w = img.shape[:2] - - # limit the max side - if self.limit_type == 'max': - if max(h, w) > limit_side_len: - if h > w: - ratio = float(limit_side_len) / h - else: - ratio = float(limit_side_len) / w - else: - ratio = 1. - else: - if min(h, w) < limit_side_len: - if h < w: - ratio = float(limit_side_len) / h - else: - ratio = float(limit_side_len) / w - else: - ratio = 1. - resize_h = int(h * ratio) - resize_w = int(w * ratio) - - resize_h = int(round(resize_h / 32) * 32) - resize_w = int(round(resize_w / 32) * 32) - - try: - if int(resize_w) <= 0 or int(resize_h) <= 0: - return None, (None, None) - img = cv2.resize(img, (int(resize_w), int(resize_h))) - except: - print(img.shape, resize_w, resize_h) - sys.exit(0) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - return img, [ratio_h, ratio_w] - - def resize_image_type2(self, img): - h, w = img.shape[:2] - - resize_w = w - resize_h = h - - # Fix the longer side - if resize_h > resize_w: - ratio = float(self.resize_long) / resize_h - else: - ratio = float(self.resize_long) / resize_w - - resize_h = int(resize_h * ratio) - resize_w = int(resize_w * ratio) - - max_stride = 128 - resize_h = (resize_h + max_stride - 1) // max_stride * max_stride - resize_w = (resize_w + max_stride - 1) // max_stride * max_stride - img = cv2.resize(img, (int(resize_w), int(resize_h))) - ratio_h = resize_h / float(h) - ratio_w = resize_w / float(w) - - return img, [ratio_h, ratio_w] - - -def transform(data, ops=None): - """ transform """ - if ops is None: - ops = [] - - for op in ops: - data = op(data) - if data is None: - return None - return data - - -def create_operators(op_param_dict): - """ - create operators based on the config - """ - ops = [] - for op_name, param in op_param_dict.items(): - if param is None: - param = {} - op = eval(op_name)(**param) - ops.append(op) - return ops - - -def draw_text_det_res(dt_boxes, img_path): - src_im = cv2.imread(img_path) - for box in dt_boxes: - box = np.array(box).astype(np.int32).reshape(-1, 2) - cv2.polylines(src_im, [box], True, - color=(255, 255, 0), thickness=2) - return src_im - - -class DBPostProcess(object): - """The post process for Differentiable Binarization (DB).""" - - def __init__(self, - thresh=0.3, - box_thresh=0.7, - max_candidates=1000, - unclip_ratio=2.0, - score_mode="fast", - use_dilation=False): - self.thresh = thresh - self.box_thresh = box_thresh - self.max_candidates = max_candidates - self.unclip_ratio = unclip_ratio - self.min_size = 3 - self.score_mode = score_mode - - if use_dilation: - self.dilation_kernel = np.array([[1, 1], [1, 1]]) - else: - self.dilation_kernel = None - - def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height): - ''' - _bitmap: single map with shape (1, H, W), - whose values are binarized as {0, 1} - ''' - - bitmap = _bitmap - height, width = bitmap.shape - - outs = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST, - cv2.CHAIN_APPROX_SIMPLE) - if len(outs) == 3: - img, contours, _ = outs[0], outs[1], outs[2] - elif len(outs) == 2: - contours, _ = outs[0], outs[1] - - num_contours = min(len(contours), self.max_candidates) - - boxes = [] - scores = [] - for index in range(num_contours): - contour = contours[index] - points, sside = self.get_mini_boxes(contour) - if sside < self.min_size: - continue - points = np.array(points) - if self.score_mode == "fast": - score = self.box_score_fast(pred, points.reshape(-1, 2)) - else: - score = self.box_score_slow(pred, contour) - if self.box_thresh > score: - continue - - box = self.unclip(points).reshape(-1, 1, 2) - box, sside = self.get_mini_boxes(box) - if sside < self.min_size + 2: - continue - box = np.array(box) - - box[:, 0] = np.clip( - np.round(box[:, 0] / width * dest_width), 0, dest_width) - box[:, 1] = np.clip( - np.round(box[:, 1] / height * dest_height), 0, dest_height) - boxes.append(box.astype(np.int16)) - scores.append(score) - return np.array(boxes, dtype=np.int16), scores - - def unclip(self, box): - unclip_ratio = self.unclip_ratio - poly = Polygon(box) - distance = poly.area * unclip_ratio / poly.length - offset = pyclipper.PyclipperOffset() - offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) - expanded = np.array(offset.Execute(distance)) - return expanded - - def get_mini_boxes(self, contour): - bounding_box = cv2.minAreaRect(contour) - points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) - - index_1, index_2, index_3, index_4 = 0, 1, 2, 3 - if points[1][1] > points[0][1]: - index_1 = 0 - index_4 = 1 - else: - index_1 = 1 - index_4 = 0 - if points[3][1] > points[2][1]: - index_2 = 2 - index_3 = 3 - else: - index_2 = 3 - index_3 = 2 - - box = [ - points[index_1], points[index_2], points[index_3], points[index_4] - ] - return box, min(bounding_box[1]) - - def box_score_fast(self, bitmap, _box): - h, w = bitmap.shape[:2] - box = _box.copy() - xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int32), 0, w - 1) - xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int32), 0, w - 1) - ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int32), 0, h - 1) - ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int32), 0, h - 1) - - mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) - box[:, 0] = box[:, 0] - xmin - box[:, 1] = box[:, 1] - ymin - cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1) - return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0] - - def box_score_slow(self, bitmap, contour): - ''' - box_score_slow: use polyon mean score as the mean score - ''' - h, w = bitmap.shape[:2] - contour = contour.copy() - contour = np.reshape(contour, (-1, 2)) - - xmin = np.clip(np.min(contour[:, 0]), 0, w - 1) - xmax = np.clip(np.max(contour[:, 0]), 0, w - 1) - ymin = np.clip(np.min(contour[:, 1]), 0, h - 1) - ymax = np.clip(np.max(contour[:, 1]), 0, h - 1) - - mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) - - contour[:, 0] = contour[:, 0] - xmin - contour[:, 1] = contour[:, 1] - ymin - - cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype(np.int32), 1) - return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0] - - def __call__(self, pred, shape_list): - pred = pred[:, 0, :, :] - segmentation = pred > self.thresh - - boxes_batch = [] - for batch_index in range(pred.shape[0]): - src_h, src_w, ratio_h, ratio_w = shape_list[batch_index] - if self.dilation_kernel is not None: - mask = cv2.dilate( - np.array(segmentation[batch_index]).astype(np.uint8), - self.dilation_kernel) - else: - mask = segmentation[batch_index] - boxes, scores = self.boxes_from_bitmap(pred[batch_index], mask, - src_w, src_h) - - boxes_batch.append({'points': boxes}) - return boxes_batch diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/__init__.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/__init__.py deleted file mode 100644 index ea54cd3..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -from .text_recognize import TextRecognizer \ No newline at end of file diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/config.yaml b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/config.yaml deleted file mode 100644 index 4b71491..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/config.yaml +++ /dev/null @@ -1,12 +0,0 @@ -model_path: models/ch_PP-OCRv3_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 - cudnn_conv_algo_search: EXHAUSTIVE - do_copy_in_default_stream: true - -rec_img_shape: [3, 48, 320] -rec_batch_num: 6 diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/text_recognize.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/text_recognize.py deleted file mode 100644 index df1bda0..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/text_recognize.py +++ /dev/null @@ -1,119 +0,0 @@ -# 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 argparse -import math -import time -from typing import List - -import cv2 -import numpy as np - -try: - from .utils import CTCLabelDecode, read_yaml, OrtInferSession -except: - from utils import CTCLabelDecode, read_yaml, OrtInferSession - - -class TextRecognizer(object): - def __init__(self, config): - session_instance = OrtInferSession(config) - self.session = session_instance.session - self.input_name = session_instance.get_input_name() - - if session_instance.have_key(): - self.character_dict_path = session_instance.get_character_list() - else: - self.character_dict_path = config.get('keys_path', None) - self.postprocess_op = CTCLabelDecode(self.character_dict_path) - - self.rec_batch_num = config['rec_batch_num'] - self.rec_image_shape = config['rec_img_shape'] - - def __call__(self, img_list: List[np.ndarray]): - if isinstance(img_list, np.ndarray): - img_list = [img_list] - - # Calculate the aspect ratio of all text bars - width_list = [img.shape[1] / float(img.shape[0]) for img in img_list] - - # Sorting can speed up the recognition process - indices = np.argsort(np.array(width_list)) - - img_num = len(img_list) - rec_res = [['', 0.0]] * img_num - - batch_num = self.rec_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) - 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) - - norm_img_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_batch.append(norm_img[np.newaxis, :]) - norm_img_batch = np.concatenate(norm_img_batch).astype(np.float32) - - starttime = time.time() - onnx_inputs = {self.input_name: norm_img_batch} - preds = self.session.run(None, onnx_inputs)[0] - rec_result = self.postprocess_op(preds) - - for rno in range(len(rec_result)): - rec_res[indices[beg_img_no + rno]] = rec_result[rno] - elapse += time.time() - starttime - return rec_res, elapse - - def resize_norm_img(self, img, max_wh_ratio): - img_channel, img_height, img_width = self.rec_image_shape - assert img_channel == img.shape[2] - - img_width = int(img_height * max_wh_ratio) - - h, w = img.shape[:2] - ratio = w / float(h) - if math.ceil(img_height * ratio) > img_width: - resized_w = img_width - else: - resized_w = int(math.ceil(img_height * ratio)) - - resized_image = cv2.resize(img, (resized_w, img_height)) - resized_image = resized_image.astype('float32') - resized_image = resized_image.transpose((2, 0, 1)) / 255 - resized_image -= 0.5 - resized_image /= 0.5 - - padding_im = np.zeros((img_channel, img_height, img_width), - dtype=np.float32) - padding_im[:, :, 0:resized_w] = resized_image - return padding_im - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--image_path', type=str, help='image_dir|image_path') - parser.add_argument('--config_path', type=str, default='config.yaml') - args = parser.parse_args() - - config = read_yaml(args.config_path) - text_recognizer = TextRecognizer(config) - - img = cv2.imread(args.image_path) - rec_res, predict_time = text_recognizer(img) - print(f'rec result: {rec_res}\t cost: {predict_time}s') diff --git a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/utils.py b/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/utils.py deleted file mode 100644 index 0860460..0000000 --- a/ocrweb/rapidocr_onnxruntime/ch_ppocr_v3_rec/utils.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -import warnings -from pathlib import Path - -import numpy as np -import yaml -from onnxruntime import (GraphOptimizationLevel, InferenceSession, - SessionOptions, get_available_providers, get_device) - -root_dir = Path(__file__).resolve().parent.parent - - -class OrtInferSession(object): - def __init__(self, config): - sess_opt = SessionOptions() - sess_opt.log_severity_level = 4 - sess_opt.enable_cpu_mem_arena = False - sess_opt.graph_optimization_level = GraphOptimizationLevel.ORT_ENABLE_ALL - - cuda_ep = 'CUDAExecutionProvider' - cpu_ep = 'CPUExecutionProvider' - cpu_provider_options = { - "arena_extend_strategy": "kSameAsRequested", - } - - EP_list = [] - if config['use_cuda'] and get_device() == 'GPU' \ - and cuda_ep in get_available_providers(): - EP_list = [(cuda_ep, config[cuda_ep])] - EP_list.append((cpu_ep, cpu_provider_options)) - - config['model_path'] = str(root_dir / config['model_path']) - self._verify_model(config['model_path']) - 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 get_character_list(self, key: str = 'character'): - return self.meta_dict[key].splitlines() - - def have_key(self, key: str = 'character') -> bool: - self.meta_dict = self.session.get_modelmeta().custom_metadata_map - if key in self.meta_dict.keys(): - return True - return False - - @staticmethod - def _verify_model(model_path): - model_path = Path(model_path) - if not model_path.exists(): - raise FileNotFoundError(f'{model_path} does not exists.') - if not model_path.is_file(): - raise FileExistsError(f'{model_path} is not a file.') - - -def read_yaml(yaml_path): - with open(yaml_path, 'rb') as f: - data = yaml.load(f, Loader=yaml.Loader) - return data - - -class CTCLabelDecode(object): - """ Convert between text-label and text-index """ - - def __init__(self, character_dict_path): - super(CTCLabelDecode, self).__init__() - - self.character_str = [] - assert character_dict_path is not None, "character_dict_path should not be None" - - if isinstance(character_dict_path, str): - with open(character_dict_path, "rb") as fin: - lines = fin.readlines() - for line in lines: - line = line.decode('utf-8').strip("\n").strip("\r\n") - self.character_str.append(line) - else: - self.character_str = character_dict_path - self.character_str.append(' ') - - dict_character = self.add_special_char(self.character_str) - self.character = dict_character - - self.dict = {} - for i, char in enumerate(dict_character): - self.dict[char] = i - - def __call__(self, preds, label=None): - preds_idx = preds.argmax(axis=2) - preds_prob = preds.max(axis=2) - text = self.decode(preds_idx, preds_prob, - is_remove_duplicate=True) - if label is None: - return text - label = self.decode(label) - return text, label - - def add_special_char(self, dict_character): - dict_character = ['blank'] + dict_character - return dict_character - - def get_ignored_tokens(self): - return [0] # for ctc blank - - def decode(self, text_index, text_prob=None, is_remove_duplicate=False): - """ convert text-index into text-label. """ - - result_list = [] - ignored_tokens = self.get_ignored_tokens() - batch_size = len(text_index) - for batch_idx in range(batch_size): - char_list = [] - conf_list = [] - for idx in range(len(text_index[batch_idx])): - if text_index[batch_idx][idx] in ignored_tokens: - continue - if is_remove_duplicate: - # only for predict - if idx > 0 and text_index[batch_idx][idx - 1] == text_index[ - batch_idx][idx]: - continue - char_list.append(self.character[int(text_index[batch_idx][ - idx])]) - if text_prob is not None: - conf_list.append(text_prob[batch_idx][idx]) - else: - conf_list.append(1) - text = ''.join(char_list) - result_list.append((text, np.mean(conf_list + [1e-50]))) - return result_list diff --git a/ocrweb/rapidocr_onnxruntime/config.yaml b/ocrweb/rapidocr_onnxruntime/config.yaml deleted file mode 100644 index 4b849b7..0000000 --- a/ocrweb/rapidocr_onnxruntime/config.yaml +++ /dev/null @@ -1,73 +0,0 @@ -Global: - text_score: 0.5 - use_angle_cls: true - use_text_det: true - print_verbose: true - min_height: 30 - width_height_ratio: 8 - -Det: - module_name: ch_ppocr_v3_det - class_name: TextDetector - model_path: models/ch_PP-OCRv3_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 - cudnn_conv_algo_search: EXHAUSTIVE - do_copy_in_default_stream: true - - pre_process: - DetResizeForTest: - limit_side_len: 736 - limit_type: min - NormalizeImage: - std: [0.229, 0.224, 0.225] - mean: [0.485, 0.456, 0.406] - scale: 1./255. - order: hwc - ToCHWImage: - KeepKeys: - keep_keys: ['image', 'shape'] - - post_process: - thresh: 0.3 - box_thresh: 0.5 - max_candidates: 1000 - unclip_ratio: 1.6 - use_dilation: true - score_mode: fast - -Cls: - module_name: ch_ppocr_v2_cls - class_name: TextClassifier - model_path: models/ch_ppocr_mobile_v2.0_cls_infer.onnx - - use_cuda: false - CUDAExecutionProvider: - device_id: 0 - arena_extend_strategy: kNextPowerOfTwo - 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 - label_list: ['0', '180'] - -Rec: - module_name: ch_ppocr_v3_rec - class_name: TextRecognizer - model_path: models/ch_PP-OCRv3_rec_infer.onnx - - use_cuda: false - CUDAExecutionProvider: - device_id: 0 - arena_extend_strategy: kNextPowerOfTwo - cudnn_conv_algo_search: EXHAUSTIVE - do_copy_in_default_stream: true - - rec_img_shape: [3, 48, 320] - rec_batch_num: 6 diff --git a/ocrweb/rapidocr_onnxruntime/models/.gitkeep b/ocrweb/rapidocr_onnxruntime/models/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ocrweb/rapidocr_onnxruntime/rapid_ocr_api.py b/ocrweb/rapidocr_onnxruntime/rapid_ocr_api.py deleted file mode 100644 index 4261223..0000000 --- a/ocrweb/rapidocr_onnxruntime/rapid_ocr_api.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -import copy -import importlib -import sys -from pathlib import Path - -import cv2 -import numpy as np -import yaml - -root_dir = Path(__file__).resolve().parent -sys.path.append(str(root_dir)) - - -class RapidOCR(): - def __init__(self, config_path=str(root_dir / 'config.yaml')): - super(RapidOCR).__init__() - if not Path(config_path).exists(): - raise FileExistsError(f'{config_path} does not exist!') - - config = self.read_yaml(config_path) - - global_config = config['Global'] - self.print_verbose = global_config['print_verbose'] - self.text_score = global_config['text_score'] - self.min_height = global_config['min_height'] - self.width_height_ratio = global_config['width_height_ratio'] - - self.use_text_det = config['Global']['use_text_det'] - if self.use_text_det: - TextDetector = self.init_module(config['Det']['module_name'], - config['Det']['class_name']) - self.text_detector = TextDetector(config['Det']) - - TextRecognizer = self.init_module(config['Rec']['module_name'], - config['Rec']['class_name']) - self.text_recognizer = TextRecognizer(config['Rec']) - - self.use_angle_cls = config['Global']['use_angle_cls'] - if self.use_angle_cls: - TextClassifier = self.init_module(config['Cls']['module_name'], - config['Cls']['class_name']) - self.text_cls = TextClassifier(config['Cls']) - - def __call__(self, img: np.ndarray): - h, w = img.shape[:2] - if self.width_height_ratio == -1: - use_limit_ratio = False - else: - use_limit_ratio = w / h > self.width_height_ratio - - if not self.use_text_det \ - or h <= self.min_height \ - or use_limit_ratio: - dt_boxes, img_crop_list = self.get_boxes_img_without_det(img, h, w) - det_elapse = 0.0 - else: - dt_boxes, det_elapse = self.text_detector(img) - if dt_boxes is None or len(dt_boxes) < 1: - return None - - if self.print_verbose: - print(f'dt_boxes num: {len(dt_boxes)}, elapse: {det_elapse}') - - dt_boxes = self.sorted_boxes(dt_boxes) - img_crop_list = self.get_crop_img_list(img, dt_boxes) - - cls_elapse = 0.0 - if self.use_angle_cls: - img_crop_list, _, cls_elapse = self.text_cls(img_crop_list) - - if self.print_verbose: - print(f'cls num: {len(img_crop_list)}, elapse: {cls_elapse}') - - rec_res, rec_elapse = self.text_recognizer(img_crop_list) - if self.print_verbose: - print(f'rec_res num: {len(rec_res)}, elapse: {rec_elapse}') - - filter_boxes, filter_rec_res = self.filter_boxes_rec_by_score(dt_boxes, - rec_res) - fina_result = [[dt.tolist(), rec[0], str(rec[1])] - for dt, rec in zip(filter_boxes, filter_rec_res)] - return fina_result, [det_elapse, cls_elapse, rec_elapse] - - @staticmethod - def read_yaml(yaml_path): - with open(yaml_path, 'rb') as f: - data = yaml.load(f, Loader=yaml.Loader) - return data - - @staticmethod - def init_module(module_name, class_name): - module_part = importlib.import_module(module_name) - return getattr(module_part, class_name) - - def get_boxes_img_without_det(self, img, h, w): - x0, y0, x1, y1 = 0, 0, w, h - dt_boxes = np.array([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]) - dt_boxes = dt_boxes[np.newaxis, ...] - img_crop_list = [img] - return dt_boxes, img_crop_list - - def get_crop_img_list(self, img, dt_boxes): - def get_rotate_crop_image(img, points): - img_crop_width = int( - max( - np.linalg.norm(points[0] - points[1]), - np.linalg.norm(points[2] - points[3]))) - img_crop_height = int( - max( - np.linalg.norm(points[0] - points[3]), - np.linalg.norm(points[1] - points[2]))) - pts_std = np.float32([[0, 0], [img_crop_width, 0], - [img_crop_width, img_crop_height], - [0, img_crop_height]]) - M = cv2.getPerspectiveTransform(points, pts_std) - dst_img = cv2.warpPerspective( - img, - M, (img_crop_width, img_crop_height), - borderMode=cv2.BORDER_REPLICATE, - flags=cv2.INTER_CUBIC) - dst_img_height, dst_img_width = dst_img.shape[0:2] - if dst_img_height * 1.0 / dst_img_width >= 1.5: - dst_img = np.rot90(dst_img) - return dst_img - - img_crop_list = [] - for box in dt_boxes: - tmp_box = copy.deepcopy(box) - img_crop = get_rotate_crop_image(img, tmp_box) - img_crop_list.append(img_crop) - return img_crop_list - - @staticmethod - def sorted_boxes(dt_boxes): - """ - Sort text boxes in order from top to bottom, left to right - args: - dt_boxes(array):detected text boxes with shape [4, 2] - return: - sorted boxes(array) with shape [4, 2] - """ - 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 - - def filter_boxes_rec_by_score(self, dt_boxes, rec_res): - filter_boxes, filter_rec_res = [], [] - for box, rec_reuslt in zip(dt_boxes, rec_res): - text, score = rec_reuslt - if score >= self.text_score: - filter_boxes.append(box) - filter_rec_res.append(rec_reuslt) - return filter_boxes, filter_rec_res - - -if __name__ == '__main__': - text_sys = RapidOCR('config.yaml') - - import cv2 - img = cv2.imread('resources/test_images/det_images/ch_en_num.jpg') - - result = text_sys(img) - print(result) diff --git a/python/README.md b/python/README.md index 6666b48..fbf9a50 100755 --- a/python/README.md +++ b/python/README.md @@ -1,6 +1,6 @@ ## RapidOCR Python

- +

diff --git a/python/rapid_structure/README.md b/python/rapid_structure/README.md index fe8aa5e..0cdd090 100644 --- a/python/rapid_structure/README.md +++ b/python/rapid_structure/README.md @@ -4,7 +4,7 @@ #### Rapid Layout

- + PyPI