glenn-jocher commited on
Commit
bcd452c
1 Parent(s): bb87276

replace random_affine() with random_perspective()

Browse files

Signed-off-by: Glenn Jocher <[email protected]>

Files changed (2) hide show
  1. train.py +1 -1
  2. utils/datasets.py +47 -25
train.py CHANGED
@@ -32,7 +32,7 @@ hyp = {'optimizer': 'SGD', # ['adam', 'SGD', None] if none, default is SGD
32
  'hsv_s': 0.7, # image HSV-Saturation augmentation (fraction)
33
  'hsv_v': 0.4, # image HSV-Value augmentation (fraction)
34
  'degrees': 0.0, # image rotation (+/- deg)
35
- 'translate': 0.0, # image translation (+/- fraction)
36
  'scale': 0.5, # image scale (+/- gain)
37
  'shear': 0.0} # image shear (+/- deg)
38
 
 
32
  'hsv_s': 0.7, # image HSV-Saturation augmentation (fraction)
33
  'hsv_v': 0.4, # image HSV-Value augmentation (fraction)
34
  'degrees': 0.0, # image rotation (+/- deg)
35
+ 'translate': 0.5, # image translation (+/- fraction)
36
  'scale': 0.5, # image scale (+/- gain)
37
  'shear': 0.0} # image shear (+/- deg)
38
 
utils/datasets.py CHANGED
@@ -485,9 +485,9 @@ class LoadImagesAndLabels(Dataset): # for training/testing
485
 
486
  # MixUp https://arxiv.org/pdf/1710.09412.pdf
487
  # if random.random() < 0.5:
488
- # img2, labels2 = load_mosaic(self, random.randint(0, len(self.labels) - 1))
489
  # r = np.random.beta(0.3, 0.3) # mixup ratio, alpha=beta=0.3
490
- # img = (img * r + img2 * (1 - r)).astype(np.uint8)
491
  # labels = np.concatenate((labels, labels2), 0)
492
 
493
  else:
@@ -513,11 +513,11 @@ class LoadImagesAndLabels(Dataset): # for training/testing
513
  if self.augment:
514
  # Augment imagespace
515
  if not self.mosaic:
516
- img, labels = random_affine(img, labels,
517
- degrees=hyp['degrees'],
518
- translate=hyp['translate'],
519
- scale=hyp['scale'],
520
- shear=hyp['shear'])
521
 
522
  # Augment colorspace
523
  augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
@@ -610,7 +610,7 @@ def load_mosaic(self, index):
610
 
611
  labels4 = []
612
  s = self.img_size
613
- yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y
614
  indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)] # 3 additional image indices
615
  for i, index in enumerate(indices):
616
  # Load image
@@ -656,12 +656,12 @@ def load_mosaic(self, index):
656
 
657
  # Augment
658
  # img4 = img4[s // 2: int(s * 1.5), s // 2:int(s * 1.5)] # center crop (WARNING, requires box pruning)
659
- img4, labels4 = random_affine(img4, labels4,
660
- degrees=self.hyp['degrees'],
661
- translate=self.hyp['translate'],
662
- scale=self.hyp['scale'],
663
- shear=self.hyp['shear'],
664
- border=self.mosaic_border) # border to remove
665
 
666
  return img4, labels4
667
 
@@ -716,36 +716,54 @@ def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scale
716
  return img, ratio, (dw, dh)
717
 
718
 
719
- def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=(0, 0)):
720
  # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
721
- # https://medium.com/uruvideo/dataset-augmentation-with-random-homographies-a8f4b44830d4
722
  # targets = [cls, xyxy]
723
 
724
  height = img.shape[0] + border[0] * 2 # shape(h,w,c)
725
  width = img.shape[1] + border[1] * 2
726
 
 
 
 
 
 
 
 
 
 
 
727
  # Rotation and Scale
728
  R = np.eye(3)
729
  a = random.uniform(-degrees, degrees)
730
  # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
731
  s = random.uniform(1 - scale, 1 + scale)
732
  # s = 2 ** random.uniform(-scale, scale)
733
- R[:2] = cv2.getRotationMatrix2D(angle=a, center=(img.shape[1] / 2, img.shape[0] / 2), scale=s)
734
-
735
- # Translation
736
- T = np.eye(3)
737
- T[0, 2] = random.uniform(-translate, translate) * img.shape[1] + border[1] # x translation (pixels)
738
- T[1, 2] = random.uniform(-translate, translate) * img.shape[0] + border[0] # y translation (pixels)
739
 
740
  # Shear
741
  S = np.eye(3)
742
  S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
743
  S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
