示例#1
0
    def _rotate_image_by_angle(self, image, angle):
        """ Rotate an image by a given angle.
            From: https://stackoverflow.com/questions/22041699 """

        logger.trace("Rotating image: (image: %s, angle: %s)", image.shape,
                     angle)
        channels_first = image.shape[0] <= 4
        if channels_first:
            image = np.moveaxis(image, 0, 2)

        height, width = image.shape[:2]
        image_center = (width / 2, height / 2)
        rotation_matrix = cv2.getRotationMatrix2D(  # pylint: disable=no-member
            image_center, -1. * angle, 1.)
        rotation_matrix[0, 2] += self.input_size / 2 - image_center[0]
        rotation_matrix[1, 2] += self.input_size / 2 - image_center[1]
        logger.trace("Rotated image: (rotation_matrix: %s", rotation_matrix)
        image = cv2.warpAffine(
            image,  # pylint: disable=no-member
            rotation_matrix,
            (self.input_size, self.input_size))
        if channels_first:
            image = np.moveaxis(image, 2, 0)

        return image, rotation_matrix
示例#2
0
    def _predict(self, batch):
        """ Wrap models predict function in rotations """
        batch["rotmat"] = [np.array([]) for _ in range(len(batch["feed"]))]
        found_faces = [np.array([]) for _ in range(len(batch["feed"]))]
        for angle in self.rotation:
            # Rotate the batch and insert placeholders for already found faces
            self._rotate_batch(batch, angle)
            try:
                batch = self.predict(batch)
            except tf_errors.ResourceExhaustedError as err:
                msg = (
                    "You do not have enough GPU memory available to run detection at the "
                    "selected batch size. You can try a number of things:"
                    "\n1) Close any other application that is using your GPU (web browsers are "
                    "particularly bad for this)."
                    "\n2) Lower the batchsize (the amount of images fed into the model) by "
                    "editing the plugin settings (GUI: Settings > Configure extract settings, "
                    "CLI: Edit the file faceswap/config/extract.ini)."
                    "\n3) Enable 'Single Process' mode.")
                raise FaceswapError(msg) from err
            except Exception as err:
                if get_backend() == "amd":
                    # pylint:disable=import-outside-toplevel
                    from lib.plaidml_utils import is_plaidml_error
                    if (is_plaidml_error(err)
                            and ("CL_MEM_OBJECT_ALLOCATION_FAILURE"
                                 in str(err).upper()
                                 or "enough memory for the current schedule"
                                 in str(err).lower())):
                        msg = (
                            "You do not have enough GPU memory available to run detection at "
                            "the selected batch size. You can try a number of things:"
                            "\n1) Close any other application that is using your GPU (web "
                            "browsers are particularly bad for this)."
                            "\n2) Lower the batchsize (the amount of images fed into the "
                            "model) by editing the plugin settings (GUI: Settings > Configure "
                            "extract settings, CLI: Edit the file "
                            "faceswap/config/extract.ini).")
                        raise FaceswapError(msg) from err
                raise

            if angle != 0 and any([face.any()
                                   for face in batch["prediction"]]):
                logger.verbose("found face(s) by rotating image %s degrees",
                               angle)

            found_faces = [
                face if not found.any() else found
                for face, found in zip(batch["prediction"], found_faces)
            ]

            if all([face.any() for face in found_faces]):
                logger.trace("Faces found for all images")
                break

        batch["prediction"] = found_faces
        logger.trace(
            "detect_prediction output: (filenames: %s, prediction: %s, rotmat: %s)",
            batch["filename"], batch["prediction"], batch["rotmat"])
        return batch
