glenn-jocher commited on
Commit
bdd88e1
1 Parent(s): 404749a

YOLOv5 Segmentation Dataloader Updates (#2188)

Browse files

* Update C3 module

* Update C3 module

* Update C3 module

* Update C3 module

* update

* update

* update

* update

* update

* update

* update

* update

* update

* updates

* updates

* updates

* updates

* updates

* updates

* updates

* updates

* updates

* updates

* update

* update

* update

* update

* updates

* updates

* updates

* updates

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update datasets

* update

* update

* update

* update attempt_downlaod()

* merge

* merge

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* parameterize eps

* comments

* gs-multiple

* update

* max_nms implemented

* Create one_cycle() function

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* GitHub API rate limit fix

* update

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* ComputeLoss

* astuple

* epochs

* update

* update

* ComputeLoss()

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* merge

* merge

* merge

* merge

* update

* update

* update

* update

* commit=tag == tags[-1]

* Update cudnn.benchmark

* update

* update

* update

* updates

* updates

* updates

* updates

* updates

* updates

* updates

* update

* update

* update

* update

* update

* mosaic9

* update

* update

* update

* update

* update

* update

* institute cache versioning

* only display on existing cache

* reverse cache exists booleans

Files changed (4) hide show
  1. data/scripts/get_coco.sh +1 -1
  2. utils/datasets.py +76 -58
  3. utils/general.py +35 -1
  4. utils/loss.py +1 -1
data/scripts/get_coco.sh CHANGED
@@ -10,7 +10,7 @@
10
  # Download/unzip labels
11
  d='../' # unzip directory
12
  url=https://github.com/ultralytics/yolov5/releases/download/v1.0/
13
- f='coco2017labels.zip' # 68 MB
14
  echo 'Downloading' $url$f ' ...'
15
  curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background
16
 
 
10
  # Download/unzip labels
11
  d='../' # unzip directory
12
  url=https://github.com/ultralytics/yolov5/releases/download/v1.0/
13
+ f='coco2017labels.zip' # or 'coco2017labels-segments.zip', 68 MB
14
  echo 'Downloading' $url$f ' ...'
15
  curl -L $url$f -o $f && unzip -q $f -d $d && rm $f & # download, unzip, remove in background
16
 
utils/datasets.py CHANGED
@@ -20,7 +20,8 @@ from PIL import Image, ExifTags
20
  from torch.utils.data import Dataset
21
  from tqdm import tqdm
22
 
23
- from utils.general import xyxy2xywh, xywh2xyxy, xywhn2xyxy, clean_str
 
24
  from utils.torch_utils import torch_distributed_zero_first
25
 
26
  # Parameters
@@ -374,21 +375,23 @@ class LoadImagesAndLabels(Dataset): # for training/testing
374
  self.label_files = img2label_paths(self.img_files) # labels
375
  cache_path = (p if p.is_file() else Path(self.label_files[0]).parent).with_suffix('.cache') # cached labels
376
  if cache_path.is_file():
377
- cache = torch.load(cache_path) # load
378
- if cache['hash'] != get_hash(self.label_files + self.img_files) or 'results' not in cache: # changed
379
- cache = self.cache_labels(cache_path, prefix) # re-cache
380
  else:
381
- cache = self.cache_labels(cache_path, prefix) # cache
382
 
383
  # Display cache
384
- [nf, nm, ne, nc, n] = cache.pop('results') # found, missing, empty, corrupted, total
385
- desc = f"Scanning '{cache_path}' for images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted"
386
- tqdm(None, desc=prefix + desc, total=n, initial=n)
 
387
  assert nf > 0 or not augment, f'{prefix}No labels in {cache_path}. Can not train without labels. See {help_url}'
388
 
389
  # Read cache
390
  cache.pop('hash') # remove hash
391
- labels, shapes = zip(*cache.values())
 
392
  self.labels = list(labels)
393
  self.shapes = np.array(shapes, dtype=np.float64)
394
  self.img_files = list(cache.keys()) # update
@@ -451,6 +454,7 @@ class LoadImagesAndLabels(Dataset): # for training/testing
451
  im = Image.open(im_file)
452
  im.verify() # PIL verify
453
  shape = exif_size(im) # image size
 
454
  assert (shape[0] > 9) & (shape[1] > 9), f'image size {shape} <10 pixels'
455
  assert im.format.lower() in img_formats, f'invalid image format {im.format}'
456
 
@@ -458,7 +462,12 @@ class LoadImagesAndLabels(Dataset): # for training/testing
458
  if os.path.isfile(lb_file):
459
  nf += 1 # label found
460
  with open(lb_file, 'r') as f:
461
- l = np.array([x.split() for x in f.read().strip().splitlines()], dtype=np.float32) # labels
 
 
 
 
 
462
  if len(l):
463
  assert l.shape[1] == 5, 'labels require 5 columns each'
464
  assert (l >= 0).all(), 'negative labels'
@@ -470,7 +479,7 @@ class LoadImagesAndLabels(Dataset): # for training/testing
470
  else:
471
  nm += 1 # label missing
472
  l = np.zeros((0, 5), dtype=np.float32)
473
- x[im_file] = [l, shape]
474
  except Exception as e:
475
  nc += 1
476
  print(f'{prefix}WARNING: Ignoring corrupted image and/or label {im_file}: {e}')
@@ -482,7 +491,8 @@ class LoadImagesAndLabels(Dataset): # for training/testing
482
  print(f'{prefix}WARNING: No labels found in {path}. See {help_url}')
483
 
484
  x['hash'] = get_hash(self.label_files + self.img_files)
485
- x['results'] = [nf, nm, ne, nc, i + 1]
 
486
  torch.save(x, path) # save for next time
487
  logging.info(f'{prefix}New cache created: {path}')
488
  return x
@@ -652,7 +662,7 @@ def hist_equalize(img, clahe=True, bgr=False):
652
  def load_mosaic(self, index):
653
  # loads images in a 4-mosaic
654
 
655
- labels4 = []
656
  s = self.img_size
657
  yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y
658
  indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(3)] # 3 additional image indices
