-
Notifications
You must be signed in to change notification settings - Fork 0
/
bbox.py
157 lines (123 loc) · 5.55 KB
/
bbox.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import logging
import cv2
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")
import logging.config
# Disable existing loggers to avoid conflicting messages
logging.config.dictConfig({"version": 1, "disable_existing_loggers": True})
class Bbox:
"""
A class for representing and manipulating bounding boxes.
"""
@staticmethod
def get_bbox_from_details(details: list[list]) -> list[list]:
"""
Extracts a list of bounding boxes from a list of details.
Args:
details: A list of detail lists, where each detail is expected to have 3 elements.
Returns:
A list of bounding boxes, where each box is represented as a list of coordinates.
Raises:
AssertionError: If any detail element doesn't have 3 elements.
"""
bbox_array = []
for detail in details:
assert len(detail) == 3, "Each detail element must have 3 elements!"
bbox_array.append(detail[0])
return bbox_array
@staticmethod
def compress_bbox(bbox: list) -> list:
"""
Compresses a bounding box representation from a list of 4 points to a list of 2 points.
Args:
bbox: A list containing 4 points of the bounding box.
Returns:
A list containing the top-left and bottom-right points of the bounding box or None.
Raises:
logging.error: If the input bbox is not a list of length 4.
"""
if isinstance(bbox, list) and len(bbox) == 4:
new_bbox = [bbox[0][0], bbox[0][1], bbox[2][0], bbox[2][1]]
return new_bbox
logging.error(f"Invalid bbox: {bbox}")
return None
@staticmethod
def bbox_intersection(boxA: list, boxB: list) -> float:
"""
Calculates the area of intersection between two bounding boxes.
Args:
boxA: A list containing the coordinates of the first bounding box.
boxB: A list containing the coordinates of the second bounding box.
Returns:
The area of intersection between the two bounding boxes.
"""
xA = max(boxA[0], boxB[0])
yA = max(boxA[1], boxB[1])
xB = min(boxA[2], boxB[2])
yB = min(boxA[3], boxB[3])
return abs(max((xB - xA, 0)) * max((yB - yA), 0))
def draw_bbox_2_colors(bbox: list, img: cv2.Mat, text: str, colors: tuple) -> None:
"""
Draws a white rectangle with a black outline around a bounding box and adds text if provided.
Args:
bbox: A list of four coordinates representing the bounding box.
img: The NumPy array representing the image.
text: The text to be added to the image.
colors: A tuple of two RGB color values for the rectangle border.
Returns:
None
"""
# Validate color format
if len(colors) != 2 or not all(
isinstance(color, tuple) and len(color) == 3 and all(0 <= i <= 255 for i in color)
for color in colors):
raise ValueError("Invalid colors format. Expected a tuple of two integers between 0 and 255.")
# Convert coordinates to integers
bbox = [
(round(coordinate[0]), round(coordinate[1]))
for coordinate in bbox
]
# Extract coordinates
tl, tr, br, bl = bbox
# Draw white rectangle with black outline
cv2.rectangle(img, tl, br, colors[0], 1)
offset = 2 # Offset value to avoid overlapping
tl_offset = (tl[0] + offset, tl[1] + offset)
tr_offset = (tr[0] + offset, tr[1] + offset)
br_offset = (br[0] + offset, br[1] + offset)
bl_offset = (bl[0] + offset, bl[1] + offset)
cv2.rectangle(img, tl_offset, br_offset, colors[1], 1)
# Add text if provided
if text:
text = text.strip()
# Remove non-ASCII characters to avoid encoding issues
text = "".join([c if ord(c) < 128 else "" for c in text])
cv2.putText(img, text, (tl[0], tl[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 1)
cv2.putText(img, text, (tl[0], tl[1] - 8),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, colors[1], 1)
@staticmethod
def find_obstruction(details: list[list], sub_box_array: list[list]) -> int:
"""
Calculates the total area of obstruction between text boxes and sub boxes.
Args:
details: A list of detail lists containing text information.
sub_box_array: A list of lists containing coordinates of potential obstructions.
Returns:
The total area of the obstructed text.
"""
# Get a list of bounding boxes from details
txt_box_array = Bbox.get_bbox_from_details(details)
total_obstruction = 0 # Initialize total obstruction area
# Iterate over each sub-box
for sub_box in sub_box_array:
# Compress the sub-box into a 2-point representation
sub_box = Bbox.compress_bbox(sub_box)
# Iterate over each text box
for txt_box in txt_box_array:
# Compress the text box into a 2-point representation
txt_box = Bbox.compress_bbox(txt_box)
# Calculate the area of intersection between the sub-box and the text box
intersection_area = Bbox.bbox_intersection(sub_box, txt_box)
# Update the total obstruction area
total_obstruction += intersection_area
return total_obstruction