示例#3
0
文件: _base.py 项目: endcrypt/Deepvid
    def _get_adjusted_boxes(self, original_boxes):
        """ Obtain an array of adjusted bounding boxes based on the number of re-feed iterations
        that have been selected and the minimum dimension of the original bounding box.

        Parameters
        ----------
        original_boxes: :class:`numpy.ndarray`
            The original ('x', 'y', 'w', 'h') detected face boxes corresponding to the incoming
            detected face objects

        Returns
        -------
        :class:`numpy.ndarray`
            The original boxes (in position 0) and the randomly adjusted bounding boxes
        """
        if self._re_feed == 0:
            return original_boxes[None, ...]
        beta = 0.05
        max_shift = np.min(original_boxes[..., 2:], axis=1) * beta
        rands = np.random.rand(self._re_feed, *original_boxes.shape) * 2 - 1
        new_boxes = np.rint(original_boxes +
                            (rands * max_shift[None, :, None])).astype("int32")
        retval = np.concatenate((original_boxes[None, ...], new_boxes))
        logger.trace(retval)
        return retval
示例#4
0
    def _predict(self, batch):
        """ Wrap models predict function in rotations """
        batch["rotmat"] = [np.array([]) for _ in range(len(batch["feed"]))]
        found_faces = [np.array([]) for _ in range(len(batch["feed"]))]
        for angle in self.rotation:
            # Rotate the batch and insert placeholders for already found faces
            self._rotate_batch(batch, angle)
            batch = self.predict(batch)

            if angle != 0 and any([face.any()
                                   for face in batch["prediction"]]):
                logger.verbose("found face(s) by rotating image %s degrees",
                               angle)

            found_faces = [
                face if not found.any() else found
                for face, found in zip(batch["prediction"], found_faces)
            ]

            if all([face.any() for face in found_faces]):
                logger.trace("Faces found for all images")
                break

        batch["prediction"] = found_faces
        logger.trace(
            "detect_prediction output: (filenames: %s, prediction: %s, rotmat: %s)",
            batch["filename"], batch["prediction"], batch["rotmat"])
        return batch
示例#5
0
 def _remove_zero_sized_faces(batch):
     """ Remove items from dict where detected face is of zero size
         or face falls entirely outside of image """
     dims = [img.shape[:2] for img in batch["image"]]
     logger.trace("image dims: %s", dims)
     batch["detected_faces"] = [[
         face for face in faces if face.right > 0 and face.left < dim[1]
         and face.bottom > 0 and face.top < dim[0]
     ] for dim, faces in zip(dims, batch.get("detected_faces", list()))]
示例#6
0
    def get_batch(self, queue):
        """ Get items for inputting to the detector plugin in batches

        Items are returned from the ``queue`` in batches of
        :attr:`~plugins.extract._base.Extractor.batchsize`

        Remember to put ``'EOF'`` to the out queue after processing
        the final batch

        Outputs items in the following format. All lists are of length
        :attr:`~plugins.extract._base.Extractor.batchsize`:

        >>> {'filename': [<filenames of source frames>],
        >>>  'image': [<source images>],
        >>>  'scaled_image': <np.array of images standardized for prediction>,
        >>>  'scale': [<scaling factors for each image>],
        >>>  'pad': [<padding for each image>],
        >>>  'detected_faces': [[<lib.faces_detect.DetectedFace objects]]}

        Parameters
        ----------
        queue : queue.Queue()
            The ``queue`` that the batch will be fed from. This will be a queue that loads
            images.

        Returns
        -------
        exhausted, bool
            ``True`` if queue is exhausted, ``False`` if not.
        batch, dict
            A dictionary of lists of :attr:`~plugins.extract._base.Extractor.batchsize`.
        """
        exhausted = False
        batch = dict()
        for _ in range(self.batchsize):
            item = self._get_item(queue)
            if item == "EOF":
                exhausted = True
                break
            for key, val in item.items():
                batch.setdefault(key, []).append(val)
            scaled_image, scale, pad = self._compile_detection_image(
                item["image"])
            batch.setdefault("scaled_image", []).append(scaled_image)
            batch.setdefault("scale", []).append(scale)
            batch.setdefault("pad", []).append(pad)
        if batch:
            batch["scaled_image"] = np.array(batch["scaled_image"],
                                             dtype="float32")
            logger.trace(
                "Returning batch: %s", {
                    k: v.shape if isinstance(v, np.ndarray) else v
                    for k, v in batch.items() if k != "image"
                })
        else:
            logger.trace(item)
        return exhausted, batch
