-
Notifications
You must be signed in to change notification settings - Fork 0
/
bbox.py
159 lines (140 loc) · 5.62 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
158
159
import numpy as np
import cv2
import copy
import math
class Bbox():
def __init__(self, np_bbox, apply_convex_hull=False):
'''
np_bbox: should be in clockwise order (Ex. from cv2.boxPoints)
'''
assert isinstance(np_bbox, np.ndarray)
assert np_bbox.shape == (4, 2)
assert isinstance(apply_convex_hull, bool)
self.np_bbox = np_bbox
self.apply_convex_hull = apply_convex_hull
if self.apply_convex_hull:
# NOTE: this exploit's cv2.convexHull clockwise order of its return points
hull = cv2.convexHull(np_bbox)
assert hull.shape[1:3] == (1, 2)
hull = np.squeeze(hull, axis=1)
self.np_bbox = hull
def error(self):
return self.np_bbox.shape[0] != 4 or (not self.check_clockwise())
def check_clockwise(self):
'''
Ref:
https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
https://www.element84.com/blog/determining-the-winding-of-a-polygon-given-as-a-set-of-ordered-points
'''
sum_ = 0
for i in range(4):
j = (i + 1) % 4
x_i, y_i = self.np_bbox[i]
x_j, y_j = self.np_bbox[j]
# Compute the signed trapezoid region of the line segment (x_i, y_i) to (x_j, y_i)
sum_ += (x_j - x_i) * (y_j + y_i)
return sum_ < 0
def get_reordered_bbox(self, distance_type="manhattan", ratio=0.3):
'''
[0]--------------[1]
| |
| |
[3]--------------[2]
'''
distance = None
if distance_type == "manhattan":
distance = lambda point: point.sum()
distances = [distance(point) for point in self.np_bbox]
# handle the case when there exist two points with almost the same distance
startidx = np.argmin(distances)
for i, dist in enumerate(distances):
if i != startidx and math.isclose(distances[startidx], dist):
idx_with_smaller_y = min([startidx, i], key=lambda index: self.np_bbox[index][1])
idx_with_larger_y = max([startidx, i], key=lambda index: self.np_bbox[index][1])
# Compute the lengths (w, h) of this box use idx_with_smaller_y as a base point
width = np.linalg.norm(
self.np_bbox[idx_with_smaller_y] - self.np_bbox[(idx_with_smaller_y + 1) % 4])
height = np.linalg.norm(
self.np_bbox[idx_with_smaller_y] - self.np_bbox[(idx_with_smaller_y - 1 + 4) % 4])
if width > height * (1 + ratio):
startidx = idx_with_smaller_y # favor the smaller y
else:
startidx = idx_with_larger_y
break
bbox = copy.deepcopy(self.np_bbox)
bbox = np.roll(bbox, 4 - startidx, 0)
return bbox
def crop_image(self, img):
'''
Case 1: Normal case
w
[0]------------------[1]
| |
| 1 2 3 4 5 | h
| |
[3]------------------[2]
Case 2:
w
[0]-----[1]
| 5 |
| 4 |
| 3 | h
| 2 |
| 1 |
[3]-----[2]
'''
bbox = self.get_reordered_bbox()
w, h = int(np.linalg.norm(bbox[0] - bbox[1])), int(np.linalg.norm(bbox[0] - bbox[3]))
if h < w * 1.5:
# Case 1
dst = np.array([[0, 0], [w, 0], [w, h], [0, h]], dtype=np.float32)
M = cv2.getPerspectiveTransform(bbox.astype(np.float32), dst)
crop_width, crop_height = w, h
else:
# Case 2
dst = np.array([[h, 0], [h, w], [0, w], [0, 0]], dtype=np.float32)
M = cv2.getPerspectiveTransform(bbox.astype(np.float32), dst)
crop_width, crop_height = h, w
cropped = cv2.warpPerspective(img, M, (crop_width, crop_height))
return cropped
def get_w_h(self):
bbox = self.get_reordered_bbox()
w = np.linalg.norm(bbox[0] - bbox[1])
h = np.linalg.norm(bbox[0] - bbox[3])
return (w, h)
def compute_angle(self):
'''
Under the coordinate system like this:
------------------->x
|
|
|
|
v
y
If we put the rotation center in this box,
how much angle should we rotate this box clockwisely to make it axis aligned?
NOTE:
cv2.getRotationMatrix2D()'s angle parameter is rotating COUNTER-clockwisely.
'''
box = self.get_reordered_bbox()
w, h = box[1] - box[0]
success = True
if w == 0:
angle = 0.
success = False
else:
angle = -math.atan(h / w)
angle = angle * 180 / math.pi # convert it to degree
return angle, success
if __name__ == "__main__":
np_bbox = np.array([[98, 99], [3, 5], [5, 3], [100, 97]])
bbox = Bbox(np_bbox)
assert np.allclose(bbox.get_reordered_bbox(),
np.array([[5, 3], [100, 97], [98, 99], [3, 5]]))
np_bbox = np.array([[5, 100], [3, 97], [98, 2], [100, 5]])
bbox = Bbox(np_bbox)
assert np.allclose(bbox.get_reordered_bbox(),
np.array([[3, 97], [98, 2], [100, 5], [5, 100]]))
assert bbox.check_clockwise()
assert not bbox.error()