|
|
|
|
|
import numpy as np |
|
import cv2 |
|
import random |
|
import math |
|
|
|
|
|
def augment_hsv(img, hgain=0.5, sgain=0.5, vgain=0.5): |
|
"""change color hue, saturation, value""" |
|
r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 |
|
hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV)) |
|
dtype = img.dtype |
|
|
|
x = np.arange(0, 256, dtype=np.int16) |
|
lut_hue = ((x * r[0]) % 180).astype(dtype) |
|
lut_sat = np.clip(x * r[1], 0, 255).astype(dtype) |
|
lut_val = np.clip(x * r[2], 0, 255).astype(dtype) |
|
|
|
img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))).astype(dtype) |
|
cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def random_perspective(combination, targets=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)): |
|
"""combination of img transform""" |
|
|
|
|
|
img, gray, line = combination |
|
height = img.shape[0] + border[0] * 2 |
|
width = img.shape[1] + border[1] * 2 |
|
|
|
|
|
C = np.eye(3) |
|
C[0, 2] = -img.shape[1] / 2 |
|
C[1, 2] = -img.shape[0] / 2 |
|
|
|
|
|
P = np.eye(3) |
|
P[2, 0] = random.uniform(-perspective, perspective) |
|
P[2, 1] = random.uniform(-perspective, perspective) |
|
|
|
|
|
R = np.eye(3) |
|
a = random.uniform(-degrees, degrees) |
|
|
|
s = random.uniform(1 - scale, 1 + scale) |
|
|
|
R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s) |
|
|
|
|
|
S = np.eye(3) |
|
S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) |
|
S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) |
|
|
|
|
|
T = np.eye(3) |
|
T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width |
|
T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height |
|
|
|
|
|
M = T @ S @ R @ P @ C |
|
if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): |
|
if perspective: |
|
img = cv2.warpPerspective(img, M, dsize=(width, height), borderValue=(114, 114, 114)) |
|
gray = cv2.warpPerspective(gray, M, dsize=(width, height), borderValue=0) |
|
line = cv2.warpPerspective(line, M, dsize=(width, height), borderValue=0) |
|
else: |
|
img = cv2.warpAffine(img, M[:2], dsize=(width, height), borderValue=(114, 114, 114)) |
|
gray = cv2.warpAffine(gray, M[:2], dsize=(width, height), borderValue=0) |
|
line = cv2.warpAffine(line, M[:2], dsize=(width, height), borderValue=0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
n = len(targets) |
|
if n: |
|
|
|
xy = np.ones((n * 4, 3)) |
|
xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) |
|
xy = xy @ M.T |
|
if perspective: |
|
xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 8) |
|
else: |
|
xy = xy[:, :2].reshape(n, 8) |
|
|
|
|
|
x = xy[:, [0, 2, 4, 6]] |
|
y = xy[:, [1, 3, 5, 7]] |
|
xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width) |
|
xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height) |
|
|
|
|
|
i = _box_candidates(box1=targets[:, 1:5].T * s, box2=xy.T) |
|
targets = targets[i] |
|
targets[:, 1:5] = xy[i] |
|
|
|
combination = (img, gray, line) |
|
return combination, targets |
|
|
|
|
|
def cutout(combination, labels): |
|
|
|
image, gray = combination |
|
h, w = image.shape[:2] |
|
|
|
def bbox_ioa(box1, box2): |
|
|
|
box2 = box2.transpose() |
|
|
|
|
|
b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3] |
|
b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3] |
|
|
|
|
|
inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * \ |
|
(np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0) |
|
|
|
|
|
box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + 1e-16 |
|
|
|
|
|
return inter_area / box2_area |
|
|
|
|
|
scales = [0.5] * 1 + [0.25] * 2 + [0.125] * 4 + [0.0625] * 8 + [0.03125] * 16 |
|
for s in scales: |
|
mask_h = random.randint(1, int(h * s)) |
|
mask_w = random.randint(1, int(w * s)) |
|
|
|
|
|
xmin = max(0, random.randint(0, w) - mask_w // 2) |
|
ymin = max(0, random.randint(0, h) - mask_h // 2) |
|
xmax = min(w, xmin + mask_w) |
|
ymax = min(h, ymin + mask_h) |
|
|
|
|
|
|
|
image[ymin:ymax, xmin:xmax] = [random.randint(64, 191) for _ in range(3)] |
|
gray[ymin:ymax, xmin:xmax] = -1 |
|
|
|
|
|
if len(labels) and s > 0.03: |
|
box = np.array([xmin, ymin, xmax, ymax], dtype=np.float32) |
|
ioa = bbox_ioa(box, labels[:, 1:5]) |
|
labels = labels[ioa < 0.60] |
|
|
|
return image, gray, labels |
|
|
|
|
|
def letterbox(combination, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True): |
|
"""Resize the input image and automatically padding to suitable shape :https://zhuanlan.zhihu.com/p/172121380""" |
|
|
|
img, gray, line = combination |
|
shape = img.shape[:2] |
|
if isinstance(new_shape, int): |
|
new_shape = (new_shape, new_shape) |
|
|
|
|
|
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) |
|
if not scaleup: |
|
r = min(r, 1.0) |
|
|
|
|
|
ratio = r, r |
|
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) |
|
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] |
|
if auto: |
|
dw, dh = np.mod(dw, 32), np.mod(dh, 32) |
|
elif scaleFill: |
|
dw, dh = 0.0, 0.0 |
|
new_unpad = (new_shape[1], new_shape[0]) |
|
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] |
|
|
|
dw /= 2 |
|
dh /= 2 |
|
|
|
if shape[::-1] != new_unpad: |
|
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) |
|
gray = cv2.resize(gray, new_unpad, interpolation=cv2.INTER_LINEAR) |
|
line = cv2.resize(line, new_unpad, interpolation=cv2.INTER_LINEAR) |
|
|
|
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) |
|
left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) |
|
|
|
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) |
|
gray = cv2.copyMakeBorder(gray, top, bottom, left, right, cv2.BORDER_CONSTANT, value=0) |
|
line = cv2.copyMakeBorder(line, top, bottom, left, right, cv2.BORDER_CONSTANT, value=0) |
|
|
|
|
|
combination = (img, gray, line) |
|
return combination, ratio, (dw, dh) |
|
|
|
def letterbox_for_img(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True): |
|
|
|
shape = img.shape[:2] |
|
if isinstance(new_shape, int): |
|
new_shape = (new_shape, new_shape) |
|
|
|
|
|
|
|
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) |
|
if not scaleup: |
|
r = min(r, 1.0) |
|
|
|
|
|
ratio = r, r |
|
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) |
|
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] |
|
if auto: |
|
dw, dh = np.mod(dw, 32), np.mod(dh, 32) |
|
elif scaleFill: |
|
dw, dh = 0.0, 0.0 |
|
new_unpad = (new_shape[1], new_shape[0]) |
|
ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] |
|
|
|
dw /= 2 |
|
dh /= 2 |
|
if shape[::-1] != new_unpad: |
|
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_AREA) |
|
|
|
top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) |
|
left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) |
|
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) |
|
return img, ratio, (dw, dh) |
|
|
|
|
|
def _box_candidates(box1, box2, wh_thr=2, ar_thr=20, area_thr=0.1): |
|
|
|
w1, h1 = box1[2] - box1[0], box1[3] - box1[1] |
|
w2, h2 = box2[2] - box2[0], box2[3] - box2[1] |
|
ar = np.maximum(w2 / (h2 + 1e-16), h2 / (w2 + 1e-16)) |
|
return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + 1e-16) > area_thr) & (ar < ar_thr) |
|
|