示例#7
0
    def finalize(self, batch):
        """ Finalize the output from Masker

        This should be called as the final task of each `plugin`.

        It strips unneeded items from the :attr:`batch` ``dict`` and pairs the detected faces back
        up with their original frame before yielding each frame.

        Outputs items in the format:

        >>> {'image': [<original frame>],
        >>>  'filename': [<frame filename>),
        >>>  'detected_faces': [<lib.faces_detect.DetectedFace objects>]}

        Parameters
        ----------
        batch : dict
            The final ``dict`` from the `plugin` process. It must contain the `keys`:
            ``detected_faces``, ``filename``, ``image``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and list of
            :class:`lib.faces_detect.DetectedFace` objects.

        """
        for mask, face in zip(batch["prediction"], batch["detected_faces"]):
            face.add_mask(self._storage_name,
                          mask,
                          face.feed_matrix,
                          face.feed_interpolators[1],
                          storage_size=self._storage_size)
            face.feed = dict()

        self._remove_invalid_keys(batch,
                                  ("detected_faces", "filename", "image"))
        logger.trace(
            "Item out: %s",
            {key: val
             for key, val in batch.items() if key != "image"})
        for filename, image, face in zip(batch["filename"], batch["image"],
                                         batch["detected_faces"]):
            self._output_faces.append(face)
            if len(self._output_faces) != self._faces_per_filename[filename]:
                continue
            retval = dict(filename=filename,
                          image=image,
                          detected_faces=self._output_faces)

            self._output_faces = []
            logger.trace(
                "Yielding: (filename: '%s', image: %s, detected_faces: %s)",
                retval["filename"], retval["image"].shape,
                len(retval["detected_faces"]))
            yield retval
示例#8
0
 def _scale_image(image, image_size, scale):
     """ Scale the image and optional pad to given size """
     interpln = cv2.INTER_CUBIC if scale > 1.0 else cv2.INTER_AREA
     if scale != 1.0:
         dims = (int(image_size[1] * scale), int(image_size[0] * scale))
         logger.trace("Resizing detection image from %s to %s. Scale=%s",
                      "x".join(str(i) for i in reversed(image_size)),
                      "x".join(str(i) for i in dims), scale)
         image = cv2.resize(image, dims, interpolation=interpln)
     logger.trace("Resized image shape: %s", image.shape)
     return image
示例#9
0
 def _remove_zero_sized_faces(self, batch_faces):
     """ Remove items from batch_faces where detected face is of zero size
         or face falls entirely outside of image """
     logger.trace("Input sizes: %s", [len(face) for face in batch_faces])
     retval = [[face
                for face in faces
                if face.right > 0 and face.left < self.input_size
                and face.bottom > 0 and face.top < self.input_size]
               for faces in batch_faces]
     logger.trace("Output sizes: %s", [len(face) for face in retval])
     return retval
示例#10
0
    def _normalize_faces(self, faces):
        """ Normalizes the face for feeding into model

        The normalization method is dictated by the command line argument `-nh (--normalization)`
        """
        if self.normalize_method is None:
            return faces
        logger.trace("Normalizing faces")
        meth = getattr(self, "_normalize_{}".format(self.normalize_method.lower()))
        faces = [meth(face) for face in faces]
        logger.trace("Normalized faces")
        return faces
示例#11
0
 def _pad_image(self, image):
     """ Pad a resized image to input size """
     height, width = image.shape[:2]
     if width < self.input_size or height < self.input_size:
         pad_l = (self.input_size - width) // 2
         pad_r = (self.input_size - width) - pad_l
         pad_t = (self.input_size - height) // 2
         pad_b = (self.input_size - height) - pad_t
         image = cv2.copyMakeBorder(  # pylint:disable=no-member
             image, pad_t, pad_b, pad_l, pad_r, cv2.BORDER_CONSTANT)  # pylint:disable=no-member
     logger.trace("Padded image shape: %s", image.shape)
     return image
