DFL-2.0 initial branch commit

This commit is contained in:
Colombo 2020-01-21 18:43:39 +04:00
parent 52a67a61b3
commit 38b85108b3
154 changed files with 5251 additions and 9414 deletions

View file

@ -12,22 +12,22 @@ import cv2
import numpy as np
import facelib
import imagelib
import mathlib
from facelib import FaceType, LandmarksProcessor
from interact import interact as io
from joblib import Subprocessor
from nnlib import TernausNet, nnlib
from utils import Path_utils
from utils.cv2_utils import *
from core import imagelib
from core import mathlib
from facelib import FaceType, LandmarksProcessor, TernausNet
from core.interact import interact as io
from core.joblib import Subprocessor
from core.leras import nn
from core import pathex
from core.cv2ex import *
from DFLIMG import *
DEBUG = False
class ExtractSubprocessor(Subprocessor):
class Data(object):
def __init__(self, filename=None, rects=None, landmarks = None, landmarks_accurate=True, manual=False, force_output_path=None, final_output_files = None):
self.filename = filename
def __init__(self, filepath=None, rects=None, landmarks = None, landmarks_accurate=True, manual=False, force_output_path=None, final_output_files = None):
self.filepath = filepath
self.rects = rects or []
self.rects_rotation = 0
self.landmarks_accurate = landmarks_accurate
@ -41,289 +41,295 @@ class ExtractSubprocessor(Subprocessor):
#override
def on_initialize(self, client_dict):
self.type = client_dict['type']
self.image_size = client_dict['image_size']
self.face_type = client_dict['face_type']
self.type = client_dict['type']
self.image_size = client_dict['image_size']
self.face_type = client_dict['face_type']
self.max_faces_from_image = client_dict['max_faces_from_image']
self.device_idx = client_dict['device_idx']
self.cpu_only = client_dict['device_type'] == 'CPU'
self.final_output_path = Path(client_dict['final_output_dir']) if 'final_output_dir' in client_dict.keys() else None
self.debug_dir = client_dict['debug_dir']
self.device_idx = client_dict['device_idx']
self.cpu_only = client_dict['device_type'] == 'CPU'
self.final_output_path = client_dict['final_output_path']
self.output_debug_path = client_dict['output_debug_path']
#transfer and set stdin in order to work code.interact in debug subprocess
stdin_fd = client_dict['stdin_fd']
if stdin_fd is not None and DEBUG:
sys.stdin = os.fdopen(stdin_fd)
self.log_info (f"Running on {client_dict['device_name'] }")
if self.cpu_only:
device_config = nn.DeviceConfig.CPU()
place_model_on_cpu = True
else:
device_config = nn.DeviceConfig.GPUIndexes ([self.device_idx])
place_model_on_cpu = device_config.devices[0].total_mem_gb < 4
if self.type == 'all' or 'rects' in self.type or 'landmarks' in self.type:
nn.initialize (device_config)
if self.type == 'all' or self.type == 'rects-s3fd' or 'landmarks' in self.type:
self.rects_extractor = facelib.S3FDExtractor(place_model_on_cpu=place_model_on_cpu)
if self.type == 'all' or 'landmarks' in self.type:
self.landmarks_extractor = facelib.FANExtractor(place_model_on_cpu=place_model_on_cpu)
self.cached_image = (None, None)
self.e = None
device_config = nnlib.DeviceConfig ( cpu_only=self.cpu_only, force_gpu_idx=self.device_idx, allow_growth=True)
self.device_vram = device_config.gpu_vram_gb[0]
intro_str = 'Running on %s.' % (client_dict['device_name'])
if not self.cpu_only and self.device_vram <= 2:
intro_str += " Recommended to close all programs using this device."
self.log_info (intro_str)
if 'rects' in self.type:
if self.type == 'rects-mt':
nnlib.import_all (device_config)
self.e = facelib.MTCExtractor()
elif self.type == 'rects-dlib':
nnlib.import_dlib (device_config)
self.e = facelib.DLIBExtractor(nnlib.dlib)
elif self.type == 'rects-s3fd':
nnlib.import_all (device_config)
self.e = facelib.S3FDExtractor(do_dummy_predict=True)
else:
raise ValueError ("Wrong type.")
if self.e is not None:
self.e.__enter__()
elif self.type == 'landmarks':
nnlib.import_all (device_config)
self.e = facelib.FANExtractor()
self.e.__enter__()
if self.device_vram >= 2:
self.second_pass_e = facelib.S3FDExtractor(do_dummy_predict=False)
self.second_pass_e.__enter__()
else:
self.second_pass_e = None
elif self.type == 'fanseg':
nnlib.import_all (device_config)
self.e = TernausNet(256, FaceType.toString(FaceType.FULL) )
self.e.__enter__()
elif self.type == 'final':
pass
#override
def on_finalize(self):
if self.e is not None:
self.e.__exit__()
#override
def process_data(self, data):
filename_path = Path( data.filename )
filename_path_str = str(filename_path)
if self.type == 'landmarks' and len(data.rects) == 0:
return data
if self.cached_image[0] == filename_path_str:
image = self.cached_image[1] #cached image for manual extractor
else:
image = cv2_imread( filename_path_str )
if image is None:
self.log_err ( 'Failed to extract %s, reason: cv2_imread() fail.' % ( str(filename_path) ) )
return data
image = imagelib.normalize_channels(image, 3)
h, w, ch = image.shape
wm, hm = w % 2, h % 2
if wm + hm != 0: #fix odd image
image = image[0:h-hm,0:w-wm,:]
self.cached_image = ( filename_path_str, image )
src_dflimg = None
h, w, ch = image.shape
if h == w:
#extracting from already extracted jpg image?
src_dflimg = DFLIMG.load (filename_path)
if 'rects' in self.type:
if min(w,h) < 128:
self.log_err ( 'Image is too small %s : [%d, %d]' % ( str(filename_path), w, h ) )
data.rects = []
else:
for rot in ([0, 90, 270, 180]):
data.rects_rotation = rot
if rot == 0:
rotated_image = image
elif rot == 90:
rotated_image = image.swapaxes( 0,1 )[:,::-1,:]
elif rot == 180:
rotated_image = image[::-1,::-1,:]
elif rot == 270:
rotated_image = image.swapaxes( 0,1 )[::-1,:,:]
rects = data.rects = self.e.extract (rotated_image, is_bgr=True)
if len(rects) != 0:
break
if self.max_faces_from_image != 0 and len(data.rects) > 1:
data.rects = data.rects[0:self.max_faces_from_image]
return data
elif self.type == 'landmarks':
if data.rects_rotation == 0:
rotated_image = image
elif data.rects_rotation == 90:
rotated_image = image.swapaxes( 0,1 )[:,::-1,:]
elif data.rects_rotation == 180:
rotated_image = image[::-1,::-1,:]
elif data.rects_rotation == 270:
rotated_image = image.swapaxes( 0,1 )[::-1,:,:]
data.landmarks = self.e.extract (rotated_image, data.rects, self.second_pass_e if (src_dflimg is None and data.landmarks_accurate) else None, is_bgr=True)
if data.rects_rotation != 0:
for i, (rect, lmrks) in enumerate(zip(data.rects, data.landmarks)):
new_rect, new_lmrks = rect, lmrks
(l,t,r,b) = rect
if data.rects_rotation == 90:
new_rect = ( t, h-l, b, h-r)
if lmrks is not None:
new_lmrks = lmrks[:,::-1].copy()
new_lmrks[:,1] = h - new_lmrks[:,1]
elif data.rects_rotation == 180:
if lmrks is not None:
new_rect = ( w-l, h-t, w-r, h-b)
new_lmrks = lmrks.copy()
new_lmrks[:,0] = w - new_lmrks[:,0]
new_lmrks[:,1] = h - new_lmrks[:,1]
elif data.rects_rotation == 270:
new_rect = ( w-b, l, w-t, r )
if lmrks is not None:
new_lmrks = lmrks[:,::-1].copy()
new_lmrks[:,0] = w - new_lmrks[:,0]
data.rects[i], data.landmarks[i] = new_rect, new_lmrks
return data
elif self.type == 'final':
data.final_output_files = []
rects = data.rects
landmarks = data.landmarks
if self.debug_dir is not None:
debug_output_file = str( Path(self.debug_dir) / (filename_path.stem+'.jpg') )
debug_image = image.copy()
if src_dflimg is not None and len(rects) != 1:
#if re-extracting from dflimg and more than 1 or zero faces detected - dont process and just copy it
print("src_dflimg is not None and len(rects) != 1", str(filename_path) )
output_file = str(self.final_output_path / filename_path.name)
if str(filename_path) != str(output_file):
shutil.copy ( str(filename_path), str(output_file) )
data.final_output_files.append (output_file)
else:
face_idx = 0
for rect, image_landmarks in zip( rects, landmarks ):
if src_dflimg is not None and face_idx > 1:
#cannot extract more than 1 face from dflimg
break
if image_landmarks is None:
continue
rect = np.array(rect)
if self.face_type == FaceType.MARK_ONLY:
image_to_face_mat = None
face_image = image
face_image_landmarks = image_landmarks
else:
image_to_face_mat = LandmarksProcessor.get_transform_mat (image_landmarks, self.image_size, self.face_type)
face_image = cv2.warpAffine(image, image_to_face_mat, (self.image_size, self.image_size), cv2.INTER_LANCZOS4)
face_image_landmarks = LandmarksProcessor.transform_points (image_landmarks, image_to_face_mat)
landmarks_bbox = LandmarksProcessor.transform_points ( [ (0,0), (0,self.image_size-1), (self.image_size-1, self.image_size-1), (self.image_size-1,0) ], image_to_face_mat, True)
rect_area = mathlib.polygon_area(np.array(rect[[0,2,2,0]]), np.array(rect[[1,1,3,3]]))
landmarks_area = mathlib.polygon_area(landmarks_bbox[:,0], landmarks_bbox[:,1] )
if not data.manual and self.face_type <= FaceType.FULL_NO_ALIGN and landmarks_area > 4*rect_area: #get rid of faces which umeyama-landmark-area > 4*detector-rect-area
continue
if self.debug_dir is not None:
LandmarksProcessor.draw_rect_landmarks (debug_image, rect, image_landmarks, self.image_size, self.face_type, transparent_mask=True)
final_output_path = self.final_output_path
if data.force_output_path is not None:
final_output_path = data.force_output_path
if src_dflimg is not None and filename_path.suffix == '.jpg':
#if extracting from dflimg and jpg copy it in order not to lose quality
output_file = str(final_output_path / filename_path.name)
if str(filename_path) != str(output_file):
shutil.copy ( str(filename_path), str(output_file) )
else:
output_file = '{}_{}{}'.format(str(final_output_path / filename_path.stem), str(face_idx), '.jpg')
cv2_imwrite(output_file, face_image, [int(cv2.IMWRITE_JPEG_QUALITY), 100] )
DFLJPG.embed_data(output_file, face_type=FaceType.toString(self.face_type),
landmarks=face_image_landmarks.tolist(),
source_filename=filename_path.name,
source_rect=rect,
source_landmarks=image_landmarks.tolist(),
image_to_face_mat=image_to_face_mat
)
data.final_output_files.append (output_file)
face_idx += 1
data.faces_detected = face_idx
if self.debug_dir is not None:
cv2_imwrite(debug_output_file, debug_image, [int(cv2.IMWRITE_JPEG_QUALITY), 50] )
if 'landmarks' in self.type and len(data.rects) == 0:
return data
elif self.type == 'fanseg':
if src_dflimg is not None:
fanseg_mask = self.e.extract( image / 255.0 )
src_dflimg.embed_and_set( filename_path_str,
fanseg_mask=fanseg_mask,
)
filepath = data.filepath
cached_filepath, image = self.cached_image
if cached_filepath != filepath:
image = cv2_imread( filepath )
if image is None:
self.log_err (f'Failed to open {filepath}, reason: cv2_imread() fail.')
return data
image = imagelib.normalize_channels(image, 3)
image = imagelib.cut_odd_image(image)
self.cached_image = ( filepath, image )
h, w, c = image.shape
extract_from_dflimg = (h == w and DFLIMG.load (filepath) is not None)
if 'rects' in self.type or self.type == 'all':
data = ExtractSubprocessor.Cli.rects_stage (data=data,
image=image,
max_faces_from_image=self.max_faces_from_image,
rects_extractor=self.rects_extractor,
)
if 'landmarks' in self.type or self.type == 'all':
data = ExtractSubprocessor.Cli.landmarks_stage (data=data,
image=image,
extract_from_dflimg=extract_from_dflimg,
landmarks_extractor=self.landmarks_extractor,
rects_extractor=self.rects_extractor,
)
if self.type == 'final' or self.type == 'all':
data = ExtractSubprocessor.Cli.final_stage(data=data,
image=image,
face_type=self.face_type,
image_size=self.image_size,
extract_from_dflimg=extract_from_dflimg,
output_debug_path=self.output_debug_path,
final_output_path=self.final_output_path,
)
return data
@staticmethod
def rects_stage(data,
image,
max_faces_from_image,
rects_extractor,
):
h,w,c = image.shape
if min(h,w) < 128:
# Image is too small
data.rects = []
else:
for rot in ([0, 90, 270, 180]):
if rot == 0:
rotated_image = image
elif rot == 90:
rotated_image = image.swapaxes( 0,1 )[:,::-1,:]
elif rot == 180:
rotated_image = image[::-1,::-1,:]
elif rot == 270:
rotated_image = image.swapaxes( 0,1 )[::-1,:,:]
rects = data.rects = rects_extractor.extract (rotated_image, is_bgr=True)
if len(rects) != 0:
data.rects_rotation = rot
break
if max_faces_from_image != 0 and len(data.rects) > 1:
data.rects = data.rects[0:max_faces_from_image]
return data
@staticmethod
def landmarks_stage(data,
image,
extract_from_dflimg,
landmarks_extractor,
rects_extractor,
):
if data.rects_rotation == 0:
rotated_image = image
elif data.rects_rotation == 90:
rotated_image = image.swapaxes( 0,1 )[:,::-1,:]
elif data.rects_rotation == 180:
rotated_image = image[::-1,::-1,:]
elif data.rects_rotation == 270:
rotated_image = image.swapaxes( 0,1 )[::-1,:,:]
data.landmarks = landmarks_extractor.extract (rotated_image, data.rects, rects_extractor if (not extract_from_dflimg and data.landmarks_accurate) else None, is_bgr=True)
if data.rects_rotation != 0:
for i, (rect, lmrks) in enumerate(zip(data.rects, data.landmarks)):
new_rect, new_lmrks = rect, lmrks
(l,t,r,b) = rect
if data.rects_rotation == 90:
new_rect = ( t, h-l, b, h-r)
if lmrks is not None:
new_lmrks = lmrks[:,::-1].copy()
new_lmrks[:,1] = h - new_lmrks[:,1]
elif data.rects_rotation == 180:
if lmrks is not None:
new_rect = ( w-l, h-t, w-r, h-b)
new_lmrks = lmrks.copy()
new_lmrks[:,0] = w - new_lmrks[:,0]
new_lmrks[:,1] = h - new_lmrks[:,1]
elif data.rects_rotation == 270:
new_rect = ( w-b, l, w-t, r )
if lmrks is not None:
new_lmrks = lmrks[:,::-1].copy()
new_lmrks[:,0] = w - new_lmrks[:,0]
data.rects[i], data.landmarks[i] = new_rect, new_lmrks
return data
@staticmethod
def final_stage(data,
image,
face_type,
image_size,
extract_from_dflimg = False,
output_debug_path=None,
final_output_path=None,
):
data.final_output_files = []
filepath = data.filepath
rects = data.rects
landmarks = data.landmarks
if output_debug_path is not None:
debug_image = image.copy()
if extract_from_dflimg and len(rects) != 1:
#if re-extracting from dflimg and more than 1 or zero faces detected - dont process and just copy it
print("extract_from_dflimg and len(rects) != 1", filepath )
output_filepath = final_output_path / filepath.name
if filepath != str(output_file):
shutil.copy ( str(filepath), str(output_filepath) )
data.final_output_files.append (output_filepath)
else:
face_idx = 0
for rect, image_landmarks in zip( rects, landmarks ):
if extract_from_dflimg and face_idx > 1:
#cannot extract more than 1 face from dflimg
break
if image_landmarks is None:
continue
rect = np.array(rect)
if face_type == FaceType.MARK_ONLY:
image_to_face_mat = None
face_image = image
face_image_landmarks = image_landmarks
else:
image_to_face_mat = LandmarksProcessor.get_transform_mat (image_landmarks, image_size, face_type)
face_image = cv2.warpAffine(image, image_to_face_mat, (image_size, image_size), cv2.INTER_LANCZOS4)
face_image_landmarks = LandmarksProcessor.transform_points (image_landmarks, image_to_face_mat)
landmarks_bbox = LandmarksProcessor.transform_points ( [ (0,0), (0,image_size-1), (image_size-1, image_size-1), (image_size-1,0) ], image_to_face_mat, True)
rect_area = mathlib.polygon_area(np.array(rect[[0,2,2,0]]), np.array(rect[[1,1,3,3]]))
landmarks_area = mathlib.polygon_area(landmarks_bbox[:,0], landmarks_bbox[:,1] )
if not data.manual and face_type <= FaceType.FULL_NO_ALIGN and landmarks_area > 4*rect_area: #get rid of faces which umeyama-landmark-area > 4*detector-rect-area
continue
if output_debug_path is not None:
LandmarksProcessor.draw_rect_landmarks (debug_image, rect, image_landmarks, image_size, face_type, transparent_mask=True)
output_path = final_output_path
if data.force_output_path is not None:
output_path = data.force_output_path
if extract_from_dflimg and filepath.suffix == '.jpg':
#if extracting from dflimg and jpg copy it in order not to lose quality
output_filepath = output_path / filepath.name
if filepath != output_filepath:
shutil.copy ( str(filepath), str(output_filepath) )
else:
output_filepath = output_path / f"{filepath.stem}_{face_idx}.jpg"
cv2_imwrite(output_filepath, face_image, [int(cv2.IMWRITE_JPEG_QUALITY), 100] )
DFLJPG.embed_data(output_filepath, face_type=FaceType.toString(face_type),
landmarks=face_image_landmarks.tolist(),
source_filename=filepath.name,
source_rect=rect,
source_landmarks=image_landmarks.tolist(),
image_to_face_mat=image_to_face_mat
)
data.final_output_files.append (output_filepath)
face_idx += 1
data.faces_detected = face_idx
if output_debug_path is not None:
cv2_imwrite( output_debug_path / (filepath.stem+'.jpg'), debug_image, [int(cv2.IMWRITE_JPEG_QUALITY), 50] )
return data
#overridable
def get_data_name (self, data):
#return string identificator of your data
return data.filename
return data.filepath
@staticmethod
def get_devices_for_config (type, device_config):
devices = device_config.devices
cpu_only = len(devices) == 0
if 'rects' in type or \
'landmarks' in type or \
'all' in type:
if not cpu_only:
if type == 'landmarks-manual':
devices = [devices.get_best_device()]
result = [ (device.index, 'GPU', device.name, device.total_mem_gb) for device in devices ]
return result
else:
if type == 'landmarks-manual':
return [ (0, 'CPU', 'CPU', 0 ) ]
else:
return [ (i, 'CPU', 'CPU%d' % (i), 0 ) for i in range( min(8, multiprocessing.cpu_count() // 2) ) ]
elif type == 'final':
return [ (i, 'CPU', 'CPU%d' % (i), 0 ) for i in (range(min(8, multiprocessing.cpu_count())) if not DEBUG else [0]) ]
def __init__(self, input_data, type, image_size=None, face_type=None, output_debug_path=None, manual_window_size=0, max_faces_from_image=0, final_output_path=None, device_config=None):
if type == 'landmarks-manual':
for x in input_data:
x.manual = True
#override
def __init__(self, input_data, type, image_size=None, face_type=None, debug_dir=None, multi_gpu=False, cpu_only=False, manual=False, manual_window_size=0, max_faces_from_image=0, final_output_path=None):
self.input_data = input_data
self.type = type
self.image_size = image_size
self.face_type = face_type
self.debug_dir = debug_dir
self.output_debug_path = output_debug_path
self.final_output_path = final_output_path
self.manual = manual
self.manual_window_size = manual_window_size
self.max_faces_from_image = max_faces_from_image
self.result = []
self.devices = ExtractSubprocessor.get_devices_for_config(self.manual, self.type, multi_gpu, cpu_only)
if self.manual or DEBUG:
no_response_time_sec = 999999
elif nnlib.device.backend == 'plaidML':
no_response_time_sec = 600
else:
no_response_time_sec = 60
super().__init__('Extractor', ExtractSubprocessor.Cli, no_response_time_sec)
self.devices = ExtractSubprocessor.get_devices_for_config(self.type, device_config)
#override
def on_check_run(self):
if len(self.devices) == 0:
io.log_err("No devices found to start subprocessor.")
return False
return True
super().__init__('Extractor', ExtractSubprocessor.Cli,
999999 if type == 'landmarks-manual' or DEBUG else 120)
#override
def on_clients_initialized(self):
if self.manual == True:
if self.type == 'landmarks-manual':
self.wnd_name = 'Manual pass'
io.named_window(self.wnd_name)
io.capture_mouse(self.wnd_name)
@ -346,7 +352,7 @@ class ExtractSubprocessor(Subprocessor):
#override
def on_clients_finalized(self):
if self.manual == True:
if self.type == 'landmarks-manual':
io.destroy_all_windows()
io.progress_bar_close()
@ -357,8 +363,8 @@ class ExtractSubprocessor(Subprocessor):
'image_size': self.image_size,
'face_type': self.face_type,
'max_faces_from_image':self.max_faces_from_image,
'debug_dir': self.debug_dir,
'final_output_dir': str(self.final_output_path),
'output_debug_path': self.output_debug_path,
'final_output_path': self.final_output_path,
'stdin_fd': sys.stdin.fileno() }
@ -371,15 +377,12 @@ class ExtractSubprocessor(Subprocessor):
#override
def get_data(self, host_dict):
if not self.manual:
if len (self.input_data) > 0:
return self.input_data.pop(0)
else:
if self.type == 'landmarks-manual':
need_remark_face = False
redraw_needed = False
while len (self.input_data) > 0:
data = self.input_data[0]
filename, data_rects, data_landmarks = data.filename, data.rects, data.landmarks
filepath, data_rects, data_landmarks = data.filepath, data.rects, data.landmarks
is_frame_done = False
if need_remark_face: # need remark image from input data that already has a marked face?
@ -396,21 +399,21 @@ class ExtractSubprocessor(Subprocessor):
self.y = ( self.rect[1] + self.rect[3] ) / 2
if len(data_rects) == 0:
if self.cache_original_image[0] == filename:
if self.cache_original_image[0] == filepath:
self.original_image = self.cache_original_image[1]
else:
self.original_image = imagelib.normalize_channels( cv2_imread( filename ), 3 )
self.cache_original_image = (filename, self.original_image )
self.original_image = imagelib.normalize_channels( cv2_imread( filepath ), 3 )
self.cache_original_image = (filepath, self.original_image )
(h,w,c) = self.original_image.shape
self.view_scale = 1.0 if self.manual_window_size == 0 else self.manual_window_size / ( h * (16.0/9.0) )
if self.cache_image[0] == (h,w,c) + (self.view_scale,filename):
if self.cache_image[0] == (h,w,c) + (self.view_scale,filepath):
self.image = self.cache_image[1]
else:
self.image = cv2.resize (self.original_image, ( int(w*self.view_scale), int(h*self.view_scale) ), interpolation=cv2.INTER_LINEAR)
self.cache_image = ( (h,w,c) + (self.view_scale,filename), self.image )
self.cache_image = ( (h,w,c) + (self.view_scale,filepath), self.image )
(h,w,c) = self.image.shape
@ -526,9 +529,9 @@ class ExtractSubprocessor(Subprocessor):
if redraw_needed:
redraw_needed = False
return ExtractSubprocessor.Data (filename, landmarks_accurate=self.landmarks_accurate)
return ExtractSubprocessor.Data (filepath, landmarks_accurate=self.landmarks_accurate)
else:
return ExtractSubprocessor.Data (filename, rects=[self.rect], landmarks_accurate=self.landmarks_accurate)
return ExtractSubprocessor.Data (filepath, rects=[self.rect], landmarks_accurate=self.landmarks_accurate)
else:
is_frame_done = True
@ -539,19 +542,22 @@ class ExtractSubprocessor(Subprocessor):
io.progress_bar_inc(1)
self.extract_needed = True
self.rect_locked = False
else:
if len (self.input_data) > 0:
return self.input_data.pop(0)
return None
#override
def on_data_return (self, host_dict, data):
if not self.manual:
if not self.type != 'landmarks-manual':
self.input_data.insert(0, data)
#override
def on_result (self, host_dict, data, result):
if self.manual == True:
filename, landmarks = result.filename, result.landmarks
if self.type == 'landmarks-manual':
filepath, landmarks = result.filepath, result.landmarks
if len(landmarks) != 0 and landmarks[0] is not None:
self.landmarks = landmarks[0]
@ -596,56 +602,6 @@ class ExtractSubprocessor(Subprocessor):
def get_result(self):
return self.result
@staticmethod
def get_devices_for_config (manual, type, multi_gpu, cpu_only):
backend = nnlib.device.backend
if 'cpu' in backend:
cpu_only = True
if 'rects' in type or type == 'landmarks' or type == 'fanseg':
if not cpu_only and type == 'rects-mt' and backend == "plaidML": #plaidML works with MT very slowly
cpu_only = True
if not cpu_only:
devices = []
if not manual and multi_gpu:
devices = nnlib.device.getValidDevicesWithAtLeastTotalMemoryGB(2)
if len(devices) == 0:
idx = nnlib.device.getBestValidDeviceIdx()
if idx != -1:
devices = [idx]
if len(devices) == 0:
cpu_only = True
result = []
for idx in devices:
dev_name = nnlib.device.getDeviceName(idx)
dev_vram = nnlib.device.getDeviceVRAMTotalGb(idx)
count = 1
if not manual:
if (type == 'rects-mt' ):
count = int (max (1, dev_vram / 2) )
if count == 1:
result += [ (idx, 'GPU', dev_name, dev_vram) ]
else:
for i in range (count):
result += [ (idx, 'GPU', '%s #%d' % (dev_name,i) , dev_vram) ]
return result
if cpu_only:
if manual:
return [ (0, 'CPU', 'CPU', 0 ) ]
else:
return [ (i, 'CPU', 'CPU%d' % (i), 0 ) for i in range( min(8, multiprocessing.cpu_count() // 2) ) ]
elif type == 'final':
return [ (i, 'CPU', 'CPU%d' % (i), 0 ) for i in (range(min(8, multiprocessing.cpu_count())) if not DEBUG else [0]) ]
class DeletedFilesSearcherSubprocessor(Subprocessor):
class Cli(Subprocessor.Cli):
@ -704,87 +660,100 @@ class DeletedFilesSearcherSubprocessor(Subprocessor):
def get_result(self):
return self.result
def main(input_dir,
output_dir,
debug_dir=None,
detector='mt',
def main(detector=None,
input_path=None,
output_path=None,
output_debug=None,
manual_fix=False,
manual_output_debug_fix=False,
manual_window_size=1368,
image_size=256,
face_type='full_face',
max_faces_from_image=0,
device_args={}):
input_path = Path(input_dir)
output_path = Path(output_dir)
cpu_only = False,
force_gpu_idxs = None,
):
face_type = FaceType.fromString(face_type)
multi_gpu = device_args.get('multi_gpu', False)
cpu_only = device_args.get('cpu_only', False)
if not input_path.exists():
raise ValueError('Input directory not found. Please ensure it exists.')
io.log_err ('Input directory not found. Please ensure it exists.')
return
if detector is None:
io.log_info ("Choose detector type.")
io.log_info ("[0] S3FD")
io.log_info ("[1] manual")
detector = {0:'s3fd', 1:'manual'}[ io.input_int("", 0, [0,1]) ]
device_config = nn.DeviceConfig.GPUIndexes( force_gpu_idxs or nn.ask_choose_device_idxs(choose_only_one=detector=='manual', suggest_all_gpu=True) ) \
if not cpu_only else nn.DeviceConfig.CPU()
output_debug_path = output_path.parent / (output_path.name + '_debug')
if output_debug is None:
output_debug = io.input_bool (f"Write debug images to {output_debug_path.name}?", False)
if output_path.exists():
if not manual_output_debug_fix and input_path != output_path:
output_images_paths = Path_utils.get_image_paths(output_path)
output_images_paths = pathex.get_image_paths(output_path)
if len(output_images_paths) > 0:
io.input_bool("WARNING !!! \n %s contains files! \n They will be deleted. \n Press enter to continue." % (str(output_path)), False )
io.input(f"WARNING !!! \n {output_path} contains files! \n They will be deleted. \n Press enter to continue.")
for filename in output_images_paths:
Path(filename).unlink()
else:
output_path.mkdir(parents=True, exist_ok=True)
input_path_image_paths = pathex.get_image_unique_filestem_paths(input_path, verbose_print_func=io.log_info)
if manual_output_debug_fix:
if debug_dir is None:
raise ValueError('debug-dir must be specified')
detector = 'manual'
io.log_info('Performing re-extract frames which were deleted from _debug directory.')
if not output_debug_path.exists():
io.log_err(f'{output_debug_path} not found. Re-extract faces with "Write debug images" option.')
return
else:
detector = 'manual'
io.log_info('Performing re-extract frames which were deleted from _debug directory.')
input_path_image_paths = Path_utils.get_image_unique_filestem_paths(input_path, verbose_print_func=io.log_info)
if debug_dir is not None:
debug_output_path = Path(debug_dir)
if manual_output_debug_fix:
if not debug_output_path.exists():
raise ValueError("%s not found " % ( str(debug_output_path) ))
input_path_image_paths = DeletedFilesSearcherSubprocessor (input_path_image_paths, Path_utils.get_image_paths(debug_output_path) ).run()
input_path_image_paths = DeletedFilesSearcherSubprocessor (input_path_image_paths, pathex.get_image_paths(output_debug_path) ).run()
input_path_image_paths = sorted (input_path_image_paths)
io.log_info('Found %d images.' % (len(input_path_image_paths)))
else:
if output_debug_path.exists():
for filename in pathex.get_image_paths(output_debug_path):
Path(filename).unlink()
else:
if debug_output_path.exists():
for filename in Path_utils.get_image_paths(debug_output_path):
Path(filename).unlink()
else:
debug_output_path.mkdir(parents=True, exist_ok=True)
output_debug_path.mkdir(parents=True, exist_ok=True)
images_found = len(input_path_image_paths)
faces_detected = 0
if images_found != 0:
if detector == 'manual':
io.log_info ('Performing manual extract...')
data = ExtractSubprocessor ([ ExtractSubprocessor.Data(filename, manual=True) for filename in input_path_image_paths ], 'landmarks', image_size, face_type, debug_dir, cpu_only=cpu_only, manual=True, manual_window_size=manual_window_size).run()
data = ExtractSubprocessor ([ ExtractSubprocessor.Data(Path(filename)) for filename in input_path_image_paths ], 'landmarks-manual', image_size, face_type, output_debug_path if output_debug else None, manual_window_size=manual_window_size, device_config=device_config).run()
io.log_info ('Performing 3rd pass...')
data = ExtractSubprocessor (data, 'final', image_size, face_type, output_debug_path if output_debug else None, final_output_path=output_path, device_config=device_config).run()
else:
io.log_info ('Performing 1st pass...')
data = ExtractSubprocessor ([ ExtractSubprocessor.Data(filename) for filename in input_path_image_paths ], 'rects-'+detector, image_size, face_type, debug_dir, multi_gpu=multi_gpu, cpu_only=cpu_only, manual=False, max_faces_from_image=max_faces_from_image).run()
io.log_info ('Performing 2nd pass...')
data = ExtractSubprocessor (data, 'landmarks', image_size, face_type, debug_dir, multi_gpu=multi_gpu, cpu_only=cpu_only, manual=False).run()
io.log_info ('Performing 3rd pass...')
data = ExtractSubprocessor (data, 'final', image_size, face_type, debug_dir, multi_gpu=multi_gpu, cpu_only=cpu_only, manual=False, final_output_path=output_path).run()
io.log_info ('Extracting faces...')
data = ExtractSubprocessor ([ ExtractSubprocessor.Data(Path(filename)) for filename in input_path_image_paths ],
'all',
image_size,
face_type,
output_debug_path if output_debug else None,
max_faces_from_image=max_faces_from_image,
final_output_path=output_path,
device_config=device_config).run()
faces_detected += sum([d.faces_detected for d in data])
if manual_fix:
if all ( np.array ( [ d.faces_detected > 0 for d in data] ) == True ):
io.log_info ('All faces are detected, manual fix not needed.')
else:
fix_data = [ ExtractSubprocessor.Data(d.filename, manual=True) for d in data if d.faces_detected == 0 ]
fix_data = [ ExtractSubprocessor.Data(d.filepath) for d in data if d.faces_detected == 0 ]
io.log_info ('Performing manual fix for %d images...' % (len(fix_data)) )
fix_data = ExtractSubprocessor (fix_data, 'landmarks', image_size, face_type, debug_dir, manual=True, manual_window_size=manual_window_size).run()
fix_data = ExtractSubprocessor (fix_data, 'final', image_size, face_type, debug_dir, multi_gpu=multi_gpu, cpu_only=cpu_only, manual=False, final_output_path=output_path).run()
fix_data = ExtractSubprocessor (fix_data, 'landmarks-manual', image_size, face_type, output_debug_path if output_debug else None, manual_window_size=manual_window_size, device_config=device_config).run()
fix_data = ExtractSubprocessor (fix_data, 'final', image_size, face_type, output_debug_path if output_debug else None, final_output_path=output_path, device_config=device_config).run()
faces_detected += sum([d.faces_detected for d in fix_data])