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
- data/scripts/get_coco.sh +1 -1
- utils/datasets.py +76 -58
- utils/general.py +35 -1
- utils/loss.py +1 -1
@@ -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 |
|
@@ -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,
|
|
|
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 '
|
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 |
-
|
385 |
-
|
386 |
-
|
|
|
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 |
-
|
|
|
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 =
|
|
|
|
|
|
|
|
|
|
|
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'] =
|
|
|
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 |
-
|
690 |
-
|
691 |
-
np.clip(
|
692 |
-
|
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
|
753 |
img9 = img9[yc:yc + 2 * s, xc:xc + 2 * s]
|
754 |
|
755 |
# Concat/clip labels
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
|
|
760 |
|
761 |
-
|
762 |
-
|
|
|
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,
|
|
|
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 |
-
|
879 |
-
|
880 |
-
|
881 |
-
|
882 |
-
|
883 |
-
|
884 |
-
|
885 |
-
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
|
903 |
-
|
|
|
904 |
|
905 |
# filter candidates
|
906 |
-
i = box_candidates(box1=targets[:, 1:5].T * s, box2=
|
907 |
targets = targets[i]
|
908 |
-
targets[:, 1:5] =
|
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 |
|
@@ -225,7 +225,7 @@ def xywh2xyxy(x):
|
|
225 |
return y
|
226 |
|
227 |
|
228 |
-
def xywhn2xyxy(x, w=640, h=640, padw=
|
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
|
@@ -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: [
|
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':
|