def _annotate_image(self, frame_name): """ Annotate the frame with each face that appears in the alignments file. Parameters ---------- frame_name: str The full path to the original frame """ logger.trace("Annotating frame: '%s'", frame_name) image = self._frames.load_image(frame_name) for idx, alignment in enumerate( self._alignments.get_faces_in_frame(frame_name)): face = DetectedFace() face.from_alignment(alignment, image=image) # Bounding Box cv2.rectangle(image, (face.left, face.top), (face.right, face.bottom), (255, 0, 0), 1) self._annotate_landmarks( image, np.rint(face.landmarks_xy).astype("int32")) self._annotate_extract_boxes(image, face, idx) self._annotate_pose(image, face) # Pose (head is still loaded) self._frames.save_image(self._output_folder, frame_name, image)
def _alignments_faces(self, frame_name, image): """ Return detected faces from an alignments file. Parameters ---------- frame_name: str The name of the frame to return the detected faces for image: :class:`numpy.ndarray` The frame that the detected faces exist in Returns ------- list List of :class:`lib.align.DetectedFace` objects """ if not self._check_alignments(frame_name): return list() faces = self._alignments.get_faces_in_frame(frame_name) detected_faces = list() for rawface in faces: face = DetectedFace() face.from_alignment(rawface, image=image) detected_faces.append(face) return detected_faces
def extract_one_face(self, alignment, image): """ Extract one face from image """ logger.trace("Extracting one face: (frame: '%s', alignment: %s)", self.current_frame, alignment) face = DetectedFace() face.from_alignment(alignment, image=image) face.load_aligned(image, size=self.size, centering="head") face.thumbnail = generate_thumbnail(face.aligned.face, size=80, quality=60) return face
def load(self): """ Load the faces from the alignments file, convert to :class:`~lib.align.DetectedFace`. objects and add to :attr:`_frame_faces`. """ for key in sorted(self._alignments.data): this_frame_faces = [] for item in self._alignments.data[key]["faces"]: face = DetectedFace() face.from_alignment(item, with_thumb=True) this_frame_faces.append(face) self._frame_faces.append(this_frame_faces)
def load(self): """ Load the faces from the alignments file, convert to :class:`~lib.align.DetectedFace`. objects and add to :attr:`_frame_faces`. """ for key in sorted(self._alignments.data): this_frame_faces = [] for item in self._alignments.data[key]["faces"]: face = DetectedFace() face.from_alignment(item, with_thumb=True) face.load_aligned(None) _ = face.aligned.average_distance # cache the distances this_frame_faces.append(face) self._frame_faces.append(this_frame_faces) self._sorted_frame_names = sorted(self._alignments.data)
def _update_png_headers(self): """ Update the EXIF iTXt field of any face PNGs that have had their face index changed. Notes ----- This could be quicker if parellizing in threads, however, Windows (at least) does not seem to like this and has a tendency to throw permission errors, so this remains single threaded for now. """ to_update = [ # Items whose face index has changed x for x in self._items.file_list_sorted if x["face_index"] != self._items.items[x["source_filename"]].index(x["face_index"]) ] for file_info in tqdm(to_update, desc="Updating PNG Headers", leave=False): frame = file_info["source_filename"] face_index = file_info["face_index"] new_index = self._items.items[frame].index(face_index) fullpath = os.path.join(self._items.folder, file_info["current_filename"]) logger.debug( "Updating png header for '%s': face index from %s to %s", fullpath, face_index, new_index) # Update file_list_sorted for rename task orig_filename = "{}_{}.png".format( os.path.splitext(frame)[0], new_index) file_info["face_index"] = new_index file_info["original_filename"] = orig_filename face = DetectedFace() face.from_alignment( self._alignments.get_faces_in_frame(frame)[new_index]) meta = dict( alignments=face.to_png_meta(), source=dict( alignments_version=file_info["alignments_version"], original_filename=orig_filename, face_index=new_index, source_filename=frame, source_is_video=file_info["source_is_video"], source_frame_dims=file_info.get("source_frame_dims"))) update_existing_metadata(fullpath, meta) logger.info( "%s Extracted face(s) had their header information updated", len(to_update))
def _get_detected_face(alignment): """ Convert an alignment dict item to a detected_face object Parameters ---------- alignment: dict The alignment dict for a face Returns ------- :class:`lib.FacesDetect.detected_face`: The corresponding detected_face object for the alignment """ detected_face = DetectedFace() detected_face.from_alignment(alignment) return detected_face
def process(self): """ Run the job to remove faces from an alignments file that do not exist within a faces folder. """ logger.info("[REMOVE FACES FROM ALIGNMENTS]") # Tidy up cli output frame_face_indices = self._items.items if not frame_face_indices: logger.error( "No matching faces found in your faces folder. This would remove all " "faces from your alignments file. Process aborted.") return pre_face_count = self._alignments.faces_count self._alignments.filter_faces(frame_face_indices, filter_out=False) del_count = pre_face_count - self._alignments.faces_count if del_count == 0: logger.info("No changes made to alignments file. Exiting") return logger.info("%s alignment(s) were removed from alignments file", del_count) # PNG Header Updates updated_headers = 0 for file_info in tqdm(self._items.file_list_sorted, desc="Updating PNG Headers"): frame = file_info["source_filename"] face_index = file_info["face_index"] new_index = frame_face_indices[frame].index(face_index) if new_index == face_index: # face index has not changed continue fullpath = os.path.join(self._items.folder, file_info["current_filename"]) logger.debug( "Updating png header for '%s': face index from %s to %s", fullpath, face_index, new_index) # Update file_list_sorted for rename task orig_filename = "{}_{}.png".format( os.path.splitext(frame)[0], new_index) file_info["face_index"] = new_index file_info["original_filename"] = orig_filename face = DetectedFace() face.from_alignment( self._alignments.get_faces_in_frame(frame)[new_index]) meta = dict(alignments=face.to_png_meta(), source=dict( alignments_version=file_info["alignments_version"], original_filename=orig_filename, face_index=new_index, source_filename=frame, source_is_video=file_info["source_is_video"])) update_existing_metadata(fullpath, meta) updated_headers += 1 logger.info( "%s Extracted face(s) had their header information updated", updated_headers) self._alignments.save() rename = Rename(self._alignments, None, self._items) rename.process()
def __init__(self, arguments): logger.debug("Initializing %s: (args: %s)", self.__class__.__name__, arguments) self._args = arguments # load faces faces_alignments = AlignmentsBase(self._args.faces_align_dir) print() print(f'Faces alignments: {len(faces_alignments._data.keys())}') print(faces_alignments._data.keys()) self._faces = {} faces_loader = ImagesLoader(self._args.faces_dir) for filename, image in faces_loader.load(): face_name = os.path.basename(filename) faces = faces_alignments.get_faces_in_frame(face_name) detected_faces = list() for rawface in faces: face = DetectedFace() face.from_alignment(rawface, image=image) feed_face = AlignedFace(face.landmarks_xy, image=image, centering='face', size=image.shape[0], coverage_ratio=1.0, dtype="float32") detected_faces.append(feed_face) self._faces[face_name] = (filename, image, detected_faces) print('Faces:', len(self._faces)) print(self._faces.keys()) print() self._patch_threads = None self._images = ImagesLoader(self._args.input_dir, fast_count=True) self._alignments = Alignments(self._args, False, self._images.is_video) if self._alignments.version == 1.0: logger.error("The alignments file format has been updated since the given alignments " "file was generated. You need to update the file to proceed.") logger.error("To do this run the 'Alignments Tool' > 'Extract' Job.") sys.exit(1) self._opts = OptionalActions(self._args, self._images.file_list, self._alignments) self._add_queues() self._disk_io = DiskIO(self._alignments, self._images, arguments) self._predictor = Predict(self._disk_io.load_queue, self._queue_size, self._faces, arguments) self._validate() get_folder(self._args.output_dir) configfile = self._args.configfile if hasattr(self._args, "configfile") else None self._converter = Converter(self._predictor.output_size, self._predictor.coverage_ratio, self._predictor.centering, self._disk_io.draw_transparent, self._disk_io.pre_encode, arguments, configfile=configfile) logger.debug("Initialized %s", self.__class__.__name__)