@@ -680,19 +690,21 @@ def load_mosaic(self, index):
680
  padh = y1a - y1b
681
 
682
  # Labels
683
- labels = self.labels[index].copy()
684
  if labels.size:
685
  labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh) # normalized xywh to pixel xyxy format
 
686
  labels4.append(labels)
 
687
 
688
  # Concat/clip labels
689
- if len(labels4):
690
- labels4 = np.concatenate(labels4, 0)
691
- np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:]) # use with random_perspective
692
- # img4, labels4 = replicate(img4, labels4) # replicate
693
 
694
  # Augment
695
- img4, labels4 = random_perspective(img4, labels4,
696
  degrees=self.hyp['degrees'],
697
  translate=self.hyp['translate'],
698
  scale=self.hyp['scale'],
@@ -706,7 +718,7 @@ def load_mosaic(self, index):
706
  def load_mosaic9(self, index):
707
  # loads images in a 9-mosaic
708
 
709
- labels9 = []
710
  s = self.img_size
711
  indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(8)] # 8 additional image indices
712
  for i, index in enumerate(indices):
@@ -739,30 +751,34 @@ def load_mosaic9(self, index):
739
  x1, y1, x2, y2 = [max(x, 0) for x in c] # allocate coords
740
 
741
  # Labels
742
- labels = self.labels[index].copy()
743
  if labels.size:
744
  labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padx, pady) # normalized xywh to pixel xyxy format
 
745
  labels9.append(labels)
 
746
 
747
  # Image
748
  img9[y1:y2, x1:x2] = img[y1 - pady:, x1 - padx:] # img9[ymin:ymax, xmin:xmax]
749
  hp, wp = h, w # height, width previous
750
 
751
  # Offset
752
- yc, xc = [int(random.uniform(0, s)) for x in self.mosaic_border] # mosaic center x, y
753
  img9 = img9[yc:yc + 2 * s, xc:xc + 2 * s]
754
 
755
  # Concat/clip labels
756
- if len(labels9):
757
- labels9 = np.concatenate(labels9, 0)
758
- labels9[:, [1, 3]] -= xc
759
- labels9[:, [2, 4]] -= yc
 
760
 
761
- np.clip(labels9[:, 1:], 0, 2 * s, out=labels9[:, 1:]) # use with random_perspective
762
- # img9, labels9 = replicate(img9, labels9) # replicate
 
763
 
764
  # Augment
765
- img9, labels9 = random_perspective(img9, labels9,
766
  degrees=self.hyp['degrees'],
767
  translate=self.hyp['translate'],
768
  scale=self.hyp['scale'],
@@ -823,7 +839,8 @@ def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scale
823
  return img, ratio, (dw, dh)
824
 
825
 
826
- def random_perspective(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)):
 
