glenn-jocher
commited on
Commit
•
89c7a5b
1
Parent(s):
44f42b1
Update caching (#1496)
Browse files- utils/datasets.py +93 -87
utils/datasets.py
CHANGED
@@ -22,12 +22,11 @@ from tqdm import tqdm
|
|
22 |
from utils.general import xyxy2xywh, xywh2xyxy
|
23 |
from utils.torch_utils import torch_distributed_zero_first
|
24 |
|
25 |
-
logger = logging.getLogger(__name__)
|
26 |
-
|
27 |
# Parameters
|
28 |
help_url = 'https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data'
|
29 |
img_formats = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng'] # acceptable image suffixes
|
30 |
vid_formats = ['mov', 'avi', 'mp4', 'mpg', 'mpeg', 'm4v', 'wmv', 'mkv'] # acceptable video suffixes
|
|
|
31 |
|
32 |
# Get orientation exif tag
|
33 |
for orientation in ExifTags.TAGS.keys():
|
@@ -168,14 +167,14 @@ class LoadImages: # for inference
|
|
168 |
ret_val, img0 = self.cap.read()
|
169 |
|
170 |
self.frame += 1
|
171 |
-
|
172 |
|
173 |
else:
|
174 |
# Read image
|
175 |
self.count += 1
|
176 |
img0 = cv2.imread(path) # BGR
|
177 |
assert img0 is not None, 'Image Not Found ' + path
|
178 |
-
|
179 |
|
180 |
# Padded resize
|
181 |
img = letterbox(img0, new_shape=self.img_size)[0]
|
@@ -237,7 +236,7 @@ class LoadWebcam: # for inference
|
|
237 |
# Print
|
238 |
assert ret_val, 'Camera Error %s' % self.pipe
|
239 |
img_path = 'webcam.jpg'
|
240 |
-
|
241 |
|
242 |
# Padded resize
|
243 |
img = letterbox(img0, new_shape=self.img_size)[0]
|
@@ -268,7 +267,7 @@ class LoadStreams: # multiple IP or RTSP cameras
|
|
268 |
self.sources = sources
|
269 |
for i, s in enumerate(sources):
|
270 |
# Start the thread to read frames from the video stream
|
271 |
-
|
272 |
cap = cv2.VideoCapture(eval(s) if s.isnumeric() else s)
|
273 |
assert cap.isOpened(), 'Failed to open %s' % s
|
274 |
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
@@ -276,14 +275,15 @@ class LoadStreams: # multiple IP or RTSP cameras
|
|
276 |
fps = cap.get(cv2.CAP_PROP_FPS) % 100
|
277 |
_, self.imgs[i] = cap.read() # guarantee first frame
|
278 |
thread = Thread(target=self.update, args=([i, cap]), daemon=True)
|
279 |
-
|
280 |
thread.start()
|
|
|
281 |
|
282 |
# check for common shapes
|
283 |
s = np.stack([letterbox(x, new_shape=self.img_size)[0].shape for x in self.imgs], 0) # inference shapes
|
284 |
self.rect = np.unique(s, axis=0).shape[0] == 1 # rect inference if all shapes equal
|
285 |
if not self.rect:
|
286 |
-
|
287 |
|
288 |
def update(self, index, cap):
|
289 |
# Read next stream frame in a daemon thread
|
@@ -324,6 +324,12 @@ class LoadStreams: # multiple IP or RTSP cameras
|
|
324 |
return 0 # 1E12 frames = 32 streams at 30 FPS for 30 years
|
325 |
|
326 |
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
class LoadImagesAndLabels(Dataset): # for training/testing
|
328 |
def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,
|
329 |
cache_images=False, single_cls=False, stride=32, pad=0.0, rank=-1):
|
@@ -336,11 +342,6 @@ class LoadImagesAndLabels(Dataset): # for training/testing
|
|
336 |
self.mosaic_border = [-img_size // 2, -img_size // 2]
|
337 |
self.stride = stride
|
338 |
|
339 |
-
def img2label_paths(img_paths):
|
340 |
-
# Define label paths as a function of image paths
|
341 |
-
sa, sb = os.sep + 'images' + os.sep, os.sep + 'labels' + os.sep # /images/, /labels/ substrings
|
342 |
-
return [x.replace(sa, sb, 1).replace('.' + x.split('.')[-1], '.txt') for x in img_paths]
|
343 |
-
|
344 |
try:
|
345 |
f = [] # image files
|
346 |
for p in path if isinstance(path, list) else [path]:
|
@@ -361,14 +362,20 @@ class LoadImagesAndLabels(Dataset): # for training/testing
|
|
361 |
|
362 |
# Check cache
|
363 |
self.label_files = img2label_paths(self.img_files) # labels
|
364 |
-
cache_path =
|
365 |
-
if
|
366 |
cache = torch.load(cache_path) # load
|
367 |
if cache['hash'] != get_hash(self.label_files + self.img_files): # dataset changed
|
368 |
cache = self.cache_labels(cache_path) # re-cache
|
369 |
else:
|
370 |
cache = self.cache_labels(cache_path) # cache
|
371 |
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
# Read cache
|
373 |
cache.pop('hash') # remove hash
|
374 |
labels, shapes = zip(*cache.values())
|
@@ -376,6 +383,9 @@ class LoadImagesAndLabels(Dataset): # for training/testing
|
|
376 |
self.shapes = np.array(shapes, dtype=np.float64)
|
377 |
self.img_files = list(cache.keys()) # update
|
378 |
self.label_files = img2label_paths(cache.keys()) # update
|
|
|
|
|
|
|
379 |
|
380 |
n = len(shapes) # number of images
|
381 |
bi = np.floor(np.arange(n) / batch_size).astype(np.int) # batch index
|
@@ -407,67 +417,6 @@ class LoadImagesAndLabels(Dataset): # for training/testing
|
|
407 |
|
408 |
self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(np.int) * stride
|
409 |
|
410 |
-
# Check labels
|
411 |
-
create_datasubset, extract_bounding_boxes, labels_loaded = False, False, False
|
412 |
-
nm, nf, ne, ns, nd = 0, 0, 0, 0, 0 # number missing, found, empty, datasubset, duplicate
|
413 |
-
pbar = enumerate(self.label_files)
|
414 |
-
if rank in [-1, 0]:
|
415 |
-
pbar = tqdm(pbar)
|
416 |
-
for i, file in pbar:
|
417 |
-
l = self.labels[i] # label
|
418 |
-
if l is not None and l.shape[0]:
|
419 |
-
assert l.shape[1] == 5, '> 5 label columns: %s' % file
|
420 |
-
assert (l >= 0).all(), 'negative labels: %s' % file
|
421 |
-
assert (l[:, 1:] <= 1).all(), 'non-normalized or out of bounds coordinate labels: %s' % file
|
422 |
-
if np.unique(l, axis=0).shape[0] < l.shape[0]: # duplicate rows
|
423 |
-
nd += 1 # logger.warning('WARNING: duplicate rows in %s', self.label_files[i]) # duplicate rows
|
424 |
-
if single_cls:
|
425 |
-
l[:, 0] = 0 # force dataset into single-class mode
|
426 |
-
self.labels[i] = l
|
427 |
-
nf += 1 # file found
|
428 |
-
|
429 |
-
# Create subdataset (a smaller dataset)
|
430 |
-
if create_datasubset and ns < 1E4:
|
431 |
-
if ns == 0:
|
432 |
-
create_folder(path='./datasubset')
|
433 |
-
os.makedirs('./datasubset/images')
|
434 |
-
exclude_classes = 43
|
435 |
-
if exclude_classes not in l[:, 0]:
|
436 |
-
ns += 1
|
437 |
-
# shutil.copy(src=self.img_files[i], dst='./datasubset/images/') # copy image
|
438 |
-
with open('./datasubset/images.txt', 'a') as f:
|
439 |
-
f.write(self.img_files[i] + '\n')
|
440 |
-
|
441 |
-
# Extract object detection boxes for a second stage classifier
|
442 |
-
if extract_bounding_boxes:
|
443 |
-
p = Path(self.img_files[i])
|
444 |
-
img = cv2.imread(str(p))
|
445 |
-
h, w = img.shape[:2]
|
446 |
-
for j, x in enumerate(l):
|
447 |
-
f = '%s%sclassifier%s%g_%g_%s' % (p.parent.parent, os.sep, os.sep, x[0], j, p.name)
|
448 |
-
if not os.path.exists(Path(f).parent):
|
449 |
-
os.makedirs(Path(f).parent) # make new output folder
|
450 |
-
|
451 |
-
b = x[1:] * [w, h, w, h] # box
|
452 |
-
b[2:] = b[2:].max() # rectangle to square
|
453 |
-
b[2:] = b[2:] * 1.3 + 30 # pad
|
454 |
-
b = xywh2xyxy(b.reshape(-1, 4)).ravel().astype(np.int)
|
455 |
-
|
456 |
-
b[[0, 2]] = np.clip(b[[0, 2]], 0, w) # clip boxes outside of image
|
457 |
-
b[[1, 3]] = np.clip(b[[1, 3]], 0, h)
|
458 |
-
assert cv2.imwrite(f, img[b[1]:b[3], b[0]:b[2]]), 'Failure extracting classifier boxes'
|
459 |
-
else:
|
460 |
-
ne += 1 # logger.info('empty labels for image %s', self.img_files[i]) # file empty
|
461 |
-
# os.system("rm '%s' '%s'" % (self.img_files[i], self.label_files[i])) # remove
|
462 |
-
|
463 |
-
if rank in [-1, 0]:
|
464 |
-
pbar.desc = 'Scanning labels %s (%g found, %g missing, %g empty, %g duplicate, for %g images)' % (
|
465 |
-
cache_path, nf, nm, ne, nd, n)
|
466 |
-
if nf == 0:
|
467 |
-
s = 'WARNING: No labels found in %s. See %s' % (os.path.dirname(file) + os.sep, help_url)
|
468 |
-
logger.info(s)
|
469 |
-
assert not augment, '%s. Can not train without labels.' % s
|
470 |
-
|
471 |
# Cache images into memory for faster training (WARNING: large datasets may exceed system RAM)
|
472 |
self.imgs = [None] * n
|
473 |
if cache_images:
|
@@ -480,28 +429,50 @@ class LoadImagesAndLabels(Dataset): # for training/testing
|
|
480 |
gb += self.imgs[i].nbytes
|
481 |
pbar.desc = 'Caching images (%.1fGB)' % (gb / 1E9)
|
482 |
|
483 |
-
def cache_labels(self, path='labels.cache'):
|
484 |
# Cache dataset labels, check images and read shapes
|
485 |
x = {} # dict
|
|
|
486 |
pbar = tqdm(zip(self.img_files, self.label_files), desc='Scanning images', total=len(self.img_files))
|
487 |
-
for (
|
488 |
try:
|
489 |
-
|
490 |
-
im = Image.open(
|
491 |
im.verify() # PIL verify
|
492 |
shape = exif_size(im) # image size
|
493 |
assert (shape[0] > 9) & (shape[1] > 9), 'image size <10 pixels'
|
494 |
-
|
495 |
-
|
|
|
|
|
|
|
|
|
496 |
l = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) # labels
|
497 |
-
|
498 |
-
|
499 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
500 |
except Exception as e:
|
501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
502 |
|
503 |
x['hash'] = get_hash(self.label_files + self.img_files)
|
|
|
504 |
torch.save(x, path) # save for next time
|
|
|
505 |
return x
|
506 |
|
507 |
def __len__(self):
|
@@ -509,7 +480,7 @@ class LoadImagesAndLabels(Dataset): # for training/testing
|
|
509 |
|
510 |
# def __iter__(self):
|
511 |
# self.count = -1
|
512 |
-
#
|
513 |
# #self.shuffled_vector = np.random.permutation(self.nF) if self.augment else np.arange(self.nF)
|
514 |
# return self
|
515 |
|
@@ -906,6 +877,41 @@ def flatten_recursive(path='../coco128'):
|
|
906 |
shutil.copyfile(file, new_path / Path(file).name)
|
907 |
|
908 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
909 |
def autosplit(path='../coco128', weights=(0.9, 0.1, 0.0)): # from utils.datasets import *; autosplit('../coco128')
|
910 |
""" Autosplit a dataset into train/val/test splits and save path/autosplit_*.txt files
|
911 |
# Arguments
|
|
|
22 |
from utils.general import xyxy2xywh, xywh2xyxy
|
23 |
from utils.torch_utils import torch_distributed_zero_first
|
24 |
|
|
|
|
|
25 |
# Parameters
|
26 |
help_url = 'https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data'
|
27 |
img_formats = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng'] # acceptable image suffixes
|
28 |
vid_formats = ['mov', 'avi', 'mp4', 'mpg', 'mpeg', 'm4v', 'wmv', 'mkv'] # acceptable video suffixes
|
29 |
+
logger = logging.getLogger(__name__)
|
30 |
|
31 |
# Get orientation exif tag
|
32 |
for orientation in ExifTags.TAGS.keys():
|
|
|
167 |
ret_val, img0 = self.cap.read()
|
168 |
|
169 |
self.frame += 1
|
170 |
+
print('video %g/%g (%g/%g) %s: ' % (self.count + 1, self.nf, self.frame, self.nframes, path), end='')
|
171 |
|
172 |
else:
|
173 |
# Read image
|
174 |
self.count += 1
|
175 |
img0 = cv2.imread(path) # BGR
|
176 |
assert img0 is not None, 'Image Not Found ' + path
|
177 |
+
print('image %g/%g %s: ' % (self.count, self.nf, path), end='')
|
178 |
|
179 |
# Padded resize
|
180 |
img = letterbox(img0, new_shape=self.img_size)[0]
|
|
|
236 |
# Print
|
237 |
assert ret_val, 'Camera Error %s' % self.pipe
|
238 |
img_path = 'webcam.jpg'
|
239 |
+
print('webcam %g: ' % self.count, end='')
|
240 |
|
241 |
# Padded resize
|
242 |
img = letterbox(img0, new_shape=self.img_size)[0]
|
|
|
267 |
self.sources = sources
|
268 |
for i, s in enumerate(sources):
|
269 |
# Start the thread to read frames from the video stream
|
270 |
+
print('%g/%g: %s... ' % (i + 1, n, s), end='')
|
271 |
cap = cv2.VideoCapture(eval(s) if s.isnumeric() else s)
|
272 |
assert cap.isOpened(), 'Failed to open %s' % s
|
273 |
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
|
275 |
fps = cap.get(cv2.CAP_PROP_FPS) % 100
|
276 |
_, self.imgs[i] = cap.read() # guarantee first frame
|
277 |
thread = Thread(target=self.update, args=([i, cap]), daemon=True)
|
278 |
+
print(' success (%gx%g at %.2f FPS).' % (w, h, fps))
|
279 |
thread.start()
|
280 |
+
print('') # newline
|
281 |
|
282 |
# check for common shapes
|
283 |
s = np.stack([letterbox(x, new_shape=self.img_size)[0].shape for x in self.imgs], 0) # inference shapes
|
284 |
self.rect = np.unique(s, axis=0).shape[0] == 1 # rect inference if all shapes equal
|
285 |
if not self.rect:
|
286 |
+
print('WARNING: Different stream shapes detected. For optimal performance supply similarly-shaped streams.')
|
287 |
|
288 |
def update(self, index, cap):
|
289 |
# Read next stream frame in a daemon thread
|
|
|
324 |
return 0 # 1E12 frames = 32 streams at 30 FPS for 30 years
|
325 |
|
326 |
|
327 |
+
def img2label_paths(img_paths):
|
328 |
+
# Define label paths as a function of image paths
|
329 |
+
sa, sb = os.sep + 'images' + os.sep, os.sep + 'labels' + os.sep # /images/, /labels/ substrings
|
330 |
+
return [x.replace(sa, sb, 1).replace('.' + x.split('.')[-1], '.txt') for x in img_paths]
|
331 |
+
|
332 |
+
|
333 |
class LoadImagesAndLabels(Dataset): # for training/testing
|
334 |
def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,
|
335 |
cache_images=False, single_cls=False, stride=32, pad=0.0, rank=-1):
|
|
|
342 |
self.mosaic_border = [-img_size // 2, -img_size // 2]
|
343 |
self.stride = stride
|
344 |
|
|
|
|
|
|
|
|
|
|
|
345 |
try:
|
346 |
f = [] # image files
|
347 |
for p in path if isinstance(path, list) else [path]:
|
|
|
362 |
|
363 |
# Check cache
|
364 |
self.label_files = img2label_paths(self.img_files) # labels
|
365 |
+
cache_path = Path(self.label_files[0]).parent.with_suffix('.cache') # cached labels
|
366 |
+
if cache_path.is_file():
|
367 |
cache = torch.load(cache_path) # load
|
368 |
if cache['hash'] != get_hash(self.label_files + self.img_files): # dataset changed
|
369 |
cache = self.cache_labels(cache_path) # re-cache
|
370 |
else:
|
371 |
cache = self.cache_labels(cache_path) # cache
|
372 |
|
373 |
+
# Display cache
|
374 |
+
[nf, nm, ne, nc, n] = cache.pop('results') # found, missing, empty, corrupted, total
|
375 |
+
desc = f"Scanning '{cache_path}' for images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted"
|
376 |
+
tqdm(None, desc=desc, total=n, initial=n)
|
377 |
+
assert nf > 0 or not augment, f'No labels found in {cache_path}. Can not train without labels. See {help_url}'
|
378 |
+
|
379 |
# Read cache
|
380 |
cache.pop('hash') # remove hash
|
381 |
labels, shapes = zip(*cache.values())
|
|
|
383 |
self.shapes = np.array(shapes, dtype=np.float64)
|
384 |
self.img_files = list(cache.keys()) # update
|
385 |
self.label_files = img2label_paths(cache.keys()) # update
|
386 |
+
if single_cls:
|
387 |
+
for x in self.labels:
|
388 |
+
x[:, 0] = 0
|
389 |
|
390 |
n = len(shapes) # number of images
|
391 |
bi = np.floor(np.arange(n) / batch_size).astype(np.int) # batch index
|
|
|
417 |
|
418 |
self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(np.int) * stride
|
419 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
420 |
# Cache images into memory for faster training (WARNING: large datasets may exceed system RAM)
|
421 |
self.imgs = [None] * n
|
422 |
if cache_images:
|
|
|
429 |
gb += self.imgs[i].nbytes
|
430 |
pbar.desc = 'Caching images (%.1fGB)' % (gb / 1E9)
|
431 |
|
432 |
+
def cache_labels(self, path=Path('./labels.cache')):
|
433 |
# Cache dataset labels, check images and read shapes
|
434 |
x = {} # dict
|
435 |
+
nm, nf, ne, nc = 0, 0, 0, 0 # number missing, found, empty, duplicate
|
436 |
pbar = tqdm(zip(self.img_files, self.label_files), desc='Scanning images', total=len(self.img_files))
|
437 |
+
for i, (im_file, lb_file) in enumerate(pbar):
|
438 |
try:
|
439 |
+
# verify images
|
440 |
+
im = Image.open(im_file)
|
441 |
im.verify() # PIL verify
|
442 |
shape = exif_size(im) # image size
|
443 |
assert (shape[0] > 9) & (shape[1] > 9), 'image size <10 pixels'
|
444 |
+
|
445 |
+
# verify labels
|
446 |
+
l = []
|
447 |
+
if os.path.isfile(lb_file):
|
448 |
+
nf += 1 # label found
|
449 |
+
with open(lb_file, 'r') as f:
|
450 |
l = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) # labels
|
451 |
+
if len(l):
|
452 |
+
assert l.shape[1] == 5, 'labels require 5 columns each'
|
453 |
+
assert (l >= 0).all(), 'negative labels'
|
454 |
+
assert (l[:, 1:] <= 1).all(), 'non-normalized or out of bounds coordinate labels'
|
455 |
+
assert np.unique(l, axis=0).shape[0] == l.shape[0], 'duplicate labels'
|
456 |
+
else:
|
457 |
+
ne += 1 # label empty
|
458 |
+
l = np.zeros((0, 5), dtype=np.float32)
|
459 |
+
else:
|
460 |
+
nm += 1 # label missing
|
461 |
+
x[im_file] = [l, shape]
|
462 |
except Exception as e:
|
463 |
+
nc += 1
|
464 |
+
print('WARNING: Ignoring corrupted image and/or label %s: %s' % (im_file, e))
|
465 |
+
|
466 |
+
pbar.desc = f"Scanning '{path.parent / path.stem}' for images and labels... " \
|
467 |
+
f"{nf} found, {nm} missing, {ne} empty, {nc} corrupted"
|
468 |
+
|
469 |
+
if nf == 0:
|
470 |
+
print(f'WARNING: No labels found in {path}. See {help_url}')
|
471 |
|
472 |
x['hash'] = get_hash(self.label_files + self.img_files)
|
473 |
+
x['results'] = [nf, nm, ne, nc, i]
|
474 |
torch.save(x, path) # save for next time
|
475 |
+
logging.info(f"New cache created: '{path}'")
|
476 |
return x
|
477 |
|
478 |
def __len__(self):
|
|
|
480 |
|
481 |
# def __iter__(self):
|
482 |
# self.count = -1
|
483 |
+
# print('ran dataset iter')
|
484 |
# #self.shuffled_vector = np.random.permutation(self.nF) if self.augment else np.arange(self.nF)
|
485 |
# return self
|
486 |
|
|
|
877 |
shutil.copyfile(file, new_path / Path(file).name)
|
878 |
|
879 |
|
880 |
+
def extract_boxes(path='../coco128/'): # from utils.datasets import *; extract_boxes('../coco128')
|
881 |
+
# Convert detection dataset into classification dataset, with one directory per class
|
882 |
+
|
883 |
+
path = Path(path) # images dir
|
884 |
+
shutil.rmtree(path / 'classifier') if (path / 'classifier').is_dir() else None # remove existing
|
885 |
+
files = list(path.rglob('*.*'))
|
886 |
+
n = len(files) # number of files
|
887 |
+
for im_file in tqdm(files, total=n):
|
888 |
+
if im_file.suffix[1:] in img_formats:
|
889 |
+
# image
|
890 |
+
im = cv2.imread(str(im_file))[..., ::-1] # BGR to RGB
|
891 |
+
h, w = im.shape[:2]
|
892 |
+
|
893 |
+
# labels
|
894 |
+
lb_file = Path(img2label_paths([str(im_file)])[0])
|
895 |
+
if Path(lb_file).exists():
|
896 |
+
with open(lb_file, 'r') as f:
|
897 |
+
lb = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32) # labels
|
898 |
+
|
899 |
+
for j, x in enumerate(lb):
|
900 |
+
c = int(x[0]) # class
|
901 |
+
f = (path / 'classifier') / f'{c}' / f'{path.stem}_{im_file.stem}_{j}.jpg' # new filename
|
902 |
+
if not f.parent.is_dir():
|
903 |
+
f.parent.mkdir(parents=True)
|
904 |
+
|
905 |
+
b = x[1:] * [w, h, w, h] # box
|
906 |
+
# b[2:] = b[2:].max() # rectangle to square
|
907 |
+
b[2:] = b[2:] * 1.2 + 3 # pad
|
908 |
+
b = xywh2xyxy(b.reshape(-1, 4)).ravel().astype(np.int)
|
909 |
+
|
910 |
+
b[[0, 2]] = np.clip(b[[0, 2]], 0, w) # clip boxes outside of image
|
911 |
+
b[[1, 3]] = np.clip(b[[1, 3]], 0, h)
|
912 |
+
assert cv2.imwrite(str(f), im[b[1]:b[3], b[0]:b[2]]), f'box failure in {f}'
|
913 |
+
|
914 |
+
|
915 |
def autosplit(path='../coco128', weights=(0.9, 0.1, 0.0)): # from utils.datasets import *; autosplit('../coco128')
|
916 |
""" Autosplit a dataset into train/val/test splits and save path/autosplit_*.txt files
|
917 |
# Arguments
|