mckabue commited on
Commit
eb2bcc4
·
verified ·
1 Parent(s): 0123dbc

RE_UPLOAD-REBUILD-RESTART

Browse files
Files changed (1) hide show
  1. utils/visualize_bboxes_on_image.py +177 -0
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