827
  # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
828
  # targets = [cls, xyxy]
829
 
@@ -875,37 +892,38 @@ def random_perspective(img, targets=(), degrees=10, translate=.1, scale=.1, shea
875
  # Transform label coordinates
876
  n = len(targets)
877
  if n:
878
- # warp points
879
- xy = np.ones((n * 4, 3))
880
- xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
881
- xy = xy @ M.T # transform
882
- if perspective:
883
- xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 8) # rescale
884
- else: # affine
885
- xy = xy[:, :2].reshape(n, 8)
886
-
887
- # create new boxes
888
- x = xy[:, [0, 2, 4, 6]]
889
- y = xy[:, [1, 3, 5, 7]]
890
- xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
891
-
892
- # # apply angle-based reduction of bounding boxes
893
- # radians = a * math.pi / 180
894
- # reduction = max(abs(math.sin(radians)), abs(math.cos(radians))) ** 0.5
895
- # x = (xy[:, 2] + xy[:, 0]) / 2
896
- # y = (xy[:, 3] + xy[:, 1]) / 2
897
- # w = (xy[:, 2] - xy[:, 0]) * reduction
898
- # h = (xy[:, 3] - xy[:, 1]) * reduction
899
- # xy = np.concatenate((x - w / 2, y - h / 2, x + w / 2, y + h / 2)).reshape(4, n).T
900
-
901
- # clip boxes
902
- xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)
903
- xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)
 
904
 
905
  # filter candidates
906
- i = box_candidates(box1=targets[:, 1:5].T * s, box2=xy.T)
907
  targets = targets[i]
908
- targets[:, 1:5] = xy[i]
909
 
910
  return img, targets
911
 
 
20
  from torch.utils.data import Dataset
21
  from tqdm import tqdm
22
 
23
+ from utils.general import xyxy2xywh, xywh2xyxy, xywhn2xyxy, xyn2xy, segment2box, segments2boxes, resample_segments, \
24
+ clean_str
25
  from utils.torch_utils import torch_distributed_zero_first
26
 
27
  # Parameters
 
375
  self.label_files = img2label_paths(self.img_files) # labels
376
  cache_path = (p if p.is_file() else Path(self.label_files[0]).parent).with_suffix('.cache') # cached labels
377
  if cache_path.is_file():
378
+ cache, exists = torch.load(cache_path), True # load
379
+ if cache['hash'] != get_hash(self.label_files + self.img_files) or 'version' not in cache: # changed
380
+ cache, exists = self.cache_labels(cache_path, prefix), False # re-cache
381
  else:
382
+ cache, exists = self.cache_labels(cache_path, prefix), False # cache
383
 
384
  # Display cache
385
+ nf, nm, ne, nc, n = cache.pop('results') # found, missing, empty, corrupted, total
386
+ if exists:
387
+ d = f"Scanning '{cache_path}' for images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted"
388
+ tqdm(None, desc=prefix + d, total=n, initial=n) # display cache results
389
  assert nf > 0 or not augment, f'{prefix}No labels in {cache_path}. Can not train without labels. See {help_url}'
390
 
391
  # Read cache
392
  cache.pop('hash') # remove hash
393
+ cache.pop('version') # remove version
394
+ labels, shapes, self.segments = zip(*cache.values())
395
  self.labels = list(labels)
396
  self.shapes = np.array(shapes, dtype=np.float64)
397
  self.img_files = list(cache.keys()) # update
 
454
  im = Image.open(im_file)
455
  im.verify() # PIL verify
456
  shape = exif_size(im) # image size
457
+ segments = [] # instance segments
458
  assert (shape[0] > 9) & (shape[1] > 9), f'image size {shape} <10 pixels'
459
  assert im.format.lower() in img_formats, f'invalid image format {im.format}'
460
 
 
462
  if os.path.isfile(lb_file):
463
  nf += 1 # label found
464
  with open(lb_file, 'r') as f:
465
+ l = [x.split() for x in f.read().strip().splitlines()]
466
+ if any([len(x) > 8 for x in l]): # is segment
467
+ classes = np.array([x[0] for x in l], dtype=np.float32)
468
+ segments = [np.array(x[1:], dtype=np.float32).reshape(-1, 2) for x in l] # (cls, xy1...)
469
+ l = np.concatenate((classes.reshape(-1, 1), segments2boxes(segments)), 1) # (cls, xywh)
470
+ l = np.array(l, dtype=np.float32)
471
  if len(l):
