diff --git a/README.md b/README.md index d324f1d..a1f8128 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ dst - controller face (your face) converter --input-dir must contains *extracted dst faces* in sequence to be converted, its mean you can train on for example 1500 dst faces, but use for example 100 faces for convert. +![](https://github.com/iperov/DeepFaceLab/blob/master/doc/DeepFaceLab_convertor_overview.png) + - Video comparison of different Cage facesets. Vertical: 1 - mix of various Cage face shape and light conditions. 2,3,4 - without mix. Horizontal: 1 - DF, 2 - LIAEF128. diff --git a/main.py b/main.py index cb85cb0..2a36300 100644 --- a/main.py +++ b/main.py @@ -121,18 +121,24 @@ if __name__ == "__main__": if arguments.mode == 'hist-match' or arguments.mode == 'hist-match-bw': try: - choice = int ( input ("Masked hist match? [0..1] (default - model choice) : ") ) - arguments.masked_hist_match = (choice != 0) + arguments.masked_hist_match = bool ( {"1":True,"0":False}[input("Masked hist match? [0 or 1] (default 1) : ").lower()] ) except: - arguments.masked_hist_match = None + arguments.masked_hist_match = True + + if arguments.mode == 'hist-match' or arguments.mode == 'hist-match-bw' or arguments.mode == 'seamless-hist-match': + try: + hist_match_threshold = int ( input ("Hist match threshold. [0..255] (default - 255) : ") ) + arguments.hist_match_threshold = hist_match_threshold + except: + arguments.hist_match_threshold = 255 try: - arguments.erode_mask_modifier = int ( input ("Choose erode mask modifier [-100..100] (default 0) : ") ) + arguments.erode_mask_modifier = int ( input ("Choose erode mask modifier [-200..200] (default 0) : ") ) except: arguments.erode_mask_modifier = 0 try: - arguments.blur_mask_modifier = int ( input ("Choose blur mask modifier [-100..200] (default 0) : ") ) + arguments.blur_mask_modifier = int ( input ("Choose blur mask modifier [-200..200] (default 0) : ") ) except: arguments.blur_mask_modifier = 0 @@ -142,7 +148,7 @@ if __name__ == "__main__": arguments.output_face_scale_modifier = 0 try: - arguments.transfercolor = bool ( {"1":True,"0":False}[input("Transfer color from original DST image? [0..1] (default 0) : ").lower()] ) + arguments.transfercolor = bool ( {"1":True,"0":False}[input("Transfer color from dst face to converted final face? [0 or 1] (default 0) : ").lower()] ) except: arguments.transfercolor = False @@ -158,8 +164,8 @@ if __name__ == "__main__": - arguments.erode_mask_modifier = np.clip ( int(arguments.erode_mask_modifier), -100, 100) - arguments.blur_mask_modifier = np.clip ( int(arguments.blur_mask_modifier), -100, 200) + arguments.erode_mask_modifier = np.clip ( int(arguments.erode_mask_modifier), -200, 200) + arguments.blur_mask_modifier = np.clip ( int(arguments.blur_mask_modifier), -200, 200) arguments.output_face_scale_modifier = np.clip ( int(arguments.output_face_scale_modifier), -50, 50) from mainscripts import Converter @@ -172,6 +178,7 @@ if __name__ == "__main__": debug = arguments.debug, mode = arguments.mode, masked_hist_match = arguments.masked_hist_match, + hist_match_threshold = arguments.hist_match_threshold, erode_mask_modifier = arguments.erode_mask_modifier, blur_mask_modifier = arguments.blur_mask_modifier, output_face_scale_modifier = arguments.output_face_scale_modifier, @@ -189,13 +196,14 @@ if __name__ == "__main__": convert_parser.add_argument('--model', required=True, dest="model_name", choices=Path_utils.get_all_dir_names_startswith ( Path(__file__).parent / 'models' , 'Model_'), help="Type of model") convert_parser.add_argument('--ask-for-params', action="store_true", dest="ask_for_params", default=False, help="Ask for params.") convert_parser.add_argument('--mode', dest="mode", choices=['seamless','hist-match', 'hist-match-bw','seamless-hist-match'], default='seamless', help="Face overlaying mode. Seriously affects result.") - convert_parser.add_argument('--masked-hist-match', type=str2bool, nargs='?', const=True, default=None, help="True or False. Excludes background for hist match. Default - model decide.") - convert_parser.add_argument('--erode-mask-modifier', type=int, dest="erode_mask_modifier", default=0, help="Automatic erode mask modifier. Valid range [-100..100].") - convert_parser.add_argument('--blur-mask-modifier', type=int, dest="blur_mask_modifier", default=0, help="Automatic blur mask modifier. Valid range [-100..200].") + convert_parser.add_argument('--masked-hist-match', type=str2bool, nargs='?', const=True, default=True, help="True or False. Excludes background for hist match. Default - True.") + convert_parser.add_argument('--hist-match-threshold', type=int, dest="hist_match_threshold", default=255, help="Hist match threshold. Decrease to hide artifacts of hist match. Valid range [0..255]. Default 255") + convert_parser.add_argument('--erode-mask-modifier', type=int, dest="erode_mask_modifier", default=0, help="Automatic erode mask modifier. Valid range [-200..200].") + convert_parser.add_argument('--blur-mask-modifier', type=int, dest="blur_mask_modifier", default=0, help="Automatic blur mask modifier. Valid range [-200..200].") convert_parser.add_argument('--output-face-scale-modifier', type=int, dest="output_face_scale_modifier", default=0, help="Output face scale modifier. Valid range [-50..50].") convert_parser.add_argument('--final-image-color-degrade-power', type=int, dest="final_image_color_degrade_power", default=0, help="Degrades colors of final image to hide face problems. Valid range [0..100].") - convert_parser.add_argument('--transfercolor', action="store_true", dest="transfercolor", default=False, help="transfer color from dst to merged.") - convert_parser.add_argument('--alpha', action="store_true", dest="alpha", default=False, help="alpha channel.") + convert_parser.add_argument('--transfercolor', action="store_true", dest="transfercolor", default=False, help="Transfer color from dst face to converted final face.") + convert_parser.add_argument('--alpha', action="store_true", dest="alpha", default=False, help="Embeds alpha channel of face mask to final PNG. Used in manual composing video by editors such as Sony Vegas or After Effects.") convert_parser.add_argument('--debug', action="store_true", dest="debug", default=False, help="Debug converter.") convert_parser.add_argument('--force-best-gpu-idx', type=int, dest="force_best_gpu_idx", default=-1, help="Force to choose this GPU idx as best.") diff --git a/mainscripts/Converter.py b/mainscripts/Converter.py index 7f37c12..cda413f 100644 --- a/mainscripts/Converter.py +++ b/mainscripts/Converter.py @@ -65,7 +65,7 @@ class ConvertSubprocessor(SubprocessorBase): #override def __init__(self, converter, input_path_image_paths, output_path, alignments, debug = False, **in_options): - super().__init__('Converter') + super().__init__('Converter', 86400 if debug == True else 60) self.converter = converter self.input_path_image_paths = input_path_image_paths self.output_path = output_path diff --git a/models/ConverterMasked.py b/models/ConverterMasked.py index a13d17a..e531ac2 100644 --- a/models/ConverterMasked.py +++ b/models/ConverterMasked.py @@ -15,7 +15,8 @@ class ConverterMasked(ConverterBase): erode_mask = True, blur_mask = True, clip_border_mask_per = 0, - masked_hist_match = False, + masked_hist_match = True, + hist_match_threshold = 255, mode='seamless', erode_mask_modifier=0, blur_mask_modifier=0, @@ -34,6 +35,7 @@ class ConverterMasked(ConverterBase): self.blur_mask = blur_mask self.clip_border_mask_per = clip_border_mask_per self.masked_hist_match = masked_hist_match + self.hist_match_threshold = hist_match_threshold self.mode = mode self.erode_mask_modifier = erode_mask_modifier self.blur_mask_modifier = blur_mask_modifier @@ -165,7 +167,7 @@ class ConverterMasked(ConverterBase): hist_match_2 = dst_face_bgr*hist_mask_a + (1.0-hist_mask_a)* np.ones ( prd_face_bgr.shape[:2] + (1,) , dtype=prd_face_bgr.dtype) hist_match_2[ hist_match_1 > 1.0 ] = 1.0 - new_prd_face_bgr = image_utils.color_hist_match(hist_match_1, hist_match_2 ) + new_prd_face_bgr = image_utils.color_hist_match(hist_match_1, hist_match_2, self.hist_match_threshold ) prd_face_bgr = new_prd_face_bgr @@ -202,7 +204,7 @@ class ConverterMasked(ConverterBase): if self.mode == 'seamless-hist-match': out_face_bgr = cv2.warpAffine( out_img, face_mat, (self.output_size, self.output_size) ) - new_out_face_bgr = image_utils.color_hist_match(out_face_bgr, dst_face_bgr ) + new_out_face_bgr = image_utils.color_hist_match(out_face_bgr, dst_face_bgr, self.hist_match_threshold) new_out = cv2.warpAffine( new_out_face_bgr, face_mat, img_size, img_bgr.copy(), cv2.WARP_INVERSE_MAP | cv2.INTER_LANCZOS4, cv2.BORDER_TRANSPARENT ) out_img = np.clip( img_bgr*(1-img_mask_blurry_aaa) + (new_out*img_mask_blurry_aaa) , 0, 1.0 ) diff --git a/models/Model_DF/Model.py b/models/Model_DF/Model.py index c791503..76e6897 100644 --- a/models/Model_DF/Model.py +++ b/models/Model_DF/Model.py @@ -109,9 +109,6 @@ class Model(ModelBase): def get_converter(self, **in_options): from models import ConverterMasked - if 'masked_hist_match' not in in_options.keys() or in_options['masked_hist_match'] is None: - in_options['masked_hist_match'] = True - if 'erode_mask_modifier' not in in_options.keys(): in_options['erode_mask_modifier'] = 0 in_options['erode_mask_modifier'] += 30 diff --git a/models/Model_H128/Model.py b/models/Model_H128/Model.py index 9ec364f..a24ce7e 100644 --- a/models/Model_H128/Model.py +++ b/models/Model_H128/Model.py @@ -113,9 +113,6 @@ class Model(ModelBase): def get_converter(self, **in_options): from models import ConverterMasked - if 'masked_hist_match' not in in_options.keys() or in_options['masked_hist_match'] is None: - in_options['masked_hist_match'] = True - if 'erode_mask_modifier' not in in_options.keys(): in_options['erode_mask_modifier'] = 0 in_options['erode_mask_modifier'] += 100 diff --git a/models/Model_H64/Model.py b/models/Model_H64/Model.py index e4ef157..20335cb 100644 --- a/models/Model_H64/Model.py +++ b/models/Model_H64/Model.py @@ -113,9 +113,6 @@ class Model(ModelBase): def get_converter(self, **in_options): from models import ConverterMasked - if 'masked_hist_match' not in in_options.keys() or in_options['masked_hist_match'] is None: - in_options['masked_hist_match'] = True - if 'erode_mask_modifier' not in in_options.keys(): in_options['erode_mask_modifier'] = 0 in_options['erode_mask_modifier'] += 100 diff --git a/models/Model_LIAEF128/Model.py b/models/Model_LIAEF128/Model.py index 7963512..215564e 100644 --- a/models/Model_LIAEF128/Model.py +++ b/models/Model_LIAEF128/Model.py @@ -117,9 +117,6 @@ class Model(ModelBase): def get_converter(self, **in_options): from models import ConverterMasked - if 'masked_hist_match' not in in_options.keys() or in_options['masked_hist_match'] is None: - in_options['masked_hist_match'] = True - if 'erode_mask_modifier' not in in_options.keys(): in_options['erode_mask_modifier'] = 0 in_options['erode_mask_modifier'] += 30 diff --git a/models/Model_LIAEF128YAW/Model.py b/models/Model_LIAEF128YAW/Model.py index 87e83f4..fc06344 100644 --- a/models/Model_LIAEF128YAW/Model.py +++ b/models/Model_LIAEF128YAW/Model.py @@ -117,9 +117,6 @@ class Model(ModelBase): def get_converter(self, **in_options): from models import ConverterMasked - if 'masked_hist_match' not in in_options.keys() or in_options['masked_hist_match'] is None: - in_options['masked_hist_match'] = True - if 'erode_mask_modifier' not in in_options.keys(): in_options['erode_mask_modifier'] = 0 in_options['erode_mask_modifier'] += 30 diff --git a/models/Model_MIAEF128/Model.py b/models/Model_MIAEF128/Model.py index 160253d..042c905 100644 --- a/models/Model_MIAEF128/Model.py +++ b/models/Model_MIAEF128/Model.py @@ -153,9 +153,6 @@ class Model(ModelBase): def get_converter(self, **in_options): from models import ConverterMasked - if 'masked_hist_match' not in in_options.keys() or in_options['masked_hist_match'] is None: - in_options['masked_hist_match'] = False - if 'erode_mask_modifier' not in in_options.keys(): in_options['erode_mask_modifier'] = 0 in_options['erode_mask_modifier'] += 30 diff --git a/utils/image_utils.py b/utils/image_utils.py index b2d1d05..b6a419e 100644 --- a/utils/image_utils.py +++ b/utils/image_utils.py @@ -6,7 +6,7 @@ import localization from scipy.spatial import Delaunay from PIL import Image, ImageDraw, ImageFont -def channel_hist_match(source, template, mask=None): +def channel_hist_match(source, template, hist_match_threshold=255, mask=None): # Code borrowed from: # https://stackoverflow.com/questions/32655686/histogram-matching-of-two-images-in-python-2-x masked_source = source @@ -29,18 +29,18 @@ def channel_hist_match(source, template, mask=None): mt_values, mt_counts = np.unique(template, return_counts=True) s_quantiles = np.cumsum(s_counts).astype(np.float64) - s_quantiles /= s_quantiles[-1] + s_quantiles = hist_match_threshold * s_quantiles / s_quantiles[-1] t_quantiles = np.cumsum(t_counts).astype(np.float64) - t_quantiles /= t_quantiles[-1] + t_quantiles = 255 * t_quantiles / t_quantiles[-1] interp_t_values = np.interp(s_quantiles, t_quantiles, t_values) return interp_t_values[bin_idx].reshape(oldshape) -def color_hist_match(src_im, tar_im, mask=None): +def color_hist_match(src_im, tar_im, hist_match_threshold=255): h,w,c = src_im.shape - matched_R = channel_hist_match(src_im[:,:,0], tar_im[:,:,0], mask) - matched_G = channel_hist_match(src_im[:,:,1], tar_im[:,:,1], mask) - matched_B = channel_hist_match(src_im[:,:,2], tar_im[:,:,2], mask) + matched_R = channel_hist_match(src_im[:,:,0], tar_im[:,:,0], hist_match_threshold, None) + matched_G = channel_hist_match(src_im[:,:,1], tar_im[:,:,1], hist_match_threshold, None) + matched_B = channel_hist_match(src_im[:,:,2], tar_im[:,:,2], hist_match_threshold, None) to_stack = (matched_R, matched_G, matched_B) for i in range(3, c):