sczhou commited on
Commit
55ce06e
·
1 Parent(s): 50cfdd0

some modifications on detection and fusion.

Browse files
README.md CHANGED
@@ -20,6 +20,7 @@ S-Lab, Nanyang Technological University
20
 
21
  ### Updates
22
 
 
23
  - **2022.08.07**: Integrate Real-ESRGAN to support background image enhancement.
24
  - **2022.07.29**: New face detector with supporting `['YOLOv5', 'RetinaFace']`.
25
  - **2022.07.17**: The Colab demo of CodeFormer is available now. <a href="https://colab.research.google.com/drive/1m52PNveE4PBhYrecj34cnpEeiHcC5LTb?usp=sharing"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="google colab logo"></a>
@@ -110,7 +111,7 @@ If our work is useful for your research, please consider citing:
110
 
111
  ### Acknowledgement
112
 
113
- This project is based on [BasicSR](https://github.com/XPixelGroup/BasicSR). We also borrow some codes from [Unleashing Transformers](https://github.com/samb-t/unleashing-transformers), [YOLOv5-face](https://github.com/deepcam-cn/yolov5-face), and [FaceXLib](https://github.com/xinntao/facexlib).
114
 
115
  ### Contact
116
  If you have any question, please feel free to reach me out at `[email protected]`.
 
20
 
21
  ### Updates
22
 
23
+ - **2022.08.23**: Some modifications on face detection and fusion for better AI-created face enhancement.
24
  - **2022.08.07**: Integrate Real-ESRGAN to support background image enhancement.
25
  - **2022.07.29**: New face detector with supporting `['YOLOv5', 'RetinaFace']`.
26
  - **2022.07.17**: The Colab demo of CodeFormer is available now. <a href="https://colab.research.google.com/drive/1m52PNveE4PBhYrecj34cnpEeiHcC5LTb?usp=sharing"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="google colab logo"></a>
 
111
 
112
  ### Acknowledgement
113
 
114
+ This project is based on [BasicSR](https://github.com/XPixelGroup/BasicSR). We also borrow some codes from [Unleashing Transformers](https://github.com/samb-t/unleashing-transformers), [YOLOv5-face](https://github.com/deepcam-cn/yolov5-face), and [FaceXLib](https://github.com/xinntao/facexlib). Thanks for their awesome works.
115
 
116
  ### Contact
117
  If you have any question, please feel free to reach me out at `[email protected]`.
facelib/utils/face_restoration_helper.py CHANGED
@@ -321,6 +321,60 @@ class FaceRestoreHelper(object):
321
  inverse_affine[:, 2] += extra_offset
322
  inv_restored = cv2.warpAffine(restored_face, inverse_affine, (w_up, h_up))
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  if self.use_parse:
325
  # inference
326
  face_input = cv2.resize(restored_face, (512, 512), interpolation=cv2.INTER_LINEAR)
@@ -331,52 +385,27 @@ class FaceRestoreHelper(object):
331
  out = self.face_parse(face_input)[0]
332
  out = out.argmax(dim=1).squeeze().cpu().numpy()
333
 
334
- mask = np.zeros(out.shape)
335
  MASK_COLORMAP = [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 0, 0, 0]
336
  for idx, color in enumerate(MASK_COLORMAP):
337
- mask[out == idx] = color
338
  # blur the mask
339
- mask = cv2.GaussianBlur(mask, (101, 101), 11)
340
- mask = cv2.GaussianBlur(mask, (101, 101), 11)
341
  # remove the black borders
342
  thres = 10
343
- mask[:thres, :] = 0
344
- mask[-thres:, :] = 0
345
- mask[:, :thres] = 0
346
- mask[:, -thres:] = 0
347
- mask = mask / 255.
348
-
349
- mask = cv2.resize(mask, restored_face.shape[:2])
350
- mask = cv2.warpAffine(mask, inverse_affine, (w_up, h_up), flags=3)
351
- inv_soft_mask = mask[:, :, None]
352
- pasted_face = inv_restored
353
-
354
- if draw_box or not self.use_parse: # use square parse maps
355
- mask = np.ones(self.face_size, dtype=np.float32)
356
- inv_mask = cv2.warpAffine(mask, inverse_affine, (w_up, h_up))
357
- # remove the black borders
358
- inv_mask_erosion = cv2.erode(
359
- inv_mask, np.ones((int(2 * self.upscale_factor), int(2 * self.upscale_factor)), np.uint8))
360
- pasted_face = inv_mask_erosion[:, :, None] * inv_restored
361
- total_face_area = np.sum(inv_mask_erosion) # // 3
362
- # add border
363
- if draw_box:
364
- h, w = self.face_size
365
- mask_border = np.ones((h, w, 3), dtype=np.float32)
366
- border = int(1400/np.sqrt(total_face_area))
367
- mask_border[border:h-border, border:w-border,:] = 0
368
- inv_mask_border = cv2.warpAffine(mask_border, inverse_affine, (w_up, h_up))
369
- inv_mask_borders.append(inv_mask_border)
370
- if not self.use_parse:
371
- # compute the fusion edge based on the area of face
372
- w_edge = int(total_face_area**0.5) // 20
373
- erosion_radius = w_edge * 2
374
- inv_mask_center = cv2.erode(inv_mask_erosion, np.ones((erosion_radius, erosion_radius), np.uint8))
375
- blur_size = w_edge * 2
376
- inv_soft_mask = cv2.GaussianBlur(inv_mask_center, (blur_size + 1, blur_size + 1), 0)
377
- if len(upsample_img.shape) == 2: # upsample_img is gray image
378
- upsample_img = upsample_img[:, :, None]
379
- inv_soft_mask = inv_soft_mask[:, :, None]
380
 
381
  if len(upsample_img.shape) == 3 and upsample_img.shape[2] == 4: # alpha channel
382
  alpha = upsample_img[:, :, 3:]
@@ -390,7 +419,7 @@ class FaceRestoreHelper(object):
390
  else:
391
  upsample_img = upsample_img.astype(np.uint8)
392
 
393
- # add border
394
  if draw_box:
395
  # upsample_input_img = cv2.resize(input_img, (w_up, h_up))
396
  img_color = np.ones([*upsample_img.shape], dtype=np.float32)
@@ -414,4 +443,4 @@ class FaceRestoreHelper(object):
414
  self.cropped_faces = []
415
  self.inverse_affine_matrices = []
416
  self.det_faces = []
417
- self.pad_input_imgs = []
 
321
  inverse_affine[:, 2] += extra_offset
322
  inv_restored = cv2.warpAffine(restored_face, inverse_affine, (w_up, h_up))
323
 
324
+ # if draw_box or not self.use_parse: # use square parse maps
325
+ # mask = np.ones(self.face_size, dtype=np.float32)
326
+ # inv_mask = cv2.warpAffine(mask, inverse_affine, (w_up, h_up))
327
+ # # remove the black borders
328
+ # inv_mask_erosion = cv2.erode(
329
+ # inv_mask, np.ones((int(2 * self.upscale_factor), int(2 * self.upscale_factor)), np.uint8))
330
+ # pasted_face = inv_mask_erosion[:, :, None] * inv_restored
331
+ # total_face_area = np.sum(inv_mask_erosion) # // 3
332
+ # # add border
333
+ # if draw_box:
334
+ # h, w = self.face_size
335
+ # mask_border = np.ones((h, w, 3), dtype=np.float32)
336
+ # border = int(1400/np.sqrt(total_face_area))
337
+ # mask_border[border:h-border, border:w-border,:] = 0
338
+ # inv_mask_border = cv2.warpAffine(mask_border, inverse_affine, (w_up, h_up))
339
+ # inv_mask_borders.append(inv_mask_border)
340
+ # if not self.use_parse:
341
+ # # compute the fusion edge based on the area of face
342
+ # w_edge = int(total_face_area**0.5) // 20
343
+ # erosion_radius = w_edge * 2
344
+ # inv_mask_center = cv2.erode(inv_mask_erosion, np.ones((erosion_radius, erosion_radius), np.uint8))
345
+ # blur_size = w_edge * 2
346
+ # inv_soft_mask = cv2.GaussianBlur(inv_mask_center, (blur_size + 1, blur_size + 1), 0)
347
+ # if len(upsample_img.shape) == 2: # upsample_img is gray image
348
+ # upsample_img = upsample_img[:, :, None]
349
+ # inv_soft_mask = inv_soft_mask[:, :, None]
350
+
351
+ # always use square mask
352
+ mask = np.ones(self.face_size, dtype=np.float32)
353
+ inv_mask = cv2.warpAffine(mask, inverse_affine, (w_up, h_up))
354
+ # remove the black borders
355
+ inv_mask_erosion = cv2.erode(
356
+ inv_mask, np.ones((int(2 * self.upscale_factor), int(2 * self.upscale_factor)), np.uint8))
357
+ pasted_face = inv_mask_erosion[:, :, None] * inv_restored
358
+ total_face_area = np.sum(inv_mask_erosion) # // 3
359
+ # add border
360
+ if draw_box:
361
+ h, w = self.face_size
362
+ mask_border = np.ones((h, w, 3), dtype=np.float32)
363
+ border = int(1400/np.sqrt(total_face_area))
364
+ mask_border[border:h-border, border:w-border,:] = 0
365
+ inv_mask_border = cv2.warpAffine(mask_border, inverse_affine, (w_up, h_up))
366
+ inv_mask_borders.append(inv_mask_border)
367
+ # compute the fusion edge based on the area of face
368
+ w_edge = int(total_face_area**0.5) // 20
369
+ erosion_radius = w_edge * 2
370
+ inv_mask_center = cv2.erode(inv_mask_erosion, np.ones((erosion_radius, erosion_radius), np.uint8))
371
+ blur_size = w_edge * 2
372
+ inv_soft_mask = cv2.GaussianBlur(inv_mask_center, (blur_size + 1, blur_size + 1), 0)
373
+ if len(upsample_img.shape) == 2: # upsample_img is gray image
374
+ upsample_img = upsample_img[:, :, None]
375
+ inv_soft_mask = inv_soft_mask[:, :, None]
376
+
377
+ # parse mask
378
  if self.use_parse:
379
  # inference
380
  face_input = cv2.resize(restored_face, (512, 512), interpolation=cv2.INTER_LINEAR)
 
385
  out = self.face_parse(face_input)[0]
386
  out = out.argmax(dim=1).squeeze().cpu().numpy()
387
 
388
+ parse_mask = np.zeros(out.shape)
389
  MASK_COLORMAP = [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 0, 0, 0]
390
  for idx, color in enumerate(MASK_COLORMAP):
391
+ parse_mask[out == idx] = color
392
  # blur the mask
393
+ parse_mask = cv2.GaussianBlur(parse_mask, (101, 101), 11)
394
+ parse_mask = cv2.GaussianBlur(parse_mask, (101, 101), 11)
395
  # remove the black borders
396
  thres = 10
397
+ parse_mask[:thres, :] = 0
398
+ parse_mask[-thres:, :] = 0
399
+ parse_mask[:, :thres] = 0
400
+ parse_mask[:, -thres:] = 0
401
+ parse_mask = parse_mask / 255.
402
+
403
+ parse_mask = cv2.resize(parse_mask, restored_face.shape[:2])
404
+ parse_mask = cv2.warpAffine(parse_mask, inverse_affine, (w_up, h_up), flags=3)
405
+ inv_soft_parse_mask = parse_mask[:, :, None]
406
+ # pasted_face = inv_restored
407
+ fuse_mask = (inv_soft_parse_mask<inv_soft_mask).astype('int')
408
+ inv_soft_mask = inv_soft_parse_mask*fuse_mask + inv_soft_mask*(1-fuse_mask)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
  if len(upsample_img.shape) == 3 and upsample_img.shape[2] == 4: # alpha channel
411
  alpha = upsample_img[:, :, 3:]
 
419
  else:
420
  upsample_img = upsample_img.astype(np.uint8)
421
 
422
+ # draw bounding box
423
  if draw_box:
424
  # upsample_input_img = cv2.resize(input_img, (w_up, h_up))
425
  img_color = np.ones([*upsample_img.shape], dtype=np.float32)
 
443
  self.cropped_faces = []
444
  self.inverse_affine_matrices = []
445
  self.det_faces = []
446
+ self.pad_input_imgs = []
inference_codeformer.py CHANGED
@@ -25,6 +25,9 @@ if __name__ == '__main__':
25
  parser.add_argument('--test_path', type=str, default='./inputs/cropped_faces')
26
  parser.add_argument('--has_aligned', action='store_true', help='Input are cropped and aligned faces')
27
  parser.add_argument('--only_center_face', action='store_true', help='Only restore the center face')
 
 
 
28
  parser.add_argument('--draw_box', action='store_true')
29
  parser.add_argument('--bg_upsampler', type=str, default='None', help='background upsampler. Optional: realesrgan')
30
  parser.add_argument('--bg_tile', type=int, default=400, help='Tile size for background sampler. Default: 400')
@@ -55,7 +58,7 @@ if __name__ == '__main__':
55
  model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth',
56
  model=model,
57
  tile=args.bg_tile,
58
- tile_pad=10,
59
  pre_pad=0,
60
  half=True) # need to set False in CPU mode
61
  else:
@@ -75,11 +78,13 @@ if __name__ == '__main__':
75
  # ------------------ set up FaceRestoreHelper -------------------
76
  # large det_model: 'YOLOv5l', 'retinaface_resnet50'
77
  # small det_model: 'YOLOv5n', 'retinaface_mobile0.25'
 
 
78
  face_helper = FaceRestoreHelper(
79
  args.upscale,
80
  face_size=512,
81
  crop_ratio=(1, 1),
82
- det_model = 'YOLOv5l',
83
  save_ext='png',
84
  use_parse=True,
85
  device=device)
@@ -89,7 +94,7 @@ if __name__ == '__main__':
89
  for img_path in sorted(glob.glob(os.path.join(args.test_path, '*.[jp][pn]g'))):
90
  # clean all the intermediate results to process the next image
91
  face_helper.clean_all()
92
-
93
  img_name = os.path.basename(img_path)
94
  print(f'Processing: {img_name}')
95
  basename, ext = os.path.splitext(img_name)
 
25
  parser.add_argument('--test_path', type=str, default='./inputs/cropped_faces')
26
  parser.add_argument('--has_aligned', action='store_true', help='Input are cropped and aligned faces')
27
  parser.add_argument('--only_center_face', action='store_true', help='Only restore the center face')
28
+ # large det_model: 'YOLOv5l', 'retinaface_resnet50'
29
+ # small det_model: 'YOLOv5n', 'retinaface_mobile0.25'
30
+ parser.add_argument('--detection_model', type=str, default='retinaface_resnet50')
31
  parser.add_argument('--draw_box', action='store_true')
32
  parser.add_argument('--bg_upsampler', type=str, default='None', help='background upsampler. Optional: realesrgan')
33
  parser.add_argument('--bg_tile', type=int, default=400, help='Tile size for background sampler. Default: 400')
 
58
  model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth',
59
  model=model,
60
  tile=args.bg_tile,
61
+ tile_pad=40,
62
  pre_pad=0,
63
  half=True) # need to set False in CPU mode