472
  assert l.shape[1] == 5, 'labels require 5 columns each'
473
  assert (l >= 0).all(), 'negative labels'
 
479
  else:
480
  nm += 1 # label missing
481
  l = np.zeros((0, 5), dtype=np.float32)
482
+ x[im_file] = [l, shape, segments]
483
  except Exception as e:
484
  nc += 1
485
  print(f'{prefix}WARNING: Ignoring corrupted image and/or label {im_file}: {e}')
 
491
  print(f'{prefix}WARNING: No labels found in {path}. See {help_url}')
492
 
493
  x['hash'] = get_hash(self.label_files + self.img_files)
494
+ x['results'] = nf, nm, ne, nc, i + 1
495
+ x['version'] = 0.1 # cache version
496
  torch.save(x, path) # save for next time
497
  logging.info(f'{prefix}New cache created: {path}')
498
  return x
 
662
  def load_mosaic(self, index):
663
  # loads images in a 4-mosaic
664
 
665
+ labels4, segments4 = [], []
666
  s = self.img_size
667
  yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y
668
  indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(3)] # 3 additional image indices
 
690
  padh = y1a - y1b
691
 
692
  # Labels
693
+ labels, segments = self.labels[index].copy(), self.segments[index].copy()
694
  if labels.size:
695
  labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh) # normalized xywh to pixel xyxy format
696
+ segments = [xyn2xy(x, w, h, padw, padh) for x in segments]
697
  labels4.append(labels)
698
+ segments4.extend(segments)
699
 
700
  # Concat/clip labels
701
+ labels4 = np.concatenate(labels4, 0)
702
+ for x in (labels4[:, 1:], *segments4):
703
+ np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective()
704
+ # img4, labels4 = replicate(img4, labels4) # replicate
705
 
706
  # Augment
707
+ img4, labels4 = random_perspective(img4, labels4, segments4,
708
  degrees=self.hyp['degrees'],
709
  translate=self.hyp['translate'],
710
  scale=self.hyp['scale'],
 
718
  def load_mosaic9(self, index):
719
  # loads images in a 9-mosaic
720
 
721
+ labels9, segments9 = [], []
722
  s = self.img_size
723
  indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(8)] # 8 additional image indices
724
  for i, index in enumerate(indices):
 
751
  x1, y1, x2, y2 = [max(x, 0) for x in c] # allocate coords
752
 
753
  # Labels
754
+ labels, segments = self.labels[index].copy(), self.segments[index].copy()
755
  if labels.size:
756
  labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padx, pady) # normalized xywh to pixel xyxy format
757
+ segments = [xyn2xy(x, w, h, padx, pady) for x in segments]
758
  labels9.append(labels)
759
+ segments9.extend(segments)
760
 
761
  # Image
762
  img9[y1:y2, x1:x2] = img[y1 - pady:, x1 - padx:] # img9[ymin:ymax, xmin:xmax]
763
  hp, wp = h, w # height, width previous
764
 
765
  # Offset
766
+ yc, xc = [int(random.uniform(0, s)) for _ in self.mosaic_border] # mosaic center x, y
767
  img9 = img9[yc:yc + 2 * s, xc:xc + 2 * s]
768
 
769
  # Concat/clip labels
770
+ labels9 = np.concatenate(labels9, 0)
771
+ labels9[:, [1, 3]] -= xc
772
+ labels9[:, [2, 4]] -= yc
773
+ c = np.array([xc, yc]) # centers
774
+ segments9 = [x - c for x in segments9]
775
 
776
+ for x in (labels9[:, 1:], *segments9):
777
+ np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective()
778
+ # img9, labels9 = replicate(img9, labels9) # replicate
779
 
780
  # Augment
