face_datasets.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. import glob
  2. import logging
  3. import math
  4. import os
  5. import random
  6. import shutil
  7. import time
  8. from itertools import repeat
  9. from multiprocessing.pool import ThreadPool
  10. from pathlib import Path
  11. from threading import Thread
  12. import cv2
  13. import numpy as np
  14. import torch
  15. from PIL import Image, ExifTags
  16. from torch.utils.data import Dataset
  17. from tqdm import tqdm
  18. from utils.general import xyxy2xywh, xywh2xyxy, clean_str
  19. from utils.torch_utils import torch_distributed_zero_first
  20. # Parameters
  21. help_url = 'https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data'
  22. img_formats = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng'] # acceptable image suffixes
  23. vid_formats = ['mov', 'avi', 'mp4', 'mpg', 'mpeg', 'm4v', 'wmv', 'mkv'] # acceptable video suffixes
  24. logger = logging.getLogger(__name__)
  25. # Get orientation exif tag
  26. for orientation in ExifTags.TAGS.keys():
  27. if ExifTags.TAGS[orientation] == 'Orientation':
  28. break
  29. def get_hash(files):
  30. # Returns a single hash value of a list of files
  31. return sum(os.path.getsize(f) for f in files if os.path.isfile(f))
  32. def img2label_paths(img_paths):
  33. # Define label paths as a function of image paths
  34. sa, sb = os.sep + 'images' + os.sep, os.sep + 'labels' + os.sep # /images/, /labels/ substrings
  35. return [x.replace(sa, sb, 1).replace('.' + x.split('.')[-1], '.txt') for x in img_paths]
  36. # return [x.replace(".jpg",".txt") for x in img_paths]
  37. def exif_size(img):
  38. # Returns exif-corrected PIL size
  39. s = img.size # (width, height)
  40. try:
  41. rotation = dict(img._getexif().items())[orientation]
  42. if rotation == 6: # rotation 270
  43. s = (s[1], s[0])
  44. elif rotation == 8: # rotation 90
  45. s = (s[1], s[0])
  46. except:
  47. pass
  48. return s
  49. def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=False, cache=False, pad=0.0, rect=False,
  50. rank=-1, world_size=1, workers=8, image_weights=False, quad=False, prefix=''):
  51. # Make sure only the first process in DDP process the dataset first, and the following others can use the cache
  52. with torch_distributed_zero_first(rank):
  53. dataset = LoadFaceImagesAndLabels(path, imgsz, batch_size,
  54. augment=augment, # augment images
  55. hyp=hyp, # augmentation hyperparameters
  56. rect=rect, # rectangular training
  57. cache_images=cache,
  58. single_cls=opt.single_cls,
  59. stride=int(stride),
  60. pad=pad,
  61. image_weights=image_weights,
  62. )
  63. batch_size = min(batch_size, len(dataset))
  64. nw = min([os.cpu_count() // world_size, batch_size if batch_size > 1 else 0, workers]) # number of workers
  65. sampler = torch.utils.data.distributed.DistributedSampler(dataset) if rank != -1 else None
  66. loader = torch.utils.data.DataLoader if image_weights else InfiniteDataLoader
  67. # Use torch.utils.data.DataLoader() if dataset.properties will update during training else InfiniteDataLoader()
  68. dataloader = loader(dataset,
  69. batch_size=batch_size,
  70. num_workers=nw,
  71. sampler=sampler,
  72. pin_memory=True,
  73. collate_fn=LoadFaceImagesAndLabels.collate_fn4 if quad else LoadFaceImagesAndLabels.collate_fn)
  74. return dataloader, dataset
  75. class InfiniteDataLoader(torch.utils.data.dataloader.DataLoader):
  76. """ Dataloader that reuses workers
  77. Uses same syntax as vanilla DataLoader
  78. """
  79. def __init__(self, *args, **kwargs):
  80. super().__init__(*args, **kwargs)
  81. object.__setattr__(self, 'batch_sampler', _RepeatSampler(self.batch_sampler))
  82. self.iterator = super().__iter__()
  83. def __len__(self):
  84. return len(self.batch_sampler.sampler)
  85. def __iter__(self):
  86. for i in range(len(self)):
  87. yield next(self.iterator)
  88. class _RepeatSampler(object):
  89. """ Sampler that repeats forever
  90. Args:
  91. sampler (Sampler)
  92. """
  93. def __init__(self, sampler):
  94. self.sampler = sampler
  95. def __iter__(self):
  96. while True:
  97. yield from iter(self.sampler)
  98. class LoadFaceImagesAndLabels(Dataset): # for training/testing
  99. def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,
  100. cache_images=False, single_cls=False, stride=32, pad=0.0, rank=-1):
  101. self.img_size = img_size
  102. self.augment = augment
  103. self.hyp = hyp
  104. self.image_weights = image_weights
  105. self.rect = False if image_weights else rect
  106. self.mosaic = self.augment and not self.rect # load 4 images at a time into a mosaic (only during training)
  107. self.mosaic_border = [-img_size // 2, -img_size // 2]
  108. self.stride = stride
  109. try:
  110. f = [] # image files
  111. for p in path if isinstance(path, list) else [path]:
  112. p = Path(p) # os-agnostic
  113. if p.is_dir(): # dir
  114. f += glob.glob(str(p / '**' / '*.*'), recursive=True)
  115. elif p.is_file(): # file
  116. with open(p, 'r') as t:
  117. t = t.read().strip().splitlines()
  118. parent = str(p.parent) + os.sep
  119. f += [x.replace('./', parent) if x.startswith('./') else x for x in t] # local to global path
  120. else:
  121. raise Exception('%s does not exist' % p)
  122. self.img_files = sorted([x.replace('/', os.sep) for x in f if x.split('.')[-1].lower() in img_formats])
  123. assert self.img_files, 'No images found'
  124. except Exception as e:
  125. raise Exception('Error loading data from %s: %s\nSee %s' % (path, e, help_url))
  126. # Check cache
  127. self.label_files = img2label_paths(self.img_files) # labels
  128. cache_path = Path(self.label_files[0]).parent.with_suffix('.cache') # cached labels
  129. if cache_path.is_file():
  130. cache = torch.load(cache_path) # load
  131. if cache['hash'] != get_hash(self.label_files + self.img_files) or 'results' not in cache: # changed
  132. cache = self.cache_labels(cache_path) # re-cache
  133. else:
  134. cache = self.cache_labels(cache_path) # cache
  135. # Display cache
  136. [nf, nm, ne, nc, n] = cache.pop('results') # found, missing, empty, corrupted, total
  137. desc = f"Scanning '{cache_path}' for images and labels... {nf} found, {nm} missing, {ne} empty, {nc} corrupted"
  138. tqdm(None, desc=desc, total=n, initial=n)
  139. assert nf > 0 or not augment, f'No labels found in {cache_path}. Can not train without labels. See {help_url}'
  140. # Read cache
  141. cache.pop('hash') # remove hash
  142. labels, shapes = zip(*cache.values())
  143. self.labels = list(labels)
  144. self.shapes = np.array(shapes, dtype=np.float64)
  145. self.img_files = list(cache.keys()) # update
  146. self.label_files = img2label_paths(cache.keys()) # update
  147. if single_cls:
  148. for x in self.labels:
  149. x[:, 0] = 0
  150. n = len(shapes) # number of images
  151. bi = np.floor(np.arange(n) / batch_size).astype(np.int_) # batch index
  152. nb = bi[-1] + 1 # number of batches
  153. self.batch = bi # batch index of image
  154. self.n = n
  155. self.indices = range(n)
  156. # Rectangular Training
  157. if self.rect:
  158. # Sort by aspect ratio
  159. s = self.shapes # wh
  160. ar = s[:, 1] / s[:, 0] # aspect ratio
  161. irect = ar.argsort()
  162. self.img_files = [self.img_files[i] for i in irect]
  163. self.label_files = [self.label_files[i] for i in irect]
  164. self.labels = [self.labels[i] for i in irect]
  165. self.shapes = s[irect] # wh
  166. ar = ar[irect]
  167. # Set training image shapes
  168. shapes = [[1, 1]] * nb
  169. for i in range(nb):
  170. ari = ar[bi == i]
  171. mini, maxi = ari.min(), ari.max()
  172. if maxi < 1:
  173. shapes[i] = [maxi, 1]
  174. elif mini > 1:
  175. shapes[i] = [1, 1 / mini]
  176. self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(np.int_) * stride
  177. # Cache images into memory for faster training (WARNING: large datasets may exceed system RAM)
  178. self.imgs = [None] * n
  179. if cache_images:
  180. gb = 0 # Gigabytes of cached images
  181. self.img_hw0, self.img_hw = [None] * n, [None] * n
  182. results = ThreadPool(8).imap(lambda x: load_image(*x), zip(repeat(self), range(n))) # 8 threads
  183. pbar = tqdm(enumerate(results), total=n)
  184. for i, x in pbar:
  185. self.imgs[i], self.img_hw0[i], self.img_hw[i] = x # img, hw_original, hw_resized = load_image(self, i)
  186. gb += self.imgs[i].nbytes
  187. pbar.desc = 'Caching images (%.1fGB)' % (gb / 1E9)
  188. def cache_labels(self, path=Path('./labels.cache')):
  189. # Cache dataset labels, check images and read shapes
  190. x = {} # dict
  191. nm, nf, ne, nc = 0, 0, 0, 0 # number missing, found, empty, duplicate
  192. pbar = tqdm(zip(self.img_files, self.label_files), desc='Scanning images', total=len(self.img_files))
  193. for i, (im_file, lb_file) in enumerate(pbar):
  194. try:
  195. # verify images
  196. im = Image.open(im_file)
  197. im.verify() # PIL verify
  198. shape = exif_size(im) # image size
  199. assert (shape[0] > 9) & (shape[1] > 9), 'image size <10 pixels'
  200. # verify labels
  201. if os.path.isfile(lb_file):
  202. nf += 1 # label found
  203. with open(lb_file, 'r') as f:
  204. l = np.array([x.split() for x in f.read().strip().splitlines()], dtype=np.float32) # labels
  205. if len(l):
  206. assert l.shape[1] == 13, 'labels require 13 columns each'
  207. assert (l >= -1).all(), 'negative labels'
  208. assert (l[:, 1:] <= 1).all(), 'non-normalized or out of bounds coordinate labels'
  209. assert np.unique(l, axis=0).shape[0] == l.shape[0], 'duplicate labels'
  210. else:
  211. ne += 1 # label empty
  212. l = np.zeros((0, 13), dtype=np.float32)
  213. else:
  214. nm += 1 # label missing
  215. l = np.zeros((0, 13), dtype=np.float32)
  216. x[im_file] = [l, shape]
  217. except Exception as e:
  218. nc += 1
  219. print('WARNING: Ignoring corrupted image and/or label %s: %s' % (im_file, e))
  220. pbar.desc = f"Scanning '{path.parent / path.stem}' for images and labels... " \
  221. f"{nf} found, {nm} missing, {ne} empty, {nc} corrupted"
  222. if nf == 0:
  223. print(f'WARNING: No labels found in {path}. See {help_url}')
  224. x['hash'] = get_hash(self.label_files + self.img_files)
  225. x['results'] = [nf, nm, ne, nc, i + 1]
  226. torch.save(x, path) # save for next time
  227. logging.info(f"New cache created: {path}")
  228. return x
  229. def __len__(self):
  230. return len(self.img_files)
  231. # def __iter__(self):
  232. # self.count = -1
  233. # print('ran dataset iter')
  234. # #self.shuffled_vector = np.random.permutation(self.nF) if self.augment else np.arange(self.nF)
  235. # return self
  236. def __getitem__(self, index):
  237. index = self.indices[index] # linear, shuffled, or image_weights
  238. hyp = self.hyp
  239. mosaic = self.mosaic and random.random() < hyp['mosaic']
  240. if mosaic:
  241. # Load mosaic
  242. img, labels = load_mosaic_face(self, index)
  243. shapes = None
  244. # MixUp https://arxiv.org/pdf/1710.09412.pdf
  245. if random.random() < hyp['mixup']:
  246. img2, labels2 = load_mosaic_face(self, random.randint(0, self.n - 1))
  247. r = np.random.beta(8.0, 8.0) # mixup ratio, alpha=beta=8.0
  248. img = (img * r + img2 * (1 - r)).astype(np.uint8)
  249. labels = np.concatenate((labels, labels2), 0)
  250. else:
  251. # Load image
  252. img, (h0, w0), (h, w) = load_image(self, index)
  253. # Letterbox
  254. shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size # final letterboxed shape
  255. img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment)
  256. shapes = (h0, w0), ((h / h0, w / w0), pad) # for COCO mAP rescaling
  257. # Load labels
  258. labels = []
  259. x = self.labels[index]
  260. if x.size > 0:
  261. # Normalized xywh to pixel xyxy format
  262. labels = x.copy()
  263. labels[:, 1] = ratio[0] * w * (x[:, 1] - x[:, 3] / 2) + pad[0] # pad width
  264. labels[:, 2] = ratio[1] * h * (x[:, 2] - x[:, 4] / 2) + pad[1] # pad height
  265. labels[:, 3] = ratio[0] * w * (x[:, 1] + x[:, 3] / 2) + pad[0]
  266. labels[:, 4] = ratio[1] * h * (x[:, 2] + x[:, 4] / 2) + pad[1]
  267. #labels[:, 5] = ratio[0] * w * x[:, 5] + pad[0] # pad width
  268. labels[:, 5] = np.array(x[:, 5] > 0, dtype=np.int32) * (ratio[0] * w * x[:, 5] + pad[0]) + (
  269. np.array(x[:, 5] > 0, dtype=np.int32) - 1)
  270. labels[:, 6] = np.array(x[:, 6] > 0, dtype=np.int32) * (ratio[1] * h * x[:, 6] + pad[1]) + (
  271. np.array(x[:, 6] > 0, dtype=np.int32) - 1)
  272. labels[:, 7] = np.array(x[:, 7] > 0, dtype=np.int32) * (ratio[0] * w * x[:, 7] + pad[0]) + (
  273. np.array(x[:, 7] > 0, dtype=np.int32) - 1)
  274. labels[:, 8] = np.array(x[:, 8] > 0, dtype=np.int32) * (ratio[1] * h * x[:, 8] + pad[1]) + (
  275. np.array(x[:, 8] > 0, dtype=np.int32) - 1)
  276. labels[:, 9] = np.array(x[:, 5] > 0, dtype=np.int32) * (ratio[0] * w * x[:, 9] + pad[0]) + (
  277. np.array(x[:, 9] > 0, dtype=np.int32) - 1)
  278. labels[:, 10] = np.array(x[:, 5] > 0, dtype=np.int32) * (ratio[1] * h * x[:, 10] + pad[1]) + (
  279. np.array(x[:, 10] > 0, dtype=np.int32) - 1)
  280. labels[:, 11] = np.array(x[:, 11] > 0, dtype=np.int32) * (ratio[0] * w * x[:, 11] + pad[0]) + (
  281. np.array(x[:, 11] > 0, dtype=np.int32) - 1)
  282. labels[:, 12] = np.array(x[:, 12] > 0, dtype=np.int32) * (ratio[1] * h * x[:, 12] + pad[1]) + (
  283. np.array(x[:, 12] > 0, dtype=np.int32) - 1)
  284. # labels[:, 13] = np.array(x[:, 13] > 0, dtype=np.int32) * (ratio[0] * w * x[:, 13] + pad[0]) + (
  285. # np.array(x[:, 13] > 0, dtype=np.int32) - 1)
  286. # labels[:, 14] = np.array(x[:, 14] > 0, dtype=np.int32) * (ratio[1] * h * x[:, 14] + pad[1]) + (
  287. # np.array(x[:, 14] > 0, dtype=np.int32) - 1)
  288. if self.augment:
  289. # Augment imagespace
  290. if not mosaic:
  291. img, labels = random_perspective(img, labels,
  292. degrees=hyp['degrees'],
  293. translate=hyp['translate'],
  294. scale=hyp['scale'],
  295. shear=hyp['shear'],
  296. perspective=hyp['perspective'])
  297. # Augment colorspace
  298. augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v'])
  299. # Apply cutouts
  300. # if random.random() < 0.9:
  301. # labels = cutout(img, labels)
  302. nL = len(labels) # number of labels
  303. if nL:
  304. labels[:, 1:5] = xyxy2xywh(labels[:, 1:5]) # convert xyxy to xywh
  305. labels[:, [2, 4]] /= img.shape[0] # normalized height 0-1
  306. labels[:, [1, 3]] /= img.shape[1] # normalized width 0-1
  307. labels[:, [5, 7, 9, 11]] /= img.shape[1] # normalized landmark x 0-1
  308. labels[:, [5, 7, 9, 11]] = np.where(labels[:, [5, 7, 9, 11]] < 0, -1, labels[:, [5, 7, 9, 11]])
  309. labels[:, [6, 8, 10, 12]] /= img.shape[0] # normalized landmark y 0-1
  310. labels[:, [6, 8, 10, 12]] = np.where(labels[:, [6, 8, 10, 12]] < 0, -1, labels[:, [6, 8, 10, 12]])
  311. if self.augment:
  312. # flip up-down
  313. if random.random() < hyp['flipud']:
  314. img = np.flipud(img)
  315. if nL:
  316. labels[:, 2] = 1 - labels[:, 2]
  317. labels[:, 6] = np.where(labels[:,6] < 0, -1, 1 - labels[:, 6])
  318. labels[:, 8] = np.where(labels[:, 8] < 0, -1, 1 - labels[:, 8])
  319. labels[:, 10] = np.where(labels[:, 10] < 0, -1, 1 - labels[:, 10])
  320. labels[:, 12] = np.where(labels[:, 12] < 0, -1, 1 - labels[:, 12])
  321. # labels[:, 14] = np.where(labels[:, 14] < 0, -1, 1 - labels[:, 14])
  322. # flip left-right
  323. if random.random() < hyp['fliplr']:
  324. img = np.fliplr(img)
  325. if nL:
  326. labels[:, 1] = 1 - labels[:, 1]
  327. labels[:, 5] = np.where(labels[:, 5] < 0, -1, 1 - labels[:, 5])
  328. labels[:, 7] = np.where(labels[:, 7] < 0, -1, 1 - labels[:, 7])
  329. labels[:, 9] = np.where(labels[:, 9] < 0, -1, 1 - labels[:, 9])
  330. labels[:, 11] = np.where(labels[:, 11] < 0, -1, 1 - labels[:, 11])
  331. # labels[:, 13] = np.where(labels[:, 13] < 0, -1, 1 - labels[:, 13])
  332. #左右镜像的时候,关键点应该交换位置,不然的话顺序就错了
  333. left_top = np.copy(labels[:, [5, 6]])
  334. left_bottom = np.copy(labels[:, [9, 10]])
  335. labels[:, [5, 6]] = labels[:, [7, 8]]
  336. labels[:, [7, 8]] = left_top
  337. labels[:, [9, 10]] = labels[:, [11, 12]]
  338. labels[:, [11, 12]] = left_bottom
  339. # eye_left = np.copy(labels[:, [5, 6]])
  340. # mouth_left = np.copy(labels[:, [11, 12]])
  341. # labels[:, [5, 6]] = labels[:, [7, 8]]
  342. # labels[:, [7, 8]] = eye_left
  343. # labels[:, [11, 12]] = labels[:, [13, 14]]
  344. # labels[:, [13, 14]] = mouth_left
  345. labels_out = torch.zeros((nL, 14))
  346. if nL:
  347. labels_out[:, 1:] = torch.from_numpy(labels)
  348. #showlabels(img, labels[:, 1:5], labels[:, 5:13])
  349. # Convert
  350. img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416
  351. img = np.ascontiguousarray(img)
  352. #print(index, ' --- labels_out: ', labels_out)
  353. #if nL:
  354. #print( ' : landmarks : ', torch.max(labels_out[:, 5:13]), ' --- ', torch.min(labels_out[:, 5:13]))
  355. return torch.from_numpy(img), labels_out, self.img_files[index], shapes
  356. @staticmethod
  357. def collate_fn(batch):
  358. img, label, path, shapes = zip(*batch) # transposed
  359. for i, l in enumerate(label):
  360. l[:, 0] = i # add target image index for build_targets()
  361. return torch.stack(img, 0), torch.cat(label, 0), path, shapes
  362. def showlabels(img, boxs, landmarks):
  363. for box in boxs:
  364. x,y,w,h = box[0] * img.shape[1], box[1] * img.shape[0], box[2] * img.shape[1], box[3] * img.shape[0]
  365. #cv2.rectangle(image, (x,y), (x+w,y+h), (0,255,0), 2)
  366. cv2.rectangle(img, (int(x - w/2), int(y - h/2)), (int(x + w/2), int(y + h/2)), (0, 255, 0), 2)
  367. for landmark in landmarks:
  368. #cv2.circle(img,(60,60),30,(0,0,255))
  369. for i in range(4):
  370. cv2.circle(img, (int(landmark[2*i] * img.shape[1]), int(landmark[2*i+1]*img.shape[0])), 3 ,(0,0,255), -1)
  371. cv2.imshow('test', img)
  372. cv2.waitKey(0)
  373. def load_mosaic_face(self, index):
  374. # loads images in a mosaic
  375. labels4 = []
  376. s = self.img_size
  377. yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y
  378. indices = [index] + [self.indices[random.randint(0, self.n - 1)] for _ in range(3)] # 3 additional image indices
  379. for i, index in enumerate(indices):
  380. # Load image
  381. img, _, (h, w) = load_image(self, index)
  382. # place img in img4
  383. if i == 0: # top left
  384. img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8) # base image with 4 tiles
  385. x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc # xmin, ymin, xmax, ymax (large image)
  386. x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h # xmin, ymin, xmax, ymax (small image)
  387. elif i == 1: # top right
  388. x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
  389. x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
  390. elif i == 2: # bottom left
  391. x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
  392. x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h)
  393. elif i == 3: # bottom right
  394. x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
  395. x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)
  396. img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax]
  397. padw = x1a - x1b
  398. padh = y1a - y1b
  399. # Labels
  400. x = self.labels[index]
  401. labels = x.copy()
  402. if x.size > 0: # Normalized xywh to pixel xyxy format
  403. #box, x1,y1,x2,y2
  404. labels[:, 1] = w * (x[:, 1] - x[:, 3] / 2) + padw
  405. labels[:, 2] = h * (x[:, 2] - x[:, 4] / 2) + padh
  406. labels[:, 3] = w * (x[:, 1] + x[:, 3] / 2) + padw
  407. labels[:, 4] = h * (x[:, 2] + x[:, 4] / 2) + padh
  408. #10 landmarks
  409. labels[:, 5] = np.array(x[:, 5] > 0, dtype=np.int32) * (w * x[:, 5] + padw) + (np.array(x[:, 5] > 0, dtype=np.int32) - 1)
  410. labels[:, 6] = np.array(x[:, 6] > 0, dtype=np.int32) * (h * x[:, 6] + padh) + (np.array(x[:, 6] > 0, dtype=np.int32) - 1)
  411. labels[:, 7] = np.array(x[:, 7] > 0, dtype=np.int32) * (w * x[:, 7] + padw) + (np.array(x[:, 7] > 0, dtype=np.int32) - 1)
  412. labels[:, 8] = np.array(x[:, 8] > 0, dtype=np.int32) * (h * x[:, 8] + padh) + (np.array(x[:, 8] > 0, dtype=np.int32) - 1)
  413. labels[:, 9] = np.array(x[:, 9] > 0, dtype=np.int32) * (w * x[:, 9] + padw) + (np.array(x[:, 9] > 0, dtype=np.int32) - 1)
  414. labels[:, 10] = np.array(x[:, 10] > 0, dtype=np.int32) * (h * x[:, 10] + padh) + (np.array(x[:, 10] > 0, dtype=np.int32) - 1)
  415. labels[:, 11] = np.array(x[:, 11] > 0, dtype=np.int32) * (w * x[:, 11] + padw) + (np.array(x[:, 11] > 0, dtype=np.int32) - 1)
  416. labels[:, 12] = np.array(x[:, 12] > 0, dtype=np.int32) * (h * x[:, 12] + padh) + (np.array(x[:, 12] > 0, dtype=np.int32) - 1)
  417. # labels[:, 13] = np.array(x[:, 13] > 0, dtype=np.int32) * (w * x[:, 13] + padw) + (np.array(x[:, 13] > 0, dtype=np.int32) - 1)
  418. # labels[:, 14] = np.array(x[:, 14] > 0, dtype=np.int32) * (h * x[:, 14] + padh) + (np.array(x[:, 14] > 0, dtype=np.int32) - 1)
  419. labels4.append(labels)
  420. # Concat/clip labels
  421. if len(labels4):
  422. labels4 = np.concatenate(labels4, 0)
  423. np.clip(labels4[:, 1:5], 0, 2 * s, out=labels4[:, 1:5]) # use with random_perspective
  424. # img4, labels4 = replicate(img4, labels4) # replicate
  425. #landmarks
  426. labels4[:, 5:] = np.where(labels4[:, 5:] < 0, -1, labels4[:, 5:])
  427. labels4[:, 5:] = np.where(labels4[:, 5:] > 2 * s, -1, labels4[:, 5:])
  428. labels4[:, 5] = np.where(labels4[:, 6] == -1, -1, labels4[:, 5])
  429. labels4[:, 6] = np.where(labels4[:, 5] == -1, -1, labels4[:, 6])
  430. labels4[:, 7] = np.where(labels4[:, 8] == -1, -1, labels4[:, 7])
  431. labels4[:, 8] = np.where(labels4[:, 7] == -1, -1, labels4[:, 8])
  432. labels4[:, 9] = np.where(labels4[:, 10] == -1, -1, labels4[:, 9])
  433. labels4[:, 10] = np.where(labels4[:, 9] == -1, -1, labels4[:, 10])
  434. labels4[:, 11] = np.where(labels4[:, 12] == -1, -1, labels4[:, 11])
  435. labels4[:, 12] = np.where(labels4[:, 11] == -1, -1, labels4[:, 12])
  436. # labels4[:, 13] = np.where(labels4[:, 14] == -1, -1, labels4[:, 13])
  437. # labels4[:, 14] = np.where(labels4[:, 13] == -1, -1, labels4[:, 14])
  438. # Augment
  439. img4, labels4 = random_perspective(img4, labels4,
  440. degrees=self.hyp['degrees'],
  441. translate=self.hyp['translate'],
  442. scale=self.hyp['scale'],
  443. shear=self.hyp['shear'],
  444. perspective=self.hyp['perspective'],
  445. border=self.mosaic_border) # border to remove
  446. return img4, labels4
  447. # Ancillary functions --------------------------------------------------------------------------------------------------
  448. def load_image(self, index):
  449. # loads 1 image from dataset, returns img, original hw, resized hw
  450. img = self.imgs[index]
  451. if img is None: # not cached
  452. path = self.img_files[index]
  453. img = cv2.imread(path) # BGR
  454. assert img is not None, 'Image Not Found ' + path
  455. h0, w0 = img.shape[:2] # orig hw
  456. r = self.img_size / max(h0, w0) # resize image to img_size
  457. if r != 1: # always resize down, only resize up if training with augmentation
  458. interp = cv2.INTER_AREA if r < 1 and not self.augment else cv2.INTER_LINEAR
  459. img = cv2.resize(img, (int(w0 * r), int(h0 * r)), interpolation=interp)
  460. return img, (h0, w0), img.shape[:2] # img, hw_original, hw_resized
  461. else:
  462. return self.imgs[index], self.img_hw0[index], self.img_hw[index] # img, hw_original, hw_resized
  463. def augment_hsv(img, hgain=0.5, sgain=0.5, vgain=0.5):
  464. r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 # random gains
  465. hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))
  466. dtype = img.dtype # uint8
  467. x = np.arange(0, 256, dtype=np.int16)
  468. lut_hue = ((x * r[0]) % 180).astype(dtype)
  469. lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
  470. lut_val = np.clip(x * r[2], 0, 255).astype(dtype)
  471. img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))).astype(dtype)
  472. cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR, dst=img) # no return needed
  473. # Histogram equalization
  474. # if random.random() < 0.2:
  475. # for i in range(3):
  476. # img[:, :, i] = cv2.equalizeHist(img[:, :, i])
  477. def replicate(img, labels):
  478. # Replicate labels
  479. h, w = img.shape[:2]
  480. boxes = labels[:, 1:].astype(int)
  481. x1, y1, x2, y2 = boxes.T
  482. s = ((x2 - x1) + (y2 - y1)) / 2 # side length (pixels)
  483. for i in s.argsort()[:round(s.size * 0.5)]: # smallest indices
  484. x1b, y1b, x2b, y2b = boxes[i]
  485. bh, bw = y2b - y1b, x2b - x1b
  486. yc, xc = int(random.uniform(0, h - bh)), int(random.uniform(0, w - bw)) # offset x, y
  487. x1a, y1a, x2a, y2a = [xc, yc, xc + bw, yc + bh]
  488. img[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax]
  489. labels = np.append(labels, [[labels[i, 0], x1a, y1a, x2a, y2a]], axis=0)
  490. return img, labels
  491. def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):
  492. # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232
  493. shape = img.shape[:2] # current shape [height, width]
  494. if isinstance(new_shape, int):
  495. new_shape = (new_shape, new_shape)
  496. # Scale ratio (new / old)
  497. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
  498. if not scaleup: # only scale down, do not scale up (for better test mAP)
  499. r = min(r, 1.0)
  500. # Compute padding
  501. ratio = r, r # width, height ratios
  502. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  503. dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
  504. if auto: # minimum rectangle
  505. dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh padding
  506. elif scaleFill: # stretch
  507. dw, dh = 0.0, 0.0
  508. new_unpad = (new_shape[1], new_shape[0])
  509. ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
  510. dw /= 2 # divide padding into 2 sides
  511. dh /= 2
  512. if shape[::-1] != new_unpad: # resize
  513. img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
  514. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
  515. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
  516. img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
  517. return img, ratio, (dw, dh)
  518. def random_perspective(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0, border=(0, 0)):
  519. # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10))
  520. # targets = [cls, xyxy]
  521. height = img.shape[0] + border[0] * 2 # shape(h,w,c)
  522. width = img.shape[1] + border[1] * 2
  523. # Center
  524. C = np.eye(3)
  525. C[0, 2] = -img.shape[1] / 2 # x translation (pixels)
  526. C[1, 2] = -img.shape[0] / 2 # y translation (pixels)
  527. # Perspective
  528. P = np.eye(3)
  529. P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y)
  530. P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x)
  531. # Rotation and Scale
  532. R = np.eye(3)
  533. a = random.uniform(-degrees, degrees)
  534. # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations
  535. s = random.uniform(1 - scale, 1 + scale)
  536. # s = 2 ** random.uniform(-scale, scale)
  537. R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s)
  538. # Shear
  539. S = np.eye(3)
  540. S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg)
  541. S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg)
  542. # Translation
  543. T = np.eye(3)
  544. T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width # x translation (pixels)
  545. T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height # y translation (pixels)
  546. # Combined rotation matrix
  547. M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT
  548. if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed
  549. if perspective:
  550. img = cv2.warpPerspective(img, M, dsize=(width, height), borderValue=(114, 114, 114))
  551. else: # affine
  552. img = cv2.warpAffine(img, M[:2], dsize=(width, height), borderValue=(114, 114, 114))
  553. # Visualize
  554. # import matplotlib.pyplot as plt
  555. # ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel()
  556. # ax[0].imshow(img[:, :, ::-1]) # base
  557. # ax[1].imshow(img2[:, :, ::-1]) # warped
  558. # Transform label coordinates
  559. n = len(targets)
  560. if n:
  561. # warp points
  562. #xy = np.ones((n * 4, 3))
  563. xy = np.ones((n * 8, 3))
  564. xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2, 5, 6, 7, 8, 9, 10, 11, 12]].reshape(n * 8, 2) # x1y1, x2y2, x1y2, x2y1
  565. xy = xy @ M.T # transform
  566. if perspective:
  567. xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 16) # rescale
  568. else: # affine
  569. xy = xy[:, :2].reshape(n, 16)
  570. # create new boxes
  571. x = xy[:, [0, 2, 4, 6]]
  572. y = xy[:, [1, 3, 5, 7]]
  573. landmarks = xy[:, [8, 9, 10, 11, 12, 13, 14,15]]
  574. mask = np.array(targets[:, 5:] > 0, dtype=np.int32)
  575. landmarks = landmarks * mask
  576. landmarks = landmarks + mask - 1
  577. landmarks = np.where(landmarks < 0, -1, landmarks)
  578. landmarks[:, [0, 2, 4, 6]] = np.where(landmarks[:, [0, 2, 4, 6]] > width, -1, landmarks[:, [0, 2, 4, 6]])
  579. landmarks[:, [1, 3, 5, 7]] = np.where(landmarks[:, [1, 3, 5, 7]] > height, -1,landmarks[:, [1, 3, 5, 7]])
  580. landmarks[:, 0] = np.where(landmarks[:, 1] == -1, -1, landmarks[:, 0])
  581. landmarks[:, 1] = np.where(landmarks[:, 0] == -1, -1, landmarks[:, 1])
  582. landmarks[:, 2] = np.where(landmarks[:, 3] == -1, -1, landmarks[:, 2])
  583. landmarks[:, 3] = np.where(landmarks[:, 2] == -1, -1, landmarks[:, 3])
  584. landmarks[:, 4] = np.where(landmarks[:, 5] == -1, -1, landmarks[:, 4])
  585. landmarks[:, 5] = np.where(landmarks[:, 4] == -1, -1, landmarks[:, 5])
  586. landmarks[:, 6] = np.where(landmarks[:, 7] == -1, -1, landmarks[:, 6])
  587. landmarks[:, 7] = np.where(landmarks[:, 6] == -1, -1, landmarks[:, 7])
  588. # landmarks[:, 8] = np.where(landmarks[:, 9] == -1, -1, landmarks[:, 8])
  589. # landmarks[:, 9] = np.where(landmarks[:, 8] == -1, -1, landmarks[:, 9])
  590. targets[:,5:] = landmarks
  591. xy = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T
  592. # # apply angle-based reduction of bounding boxes
  593. # radians = a * math.pi / 180
  594. # reduction = max(abs(math.sin(radians)), abs(math.cos(radians))) ** 0.5
  595. # x = (xy[:, 2] + xy[:, 0]) / 2
  596. # y = (xy[:, 3] + xy[:, 1]) / 2
  597. # w = (xy[:, 2] - xy[:, 0]) * reduction
  598. # h = (xy[:, 3] - xy[:, 1]) * reduction
  599. # xy = np.concatenate((x - w / 2, y - h / 2, x + w / 2, y + h / 2)).reshape(4, n).T
  600. # clip boxes
  601. xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width)
  602. xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height)
  603. # filter candidates
  604. i = box_candidates(box1=targets[:, 1:5].T * s, box2=xy.T)
  605. targets = targets[i]
  606. targets[:, 1:5] = xy[i]
  607. return img, targets
  608. def box_candidates(box1, box2, wh_thr=2, ar_thr=20, area_thr=0.1): # box1(4,n), box2(4,n)
  609. # Compute candidate boxes: box1 before augment, box2 after augment, wh_thr (pixels), aspect_ratio_thr, area_ratio
  610. w1, h1 = box1[2] - box1[0], box1[3] - box1[1]
  611. w2, h2 = box2[2] - box2[0], box2[3] - box2[1]
  612. ar = np.maximum(w2 / (h2 + 1e-16), h2 / (w2 + 1e-16)) # aspect ratio
  613. return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + 1e-16) > area_thr) & (ar < ar_thr) # candidates
  614. def cutout(image, labels):
  615. # Applies image cutout augmentation https://arxiv.org/abs/1708.04552
  616. h, w = image.shape[:2]
  617. def bbox_ioa(box1, box2):
  618. # Returns the intersection over box2 area given box1, box2. box1 is 4, box2 is nx4. boxes are x1y1x2y2
  619. box2 = box2.transpose()
  620. # Get the coordinates of bounding boxes
  621. b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
  622. b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
  623. # Intersection area
  624. inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * \
  625. (np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0)
  626. # box2 area
  627. box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + 1e-16
  628. # Intersection over box2 area
  629. return inter_area / box2_area
  630. # create random masks
  631. scales = [0.5] * 1 + [0.25] * 2 + [0.125] * 4 + [0.0625] * 8 + [0.03125] * 16 # image size fraction
  632. for s in scales:
  633. mask_h = random.randint(1, int(h * s))
  634. mask_w = random.randint(1, int(w * s))
  635. # box
  636. xmin = max(0, random.randint(0, w) - mask_w // 2)
  637. ymin = max(0, random.randint(0, h) - mask_h // 2)
  638. xmax = min(w, xmin + mask_w)
  639. ymax = min(h, ymin + mask_h)
  640. # apply random color mask
  641. image[ymin:ymax, xmin:xmax] = [random.randint(64, 191) for _ in range(3)]
  642. # return unobscured labels
  643. if len(labels) and s > 0.03:
  644. box = np.array([xmin, ymin, xmax, ymax], dtype=np.float32)
  645. ioa = bbox_ioa(box, labels[:, 1:5]) # intersection over area
  646. labels = labels[ioa < 0.60] # remove >60% obscured labels
  647. return labels
  648. def create_folder(path='./new'):
  649. # Create folder
  650. if os.path.exists(path):
  651. shutil.rmtree(path) # delete output folder
  652. os.makedirs(path) # make new output folder
  653. def flatten_recursive(path='../coco128'):
  654. # Flatten a recursive directory by bringing all files to top level
  655. new_path = Path(path + '_flat')
  656. create_folder(new_path)
  657. for file in tqdm(glob.glob(str(Path(path)) + '/**/*.*', recursive=True)):
  658. shutil.copyfile(file, new_path / Path(file).name)
  659. def extract_boxes(path='../coco128/'): # from utils.datasets import *; extract_boxes('../coco128')
  660. # Convert detection dataset into classification dataset, with one directory per class
  661. path = Path(path) # images dir
  662. shutil.rmtree(path / 'classifier') if (path / 'classifier').is_dir() else None # remove existing
  663. files = list(path.rglob('*.*'))
  664. n = len(files) # number of files
  665. for im_file in tqdm(files, total=n):
  666. if im_file.suffix[1:] in img_formats:
  667. # image
  668. im = cv2.imread(str(im_file))[..., ::-1] # BGR to RGB
  669. h, w = im.shape[:2]
  670. # labels
  671. lb_file = Path(img2label_paths([str(im_file)])[0])
  672. if Path(lb_file).exists():
  673. with open(lb_file, 'r') as f:
  674. lb = np.array([x.split() for x in f.read().strip().splitlines()], dtype=np.float32) # labels
  675. for j, x in enumerate(lb):
  676. c = int(x[0]) # class
  677. f = (path / 'classifier') / f'{c}' / f'{path.stem}_{im_file.stem}_{j}.jpg' # new filename
  678. if not f.parent.is_dir():
  679. f.parent.mkdir(parents=True)
  680. b = x[1:] * [w, h, w, h] # box
  681. # b[2:] = b[2:].max() # rectangle to square
  682. b[2:] = b[2:] * 1.2 + 3 # pad
  683. b = xywh2xyxy(b.reshape(-1, 4)).ravel().astype(np.int_)
  684. b[[0, 2]] = np.clip(b[[0, 2]], 0, w) # clip boxes outside of image
  685. b[[1, 3]] = np.clip(b[[1, 3]], 0, h)
  686. assert cv2.imwrite(str(f), im[b[1]:b[3], b[0]:b[2]]), f'box failure in {f}'
  687. def autosplit(path='../coco128', weights=(0.9, 0.1, 0.0)): # from utils.datasets import *; autosplit('../coco128')
  688. """ Autosplit a dataset into train/val/test splits and save path/autosplit_*.txt files
  689. # Arguments
  690. path: Path to images directory
  691. weights: Train, val, test weights (list)
  692. """
  693. path = Path(path) # images dir
  694. files = list(path.rglob('*.*'))
  695. n = len(files) # number of files
  696. indices = random.choices([0, 1, 2], weights=weights, k=n) # assign each image to a split
  697. txt = ['autosplit_train.txt', 'autosplit_val.txt', 'autosplit_test.txt'] # 3 txt files
  698. [(path / x).unlink() for x in txt if (path / x).exists()] # remove existing
  699. for i, img in tqdm(zip(indices, files), total=n):
  700. if img.suffix[1:] in img_formats:
  701. with open(path / txt[i], 'a') as f:
  702. f.write(str(img) + '\n') # add image to txt file