From 05b4b32203d634a0333e1ef768ea7d77a9a92529 Mon Sep 17 00:00:00 2001 From: SWHL Date: Fri, 8 Nov 2024 09:41:25 +0800 Subject: [PATCH] chore: optimize code --- README.md | 3 + demo.py | 31 ++- rapid_orientation/config.yaml | 8 - rapid_orientation/main.py | 45 ++--- rapid_orientation/utils.py | 258 ------------------------ rapid_orientation/utils/__init__.py | 3 + rapid_orientation/utils/infer_engine.py | 231 +++++++++++++++++++++ rapid_orientation/utils/load_image.py | 123 +++++++++++ rapid_orientation/utils/logger.py | 21 ++ rapid_orientation/utils/preprocess.py | 97 +++++++++ rapid_orientation/utils/utils.py | 10 + tests/test_files/table.jpg | Bin 59389 -> 0 bytes tests/test_orientation.py | 1 - 13 files changed, 540 insertions(+), 291 deletions(-) delete mode 100644 rapid_orientation/utils.py create mode 100644 rapid_orientation/utils/__init__.py create mode 100644 rapid_orientation/utils/infer_engine.py create mode 100644 rapid_orientation/utils/load_image.py create mode 100644 rapid_orientation/utils/logger.py create mode 100644 rapid_orientation/utils/preprocess.py create mode 100644 rapid_orientation/utils/utils.py delete mode 100644 tests/test_files/table.jpg diff --git a/README.md b/README.md index b6dd8ce..5663b5b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ PyPI + +SemVer2.0 + diff --git a/demo.py b/demo.py index 58d9a0c..75af9d5 100644 --- a/demo.py +++ b/demo.py @@ -5,7 +5,36 @@ from rapid_orientation import RapidOrientation + +def scale_resize(img, resize_value=(280, 32)): + """ + @params: + img: ndarray + resize_value: (width, height) + """ + # padding + ratio = resize_value[0] / resize_value[1] # w / h + h, w = img.shape[:2] + if w / h < ratio: + # 补宽 + t = int(h * ratio) + w_padding = (t - w) // 2 + img = cv2.copyMakeBorder( + img, 0, 0, w_padding, w_padding, cv2.BORDER_CONSTANT, value=(0, 0, 0) + ) + else: + # 补高 (top, bottom, left, right) + t = int(w / ratio) + h_padding = (t - h) // 2 + color = tuple([int(i) for i in img[0, 0, :]]) + img = cv2.copyMakeBorder( + img, h_padding, h_padding, 0, 0, cv2.BORDER_CONSTANT, value=(0, 0, 0) + ) + img = cv2.resize(img, resize_value, interpolation=cv2.INTER_LANCZOS4) + return img + + orientation_engine = RapidOrientation() -img = cv2.imread("tests/test_files/img_rot180_demo.jpg") +img = cv2.imread("tests/test_files/1.png") cls_result, _ = orientation_engine(img) print(cls_result) diff --git a/rapid_orientation/config.yaml b/rapid_orientation/config.yaml index 78847b2..cf96003 100644 --- a/rapid_orientation/config.yaml +++ b/rapid_orientation/config.yaml @@ -6,11 +6,3 @@ CUDAExecutionProvider: arena_extend_strategy: kNextPowerOfTwo cudnn_conv_algo_search: EXHAUSTIVE do_copy_in_default_stream: true - -PreProcess: - - ResizeImage: - resize_short: 256 - - CropImage: - size: 224 - - NormalizeImage: - - ToCHWImage: diff --git a/rapid_orientation/main.py b/rapid_orientation/main.py index 387ba6d..aa90fd5 100644 --- a/rapid_orientation/main.py +++ b/rapid_orientation/main.py @@ -17,53 +17,52 @@ import argparse import time from pathlib import Path -from typing import Optional, Union +from typing import Union import cv2 import numpy as np -import yaml -from .utils import LoadImage, OrtInferSession, create_operators +from .utils.infer_engine import OrtInferSession +from .utils.load_image import LoadImage +from .utils.preprocess import Preprocess +from .utils.utils import read_yaml root_dir = Path(__file__).resolve().parent +DEFAULT_PATH = root_dir / "models" / "rapid_orientation.onnx" +DEFAULT_CFG = root_dir / "config.yaml" class RapidOrientation: - def __init__(self, model_path: Optional[str] = None): - config_path = str(root_dir / "config.yaml") - config = self.read_yaml(config_path) - if model_path is None: - model_path = str(root_dir / "models" / "rapid_orientation.onnx") + def __init__( + self, + model_path: Union[str, Path] = DEFAULT_PATH, + cfg_path: Union[str, Path] = DEFAULT_CFG, + ): + config = read_yaml(cfg_path) config["model_path"] = model_path self.session = OrtInferSession(config) - self.labels = self.session.get_metadata()["character"].splitlines() - - self.preprocess_ops = create_operators(config["PreProcess"]) + self.labels = self.session.get_character_list() + self.preprocess = Preprocess() self.load_img = LoadImage() def __call__(self, img_content: Union[str, np.ndarray, bytes, Path]): - images = self.load_img(img_content) + image = self.load_img(img_content) + + s = time.perf_counter() - s = time.time() - for ops in self.preprocess_ops: - images = ops(images) - image = np.array(images)[None, ...] + image = self.preprocess(image) + image = image[None, ...] pred_output = self.session(image)[0] pred_output = pred_output.squeeze() pred_idx = np.argmax(pred_output) pred_txt = self.labels[pred_idx] - elapse = time.time() - s - return pred_txt, elapse - @staticmethod - def read_yaml(yaml_path): - with open(yaml_path, "rb") as f: - data = yaml.load(f, Loader=yaml.Loader) - return data + elapse = time.perf_counter() - s + return pred_txt, elapse def main(): diff --git a/rapid_orientation/utils.py b/rapid_orientation/utils.py deleted file mode 100644 index 018e577..0000000 --- a/rapid_orientation/utils.py +++ /dev/null @@ -1,258 +0,0 @@ -# -*- encoding: utf-8 -*- -# @Author: SWHL -# @Contact: liekkaskono@163.com -import importlib -import warnings -from io import BytesIO -from pathlib import Path -from typing import Union - -import cv2 -import numpy as np -from onnxruntime import ( - GraphOptimizationLevel, - InferenceSession, - SessionOptions, - get_available_providers, - get_device, -) -from PIL import Image, UnidentifiedImageError - -InputType = Union[str, np.ndarray, bytes, Path] - - -class OrtInferSession: - 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)) - - self._verify_model(config["model_path"]) - self.session = InferenceSession( - config["model_path"], sess_options=sess_opt, providers=EP_list - ) - - has_cuda_ep = cuda_ep not in self.session.get_providers() - if config["use_cuda"] and has_cuda_ep: - warnings.warn( - f"{cuda_ep} is not avaiable for current env," - f"the inference part is automatically shifted to " - f"be executed under {cpu_ep}. " - f"Please ensure the installed onnxruntime-gpu " - f" version matches your cuda and cudnn version, " - f"you can check their relations from the offical web site: " - f"https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html", - RuntimeWarning, - ) - - def __call__(self, input_content: np.ndarray) -> np.ndarray: - input_dict = dict(zip(self.get_input_names(), [input_content])) - try: - return self.session.run(self.get_output_names(), input_dict) - except Exception as e: - raise ONNXRuntimeError("ONNXRuntime inferece failed.") from e - - def get_input_names( - self, - ): - return [v.name for v in self.session.get_inputs()] - - def get_output_names( - self, - ): - return [v.name for v in self.session.get_outputs()] - - def get_metadata(self): - meta_dict = self.session.get_modelmeta().custom_metadata_map - return meta_dict - - @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.") - - -class ONNXRuntimeError(Exception): - pass - - -class LoadImage: - def __init__( - self, - ): - pass - - def __call__(self, img: InputType) -> np.ndarray: - if not isinstance(img, InputType.__args__): - raise LoadImageError( - f"The img type {type(img)} does not in {InputType.__args__}" - ) - - img = self.load_img(img) - - if img.ndim == 2: - return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) - - if img.ndim == 3 and img.shape[2] == 4: - return self.cvt_four_to_three(img) - - return img - - def load_img(self, img: InputType) -> np.ndarray: - if isinstance(img, (str, Path)): - self.verify_exist(img) - try: - img = np.array(Image.open(img)) - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - except UnidentifiedImageError as e: - raise LoadImageError(f"cannot identify image file {img}") from e - return img - - if isinstance(img, bytes): - img = np.array(Image.open(BytesIO(img))) - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - return img - - if isinstance(img, np.ndarray): - return img - - raise LoadImageError(f"{type(img)} is not supported!") - - @staticmethod - def cvt_four_to_three(img: np.ndarray) -> np.ndarray: - """RGBA → RGB""" - r, g, b, a = cv2.split(img) - new_img = cv2.merge((b, g, r)) - - not_a = cv2.bitwise_not(a) - not_a = cv2.cvtColor(not_a, cv2.COLOR_GRAY2BGR) - - new_img = cv2.bitwise_and(new_img, new_img, mask=a) - new_img = cv2.add(new_img, not_a) - return new_img - - @staticmethod - def verify_exist(file_path: Union[str, Path]): - if not Path(file_path).exists(): - raise LoadImageError(f"{file_path} does not exist.") - - -class LoadImageError(Exception): - pass - - -def create_operators(params): - """ - create operators based on the config - - Args: - params(list): a dict list, used to create some operators - """ - assert isinstance(params, list), "operator config should be a list" - mod = importlib.import_module(__name__) - ops = [] - for operator in params: - assert isinstance(operator, dict) and len(operator) == 1, "yaml format error" - op_name = list(operator)[0] - param = {} if operator[op_name] is None else operator[op_name] - op = getattr(mod, op_name)(**param) - ops.append(op) - return ops - - -class ResizeImage: - def __init__(self, size=None, resize_short=None): - if resize_short is not None and resize_short > 0: - self.resize_short = resize_short - self.w, self.h = None, None - elif size is not None: - self.resize_short = None - self.w = size if isinstance(size, int) else size[0] - self.h = size if isinstance(size, int) else size[1] - else: - raise ValueError( - "invalid params for ReisizeImage for '\ - 'both 'size' and 'resize_short' are None" - ) - - def __call__(self, img: np.ndarray): - img_h, img_w = img.shape[:2] - - if self.resize_short: - percent = float(self.resize_short) / min(img_w, img_h) - w = int(round(img_w * percent)) - h = int(round(img_h * percent)) - else: - w = self.w - h = self.h - return cv2.resize(img, (w, h)) - - -class NormalizeImage: - def __init__( - self, - ): - self.scale = np.float32(1.0 / 255.0) - mean = [0.485, 0.456, 0.406] - std = [0.229, 0.224, 0.225] - - shape = 1, 1, 3 - self.mean = np.array(mean).reshape(shape).astype("float32") - self.std = np.array(std).reshape(shape).astype("float32") - - def __call__(self, img): - img = np.array(img).astype(np.float32) - img = (img * self.scale - self.mean) / self.std - return img.astype(np.float32) - - -class ToCHWImage: - def __init__(self): - pass - - def __call__(self, img): - img = np.array(img) - return img.transpose((2, 0, 1)) - - -class CropImage: - def __init__(self, size): - self.size = size - if isinstance(size, int): - self.size = (size, size) - - def __call__(self, img): - w, h = self.size - img_h, img_w = img.shape[:2] - - if img_h < h or img_w < w: - raise ValueError( - f"The size({h}, {w}) of CropImage must be greater than " - f"size({img_h}, {img_w}) of image." - ) - - w_start = (img_w - w) // 2 - h_start = (img_h - h) // 2 - - w_end = w_start + w - h_end = h_start + h - return img[h_start:h_end, w_start:w_end, :] diff --git a/rapid_orientation/utils/__init__.py b/rapid_orientation/utils/__init__.py new file mode 100644 index 0000000..0ecdd4f --- /dev/null +++ b/rapid_orientation/utils/__init__.py @@ -0,0 +1,3 @@ +# -*- encoding: utf-8 -*- +# @Author: SWHL +# @Contact: liekkaskono@163.com diff --git a/rapid_orientation/utils/infer_engine.py b/rapid_orientation/utils/infer_engine.py new file mode 100644 index 0000000..fb1fd35 --- /dev/null +++ b/rapid_orientation/utils/infer_engine.py @@ -0,0 +1,231 @@ +# -*- encoding: utf-8 -*- +# @Author: SWHL +# @Contact: liekkaskono@163.com +import os +import platform +import traceback +from enum import Enum +from pathlib import Path +from typing import Any, Dict, List, Tuple, Union + +import numpy as np +from onnxruntime import ( + GraphOptimizationLevel, + InferenceSession, + SessionOptions, + get_available_providers, + get_device, +) + +from .logger import get_logger + + +class EP(Enum): + CPU_EP = "CPUExecutionProvider" + CUDA_EP = "CUDAExecutionProvider" + DIRECTML_EP = "DmlExecutionProvider" + + +class OrtInferSession: + def __init__(self, config: Dict[str, Any]): + self.logger = get_logger("OrtInferSession") + + model_path = config.get("model_path", None) + self._verify_model(model_path) + + self.cfg_use_cuda = config.get("use_cuda", None) + self.cfg_use_dml = config.get("use_dml", None) + + self.had_providers: List[str] = get_available_providers() + EP_list = self._get_ep_list() + + sess_opt = self._init_sess_opts(config) + self.session = InferenceSession( + model_path, + sess_options=sess_opt, + providers=EP_list, + ) + self._verify_providers() + + @staticmethod + def _init_sess_opts(config: Dict[str, Any]) -> SessionOptions: + 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 + + cpu_nums = os.cpu_count() + intra_op_num_threads = config.get("intra_op_num_threads", -1) + if intra_op_num_threads != -1 and 1 <= intra_op_num_threads <= cpu_nums: + sess_opt.intra_op_num_threads = intra_op_num_threads + + inter_op_num_threads = config.get("inter_op_num_threads", -1) + if inter_op_num_threads != -1 and 1 <= inter_op_num_threads <= cpu_nums: + sess_opt.inter_op_num_threads = inter_op_num_threads + + return sess_opt + + def _get_ep_list(self) -> List[Tuple[str, Dict[str, Any]]]: + cpu_provider_opts = { + "arena_extend_strategy": "kSameAsRequested", + } + EP_list = [(EP.CPU_EP.value, cpu_provider_opts)] + + cuda_provider_opts = { + "device_id": 0, + "arena_extend_strategy": "kNextPowerOfTwo", + "cudnn_conv_algo_search": "EXHAUSTIVE", + "do_copy_in_default_stream": True, + } + self.use_cuda = self._check_cuda() + if self.use_cuda: + EP_list.insert(0, (EP.CUDA_EP.value, cuda_provider_opts)) + + self.use_directml = self._check_dml() + if self.use_directml: + self.logger.info( + "Windows 10 or above detected, try to use DirectML as primary provider" + ) + directml_options = ( + cuda_provider_opts if self.use_cuda else cpu_provider_opts + ) + EP_list.insert(0, (EP.DIRECTML_EP.value, directml_options)) + return EP_list + + def _check_cuda(self) -> bool: + if not self.cfg_use_cuda: + return False + + cur_device = get_device() + if cur_device == "GPU" and EP.CUDA_EP.value in self.had_providers: + return True + + self.logger.warning( + "%s is not in available providers (%s). Use %s inference by default.", + EP.CUDA_EP.value, + self.had_providers, + self.had_providers[0], + ) + self.logger.info("!!!Recommend to use rapidocr_paddle for inference on GPU.") + self.logger.info( + "(For reference only) If you want to use GPU acceleration, you must do:" + ) + self.logger.info( + "First, uninstall all onnxruntime pakcages in current environment." + ) + self.logger.info( + "Second, install onnxruntime-gpu by `pip install onnxruntime-gpu`." + ) + self.logger.info( + "\tNote the onnxruntime-gpu version must match your cuda and cudnn version." + ) + self.logger.info( + "\tYou can refer this link: https://onnxruntime.ai/docs/execution-providers/CUDA-EP.html" + ) + self.logger.info( + "Third, ensure %s is in available providers list. e.g. ['CUDAExecutionProvider', 'CPUExecutionProvider']", + EP.CUDA_EP.value, + ) + return False + + def _check_dml(self) -> bool: + if not self.cfg_use_dml: + return False + + cur_os = platform.system() + if cur_os != "Windows": + self.logger.warning( + "DirectML is only supported in Windows OS. The current OS is %s. Use %s inference by default.", + cur_os, + self.had_providers[0], + ) + return False + + cur_window_version = int(platform.release().split(".")[0]) + if cur_window_version < 10: + self.logger.warning( + "DirectML is only supported in Windows 10 and above OS. The current Windows version is %s. Use %s inference by default.", + cur_window_version, + self.had_providers[0], + ) + return False + + if EP.DIRECTML_EP.value in self.had_providers: + return True + + self.logger.warning( + "%s is not in available providers (%s). Use %s inference by default.", + EP.DIRECTML_EP.value, + self.had_providers, + self.had_providers[0], + ) + self.logger.info("If you want to use DirectML acceleration, you must do:") + self.logger.info( + "First, uninstall all onnxruntime pakcages in current environment." + ) + self.logger.info( + "Second, install onnxruntime-directml by `pip install onnxruntime-directml`" + ) + self.logger.info( + "Third, ensure %s is in available providers list. e.g. ['DmlExecutionProvider', 'CPUExecutionProvider']", + EP.DIRECTML_EP.value, + ) + return False + + def _verify_providers(self): + session_providers = self.session.get_providers() + first_provider = session_providers[0] + + if self.use_cuda and first_provider != EP.CUDA_EP.value: + self.logger.warning( + "%s is not avaiable for current env, the inference part is automatically shifted to be executed under %s.", + EP.CUDA_EP.value, + first_provider, + ) + + if self.use_directml and first_provider != EP.DIRECTML_EP.value: + self.logger.warning( + "%s is not available for current env, the inference part is automatically shifted to be executed under %s.", + EP.DIRECTML_EP.value, + first_provider, + ) + + def __call__(self, input_content: np.ndarray) -> np.ndarray: + input_dict = dict(zip(self.get_input_names(), [input_content])) + try: + return self.session.run(self.get_output_names(), input_dict) + except Exception as e: + error_info = traceback.format_exc() + raise ONNXRuntimeError(error_info) from e + + def get_input_names(self) -> List[str]: + return [v.name for v in self.session.get_inputs()] + + def get_output_names(self) -> List[str]: + return [v.name for v in self.session.get_outputs()] + + def get_character_list(self, key: str = "character") -> List[str]: + meta_dict = self.session.get_modelmeta().custom_metadata_map + return meta_dict[key].splitlines() + + def have_key(self, key: str = "character") -> bool: + meta_dict = self.session.get_modelmeta().custom_metadata_map + if key in meta_dict.keys(): + return True + return False + + @staticmethod + def _verify_model(model_path: Union[str, Path, None]): + if model_path is None: + raise ValueError("model_path is None!") + + 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.") + + +class ONNXRuntimeError(Exception): + pass diff --git a/rapid_orientation/utils/load_image.py b/rapid_orientation/utils/load_image.py new file mode 100644 index 0000000..f34b549 --- /dev/null +++ b/rapid_orientation/utils/load_image.py @@ -0,0 +1,123 @@ +# -*- encoding: utf-8 -*- +# @Author: SWHL +# @Contact: liekkaskono@163.com +from io import BytesIO +from pathlib import Path +from typing import Any, Union + +import cv2 +import numpy as np +from PIL import Image, UnidentifiedImageError + +root_dir = Path(__file__).resolve().parent +InputType = Union[str, np.ndarray, bytes, Path, Image.Image] + + +class LoadImage: + def __init__(self): + pass + + def __call__(self, img: InputType) -> np.ndarray: + if not isinstance(img, InputType.__args__): + raise LoadImageError( + f"The img type {type(img)} does not in {InputType.__args__}" + ) + + origin_img_type = type(img) + img = self.load_img(img) + img = self.convert_img(img, origin_img_type) + return img + + def load_img(self, img: InputType) -> np.ndarray: + if isinstance(img, (str, Path)): + self.verify_exist(img) + try: + img = self.img_to_ndarray(Image.open(img)) + except UnidentifiedImageError as e: + raise LoadImageError(f"cannot identify image file {img}") from e + return img + + if isinstance(img, bytes): + img = self.img_to_ndarray(Image.open(BytesIO(img))) + return img + + if isinstance(img, np.ndarray): + return img + + if isinstance(img, Image.Image): + return self.img_to_ndarray(img) + + raise LoadImageError(f"{type(img)} is not supported!") + + def img_to_ndarray(self, img: Image.Image) -> np.ndarray: + if img.mode == "1": + img = img.convert("L") + return np.array(img) + return np.array(img) + + def convert_img(self, img: np.ndarray, origin_img_type: Any) -> np.ndarray: + if img.ndim == 2: + return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + + if img.ndim == 3: + channel = img.shape[2] + if channel == 1: + return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + + if channel == 2: + return self.cvt_two_to_three(img) + + if channel == 3: + if issubclass(origin_img_type, (str, Path, bytes, Image.Image)): + return cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + return img + + if channel == 4: + return self.cvt_four_to_three(img) + + raise LoadImageError( + f"The channel({channel}) of the img is not in [1, 2, 3, 4]" + ) + + raise LoadImageError(f"The ndim({img.ndim}) of the img is not in [2, 3]") + + @staticmethod + def cvt_two_to_three(img: np.ndarray) -> np.ndarray: + """gray + alpha → BGR""" + img_gray = img[..., 0] + img_bgr = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2BGR) + + img_alpha = img[..., 1] + not_a = cv2.bitwise_not(img_alpha) + not_a = cv2.cvtColor(not_a, cv2.COLOR_GRAY2BGR) + + new_img = cv2.bitwise_and(img_bgr, img_bgr, mask=img_alpha) + new_img = cv2.add(new_img, not_a) + return new_img + + @staticmethod + def cvt_four_to_three(img: np.ndarray) -> np.ndarray: + """RGBA → BGR""" + r, g, b, a = cv2.split(img) + new_img = cv2.merge((b, g, r)) + + not_a = cv2.bitwise_not(a) + not_a = cv2.cvtColor(not_a, cv2.COLOR_GRAY2BGR) + + new_img = cv2.bitwise_and(new_img, new_img, mask=a) + + mean_color = np.mean(new_img) + if mean_color <= 0.0: + new_img = cv2.add(new_img, not_a) + else: + new_img = cv2.bitwise_not(new_img) + return new_img + + @staticmethod + def verify_exist(file_path: Union[str, Path]): + if not Path(file_path).exists(): + raise LoadImageError(f"{file_path} does not exist.") + + +class LoadImageError(Exception): + pass diff --git a/rapid_orientation/utils/logger.py b/rapid_orientation/utils/logger.py new file mode 100644 index 0000000..66522c4 --- /dev/null +++ b/rapid_orientation/utils/logger.py @@ -0,0 +1,21 @@ +# -*- encoding: utf-8 -*- +# @Author: SWHL +# @Contact: liekkaskono@163.com +import logging +from functools import lru_cache + + +@lru_cache(maxsize=32) +def get_logger(name: str) -> logging.Logger: + logger = logging.getLogger(name) + logger.setLevel(logging.DEBUG) + + fmt = "%(asctime)s - %(name)s - %(levelname)s: %(message)s" + format_str = logging.Formatter(fmt) + + sh = logging.StreamHandler() + sh.setLevel(logging.DEBUG) + + logger.addHandler(sh) + sh.setFormatter(format_str) + return logger diff --git a/rapid_orientation/utils/preprocess.py b/rapid_orientation/utils/preprocess.py new file mode 100644 index 0000000..0631b75 --- /dev/null +++ b/rapid_orientation/utils/preprocess.py @@ -0,0 +1,97 @@ +# -*- encoding: utf-8 -*- +# @Author: SWHL +# @Contact: liekkaskono@163.com +import cv2 +import numpy as np + + +class Preprocess: + def __init__(self): + self.resize_img = ResizeImage(resize_short=256) + self.crop_img = CropImage(size=224) + self.normal_img = NormalizeImage() + self.cvt_channel = ToCHWImage() + + def __call__(self, img: np.ndarray): + img = self.resize_img(img) + img = self.crop_img(img) + img = self.normal_img(img) + img = self.cvt_channel(img) + return img + + +class ResizeImage: + def __init__(self, size=None, resize_short=None): + if resize_short is not None and resize_short > 0: + self.resize_short = resize_short + self.w, self.h = None, None + elif size is not None: + self.resize_short = None + self.w = size if isinstance(size, int) else size[0] + self.h = size if isinstance(size, int) else size[1] + else: + raise ValueError( + "invalid params for ReisizeImage for '\ + 'both 'size' and 'resize_short' are None" + ) + + def __call__(self, img: np.ndarray): + img_h, img_w = img.shape[:2] + + w, h = self.w, self.h + if self.resize_short: + percent = float(self.resize_short) / min(img_w, img_h) + w = int(round(img_w * percent)) + h = int(round(img_h * percent)) + return cv2.resize(img, (w, h), interpolation=cv2.INTER_LANCZOS4) + + +class CropImage: + def __init__(self, size): + self.size = size + if isinstance(size, int): + self.size = (size, size) + + def __call__(self, img): + w, h = self.size + img_h, img_w = img.shape[:2] + + if img_h < h or img_w < w: + raise ValueError( + f"The size({h}, {w}) of CropImage must be greater than " + f"size({img_h}, {img_w}) of image." + ) + + w_start = (img_w - w) // 2 + h_start = (img_h - h) // 2 + + w_end = w_start + w + h_end = h_start + h + return img[h_start:h_end, w_start:w_end, :] + + +class NormalizeImage: + def __init__( + self, + ): + self.scale = np.float32(1.0 / 255.0) + mean = [0.485, 0.456, 0.406] + std = [0.229, 0.224, 0.225] + + shape = 1, 1, 3 + self.mean = np.array(mean).reshape(shape).astype("float32") + self.std = np.array(std).reshape(shape).astype("float32") + + def __call__(self, img): + img = np.array(img).astype(np.float32) + img = (img * self.scale - self.mean) / self.std + return img.astype(np.float32) + + +class ToCHWImage: + def __init__(self): + pass + + def __call__(self, img): + img = np.array(img) + return img.transpose((2, 0, 1)) diff --git a/rapid_orientation/utils/utils.py b/rapid_orientation/utils/utils.py new file mode 100644 index 0000000..f55f635 --- /dev/null +++ b/rapid_orientation/utils/utils.py @@ -0,0 +1,10 @@ +# -*- encoding: utf-8 -*- +# @Author: SWHL +# @Contact: liekkaskono@163.com +import yaml + + +def read_yaml(yaml_path): + with open(yaml_path, "rb") as f: + data = yaml.load(f, Loader=yaml.Loader) + return data diff --git a/tests/test_files/table.jpg b/tests/test_files/table.jpg deleted file mode 100644 index 95fdf84d92908d4b21f49fb516601334867163b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59389 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 diff --git a/tests/test_orientation.py b/tests/test_orientation.py index bf708df..d8781dc 100644 --- a/tests/test_orientation.py +++ b/tests/test_orientation.py @@ -14,7 +14,6 @@ from rapid_orientation import RapidOrientation - test_file_dir = cur_dir / "test_files" text_orientation = RapidOrientation()