kleverocr / src /visualization.py
pmkhanh7890's picture
fix gradio app
555cf98
"""
Author: Khanh Phan
Date: 2023-11-01
"""
import math
import string
from pathlib import Path
import cv2
import numpy as np
from PIL import (
Image,
ImageDraw,
ImageFont,
)
from src.markdown import Markdown
from src.settings import (
FONTPATH,
OUT_DIR,
)
def count_characters(str: str) -> int:
"""
Count the number of Japanese characters,
a single English character and a single number
equal to half the length of Japanese characters.
args:
s(string): the input of string
return(int):
the number of Japanese characters
"""
count_zh = count_pu = 0
s_len = len(str)
en_dg_count = 0
for c in str:
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 create_blank_img(img_h: int, img_w: int) -> Image:
"""
create new blank img
args:
img_h(int): the height of blank img
img_w(int): the width of blank img
return(Image|array):
blank image
"""
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
def text_visual(
texts: list[str],
scores: list[float],
img_h: int = 400,
img_w: int = 600,
threshold: float = 0.0,
font_path: str = FONTPATH,
) -> np.array:
"""
Create new img with recognized text
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(Image|array): image with recognized text
"""
if scores is not None:
assert len(texts) == len(
scores,
), "The number of txts and corresponding scores must match"
blank_img = create_blank_img()
draw_txt = ImageDraw.Draw(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 count_characters(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 = create_blank_img()
draw_txt = ImageDraw.Draw(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 = create_blank_img()
draw_txt = ImageDraw.Draw(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 resize_img(img: np.array, input_size: int = 600) -> np.array:
"""
Resize img and limit the longest side of the image to input_size
args:
img(np.array): original image
input_size(int): new size of the longest side of the image
return(Image|array):
a new-size image
"""
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: np.array,
boxes: list,
txts: list[str] = None,
scores: list[float] = None,
drop_score: float = 0.0,
font_path: str = FONTPATH,
) -> np.array:
"""
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 > drop_threshold will be visualized
font_path: the path of font which is used to draw text
return(Image|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(image)
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_2(
img: np.array,
results: list[list, tuple([str, float])],
) -> list[np.array, np.array]:
"""
Visualize the results of OCR detection and recognition
args:
image(Image|array): RGB image
results(list): boxes with shape(N, 4, 2), texts and scores
return(Image|array):
the visualized img
"""
img = np.asarray(img)
img_text = np.ones((img.shape[0], img.shape[1], 3), np.uint8) * 255
for line in results:
text = line[1][0]
# score = line[1][1]
top = int(min(line[0][0][1], line[0][1][1]))
bottom = int(max(line[0][2][1], line[0][3][1]))
left = int(min(line[0][0][0], line[0][3][0]))
# right = int(max(line[0][1][0], line[0][2][0]))
# text_size = max(1, int((right - left) / len(text)))
text_size = max(1, int(bottom - top))
color = (
np.random.randint(0, 255),
np.random.randint(0, 255),
np.random.randint(0, 255),
)
box = np.reshape(np.array(line[0]), [-1, 1, 2]).astype(np.int64)
img = cv2.polylines(np.array(img), [box], True, color, 2)
img_text = place_text(img_text, text, (left, top), text_size, color)
return [img, img_text]
def place_text(
img: np.array,
text: str,
top_left_point: list[int, int],
text_size: int,
text_color: tuple([int, int, int]),
) -> np.array:
"""
Put text into image
args:
img(Image|array): RGB image
text(list): text to be put
text_size(int): size of text
top_left_point(array): top-left point to start the text
text_color(tuple): text color in ()
return(Image|array):
the visualized img
"""
font = ImageFont.truetype(FONTPATH, text_size, encoding="utf-8")
img_pil = Image.fromarray(img)
draw = ImageDraw.Draw(img_pil)
draw.text(top_left_point, text, fill=text_color, font=font)
return np.array(img_pil)
def visualize_result(
result: list[list, tuple([str, float])],
img: str,
) -> list[np.array, np.array]:
"""
make visualization in image foramt
args:
result(array): RGB image
img_path(str): path to input image
return(Image|array):
the visualized img
"""
result = result[0]
if isinstance(img, str):
img_path = img
image = Image.open(img_path).convert("RGB")
img_name = Path(img_path).stem
"""
boxes = [line[0] for line in result]
txts = [line[1][0] for line in result]
scores = [line[1][1] for line in result]
"""
else:
image = Image.fromarray(img)
img_name = Path("gradio_input")
# Write results to markdown file
format_md = Markdown(result, image)
md_path = (Path(OUT_DIR) / img_name).with_suffix(".out.md")
markdown = format_md.write(md_path)
# Write results to image file
[img_boxes, img_text] = draw_ocr_2(image, result)
img_combination = np.concatenate(
[np.array(img_boxes), np.array(img_text)],
axis=1,
)
img_out_path = (Path(OUT_DIR) / img_name).with_suffix(".out.jpg")
cv2.imwrite(str(img_out_path), img_combination)
return [img_boxes, img_text, markdown]