690 lines
24 KiB
Python
690 lines
24 KiB
Python
# 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 os
|
|
import sys
|
|
import functools
|
|
import logging
|
|
import cv2
|
|
import numpy as np
|
|
import PIL
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
import math
|
|
import random
|
|
|
|
|
|
def str2bool(v):
|
|
return v.lower() in ("true", "yes", "t", "y", "1")
|
|
|
|
|
|
def str2int_tuple(v):
|
|
return tuple([int(i.strip()) for i in v.split(",")])
|
|
|
|
|
|
def init_args():
|
|
parser = argparse.ArgumentParser()
|
|
# params for prediction engine
|
|
parser.add_argument("--use_gpu", type=str2bool, default=True)
|
|
parser.add_argument("--use_xpu", type=str2bool, default=False)
|
|
parser.add_argument("--use_npu", type=str2bool, default=False)
|
|
parser.add_argument("--use_mlu", type=str2bool, default=False)
|
|
parser.add_argument("--ir_optim", type=str2bool, default=True)
|
|
parser.add_argument("--use_tensorrt", type=str2bool, default=False)
|
|
parser.add_argument("--min_subgraph_size", type=int, default=15)
|
|
parser.add_argument("--precision", type=str, default="fp32")
|
|
parser.add_argument("--gpu_mem", type=int, default=500)
|
|
parser.add_argument("--gpu_id", type=int, default=0)
|
|
|
|
# params for text detector
|
|
parser.add_argument("--image_dir", type=str)
|
|
parser.add_argument("--page_num", type=int, default=0)
|
|
parser.add_argument("--det_algorithm", type=str, default="DB")
|
|
parser.add_argument("--det_model_dir", type=str)
|
|
parser.add_argument("--det_limit_side_len", type=float, default=960)
|
|
parser.add_argument("--det_limit_type", type=str, default="max")
|
|
parser.add_argument("--det_box_type", type=str, default="quad")
|
|
|
|
# DB parmas
|
|
parser.add_argument("--det_db_thresh", type=float, default=0.3)
|
|
parser.add_argument("--det_db_box_thresh", type=float, default=0.6)
|
|
parser.add_argument("--det_db_unclip_ratio", type=float, default=1.5)
|
|
parser.add_argument("--max_batch_size", type=int, default=10)
|
|
parser.add_argument("--use_dilation", type=str2bool, default=False)
|
|
parser.add_argument("--det_db_score_mode", type=str, default="fast")
|
|
|
|
# EAST parmas
|
|
parser.add_argument("--det_east_score_thresh", type=float, default=0.8)
|
|
parser.add_argument("--det_east_cover_thresh", type=float, default=0.1)
|
|
parser.add_argument("--det_east_nms_thresh", type=float, default=0.2)
|
|
|
|
# SAST parmas
|
|
parser.add_argument("--det_sast_score_thresh", type=float, default=0.5)
|
|
parser.add_argument("--det_sast_nms_thresh", type=float, default=0.2)
|
|
|
|
# PSE parmas
|
|
parser.add_argument("--det_pse_thresh", type=float, default=0)
|
|
parser.add_argument("--det_pse_box_thresh", type=float, default=0.85)
|
|
parser.add_argument("--det_pse_min_area", type=float, default=16)
|
|
parser.add_argument("--det_pse_scale", type=int, default=1)
|
|
|
|
# FCE parmas
|
|
parser.add_argument("--scales", type=list, default=[8, 16, 32])
|
|
parser.add_argument("--alpha", type=float, default=1.0)
|
|
parser.add_argument("--beta", type=float, default=1.0)
|
|
parser.add_argument("--fourier_degree", type=int, default=5)
|
|
|
|
# params for text recognizer
|
|
parser.add_argument("--rec_algorithm", type=str, default="SVTR_LCNet")
|
|
parser.add_argument("--rec_model_dir", type=str)
|
|
parser.add_argument("--rec_image_inverse", type=str2bool, default=True)
|
|
parser.add_argument("--rec_image_shape", type=str, default="3, 48, 320")
|
|
parser.add_argument("--rec_batch_num", type=int, default=6)
|
|
parser.add_argument("--max_text_length", type=int, default=25)
|
|
parser.add_argument("--rec_char_dict_path", type=str, default="./ppocr_keys_v1.txt")
|
|
parser.add_argument("--use_space_char", type=str2bool, default=True)
|
|
parser.add_argument("--vis_font_path", type=str, default="./doc/fonts/simfang.ttf")
|
|
parser.add_argument("--drop_score", type=float, default=0.5)
|
|
|
|
# params for e2e
|
|
parser.add_argument("--e2e_algorithm", type=str, default="PGNet")
|
|
parser.add_argument("--e2e_model_dir", type=str)
|
|
parser.add_argument("--e2e_limit_side_len", type=float, default=768)
|
|
parser.add_argument("--e2e_limit_type", type=str, default="max")
|
|
|
|
# PGNet parmas
|
|
parser.add_argument("--e2e_pgnet_score_thresh", type=float, default=0.5)
|
|
parser.add_argument("--e2e_char_dict_path", type=str, default="./ppocr/utils/ic15_dict.txt")
|
|
parser.add_argument("--e2e_pgnet_valid_set", type=str, default="totaltext")
|
|
parser.add_argument("--e2e_pgnet_mode", type=str, default="fast")
|
|
|
|
# params for text classifier
|
|
parser.add_argument("--use_angle_cls", type=str2bool, default=False)
|
|
parser.add_argument("--cls_model_dir", type=str)
|
|
parser.add_argument("--cls_image_shape", type=str, default="3, 48, 192")
|
|
parser.add_argument("--label_list", type=list, default=["0", "180"])
|
|
parser.add_argument("--cls_batch_num", type=int, default=6)
|
|
parser.add_argument("--cls_thresh", type=float, default=0.9)
|
|
|
|
parser.add_argument("--enable_mkldnn", type=str2bool, default=False)
|
|
parser.add_argument("--cpu_threads", type=int, default=10)
|
|
parser.add_argument("--use_pdserving", type=str2bool, default=False)
|
|
parser.add_argument("--warmup", type=str2bool, default=False)
|
|
|
|
# SR parmas
|
|
parser.add_argument("--sr_model_dir", type=str)
|
|
parser.add_argument("--sr_image_shape", type=str, default="3, 32, 128")
|
|
parser.add_argument("--sr_batch_num", type=int, default=1)
|
|
|
|
#
|
|
parser.add_argument("--draw_img_save_dir", type=str, default="./inference_results")
|
|
parser.add_argument("--save_crop_res", type=str2bool, default=False)
|
|
parser.add_argument("--crop_res_save_dir", type=str, default="./output")
|
|
|
|
# multi-process
|
|
parser.add_argument("--use_mp", type=str2bool, default=False)
|
|
parser.add_argument("--total_process_num", type=int, default=1)
|
|
parser.add_argument("--process_id", type=int, default=0)
|
|
|
|
parser.add_argument("--benchmark", type=str2bool, default=False)
|
|
parser.add_argument("--save_log_path", type=str, default="./log_output/")
|
|
|
|
parser.add_argument("--show_log", type=str2bool, default=True)
|
|
parser.add_argument("--use_onnx", type=str2bool, default=False)
|
|
|
|
# extended function
|
|
parser.add_argument(
|
|
"--return_word_box",
|
|
type=str2bool,
|
|
default=False,
|
|
help="Whether return the bbox of each word (split by space) or chinese character. Only used in ppstructure for layout recovery",
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
def parse_args():
|
|
parser = init_args()
|
|
return parser.parse_args([])
|
|
|
|
|
|
def create_predictor(args, mode, logger):
|
|
if mode == "det":
|
|
model_dir = args.det_model_dir
|
|
elif mode == "cls":
|
|
model_dir = args.cls_model_dir
|
|
elif mode == "rec":
|
|
model_dir = args.rec_model_dir
|
|
elif mode == "table":
|
|
model_dir = args.table_model_dir
|
|
elif mode == "ser":
|
|
model_dir = args.ser_model_dir
|
|
elif mode == "re":
|
|
model_dir = args.re_model_dir
|
|
elif mode == "sr":
|
|
model_dir = args.sr_model_dir
|
|
elif mode == "layout":
|
|
model_dir = args.layout_model_dir
|
|
else:
|
|
model_dir = args.e2e_model_dir
|
|
|
|
if model_dir is None:
|
|
logger.info("not find {} model file path {}".format(mode, model_dir))
|
|
sys.exit(0)
|
|
assert args.use_onnx
|
|
|
|
import onnxruntime as ort
|
|
|
|
model_file_path = model_dir
|
|
if not os.path.exists(model_file_path):
|
|
raise ValueError("not find model file path {}".format(model_file_path))
|
|
if args.use_gpu:
|
|
sess = ort.InferenceSession(model_file_path, providers=["CUDAExecutionProvider"])
|
|
else:
|
|
sess = ort.InferenceSession(model_file_path)
|
|
return sess, sess.get_inputs()[0], None, None
|
|
|
|
|
|
def get_output_tensors(args, mode, predictor):
|
|
output_names = predictor.get_output_names()
|
|
output_tensors = []
|
|
if mode == "rec" and args.rec_algorithm in ["CRNN", "SVTR_LCNet", "SVTR_HGNet"]:
|
|
output_name = "softmax_0.tmp_0"
|
|
if output_name in output_names:
|
|
return [predictor.get_output_handle(output_name)]
|
|
else:
|
|
for output_name in output_names:
|
|
output_tensor = predictor.get_output_handle(output_name)
|
|
output_tensors.append(output_tensor)
|
|
else:
|
|
for output_name in output_names:
|
|
output_tensor = predictor.get_output_handle(output_name)
|
|
output_tensors.append(output_tensor)
|
|
return output_tensors
|
|
|
|
|
|
def draw_e2e_res(dt_boxes, strs, img_path):
|
|
src_im = cv2.imread(img_path)
|
|
for box, str in zip(dt_boxes, strs):
|
|
box = box.astype(np.int32).reshape((-1, 1, 2))
|
|
cv2.polylines(src_im, [box], True, color=(255, 255, 0), thickness=2)
|
|
cv2.putText(
|
|
src_im,
|
|
str,
|
|
org=(int(box[0, 0, 0]), int(box[0, 0, 1])),
|
|
fontFace=cv2.FONT_HERSHEY_COMPLEX,
|
|
fontScale=0.7,
|
|
color=(0, 255, 0),
|
|
thickness=1,
|
|
)
|
|
return src_im
|
|
|
|
|
|
def draw_text_det_res(dt_boxes, img):
|
|
for box in dt_boxes:
|
|
box = np.array(box).astype(np.int32).reshape(-1, 2)
|
|
cv2.polylines(img, [box], True, color=(255, 255, 0), thickness=2)
|
|
return img
|
|
|
|
|
|
def resize_img(img, input_size=600):
|
|
"""
|
|
resize img and limit the longest side of the image to input_size
|
|
"""
|
|
img = np.array(img)
|
|
im_shape = img.shape
|
|
im_size_max = np.max(im_shape[0:2])
|
|
im_scale = float(input_size) / float(im_size_max)
|
|
img = cv2.resize(img, None, None, fx=im_scale, fy=im_scale)
|
|
return img
|
|
|
|
|
|
def draw_ocr(
|
|
image,
|
|
boxes,
|
|
txts=None,
|
|
scores=None,
|
|
drop_score=0.5,
|
|
font_path="./doc/fonts/simfang.ttf",
|
|
):
|
|
"""
|
|
Visualize the results of OCR detection and recognition
|
|
args:
|
|
image(Image|array): RGB image
|
|
boxes(list): boxes with shape(N, 4, 2)
|
|
txts(list): the texts
|
|
scores(list): txxs corresponding scores
|
|
drop_score(float): only scores greater than drop_threshold will be visualized
|
|
font_path: the path of font which is used to draw text
|
|
return(array):
|
|
the visualized img
|
|
"""
|
|
if scores is None:
|
|
scores = [1] * len(boxes)
|
|
box_num = len(boxes)
|
|
for i in range(box_num):
|
|
if scores is not None and (scores[i] < drop_score or math.isnan(scores[i])):
|
|
continue
|
|
box = np.reshape(np.array(boxes[i]), [-1, 1, 2]).astype(np.int64)
|
|
image = cv2.polylines(np.array(image), [box], True, (255, 0, 0), 2)
|
|
if txts is not None:
|
|
img = np.array(resize_img(image, input_size=600))
|
|
txt_img = text_visual(
|
|
txts,
|
|
scores,
|
|
img_h=img.shape[0],
|
|
img_w=600,
|
|
threshold=drop_score,
|
|
font_path=font_path,
|
|
)
|
|
img = np.concatenate([np.array(img), np.array(txt_img)], axis=1)
|
|
return img
|
|
return image
|
|
|
|
|
|
def draw_ocr_box_txt(
|
|
image,
|
|
boxes,
|
|
txts=None,
|
|
scores=None,
|
|
drop_score=0.5,
|
|
font_path="./doc/fonts/simfang.ttf",
|
|
):
|
|
h, w = image.height, image.width
|
|
img_left = image.copy()
|
|
img_right = np.ones((h, w, 3), dtype=np.uint8) * 255
|
|
random.seed(0)
|
|
|
|
draw_left = ImageDraw.Draw(img_left)
|
|
if txts is None or len(txts) != len(boxes):
|
|
txts = [None] * len(boxes)
|
|
for idx, (box, txt) in enumerate(zip(boxes, txts)):
|
|
if scores is not None and scores[idx] < drop_score:
|
|
continue
|
|
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
|
draw_left.polygon(box, fill=color)
|
|
img_right_text = draw_box_txt_fine((w, h), box, txt, font_path)
|
|
pts = np.array(box, np.int32).reshape((-1, 1, 2))
|
|
cv2.polylines(img_right_text, [pts], True, color, 1)
|
|
img_right = cv2.bitwise_and(img_right, img_right_text)
|
|
img_left = Image.blend(image, img_left, 0.5)
|
|
img_show = Image.new("RGB", (w * 2, h), (255, 255, 255))
|
|
img_show.paste(img_left, (0, 0, w, h))
|
|
img_show.paste(Image.fromarray(img_right), (w, 0, w * 2, h))
|
|
return np.array(img_show)
|
|
|
|
|
|
def draw_box_txt_fine(img_size, box, txt, font_path="./doc/fonts/simfang.ttf"):
|
|
box_height = int(math.sqrt((box[0][0] - box[3][0]) ** 2 + (box[0][1] - box[3][1]) ** 2))
|
|
box_width = int(math.sqrt((box[0][0] - box[1][0]) ** 2 + (box[0][1] - box[1][1]) ** 2))
|
|
|
|
if box_height > 2 * box_width and box_height > 30:
|
|
img_text = Image.new("RGB", (box_height, box_width), (255, 255, 255))
|
|
draw_text = ImageDraw.Draw(img_text)
|
|
if txt:
|
|
font = create_font(txt, (box_height, box_width), font_path)
|
|
draw_text.text([0, 0], txt, fill=(0, 0, 0), font=font)
|
|
img_text = img_text.transpose(Image.ROTATE_270)
|
|
else:
|
|
img_text = Image.new("RGB", (box_width, box_height), (255, 255, 255))
|
|
draw_text = ImageDraw.Draw(img_text)
|
|
if txt:
|
|
font = create_font(txt, (box_width, box_height), font_path)
|
|
draw_text.text([0, 0], txt, fill=(0, 0, 0), font=font)
|
|
|
|
pts1 = np.float32([[0, 0], [box_width, 0], [box_width, box_height], [0, box_height]])
|
|
pts2 = np.array(box, dtype=np.float32)
|
|
M = cv2.getPerspectiveTransform(pts1, pts2)
|
|
|
|
img_text = np.array(img_text, dtype=np.uint8)
|
|
img_right_text = cv2.warpPerspective(
|
|
img_text,
|
|
M,
|
|
img_size,
|
|
flags=cv2.INTER_NEAREST,
|
|
borderMode=cv2.BORDER_CONSTANT,
|
|
borderValue=(255, 255, 255),
|
|
)
|
|
return img_right_text
|
|
|
|
|
|
def create_font(txt, sz, font_path="./doc/fonts/simfang.ttf"):
|
|
font_size = int(sz[1] * 0.99)
|
|
font = ImageFont.truetype(font_path, font_size, encoding="utf-8")
|
|
if int(PIL.__version__.split(".")[0]) < 10:
|
|
length = font.getsize(txt)[0]
|
|
else:
|
|
length = font.getlength(txt)
|
|
|
|
if length > sz[0]:
|
|
font_size = int(font_size * sz[0] / length)
|
|
font = ImageFont.truetype(font_path, font_size, encoding="utf-8")
|
|
return font
|
|
|
|
|
|
def str_count(s):
|
|
"""
|
|
Count the number of Chinese characters,
|
|
a single English character and a single number
|
|
equal to half the length of Chinese characters.
|
|
args:
|
|
s(string): the input of string
|
|
return(int):
|
|
the number of Chinese characters
|
|
"""
|
|
import string
|
|
|
|
count_zh = count_pu = 0
|
|
s_len = len(s)
|
|
en_dg_count = 0
|
|
for c in s:
|
|
if c in string.ascii_letters or c.isdigit() or c.isspace():
|
|
en_dg_count += 1
|
|
elif c.isalpha():
|
|
count_zh += 1
|
|
else:
|
|
count_pu += 1
|
|
return s_len - math.ceil(en_dg_count / 2)
|
|
|
|
|
|
def text_visual(texts, scores, img_h=400, img_w=600, threshold=0.0, font_path="./doc/simfang.ttf"):
|
|
"""
|
|
create new blank img and draw txt on it
|
|
args:
|
|
texts(list): the text will be draw
|
|
scores(list|None): corresponding score of each txt
|
|
img_h(int): the height of blank img
|
|
img_w(int): the width of blank img
|
|
font_path: the path of font which is used to draw text
|
|
return(array):
|
|
"""
|
|
if scores is not None:
|
|
assert len(texts) == len(scores), "The number of txts and corresponding scores must match"
|
|
|
|
def create_blank_img():
|
|
blank_img = np.ones(shape=[img_h, img_w], dtype=np.int8) * 255
|
|
blank_img[:, img_w - 1 :] = 0
|
|
blank_img = Image.fromarray(blank_img).convert("RGB")
|
|
draw_txt = ImageDraw.Draw(blank_img)
|
|
return blank_img, draw_txt
|
|
|
|
blank_img, draw_txt = create_blank_img()
|
|
|
|
font_size = 20
|
|
txt_color = (0, 0, 0)
|
|
font = ImageFont.truetype(font_path, font_size, encoding="utf-8")
|
|
|
|
gap = font_size + 5
|
|
txt_img_list = []
|
|
count, index = 1, 0
|
|
for idx, txt in enumerate(texts):
|
|
index += 1
|
|
if scores[idx] < threshold or math.isnan(scores[idx]):
|
|
index -= 1
|
|
continue
|
|
first_line = True
|
|
while str_count(txt) >= img_w // font_size - 4:
|
|
tmp = txt
|
|
txt = tmp[: img_w // font_size - 4]
|
|
if first_line:
|
|
new_txt = str(index) + ": " + txt
|
|
first_line = False
|
|
else:
|
|
new_txt = " " + txt
|
|
draw_txt.text((0, gap * count), new_txt, txt_color, font=font)
|
|
txt = tmp[img_w // font_size - 4 :]
|
|
if count >= img_h // gap - 1:
|
|
txt_img_list.append(np.array(blank_img))
|
|
blank_img, draw_txt = create_blank_img()
|
|
count = 0
|
|
count += 1
|
|
if first_line:
|
|
new_txt = str(index) + ": " + txt + " " + "%.3f" % (scores[idx])
|
|
else:
|
|
new_txt = " " + txt + " " + "%.3f" % (scores[idx])
|
|
draw_txt.text((0, gap * count), new_txt, txt_color, font=font)
|
|
# whether add new blank img or not
|
|
if count >= img_h // gap - 1 and idx + 1 < len(texts):
|
|
txt_img_list.append(np.array(blank_img))
|
|
blank_img, draw_txt = create_blank_img()
|
|
count = 0
|
|
count += 1
|
|
txt_img_list.append(np.array(blank_img))
|
|
if len(txt_img_list) == 1:
|
|
blank_img = np.array(txt_img_list[0])
|
|
else:
|
|
blank_img = np.concatenate(txt_img_list, axis=1)
|
|
return np.array(blank_img)
|
|
|
|
|
|
def base64_to_cv2(b64str):
|
|
import base64
|
|
|
|
data = base64.b64decode(b64str.encode("utf8"))
|
|
data = np.frombuffer(data, np.uint8)
|
|
data = cv2.imdecode(data, cv2.IMREAD_COLOR)
|
|
return data
|
|
|
|
|
|
def draw_boxes(image, boxes, scores=None, drop_score=0.5):
|
|
if scores is None:
|
|
scores = [1] * len(boxes)
|
|
for box, score in zip(boxes, scores):
|
|
if score < drop_score:
|
|
continue
|
|
box = np.reshape(np.array(box), [-1, 1, 2]).astype(np.int64)
|
|
image = cv2.polylines(np.array(image), [box], True, (255, 0, 0), 2)
|
|
return image
|
|
|
|
|
|
def get_rotate_crop_image(img, points):
|
|
"""
|
|
img_height, img_width = img.shape[0:2]
|
|
left = int(np.min(points[:, 0]))
|
|
right = int(np.max(points[:, 0]))
|
|
top = int(np.min(points[:, 1]))
|
|
bottom = int(np.max(points[:, 1]))
|
|
img_crop = img[top:bottom, left:right, :].copy()
|
|
points[:, 0] = points[:, 0] - left
|
|
points[:, 1] = points[:, 1] - top
|
|
"""
|
|
assert len(points) == 4, "shape of points must be 4*2"
|
|
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
|
|
|
|
|
|
def get_minarea_rect_crop(img, points):
|
|
bounding_box = cv2.minAreaRect(np.array(points).astype(np.int32))
|
|
points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])
|
|
|
|
index_a, index_b, index_c, index_d = 0, 1, 2, 3
|
|
if points[1][1] > points[0][1]:
|
|
index_a = 0
|
|
index_d = 1
|
|
else:
|
|
index_a = 1
|
|
index_d = 0
|
|
if points[3][1] > points[2][1]:
|
|
index_b = 2
|
|
index_c = 3
|
|
else:
|
|
index_b = 3
|
|
index_c = 2
|
|
|
|
box = [points[index_a], points[index_b], points[index_c], points[index_d]]
|
|
crop_img = get_rotate_crop_image(img, np.array(box))
|
|
return crop_img
|
|
|
|
|
|
# def check_gpu(use_gpu):
|
|
# if use_gpu and (
|
|
# not paddle.is_compiled_with_cuda() or paddle.device.get_device() == "cpu"
|
|
# ):
|
|
# use_gpu = False
|
|
# return use_gpu
|
|
|
|
|
|
def _check_image_file(path):
|
|
img_end = {"jpg", "bmp", "png", "jpeg", "rgb", "tif", "tiff", "gif", "pdf"}
|
|
return any([path.lower().endswith(e) for e in img_end])
|
|
|
|
|
|
def get_image_file_list(img_file, infer_list=None):
|
|
imgs_lists = []
|
|
if img_file is None or not os.path.exists(img_file):
|
|
raise Exception("not found any img file in {}".format(img_file))
|
|
|
|
if os.path.isfile(img_file) and _check_image_file(img_file):
|
|
imgs_lists.append(img_file)
|
|
elif os.path.isdir(img_file):
|
|
for single_file in os.listdir(img_file):
|
|
file_path = os.path.join(img_file, single_file)
|
|
if os.path.isfile(file_path) and _check_image_file(file_path):
|
|
imgs_lists.append(file_path)
|
|
|
|
if len(imgs_lists) == 0:
|
|
raise Exception("not found any img file in {}".format(img_file))
|
|
imgs_lists = sorted(imgs_lists)
|
|
return imgs_lists
|
|
|
|
|
|
logger_initialized = {}
|
|
|
|
|
|
@functools.lru_cache()
|
|
def get_logger(name="ppocr", log_file=None, log_level=logging.DEBUG):
|
|
"""Initialize and get a logger by name.
|
|
If the logger has not been initialized, this method will initialize the
|
|
logger by adding one or two handlers, otherwise the initialized logger will
|
|
be directly returned. During initialization, a StreamHandler will always be
|
|
added. If `log_file` is specified a FileHandler will also be added.
|
|
Args:
|
|
name (str): Logger name.
|
|
log_file (str | None): The log filename. If specified, a FileHandler
|
|
will be added to the logger.
|
|
log_level (int): The logger level. Note that only the process of
|
|
rank 0 is affected, and other processes will set the level to
|
|
"Error" thus be silent most of the time.
|
|
Returns:
|
|
logging.Logger: The expected logger.
|
|
"""
|
|
logger = logging.getLogger(name)
|
|
if name in logger_initialized:
|
|
return logger
|
|
for logger_name in logger_initialized:
|
|
if name.startswith(logger_name):
|
|
return logger
|
|
|
|
formatter = logging.Formatter(
|
|
"[%(asctime)s] %(name)s %(levelname)s: %(message)s", datefmt="%Y/%m/%d %H:%M:%S"
|
|
)
|
|
|
|
stream_handler = logging.StreamHandler(stream=sys.stdout)
|
|
stream_handler.setFormatter(formatter)
|
|
logger.addHandler(stream_handler)
|
|
logger_initialized[name] = True
|
|
logger.propagate = False
|
|
return logger
|
|
|
|
|
|
def get_rotate_crop_image(img, points):
|
|
"""
|
|
img_height, img_width = img.shape[0:2]
|
|
left = int(np.min(points[:, 0]))
|
|
right = int(np.max(points[:, 0]))
|
|
top = int(np.min(points[:, 1]))
|
|
bottom = int(np.max(points[:, 1]))
|
|
img_crop = img[top:bottom, left:right, :].copy()
|
|
points[:, 0] = points[:, 0] - left
|
|
points[:, 1] = points[:, 1] - top
|
|
"""
|
|
assert len(points) == 4, "shape of points must be 4*2"
|
|
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
|
|
|
|
|
|
def get_minarea_rect_crop(img, points):
|
|
bounding_box = cv2.minAreaRect(np.array(points).astype(np.int32))
|
|
points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])
|
|
|
|
index_a, index_b, index_c, index_d = 0, 1, 2, 3
|
|
if points[1][1] > points[0][1]:
|
|
index_a = 0
|
|
index_d = 1
|
|
else:
|
|
index_a = 1
|
|
index_d = 0
|
|
if points[3][1] > points[2][1]:
|
|
index_b = 2
|
|
index_c = 3
|
|
else:
|
|
index_b = 3
|
|
index_c = 2
|
|
|
|
box = [points[index_a], points[index_b], points[index_c], points[index_d]]
|
|
crop_img = get_rotate_crop_image(img, np.array(box))
|
|
return crop_img
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pass
|