64
  else:
 
78
  # ------------------ set up FaceRestoreHelper -------------------
79
  # large det_model: 'YOLOv5l', 'retinaface_resnet50'
80
  # small det_model: 'YOLOv5n', 'retinaface_mobile0.25'
81
+ if not args.has_aligned:
82
+ print(f'Using [{args.detection_model}] for face detection network.')
83
  face_helper = FaceRestoreHelper(
84
  args.upscale,
85
  face_size=512,
86
  crop_ratio=(1, 1),
87
+ det_model = args.detection_model,
88
  save_ext='png',
89
  use_parse=True,
90
  device=device)
 
94
  for img_path in sorted(glob.glob(os.path.join(args.test_path, '*.[jp][pn]g'))):
95
  # clean all the intermediate results to process the next image
96
  face_helper.clean_all()
97
+
98
  img_name = os.path.basename(img_path)
99
  print(f'Processing: {img_name}')
100
  basename, ext = os.path.splitext(img_name)
inputs/whole_imgs/stable_diffusion_00.jpg ADDED
scripts/download_pretrained_models.py CHANGED
@@ -27,7 +27,8 @@ if __name__ == '__main__':
27
  'codeformer.pth': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth'
28
  },
29
  'facelib': {
30
- 'yolov5l-face.pth': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5l-face.pth',
 
31
  'parsing_parsenet.pth': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/parsing_parsenet.pth'
32
  }
33
  }
 
27
  'codeformer.pth': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/codeformer.pth'
28
  },
29
  'facelib': {
30
+ # 'yolov5l-face.pth': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/yolov5l-face.pth',
31
+ 'detection_Resnet50_Final.pth': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/detection_Resnet50_Final.pth',
32
  'parsing_parsenet.pth': 'https://github.com/sczhou/CodeFormer/releases/download/v0.1.0/parsing_parsenet.pth'
33
  }
34
  }