RE_UPLOAD-REBUILD-RESTART
Browse files
utils/visualize_bboxes_on_image.py
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# This file is used to visualize bounding boxes on an image
|
2 |
+
from urllib.parse import urlparse
|
3 |
+
from PIL import Image, ImageDraw, ImageFont
|
4 |
+
import numpy as np
|
5 |
+
import requests
|
6 |
+
from typing import List, Callable
|
7 |
+
from functools import cache
|
8 |
+
import matplotlib.colors as colors
|
9 |
+
|
10 |
+
DEFAULTS = {
|
11 |
+
'bbox_outline_width': 2,
|
12 |
+
# color name or hex code or tuple of RGBA or tuple of RGB or tuple (color_name, alpha)
|
13 |
+
# between 0 (fully transparent) and 255 (fully opaque)
|
14 |
+
'bbox_outline_color': ('blue', 123),
|
15 |
+
# color name or hex code or tuple of RGBA or tuple of RGB or tuple (color_name, alpha)
|
16 |
+
# between 0 (fully transparent) and 255 (fully opaque)
|
17 |
+
'bbox_fill_color': ('red', 50),
|
18 |
+
'label_text_color': "black",
|
19 |
+
'label_fill_color': "red",
|
20 |
+
'label_text_padding': 0,
|
21 |
+
'label_rectangle_left_margin': 0,
|
22 |
+
'label_rectangle_top_margin': 0,
|
23 |
+
'label_text_size': 12,
|
24 |
+
}
|
25 |
+
|
26 |
+
|
27 |
+
@cache
|
28 |
+
def get_font(path_or_url: str = 'https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Regular.ttf', size: int = DEFAULTS['label_text_size']):
|
29 |
+
if urlparse(path_or_url).scheme in ["http", "https"]: # Online
|
30 |
+
return ImageFont.truetype(requests.get(path_or_url, stream=True).raw, size=size)
|
31 |
+
else: # Local
|
32 |
+
return ImageFont.truetype(path_or_url, size=size)
|
33 |
+
|
34 |
+
named_colors_mapping = colors.get_named_colors_mapping()
|
35 |
+
@cache
|
36 |
+
def parse_color(color: str | tuple) -> tuple | str:
|
37 |
+
if isinstance(color, tuple):
|
38 |
+
if len(color) == 2:
|
39 |
+
real_color, alpha = (color[0], int(color[1]))
|
40 |
+
if colors.is_color_like(real_color):
|
41 |
+
real_color_rgb = colors.hex2color(named_colors_mapping.get(real_color, real_color))
|
42 |
+
if len(real_color_rgb) == 3:
|
43 |
+
real_color_alpha = (np.array(real_color_rgb, dtype=int) * 255).tolist() + [alpha]
|
44 |
+
return tuple(real_color_alpha)
|
45 |
+
return color
|
46 |
+
|
47 |
+
def draw_bounding_box(
|
48 |
+
image: Image.Image,
|
49 |
+
bbox_outline_width: int,
|
50 |
+
bbox_fill_color: str | list[tuple | str],
|
51 |
+
bbox_outline_color: str | list[tuple | str],
|
52 |
+
bbox: List[List[int]],
|
53 |
+
label_rotate_angle: int = 0,
|
54 |
+
mask_callback: Callable[[ImageDraw.ImageDraw], None] = None) -> Image.Image:
|
55 |
+
options = {
|
56 |
+
'xy': bbox,
|
57 |
+
'fill': parse_color(bbox_fill_color) if bbox_fill_color else None,
|
58 |
+
'outline': parse_color(bbox_outline_color) if bbox_outline_color else None,
|
59 |
+
'width': bbox_outline_width
|
60 |
+
}
|
61 |
+
options = {k: v for k, v in options.items() if v is not None}
|
62 |
+
rectangle_image = Image.new('RGBA', image.size)
|
63 |
+
rectangle_image_draw = ImageDraw.Draw(rectangle_image)
|
64 |
+
rectangle_image_draw.rectangle(**options)
|
65 |
+
if mask_callback:
|
66 |
+
mask_callback(rectangle_image_draw)
|
67 |
+
rectangle_image = rectangle_image.rotate(label_rotate_angle, expand=1)
|
68 |
+
image.paste(im=rectangle_image, mask=rectangle_image)
|
69 |
+
# draw.bitmap((100, 100), rectangle_image)
|
70 |
+
return image
|
71 |
+
|
72 |
+
def visualize_bboxes_on_image(
|
73 |
+
image: Image.Image,
|
74 |
+
bboxes: List[List[int]],
|
75 |
+
labels: List[str] = None,
|
76 |
+
bbox_outline_width=DEFAULTS["bbox_outline_width"],
|
77 |
+
bbox_outline_color=DEFAULTS["bbox_outline_color"],
|
78 |
+
bbox_fill_color: str | list[tuple | str] = DEFAULTS["bbox_fill_color"],
|
79 |
+
label_text_color: str | list[tuple |
|
80 |
+
str] = DEFAULTS["label_text_color"],
|
81 |
+
label_fill_color=DEFAULTS["label_fill_color"],
|
82 |
+
label_text_padding=DEFAULTS["label_text_padding"],
|
83 |
+
label_rectangle_left_margin=DEFAULTS["label_rectangle_left_margin"],
|
84 |
+
label_rectangle_top_margin=DEFAULTS['label_rectangle_top_margin'],
|
85 |
+
label_text_size=DEFAULTS["label_text_size"],
|
86 |
+
convert_to_x0y0x1y1=None,
|
87 |
+
label_rotate_angle: int = 0) -> Image.Image:
|
88 |
+
'''
|
89 |
+
Visualize bounding boxes on an image
|
90 |
+
Args:
|
91 |
+
image: Image to visualize
|
92 |
+
bboxes: List of bounding boxes
|
93 |
+
labels: Titles of the bounding boxes
|
94 |
+
bbox_outline_width: Width of the bounding box
|
95 |
+
bbox_outline_color: Color of the bounding box
|
96 |
+
bbox_fill_color: Fill color of the bounding box
|
97 |
+
label_text_color: Color of the label text
|
98 |
+
label_fill_color: Color of the label rectangle
|
99 |
+
label_text_padding: Padding of the label text
|
100 |
+
label_rectangle_left_margin: Left padding of the label rectangle
|
101 |
+
label_rectangle_top_margin: Top padding of the label rectangle
|
102 |
+
label_text_size: Font size of the label text
|
103 |
+
convert_to_x0y0x1y1: Function to convert bounding box to x0y0x1y1 format
|
104 |
+
label_rotate_angle: Angle to rotate the label text
|
105 |
+
Returns:
|
106 |
+
Image: Image annotated with bounding boxes
|
107 |
+
'''
|
108 |
+
image = image.copy().convert("RGB")
|
109 |
+
font = get_font(size=label_text_size)
|
110 |
+
labels = (labels or []) + np.full(len(bboxes) -
|
111 |
+
len(labels or []), None).tolist()
|
112 |
+
bbox_fill_colors = bbox_fill_color if isinstance(bbox_fill_color, list) else [
|
113 |
+
bbox_fill_color] * len(bboxes)
|
114 |
+
bbox_outline_colors = bbox_outline_color if isinstance(
|
115 |
+
bbox_outline_color, list) else [bbox_outline_color] * len(bboxes)
|
116 |
+
|
117 |
+
for bbox, label, _bbox_fill_color, _bbox_outline_color in zip(bboxes, labels, bbox_fill_colors, bbox_outline_colors):
|
118 |
+
x0, y0, x1, y1 = convert_to_x0y0x1y1(
|
119 |
+
bbox) if convert_to_x0y0x1y1 is not None else bbox
|
120 |
+
|
121 |
+
image = draw_bounding_box(
|
122 |
+
image = image,
|
123 |
+
bbox_outline_width = bbox_outline_width,
|
124 |
+
bbox_fill_color = _bbox_fill_color,
|
125 |
+
bbox_outline_color = _bbox_outline_color,
|
126 |
+
bbox = [x0, y0, x1, y1])
|
127 |
+
|
128 |
+
if label is not None:
|
129 |
+
image = draw_text_on_image(
|
130 |
+
image = image,
|
131 |
+
text_position_xy = [x0, y0],
|
132 |
+
label = label,
|
133 |
+
label_text_color = label_text_color,
|
134 |
+
label_fill_color = label_fill_color,
|
135 |
+
label_text_padding = label_text_padding,
|
136 |
+
label_rectangle_left_margin = label_rectangle_left_margin,
|
137 |
+
label_rectangle_top_margin = label_rectangle_top_margin,
|
138 |
+
label_text_size = label_text_size,
|
139 |
+
font = font,
|
140 |
+
label_rotate_angle = label_rotate_angle)
|
141 |
+
return image
|
142 |
+
|
143 |
+
def draw_text_on_image(
|
144 |
+
image: Image.Image,
|
145 |
+
text_position_xy: List[int],
|
146 |
+
label: str,
|
147 |
+
label_text_color=DEFAULTS["label_text_color"],
|
148 |
+
label_fill_color=DEFAULTS["label_fill_color"],
|
149 |
+
label_text_padding=DEFAULTS["label_text_padding"],
|
150 |
+
label_rectangle_left_margin=DEFAULTS["label_rectangle_left_margin"],
|
151 |
+
label_rectangle_top_margin=DEFAULTS['label_rectangle_top_margin'],
|
152 |
+
label_text_size=DEFAULTS["label_text_size"],
|
153 |
+
font: ImageFont.FreeTypeFont = None,
|
154 |
+
label_rotate_angle: int = 0) -> Image.Image:
|
155 |
+
image = image.copy().convert("RGB")
|
156 |
+
font = font or get_font(size=label_text_size)
|
157 |
+
x0, y0 = text_position_xy
|
158 |
+
text_position = (
|
159 |
+
x0 - label_rectangle_left_margin + label_text_padding,
|
160 |
+
y0 - label_rectangle_top_margin + label_text_padding)
|
161 |
+
draw = ImageDraw.Draw(image)
|
162 |
+
_, _, text_bbox_right, text_bbox_bottom = draw.textbbox(text_position, label, font=font)
|
163 |
+
xy = [
|
164 |
+
text_position[0] - label_text_padding,
|
165 |
+
text_position[1] - label_text_padding,
|
166 |
+
text_bbox_right + label_text_padding + label_text_padding,
|
167 |
+
text_bbox_bottom + label_text_padding + label_text_padding
|
168 |
+
]
|
169 |
+
image = draw_bounding_box(
|
170 |
+
image = image,
|
171 |
+
bbox_outline_width = 0,
|
172 |
+
bbox_fill_color = label_fill_color,
|
173 |
+
bbox_outline_color = None,
|
174 |
+
bbox = xy,
|
175 |
+
label_rotate_angle = label_rotate_angle,
|
176 |
+
mask_callback = lambda mask_draw: mask_draw.text(text_position, label, font=font, fill=label_text_color))
|
177 |
+
return image
|