def _update_faces(self, extractor_output): """ Update alignments for the mask if the input type is a faces folder If an output location has been indicated, then puts the mask preview to the save queue Parameters ---------- extractor_output: dict The output from the :class:`plugins.extract.pipeline.Extractor` object """ for face in extractor_output.detected_faces: frame_name = extractor_output.mask_tool_face_info[ "source_filename"] face_index = extractor_output.mask_tool_face_info["face_index"] logger.trace("Saving face: (frame: %s, face index: %s)", frame_name, face_index) self._alignments.update_face(frame_name, face_index, face.to_alignment()) metadata = dict(alignments=face.to_png_meta(), source=extractor_output.mask_tool_face_info) self._faces_saver.save( extractor_output.filename, encode_image(extractor_output.image, ".png", metadata=metadata)) if self._saver is not None: face.image = extractor_output.image self._save(frame_name, face_index, face)
def _output_faces(self, saver, extract_media): """ Output faces to save thread Set the face filename based on the frame name and put the face to the :class:`~lib.image.ImagesSaver` save queue and add the face information to the alignments data. Parameters ---------- saver: lib.images.ImagesSaver The background saver for saving the image extract_media: :class:`~plugins.extract.pipeline.ExtractMedia` The output from :class:`~plugins.extract.Pipeline.Extractor` """ logger.trace("Outputting faces for %s", extract_media.filename) final_faces = list() filename = os.path.splitext(os.path.basename(extract_media.filename))[0] extension = ".png" for idx, face in enumerate(extract_media.detected_faces): output_filename = "{}_{}{}".format(filename, str(idx), extension) meta = dict(alignments=face.to_png_meta(), source=dict(alignments_version=self._alignments.version, original_filename=output_filename, face_index=idx, source_filename=os.path.basename(extract_media.filename), source_is_video=self._images.is_video)) image = encode_image(face.aligned.face, extension, metadata=meta) if not self._args.skip_saving_faces: saver.save(output_filename, image) final_faces.append(face.to_alignment()) self._alignments.data[os.path.basename(extract_media.filename)] = dict(faces=final_faces) del extract_media
def _background_extract(self, output_folder, progress_queue): """ Perform the background extraction in a thread so GUI doesn't become unresponsive. Parameters ---------- output_folder: str The location to save the output faces to progress_queue: :class:`queue.Queue` The queue to place incremental counts to for updating the GUI's progress bar """ _io = dict(saver=ImagesSaver(str(get_folder(output_folder)), as_bytes=True), loader=ImagesLoader(self._input_location, count=self._alignments.frames_count)) for frame_idx, (filename, image) in enumerate(_io["loader"].load()): logger.trace("Outputting frame: %s: %s", frame_idx, filename) src_filename = os.path.basename(filename) frame_name = os.path.splitext(src_filename)[0] progress_queue.put(1) for face_idx, face in enumerate(self._frame_faces[frame_idx]): output = "{}_{}{}".format(frame_name, str(face_idx), ".png") aligned = AlignedFace(face.landmarks_xy, image=image, centering="head", size=512) # TODO user selectable size meta = dict(alignments=face.to_png_meta(), source=dict(alignments_version=self._alignments.version, original_filename=output, face_index=face_idx, source_filename=src_filename, source_is_video=self._globals.is_video)) b_image = encode_image(aligned.face, ".png", metadata=meta) _io["saver"].save(output, b_image) _io["saver"].close()
def _output_faces(self, filename, image): """ For each frame save out the faces Parameters ---------- filename: str The filename (without the full path) of the current frame image: :class:`numpy.ndarray` The full frame that faces are to be extracted from Returns ------- int The total number of faces that have been extracted """ logger.trace("Outputting frame: %s", filename) face_count = 0 frame_name = os.path.splitext(filename)[0] faces = self._select_valid_faces(filename, image) if not faces: return face_count if self._is_legacy: faces = self._process_legacy(filename, image, faces) for idx, face in enumerate(faces): output = "{}_{}.png".format(frame_name, str(idx)) meta = dict(alignments=face.to_png_meta(), source=dict( alignments_version=self._alignments.version, original_filename=output, face_index=idx, source_filename=filename, source_is_video=self._frames.is_video, source_frame_dims=image.shape[:2])) self._saver.save( output, encode_image(face.aligned.face, ".png", metadata=meta)) if not self._arguments.large and self._is_legacy: face.thumbnail = generate_thumbnail(face.aligned.face, size=96, quality=60) self._alignments.data[filename]["faces"][ idx] = face.to_alignment() face_count += 1 self._saver.close() return face_count
def update_legacy_png_header(filename, alignments): """ Update a legacy extracted face from pre v2.1 alignments by placing the alignment data for the face in the png exif header for the given filename with the given alignment data. If the given file is not a .png then a png is created and the original file is removed Parameters ---------- filename: str The image file to update alignments: :class:`lib.align.alignments.Alignments` The alignments data the contains the information to store in the image header. This must be a v2.0 or less alignments file as later versions no longer store the face hash (unrequired) Returns ------- dict The metadata that has been applied to the given image """ if alignments.version > 2.0: raise FaceswapError( "The faces being passed in do not correspond to the given Alignments " "file. Please double check your sources and try again.") # Track hashes for multiple files with the same hash. Not the most robust but should be # effective enough folder = os.path.dirname(filename) if folder not in _HASHES_SEEN: _HASHES_SEEN[folder] = dict() hashes_seen = _HASHES_SEEN[folder] in_image = read_image(filename, raise_error=True) in_hash = sha1(in_image).hexdigest() hashes_seen[in_hash] = hashes_seen.get(in_hash, -1) + 1 alignment = alignments.hashes_to_alignment.get(in_hash) if not alignment: logger.debug("Alignments not found for image: '%s'", filename) return None detected_face = DetectedFace() detected_face.from_alignment(alignment) # For dupe hash handling, make sure we get a different filename for repeat hashes src_fname, face_idx = list( alignments.hashes_to_frame[in_hash].items())[hashes_seen[in_hash]] orig_filename = "{}_{}.png".format( os.path.splitext(src_fname)[0], face_idx) meta = dict(alignments=detected_face.to_png_meta(), source=dict(alignments_version=alignments.version, original_filename=orig_filename, face_index=face_idx, source_filename=src_fname, source_is_video=False)) # Can't check so set false out_filename = f"{os.path.splitext(filename)[0]}.png" # Make sure saved file is png out_image = encode_image(in_image, ".png", metadata=meta) with open(out_filename, "wb") as out_file: out_file.write(out_image) if filename != out_filename: # Remove the old non-png: logger.debug("Removing replaced face with deprecated extension: '%s'", filename) os.remove(filename) return meta