示例#12
0
    def _compile_detection_image(self, input_image):
        """ Compile the detection image for feeding into the model"""
        image = self._convert_color(input_image)

        image_size = image.shape[:2]
        scale = self._set_scale(image_size)
        pad = self._set_padding(image_size, scale)

        image = self._scale_image(image, image_size, scale)
        image = self._pad_image(image)
        logger.trace("compiled: (images shape: %s, scale: %s, pad: %s)", image.shape, scale, pad)
        return image, scale, pad
示例#13
0
    def finalize(self, batch):
        """ Finalize the output from Masker

        This should be called as the final task of each `plugin`.

        Pairs the detected faces back up with their original frame before yielding each frame.

        Parameters
        ----------
        batch : dict
            The final ``dict`` from the `plugin` process. It must contain the `keys`:
            ``detected_faces``, ``filename``, ``feed_faces``, ``roi_masks``

        Yields
        ------
        :class:`~plugins.extract.pipeline.ExtractMedia`
            The :attr:`DetectedFaces` list will be populated for this class with the bounding
            boxes, landmarks and masks for the detected faces found in the frame.
        """
        for mask, face, feed_face, roi_mask in zip(batch["prediction"],
                                                   batch["detected_faces"],
                                                   batch["feed_faces"],
                                                   batch["roi_masks"]):
            self._crop_out_of_bounds(mask, roi_mask)
            face.add_mask(self._storage_name,
                          mask,
                          feed_face.adjusted_matrix,
                          feed_face.interpolators[1],
                          storage_size=self._storage_size,
                          storage_centering=self._storage_centering)
        del batch["feed_faces"]

        logger.trace(
            "Item out: %s", {
                key: val.shape if isinstance(val, np.ndarray) else val
                for key, val in batch.items()
            })
        for filename, face in zip(batch["filename"], batch["detected_faces"]):
            self._output_faces.append(face)
            if len(self._output_faces) != self._faces_per_filename[filename]:
                continue

            output = self._extract_media.pop(filename)
            output.add_detected_faces(self._output_faces)
            self._output_faces = []
            logger.trace(
                "Yielding: (filename: '%s', image: %s, detected_faces: %s)",
                output.filename, output.image_shape,
                len(output.detected_faces))
            yield output
示例#14
0
 def _collect_item(self, queue):
     """ Collect the item from the :attr:`_rollover` dict or from the queue
         Add face count per frame to self._faces_per_filename for joining
         batches back up in finalize """
     if self._rollover is not None:
         logger.trace("Getting from _rollover: (filename: `%s`, faces: %s)",
                      self._rollover.filename, len(self._rollover.detected_faces))
         item = self._rollover
         self._rollover = None
     else:
         item = self._get_item(queue)
         if item != "EOF":
             logger.trace("Getting from queue: (filename: %s, faces: %s)",
                          item.filename, len(item.detected_faces))
             self._faces_per_filename[item.filename] = len(item.detected_faces)
     return item
示例#15
0
    def _compile_detection_image(self, item):
        """ Compile the detection image for feeding into the model

        Parameters
        ----------
        item: :class:`plugins.extract.pipeline.ExtractMedia`
            The input item from the pipeline
        """
        image = item.get_image_copy(self.color_format)
        scale = self._set_scale(item.image_size)
        pad = self._set_padding(item.image_size, scale)

        image = self._scale_image(image, item.image_size, scale)
        image = self._pad_image(image)
        logger.trace("compiled: (images shape: %s, scale: %s, pad: %s)", image.shape, scale, pad)
        return image, scale, pad
示例#16
0
 def _collect_item(self, queue):
     """ Collect the item from the _rollover dict or from the queue
         Add face count per frame to self._faces_per_filename for joining
         batches back up in finalize """
     if self._rollover:
         logger.trace("Getting from _rollover: (filename: `%s`, faces: %s)",
                      self._rollover["filename"], len(self._rollover["detected_faces"]))
         item = self._rollover
         self._rollover = dict()
     else:
         item = self._get_item(queue)
         if item != "EOF":
             logger.trace("Getting from queue: (filename: %s, faces: %s)",
                          item["filename"], len(item["detected_faces"]))
             self._faces_per_filename[item["filename"]] = len(item["detected_faces"])
     return item