781
+ img9, labels9 = random_perspective(img9, labels9, segments9,
782
  degrees=self.hyp['degrees'],
783
  translate=self.hyp['translate'],
784
  scale=self.hyp['scale'],
 
839
  return img, ratio, (dw, dh)
840
 
841
 
842
+ def random_perspective(img, targets=(), segments=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0,
843
+ border=(0, 0)):
844
  # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
845
  # targets = [cls, xyxy]
846
 
 
892
  # Transform label coordinates
893
  n = len(targets)
894
  if n:
895
+ use_segments = any(x.any() for x in segments)
896
+ new = np.zeros((n, 4))
897
+ if use_segments: # warp segments
898
+ segments = resample_segments(segments) # upsample
899
+ for i, segment in enumerate(segments):
900
+ xy = np.ones((len(segment), 3))
901
+ xy[:, :2] = segment
902
+ xy = xy @ M.T # transform
903
+ xy = xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2] # perspective rescale or affine
904
+
905
+ # clip
906
+ new[i] = segment2box(xy, width, height)
907
+
908
+ else: # warp boxes
909
+ xy = np.ones((n * 4, 3))
910
+ xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1
911
+ xy = xy @ M.T # transform
912
+ xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]).reshape(n, 8) # perspective rescale or affine
913
+
914
+ # create new boxes
915
+ x = xy[:, [0, 2, 4, 6]]
916
+ y = xy[:, [1, 3, 5, 7]]
917
+ new = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
918
+
919
+ # clip
920
+ new[:, [0, 2]] = new[:, [0, 2]].clip(0, width)
921
+ new[:, [1, 3]] = new[:, [1, 3]].clip(0, height)
922
 
923
  # filter candidates
924
+ i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01 if use_segments else 0.10)
925
  targets = targets[i]
926
+ targets[:, 1:5] = new[i]
927
 
928
  return img, targets
929
 
utils/general.py CHANGED
@@ -225,7 +225,7 @@ def xywh2xyxy(x):
225
  return y
226
 
227
 
228
- def xywhn2xyxy(x, w=640, h=640, padw=32, padh=32):
229
  # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
230
  y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
231
  y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x
@@ -235,6 +235,40 @@ def xywhn2xyxy(x, w=640, h=640, padw=32, padh=32):
235
  return y
236
 
237
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
239
  # Rescale coords (xyxy) from img1_shape to img0_shape
240
  if ratio_pad is None: # calculate from img0_shape
 
225
  return y
226
 
227
 
228
+ def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
229
  # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
230
  y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
231
  y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw # top left x
 
235
  return y
236
 
237
 
238
+ def xyn2xy(x, w=640, h=640, padw=0, padh=0):
239
+ # Convert normalized segments into pixel segments, shape (n,2)
240
+ y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
241
+ y[:, 0] = w * x[:, 0] + padw # top left x
242
+ y[:, 1] = h * x[:, 1] + padh # top left y
243
+ return y
244
+
245
+
246
+ def segment2box(segment, width=640, height=640):
247
+ # Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy)
248
+ x, y = segment.T # segment xy
249
+ inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)
250
+ x, y, = x[inside], y[inside]
251
+ return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4)) # cls, xyxy
252
+
253
+
254
+ def segments2boxes(segments):
255
+ # Convert segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh)
256
+ boxes = []
257
+ for s in segments:
258
+ x, y = s.T # segment xy
259
+ boxes.append([x.min(), y.min(), x.max(), y.max()]) # cls, xyxy
260
+ return xyxy2xywh(np.array(boxes)) # cls, xywh
261
+
262
+
263
+ def resample_segments(segments, n=1000):
264
+ # Up-sample an (n,2) segment
265
+ for i, s in enumerate(segments):
266
+ x = np.linspace(0, len(s) - 1, n)
267
+ xp = np.arange(len(s))
268
+ segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T # segment xy
269
+ return segments
270
+
271
+
272
  def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
273
  # Rescale coords (xyxy) from img1_shape to img0_shape
274
  if ratio_pad is None: # calculate from img0_shape
utils/loss.py CHANGED
@@ -105,7 +105,7 @@ class ComputeLoss:
105
  BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
106
 
107
  det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module
108
- self.balance = {3: [3.67, 1.0, 0.43], 4: [4.0, 1.0, 0.25, 0.06], 5: [4.0, 1.0, 0.25, 0.06, .02]}[det.nl]
109
  self.ssi = (det.stride == 16).nonzero(as_tuple=False).item() # stride 16 index
110
  self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance
111
  for k in 'na', 'nc', 'nl', 'anchors':
 
105
  BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
106
 
107
  det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module
108
+ self.balance = {3: [4.0, 1.0, 0.4], 4: [4.0, 1.0, 0.25, 0.06], 5: [4.0, 1.0, 0.25, 0.06, .02]}[det.nl]
109
  self.ssi = (det.stride == 16).nonzero(as_tuple=False).item() # stride 16 index
110
  self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance
111
  for k in 'na', 'nc', 'nl', 'anchors':