744
 
 
 
 
 
 
745
  # Combined rotation matrix
746
- M = S @ T @ R # ORDER IS IMPORTANT HERE!!
747
  if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
748
- img = cv2.warpAffine(img, M[:2], dsize=(width, height), flags=cv2.INTER_LINEAR, borderValue=(114, 114, 114))
 
 
 
 
 
 
 
 
 
749
 
750
  # Transform label coordinates
751
  n = len(targets)
@@ -753,7 +771,11 @@ def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10,
753
  # warp points
754
  xy = np.ones((n * 4, 3))
755
  xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
756
- xy = (xy @ M.T)[:, :2].reshape(n, 8)
 
 
 
 
757
 
758
  # create new boxes
759
  x = xy[:, [0, 2, 4, 6]]
 
485
 
486
  # MixUp https://arxiv.org/pdf/1710.09412.pdf
487
  # if random.random() < 0.5:
488
+ # img2, labels2 = load_mosaic(self, random.randint(0, len(self.labels) - 1))
489
  # r = np.random.beta(0.3, 0.3) # mixup ratio, alpha=beta=0.3
490
+ # img = (img * r + img2 * (1 - r)).astype(np.uint8)
491
  # labels = np.concatenate((labels, labels2), 0)
492
 
493
  else:
 
513
  if self.augment:
514
  # Augment imagespace
515
  if not self.mosaic:
516
+ img, labels = random_perspective(img, labels,
517
+ degrees=hyp['degrees'],
518
+ translate=hyp['translate'],
519
+ scale=hyp['scale'],
520
+ shear=hyp['shear'])
521
 
522
  # Augment colorspace
523
  augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
 
610
 
611
  labels4 = []
612
  s = self.img_size
613
+ yc, xc = s, s # mosaic center x, y
614
  indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)] # 3 additional image indices
615
  for i, index in enumerate(indices):
616
  # Load image
 
656
 
657
  # Augment
658
  # img4 = img4[s // 2: int(s * 1.5), s // 2:int(s * 1.5)] # center crop (WARNING, requires box pruning)
659
+ img4, labels4 = random_perspective(img4, labels4,
660
+ degrees=self.hyp['degrees'],
661
+ translate=self.hyp['translate'],
662
+ scale=self.hyp['scale'],
663
+ shear=self.hyp['shear'],
664
+ border=self.mosaic_border) # border to remove
665
 
666
  return img4, labels4
667
 
 
716
  return img, ratio, (dw, dh)
717
 
718
 
719
+ def random_perspective(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)):
720
  # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
 
721
  # targets = [cls, xyxy]
722
 
723
  height = img.shape[0] + border[0] * 2 # shape(h,w,c)
724
  width = img.shape[1] + border[1] * 2
725
 
726
+ # Center
727
+ C = np.eye(3)
728
+ C[0, 2] = -img.shape[1] / 2 # x translation (pixels)
729
+ C[1, 2] = -img.shape[0] / 2 # y translation (pixels)
730
+
731
+ # Perspective
732
+ P = np.eye(3)
733
+ P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y)
734
+ P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x)
735
+
736
  # Rotation and Scale
737
  R = np.eye(3)
738
  a = random.uniform(-degrees, degrees)
739
  # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
740
  s = random.uniform(1 - scale, 1 + scale)
741
  # s = 2 ** random.uniform(-scale, scale)
742
+ R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
 
 
 
 
 
743
 
744
  # Shear
745
  S = np.eye(3)
746
  S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
747
  S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
748
 
749
+ # Translation
750
+ T = np.eye(3)
751
+ T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width # x translation (pixels)
752
+ T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height # y translation (pixels)
753
+
754
  # Combined rotation matrix
755
+ M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT
756
  if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
757
+ if perspective:
758
+ img = cv2.warpPerspective(img, M, dsize=(width, height), borderValue=(114, 114, 114))
759
+ else: # affine
760
+ img = cv2.warpAffine(img, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
761
+
762
+ # Visualize
763
+ # import matplotlib.pyplot as plt
764
+ # ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
765
+ # ax[0].imshow(img[:, :, ::-1]) # base
766
+ # ax[1].imshow(img2[:, :, ::-1]) # warped
767
 
768
  # Transform label coordinates
769
  n = len(targets)
 
771
  # warp points
772
  xy = np.ones((n * 4, 3))
773
  xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
774
+ xy = xy @ M.T # transform
775
+ if perspective:
776
+ xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 8) # rescale
777
+ else: # affine
778
+ xy = xy[:, :2].reshape(n, 8)
779
 
780
  # create new boxes
781
  x = xy[:, [0, 2, 4, 6]]