示例#17
0
    def finalize(self, batch):
        """ Finalize the output from Aligner

        This should be called as the final task of each `plugin`.

        Pairs the detected faces back up with their original frame before yielding each frame.

        Parameters
        ----------
        batch : dict
            The final ``dict`` from the `plugin` process. It must contain the `keys`:
            ``detected_faces``, ``landmarks``, ``filename``

        Yields
        ------
        :class:`~plugins.extract.pipeline.ExtractMedia`
            The :attr:`DetectedFaces` list will be populated for this class with the bounding boxes
            and landmarks for the detected faces found in the frame.
        """

        for face, landmarks in zip(batch["detected_faces"],
                                   batch["landmarks"]):
            if not isinstance(landmarks, np.ndarray):
                landmarks = np.array(landmarks)
            face.landmarks_xy = landmarks

        logger.trace(
            "Item out: %s", {
                key: val.shape if isinstance(val, np.ndarray) else val
                for key, val in batch.items()
            })

        for filename, face in zip(batch["filename"], batch["detected_faces"]):
            self._output_faces.append(face)
            if len(self._output_faces) != self._faces_per_filename[filename]:
                continue

            output = self._extract_media.pop(filename)
            output.add_detected_faces(self._output_faces)
            self._output_faces = []

            logger.trace(
                "Final Output: (filename: '%s', image shape: %s, detected_faces: %s, "
                "item: %s)", output.filename, output.image_shape,
                output.detected_faces, output)
            yield output
示例#18
0
    def _rotate_face(face, rotation_matrix):
        """ Rotates the detection bounding box around the given rotation matrix.

        Parameters
        ----------
        face: :class:`DetectedFace`
            A :class:`DetectedFace` containing the `x`, `w`, `y`, `h` detection bounding box
            points.
        rotation_matrix: numpy.ndarray
            The rotation matrix to rotate the given object by.

        Returns
        -------
        :class:`DetectedFace`
            The same class with the detection bounding box points rotated by the given matrix.
        """
        logger.trace("Rotating face: (face: %s, rotation_matrix: %s)", face, rotation_matrix)
        bounding_box = [[face.left, face.top],
                        [face.right, face.top],
                        [face.right, face.bottom],
                        [face.left, face.bottom]]
        rotation_matrix = cv2.invertAffineTransform(rotation_matrix)

        points = np.array(bounding_box, "int32")
        points = np.expand_dims(points, axis=0)
        transformed = cv2.transform(points, rotation_matrix).astype("int32")
        rotated = transformed.squeeze()

        # Bounding box should follow x, y planes, so get min/max for non-90 degree rotations
        pt_x = min([pnt[0] for pnt in rotated])
        pt_y = min([pnt[1] for pnt in rotated])
        pt_x1 = max([pnt[0] for pnt in rotated])
        pt_y1 = max([pnt[1] for pnt in rotated])
        width = pt_x1 - pt_x
        height = pt_y1 - pt_y

        face.x = int(pt_x)
        face.y = int(pt_y)
        face.w = int(width)
        face.h = int(height)
        return face
