hank1996 commited on
Commit
1f1a29d
·
1 Parent(s): 03b6d46

Create new file

Browse files
Files changed (1) hide show
  1. lib/utils/augmentations.py +257 -0
lib/utils/augmentations.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import numpy as np
5
+ import cv2
6
+ import random
7
+ import math
8
+
9
+
10
+ def augment_hsv(img, hgain=0.5, sgain=0.5, vgain=0.5):
11
+ """change color hue, saturation, value"""
12
+ r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 # random gains
13
+ hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
14
+ dtype = img.dtype # uint8
15
+
16
+ x = np.arange(0, 256, dtype=np.int16)
17
+ lut_hue = ((x * r[0]) % 180).astype(dtype)
18
+ lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
19
+ lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
20
+
21
+ img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))).astype(dtype)
22
+ cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) # no return needed
23
+
24
+ # Histogram equalization
25
+ # if random.random() < 0.2:
26
+ # for i in range(3):
27
+ # img[:, :, i] = cv2.equalizeHist(img[:, :, i])
28
+
29
+
30
+ def random_perspective(combination, targets=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)):
31
+ """combination of img transform"""
32
+ # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
33
+ # targets = [cls, xyxy]
34
+ img, gray, line = combination
35
+ height = img.shape[0] + border[0] * 2 # shape(h,w,c)
36
+ width = img.shape[1] + border[1] * 2
37
+
38
+ # Center
39
+ C = np.eye(3)
40
+ C[0, 2] = -img.shape[1] / 2 # x translation (pixels)
41
+ C[1, 2] = -img.shape[0] / 2 # y translation (pixels)
42
+
43
+ # Perspective
44
+ P = np.eye(3)
45
+ P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y)
46
+ P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x)
47
+
48
+ # Rotation and Scale
49
+ R = np.eye(3)
50
+ a = random.uniform(-degrees, degrees)
51
+ # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
52
+ s = random.uniform(1 - scale, 1 + scale)
53
+ # s = 2 ** random.uniform(-scale, scale)
54
+ R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
55
+
56
+ # Shear
57
+ S = np.eye(3)
58
+ S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
59
+ S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
60
+
61
+ # Translation
62
+ T = np.eye(3)
63
+ T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width # x translation (pixels)
64
+ T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height # y translation (pixels)
65
+
66
+ # Combined rotation matrix
67
+ M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT
68
+ if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
69
+ if perspective:
70
+ img = cv2.warpPerspective(img, M, dsize=(width, height), borderValue=(114, 114, 114))
71
+ gray = cv2.warpPerspective(gray, M, dsize=(width, height), borderValue=0)
72
+ line = cv2.warpPerspective(line, M, dsize=(width, height), borderValue=0)
73
+ else: # affine
74
+ img = cv2.warpAffine(img, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
75
+ gray = cv2.warpAffine(gray, M[:2], dsize=(width, height), borderValue=0)
76
+ line = cv2.warpAffine(line, M[:2], dsize=(width, height), borderValue=0)
77
+
78
+ # Visualize
79
+ # import matplotlib.pyplot as plt
80
+ # ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
81
+ # ax[0].imshow(img[:, :, ::-1]) # base
82
+ # ax[1].imshow(img2[:, :, ::-1]) # warped
83
+
84
+ # Transform label coordinates
85
+ n = len(targets)
86
+ if n:
87
+ # warp points
88
+ xy = np.ones((n * 4, 3))
89
+ xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
90
+ xy = xy @ M.T # transform
91
+ if perspective:
92
+ xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 8) # rescale
93
+ else: # affine
94
+ xy = xy[:, :2].reshape(n, 8)
95
+
96
+ # create new boxes
97
+ x = xy[:, [0, 2, 4, 6]]
98
+ y = xy[:, [1, 3, 5, 7]]
99
+ xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
100
+
101
+ # # apply angle-based reduction of bounding boxes
102
+ # radians = a * math.pi / 180
103
+ # reduction = max(abs(math.sin(radians)), abs(math.cos(radians))) ** 0.5
104
+ # x = (xy[:, 2] + xy[:, 0]) / 2
105
+ # y = (xy[:, 3] + xy[:, 1]) / 2
106
+ # w = (xy[:, 2] - xy[:, 0]) * reduction
107
+ # h = (xy[:, 3] - xy[:, 1]) * reduction
108
+ # xy = np.concatenate((x - w / 2, y - h / 2, x + w / 2, y + h / 2)).reshape(4, n).T
109
+
110
+ # clip boxes
111
+ xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)
112
+ xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)
113
+
114
+ # filter candidates
115
+ i = _box_candidates(box1=targets[:, 1:5].T * s, box2=xy.T)
116
+ targets = targets[i]
117
+ targets[:, 1:5] = xy[i]
118
+
119
+ combination = (img, gray, line)
120
+ return combination, targets
121
+
122
+
123
+ def cutout(combination, labels):
124
+ # Applies image cutout augmentation https://arxiv.org/abs/1708.04552
125
+ image, gray = combination
126
+ h, w = image.shape[:2]
127
+
128
+ def bbox_ioa(box1, box2):
129
+ # Returns the intersection over box2 area given box1, box2. box1 is 4, box2 is nx4. boxes are x1y1x2y2
130
+ box2 = box2.transpose()
131
+
132
+ # Get the coordinates of bounding boxes
133
+ b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
134
+ b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
135
+
136
+ # Intersection area
137
+ inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * \
138
+ (np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0)
139
+
140
+ # box2 area
141
+ box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + 1e-16
142
+
143
+ # Intersection over box2 area
144
+ return inter_area / box2_area
145
+
146
+ # create random masks
147
+ scales = [0.5] * 1 + [0.25] * 2 + [0.125] * 4 + [0.0625] * 8 + [0.03125] * 16 # image size fraction
148
+ for s in scales:
149
+ mask_h = random.randint(1, int(h * s))
150
+ mask_w = random.randint(1, int(w * s))
151
+
152
+ # box
153
+ xmin = max(0, random.randint(0, w) - mask_w // 2)
154
+ ymin = max(0, random.randint(0, h) - mask_h // 2)
155
+ xmax = min(w, xmin + mask_w)
156
+ ymax = min(h, ymin + mask_h)
157
+ # print('xmin:{},ymin:{},xmax:{},ymax:{}'.format(xmin,ymin,xmax,ymax))
158
+
159
+ # apply random color mask
160
+ image[ymin:ymax, xmin:xmax] = [random.randint(64, 191) for _ in range(3)]
161
+ gray[ymin:ymax, xmin:xmax] = -1
162
+
163
+ # return unobscured labels
164
+ if len(labels) and s > 0.03:
165
+ box = np.array([xmin, ymin, xmax, ymax], dtype=np.float32)
166
+ ioa = bbox_ioa(box, labels[:, 1:5]) # intersection over area
167
+ labels = labels[ioa < 0.60] # remove >60% obscured labels
168
+
169
+ return image, gray, labels
170
+
171
+
172
+ def letterbox(combination, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):
173
+ """Resize the input image and automatically padding to suitable shape :https://zhuanlan.zhihu.com/p/172121380"""
174
+ # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232
175
+ img, gray, line = combination
176
+ shape = img.shape[:2] # current shape [height, width]
177
+ if isinstance(new_shape, int):
178
+ new_shape = (new_shape, new_shape)
179
+
180
+ # Scale ratio (new / old)
181
+ r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
182
+ if not scaleup: # only scale down, do not scale up (for better test mAP)
183
+ r = min(r, 1.0)
184
+
185
+ # Compute padding
186
+ ratio = r, r # width, height ratios
187
+ new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
188
+ dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
189
+ if auto: # minimum rectangle
190
+ dw, dh = np.mod(dw, 32), np.mod(dh, 32) # wh padding
191
+ elif scaleFill: # stretch
192
+ dw, dh = 0.0, 0.0
193
+ new_unpad = (new_shape[1], new_shape[0])
194
+ ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
195
+
196
+ dw /= 2 # divide padding into 2 sides
197
+ dh /= 2
198
+
199
+ if shape[::-1] != new_unpad: # resize
200
+ img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
201
+ gray = cv2.resize(gray, new_unpad, interpolation=cv2.INTER_LINEAR)
202
+ line = cv2.resize(line, new_unpad, interpolation=cv2.INTER_LINEAR)
203
+
204
+ top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
205
+ left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
206
+
207
+ img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
208
+ gray = cv2.copyMakeBorder(gray, top, bottom, left, right, cv2.BORDER_CONSTANT, value=0) # add border
209
+ line = cv2.copyMakeBorder(line, top, bottom, left, right, cv2.BORDER_CONSTANT, value=0) # add border
210
+ # print(img.shape)
211
+
212
+ combination = (img, gray, line)
213
+ return combination, ratio, (dw, dh)
214
+
215
+ def letterbox_for_img(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):
216
+ # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232
217
+ shape = img.shape[:2] # current shape [height, width]
218
+ if isinstance(new_shape, int):
219
+ new_shape = (new_shape, new_shape)
220
+
221
+ # Scale ratio (new / old)
222
+ r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
223
+ if not scaleup: # only scale down, do not scale up (for better test mAP)
224
+ r = min(r, 1.0)
225
+
226
+ # Compute padding
227
+ ratio = r, r # width, height ratios
228
+ new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
229
+
230
+
231
+ dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
232
+
233
+ if auto: # minimum rectangle
234
+ dw, dh = np.mod(dw, 32), np.mod(dh, 32) # wh padding
235
+
236
+ elif scaleFill: # stretch
237
+ dw, dh = 0.0, 0.0
238
+ new_unpad = (new_shape[1], new_shape[0])
239
+ ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
240
+
241
+ dw /= 2 # divide padding into 2 sides
242
+ dh /= 2
243
+ if shape[::-1] != new_unpad: # resize
244
+ img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_AREA)
245
+
246
+ top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
247
+ left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
248
+ img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
249
+ return img, ratio, (dw, dh)
250
+
251
+
252
+ def _box_candidates(box1, box2, wh_thr=2, ar_thr=20, area_thr=0.1): # box1(4,n), box2(4,n)
253
+ # Compute candidate boxes: box1 before augment, box2 after augment, wh_thr (pixels), aspect_ratio_thr, area_ratio
254
+ w1, h1 = box1[2] - box1[0], box1[3] - box1[1]
255
+ w2, h2 = box2[2] - box2[0], box2[3] - box2[1]
256
+ ar = np.maximum(w2 / (h2 + 1e-16), h2 / (w2 + 1e-16)) # aspect ratio
257
+ return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + 1e-16) > area_thr) & (ar < ar_thr) # candidates