def pre_fill(self, filenames, side): """ When warp to landmarks is enabled, the cache must be pre-filled, as each side needs access to the other side's alignments. Parameters ---------- filenames: list The list of full paths to the images to load the metadata from side: str `"a"` or `"b"`. The side of the model being cached. Used for info output """ with self._lock: for filename, meta in tqdm( read_image_meta_batch(filenames), desc="WTL: Caching Landmarks ({})".format(side.upper()), total=len(filenames), leave=False): if "itxt" not in meta or "alignments" not in meta["itxt"]: raise FaceswapError( f"Invalid face image found. Aborting: '{filename}'") size = meta["width"] meta = meta["itxt"] # Version Check self._validate_version(meta, filename) detected_face = self._add_aligned_face(filename, meta["alignments"], size) self._cache[os.path.basename( filename)]["detected_face"] = detected_face self._partial_load = True
def sort_distance(self): """ Sort by comparison of face landmark points to mean face by average distance of core landmarks. """ logger.info("Sorting by average distance of landmarks...") filenames = [] distances = [] filelist = [os.path.join(self._loader.location, fname) for fname in os.listdir(self._loader.location) if os.path.splitext(fname)[-1] == ".png"] for filename, metadata in tqdm(read_image_meta_batch(filelist), total=len(filelist), desc="Calculating Distances"): if not metadata: msg = ("The images to be sorted do not contain alignment data. Images must have " "been generated by Faceswap's Extract process.\nIf you are sorting an " "older faceset, then you should re-extract the faces from your source " "alignments file to generate this data.") raise FaceswapError(msg) alignments = metadata["itxt"]["alignments"] aligned_face = AlignedFace(np.array(alignments["landmarks_xy"], dtype="float32")) filenames.append(filename) distances.append(aligned_face.average_distance) logger.info("Sorting...") matched_list = list(zip(filenames, distances)) img_list = sorted(matched_list, key=operator.itemgetter(1)) return img_list
def _get_face_metadata(self): """ Check for the existence of an aligned directory for identifying which faces in the target frames should be swapped. If it exists, scan the folder for face's metadata Returns ------- dict Dictionary of source frame names with a list of associated face indices to be skipped """ retval = dict() input_aligned_dir = self._args.input_aligned_dir if input_aligned_dir is None: logger.verbose( "Aligned directory not specified. All faces listed in the " "alignments file will be converted") return retval if not os.path.isdir(input_aligned_dir): logger.warning( "Aligned directory not found. All faces listed in the " "alignments file will be converted") return retval log_once = False filelist = get_image_paths(input_aligned_dir) for fullpath, metadata in tqdm(read_image_meta_batch(filelist), total=len(filelist), desc="Reading Face Data", leave=False): if "itxt" not in metadata or "source" not in metadata["itxt"]: # UPDATE LEGACY FACES FROM ALIGNMENTS FILE if not log_once: logger.warning( "Legacy faces discovered in '%s'. These faces will be updated", input_aligned_dir) log_once = True data = update_legacy_png_header(fullpath, self._alignments) if not data: raise FaceswapError( "Some of the faces being passed in from '{}' could not be matched to the " "alignments file '{}'\nPlease double check your sources and try " "again.".format(input_aligned_dir, self._alignments.file)) meta = data["source"] else: meta = metadata["itxt"]["source"] retval.setdefault(meta["source_filename"], list()).append(meta["face_index"]) if not retval: raise FaceswapError( "Aligned directory is empty, no faces will be converted!") if len(retval) <= len(self._input_images) / 3: logger.warning( "Aligned directory contains far fewer images than the input " "directory, are you sure this is the right folder?") return retval
def process_folder(self): """ Iterate through the faces folder pulling out various information for each face. Yields ------ dict A dictionary for each face found containing the keys returned from :class:`lib.image.read_image_meta_batch` """ logger.info("Loading file list from %s", self.folder) if self._alignments is not None: # Legacy updating filelist = [ os.path.join(self.folder, face) for face in os.listdir(self.folder) if self.valid_extension(face) ] else: filelist = [ os.path.join(self.folder, face) for face in os.listdir(self.folder) if os.path.splitext(face)[-1] == ".png" ] log_once = False for fullpath, metadata in tqdm(read_image_meta_batch(filelist), total=len(filelist), desc="Reading Face Data"): if "itxt" not in metadata or "source" not in metadata["itxt"]: if self._alignments is None: # Can't update legacy raise FaceswapError( f"The folder '{self.folder}' contains images that do not include Faceswap " "metadata.\nAll images in the provided folder should contain faces " "generated from Faceswap's extraction process.\nPlease double check the " "source and try again.") if not log_once: logger.warning( "Legacy faces discovered. These faces will be updated") log_once = True data = update_legacy_png_header(fullpath, self._alignments) if not data: raise FaceswapError( "Some of the faces being passed in from '{}' could not be matched to the " "alignments file '{}'\nPlease double check your sources and try " "again.".format(self.folder, self._alignments.file)) retval = data["source"] else: retval = metadata["itxt"]["source"] retval["current_filename"] = os.path.basename(fullpath) yield retval