示例#19
0
    def finalize(self, batch):
        """ Finalize the output from Detector

        This should be called as the final task of each ``plugin``.

        Parameters
        ----------
        batch : dict
            The final ``dict`` from the `plugin` process. It must contain the keys  ``filename``,
            ``faces``

        Yields
        ------
        :class:`~plugins.extract.pipeline.ExtractMedia`
            The :attr:`DetectedFaces` list will be populated for this class with the bounding boxes
            for the detected faces found in the frame.
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace("Item out: %s", {k: v.shape if isinstance(v, np.ndarray) else v
                                      for k, v in batch.items()})

        batch_faces = [[self.to_detected_face(face[0], face[1], face[2], face[3])
                        for face in faces]
                       for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[self._rotate_face(face, rotmat) if rotmat.any() else face
                            for face in faces]
                           for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Remove zero sized faces
        batch_faces = self._remove_zero_sized_faces(batch_faces)

        # Scale back out to original frame
        batch["detected_faces"] = [[self.to_detected_face((face.left - pad[0]) / scale,
                                                          (face.top - pad[1]) / scale,
                                                          (face.right - pad[0]) / scale,
                                                          (face.bottom - pad[1]) / scale)
                                    for face in faces]
                                   for scale, pad, faces in zip(batch["scale"],
                                                                batch["pad"],
                                                                batch_faces)]

        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(batch["detected_faces"])

        batch = self._dict_lists_to_list_dicts(batch)
        for item in batch:
            output = self._extract_media.pop(item["filename"])
            output.add_detected_faces(item["detected_faces"])
            logger.trace("final output: (filename: '%s', image shape: %s, detected_faces: %s, "
                         "item: %s", output.filename, output.image_shape, output.detected_faces,
                         output)
            yield output
示例#20
0
    def get_batch(self, queue):
        """ Get items for inputting into the masker from the queue in batches

        Items are returned from the ``queue`` in batches of
        :attr:`~plugins.extract._base.Extractor.batchsize`

        To ensure consistent batch sizes for masker the items are split into separate items for
        each :class:`lib.faces_detect.DetectedFace` object.

        Remember to put ``'EOF'`` to the out queue after processing
        the final batch

        Outputs items in the following format. All lists are of length
        :attr:`~plugins.extract._base.Extractor.batchsize`:

        >>> {'filename': [<filenames of source frames>],
        >>>  'image': [<source images>],
        >>>  'detected_faces': [[<lib.faces_detect.DetectedFace objects]]}

        Parameters
        ----------
        queue : queue.Queue()
            The ``queue`` that the plugin will be fed from.

        Returns
        -------
        exhausted, bool
            ``True`` if queue is exhausted, ``False`` if not
        batch, dict
            A dictionary of lists of :attr:`~plugins.extract._base.Extractor.batchsize`:
        """
        exhausted = False
        batch = dict()
        idx = 0
        while idx < self.batchsize:
            item = self._collect_item(queue)
            if item == "EOF":
                logger.trace("EOF received")
                exhausted = True
                break
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item["detected_faces"]:
                self._queues["out"].put(item)
                continue
            for f_idx, face in enumerate(item["detected_faces"]):
                face.image = self._convert_color(item["image"])
                face.load_feed_face(face.image,
                                    size=self.input_size,
                                    coverage_ratio=1.0,
                                    dtype="float32",
                                    is_aligned_face=self._image_is_aligned)
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item["filename"])
                batch.setdefault("image", []).append(item["image"])
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item["detected_faces"])
                    if f_idx + 1 != frame_faces:
                        self._rollover = {
                            k: v[f_idx + 1:] if k == "detected_faces" else v
                            for k, v in item.items()
                        }
                        logger.trace(
                            "Rolled over %s faces of %s to next batch for '%s'",
                            len(self._rollover["detected_faces"]), frame_faces,
                            item["filename"])
                    break
        if batch:
            logger.trace(
                "Returning batch: %s", {
                    k: v.shape if isinstance(v, np.ndarray) else v
                    for k, v in batch.items()
                })
        else:
            logger.trace(item)
        return exhausted, batch
示例#21
0
    def get_batch(self, queue):
        """ Get items for inputting into the aligner from the queue in batches

        Items are returned from the ``queue`` in batches of
        :attr:`~plugins.extract._base.Extractor.batchsize`

        Items are received as :class:`~plugins.extract.pipeline.ExtractMedia` objects and converted
        to ``dict`` for internal processing.

        To ensure consistent batch sizes for aligner the items are split into separate items for
        each :class:`~lib.faces_detect.DetectedFace` object.

        Remember to put ``'EOF'`` to the out queue after processing
        the final batch

        Outputs items in the following format. All lists are of length
        :attr:`~plugins.extract._base.Extractor.batchsize`:

        >>> {'filename': [<filenames of source frames>],
        >>>  'image': [<source images>],
        >>>  'detected_faces': [[<lib.faces_detect.DetectedFace objects]]}

        Parameters
        ----------
        queue : queue.Queue()
            The ``queue`` that the plugin will be fed from.

        Returns
        -------
        exhausted, bool
            ``True`` if queue is exhausted, ``False`` if not
        batch, dict
            A dictionary of lists of :attr:`~plugins.extract._base.Extractor.batchsize`:
        """
        exhausted = False
        batch = dict()
        idx = 0
        while idx < self.batchsize:
            item = self._collect_item(queue)
            if item == "EOF":
                logger.trace("EOF received")
                exhausted = True
                break
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item.detected_faces:
                self._queues["out"].put(item)
                continue

            converted_image = item.get_image_copy(self.color_format)
            for f_idx, face in enumerate(item.detected_faces):
                batch.setdefault("image", []).append(converted_image)
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("filename", []).append(item.filename)
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item.detected_faces)
                    if f_idx + 1 != frame_faces:
                        self._rollover = ExtractMedia(
                            item.filename,
                            item.image,
                            detected_faces=item.detected_faces[f_idx + 1:])
                        logger.trace(
                            "Rolled over %s faces of %s to next batch for '%s'",
                            len(self._rollover.detected_faces), frame_faces,
                            item.filename)
                    break
        if batch:
            logger.trace(
                "Returning batch: %s", {
                    k: v.shape if isinstance(v, np.ndarray) else v
                    for k, v in batch.items()
                })
        else:
            logger.trace(item)
        return exhausted, batch
示例#22
0
 def _set_scale(self, image_size):
     """ Set the scale factor for incoming image """
     scale = self.input_size / max(image_size)
     logger.trace("Detector scale: %s", scale)
     return scale
示例#23
0
    def get_batch(self, queue):
        """ Get items for inputting into the masker from the queue in batches

        Items are returned from the ``queue`` in batches of
        :attr:`~plugins.extract._base.Extractor.batchsize`

        Items are received as :class:`~plugins.extract.pipeline.ExtractMedia` objects and converted
        to ``dict`` for internal processing.

        To ensure consistent batch sizes for masker the items are split into separate items for
        each :class:`~lib.align.DetectedFace` object.

        Remember to put ``'EOF'`` to the out queue after processing
        the final batch

        Outputs items in the following format. All lists are of length
        :attr:`~plugins.extract._base.Extractor.batchsize`:

        >>> {'filename': [<filenames of source frames>],
        >>>  'detected_faces': [[<lib.align.DetectedFace objects]]}

        Parameters
        ----------
        queue : queue.Queue()
            The ``queue`` that the plugin will be fed from.

        Returns
        -------
        exhausted, bool
            ``True`` if queue is exhausted, ``False`` if not
        batch, dict
            A dictionary of lists of :attr:`~plugins.extract._base.Extractor.batchsize`:
        """
        exhausted = False
        batch = dict()
        idx = 0
        while idx < self.batchsize:
            item = self._collect_item(queue)
            if item == "EOF":
                logger.trace("EOF received")
                exhausted = True
                break
            # Put frames with no faces into the out queue to keep TQDM consistent
            if not item.detected_faces:
                self._queues["out"].put(item)
                continue
            for f_idx, face in enumerate(item.detected_faces):

                image = item.get_image_copy(self.color_format)
                roi = np.ones((*item.image_size[:2], 1), dtype="float32")

                if not self._image_is_aligned:
                    # Add the ROI mask to image so we can get the ROI mask with a single warp
                    image = np.concatenate([image, roi], axis=-1)

                feed_face = AlignedFace(face.landmarks_xy,
                                        image=image,
                                        centering=self._storage_centering,
                                        size=self.input_size,
                                        coverage_ratio=self.coverage_ratio,
                                        dtype="float32",
                                        is_aligned=self._image_is_aligned)

                if not self._image_is_aligned:
                    # Split roi mask from feed face alpha channel
                    roi_mask = feed_face.face[..., 3]
                    feed_face._face = feed_face.face[..., :3]  # pylint:disable=protected-access
                else:
                    # We have to do the warp here as AlignedFace did not perform it
                    roi_mask = transform_image(roi,
                                               feed_face.matrix,
                                               feed_face.size,
                                               padding=feed_face.padding)

                batch.setdefault("roi_masks", []).append(roi_mask)
                batch.setdefault("detected_faces", []).append(face)
                batch.setdefault("feed_faces", []).append(feed_face)
                batch.setdefault("filename", []).append(item.filename)
                idx += 1
                if idx == self.batchsize:
                    frame_faces = len(item.detected_faces)
                    if f_idx + 1 != frame_faces:
                        self._rollover = ExtractMedia(
                            item.filename,
                            item.image,
                            detected_faces=item.detected_faces[f_idx + 1:])
                        logger.trace(
                            "Rolled over %s faces of %s to next batch for '%s'",
                            len(self._rollover.detected_faces), frame_faces,
                            item.filename)
                    break
        if batch:
            logger.trace(
                "Returning batch: %s", {
                    k: v.shape if isinstance(v, np.ndarray) else v
                    for k, v in batch.items()
                })
        else:
            logger.trace(item)
        return exhausted, batch
示例#24
0
 def _rotate_rect(bounding_box, rotation_matrix):
     """ Rotate a bounding box dict based on the rotation_matrix"""
     logger.trace("Rotating bounding box")
     bounding_box = rotate_landmarks(bounding_box, rotation_matrix)
     return bounding_box
示例#25
0
    def finalize(self, batch):
        """ Finalize the output from Detector

        This should be called as the final task of each ``plugin``.

        It strips unneeded items from the :attr:`batch` ``dict`` and performs standard final
        processing on each item

        Outputs items in the format:

        >>> {'image': [<original frame>],
        >>>  'filename': [<frame filename>),
        >>>  'detected_faces': [<lib.faces_detect.DetectedFace objects>]}


        Parameters
        ----------
        batch : dict
            The final ``dict`` from the `plugin` process. It must contain the keys ``image``,
            ``filename``, ``faces``

        Yields
        ------
        dict
            A ``dict`` for each frame containing the ``image``, ``filename`` and ``list`` of
            ``detected_faces``
        """
        if not isinstance(batch, dict):
            logger.trace("Item out: %s", batch)
            return batch

        logger.trace(
            "Item out: %s", {
                k: v.shape if isinstance(v, np.ndarray) else v
                for k, v in batch.items()
            })

        batch_faces = [[
            self.to_detected_face(face[0], face[1], face[2], face[3])
            for face in faces
        ] for faces in batch["prediction"]]
        # Rotations
        if any(m.any() for m in batch["rotmat"]) and any(batch_faces):
            batch_faces = [[
                self._rotate_rect(face, rotmat) if rotmat.any() else face
                for face in faces
            ] for faces, rotmat in zip(batch_faces, batch["rotmat"])]

        # Scale back out to original frame
        batch["detected_faces"] = [[
            self.to_detected_face(
                (face.left - pad[0]) / scale, (face.top - pad[1]) / scale,
                (face.right - pad[0]) / scale, (face.bottom - pad[1]) / scale)
            for face in faces
        ] for scale, pad, faces in zip(batch["scale"], batch["pad"],
                                       batch_faces)]

        # Remove zero sized faces
        self._remove_zero_sized_faces(batch)
        if self.min_size > 0 and batch.get("detected_faces", None):
            batch["detected_faces"] = self._filter_small_faces(
                batch["detected_faces"])

        self._remove_invalid_keys(batch,
                                  ("detected_faces", "filename", "image"))
        batch = self._dict_lists_to_list_dicts(batch)

        for item in batch:
            logger.trace(
                "final output: %s", {
                    k: v.shape if isinstance(v, np.ndarray) else v
                    for k, v in item.items()
                })
            yield item