Skip to content

Commit

Permalink
Mask plugin cleanup
Browse files Browse the repository at this point in the history
- PEP8 Fixes
- Remove config for non NN Masks
- Tidy up defaults helptext
- cli.py fix typos
- Remove unused imports and functions _base.py
- Standardize input_size param
- Enable and update documentation
- Change references from `aligner` to `masker`
- Change  input_size, output_size and coverage_ratio from kwargs to params
- Move load_aligned to batch input iterator
- Remove unnecessary self.input param
- Add softmax layer append function to KSession
- Remove references to KSession protected objects
- Standardize plugin output into finalize method
- Make masks full frame and add to lib.faces_detect
- Add masks to alignments.json (temporary zipped base64 solution)
  • Loading branch information
torzdf committed Oct 11, 2019
1 parent 995a857 commit 468e270
Show file tree
Hide file tree
Showing 21 changed files with 222 additions and 502 deletions.
7 changes: 7 additions & 0 deletions docs/full/plugins.extract.mask._base.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins.extract.mask._base module
======================================

.. automodule:: plugins.extract.mask._base
:members:
:undoc-members:
:show-inheritance:
17 changes: 17 additions & 0 deletions docs/full/plugins.extract.mask.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins.extract.mask package
==============================

Submodules
----------

.. toctree::

plugins.extract.mask._base

Module contents
---------------

.. automodule:: plugins.extract.mask
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions docs/full/plugins.extract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Subpackages

plugins.extract.align
plugins.extract.detect
plugins.extract.mask

Submodules
----------
Expand Down
4 changes: 2 additions & 2 deletions lib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,10 +581,10 @@ def get_optional_arguments():
"channel that will not mask any portion of the image."
"\nL|components: Mask designed to provide facial "
"segmentation based on the positioning of landmark "
"locations. A convenx hull is constructed around the "
"locations. A convex hull is constructed around the "
"exterior of the landmarks to create a mask."
"\nL|extended: Mask designed to provide facial segmentation "
"based on the positioning of landmark locations. A convenx "
"based on the positioning of landmark locations. A convex "
"hull is constructed around the exterior of the landmarks "
"and the mask is extended upwards onto the forehead."
"\nL|vgg-clear: Mask designed to provide smart segmentation "
Expand Down
41 changes: 27 additions & 14 deletions lib/faces_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,26 @@ class DetectedFace():
landmarks_xy: list
The 68 point landmarks as discovered in :mod:`plugins.extract.align`. Should be a ``list``
of 68 `(x, y)` ``tuples`` with each of the landmark co-ordinates.
mask: dict
The generated mask(s) for the face as generated in :mod:`plugins.extract.mask`. Must be a
dict of `{name (str): mask (numpy.ndarray)}
"""
def __init__(self, image=None, x=None, w=None, y=None, h=None,
landmarks_xy=None, filename=None):
landmarks_xy=None, mask=None, filename=None):
logger.trace("Initializing %s: (image: %s, x: %s, w: %s, y: %s, h:%s, "
"landmarks_xy: %s, filename: %s)",
self.__class__.__name__,
image.shape if image is not None and image.any() else image,
x, w, y, h, landmarks_xy, filename)
x, w, y, h, landmarks_xy,
{k: v.shape for k, v in mask} if mask is not None else mask,
filename)
self.image = image
self.x = x
self.w = w
self.y = y
self.h = h
self.x = x # pylint:disable=invalid-name
self.w = w # pylint:disable=invalid-name
self.y = y # pylint:disable=invalid-name
self.h = h # pylint:disable=invalid-name
self.landmarks_xy = landmarks_xy
self.mask = dict() if mask is None else mask
self.filename = filename
self.hash = None
self.face = None
Expand Down Expand Up @@ -96,7 +102,7 @@ def to_alignment(self):
-------
alignment: dict
The alignment dict will be returned with the keys ``x``, ``w``, ``y``, ``h``,
``landmarks_xy``, ``hash``.
``landmarks_xy``, ``mask``, ``hash``.
"""

alignment = dict()
Expand All @@ -106,6 +112,7 @@ def to_alignment(self):
alignment["h"] = self.h
alignment["landmarks_xy"] = self.landmarks_xy
alignment["hash"] = self.hash
alignment["mask"] = self.mask
logger.trace("Returning: %s", alignment)
return alignment

Expand All @@ -117,8 +124,11 @@ def from_alignment(self, alignment, image=None):
----------
alignment: dict
A dictionary entry for a face from an alignments file containing the keys
``x``, ``w``, ``y``, ``h``, ``landmarks_xy``. Optionally the key ``hash``
will be provided, but not all use cases will know the face hash at this time.
``x``, ``w``, ``y``, ``h``, ``landmarks_xy``.
Optionally the key ``hash`` will be provided, but not all use cases will know the
face hash at this time.
Optionally the key ``mask`` will be provided, but legacy alignments will not have
this key.
image: numpy.ndarray, optional
If an image is passed in, then the ``image`` attribute will
be set to the cropped face based on the passed in bounding box co-ordinates
Expand All @@ -133,6 +143,8 @@ def from_alignment(self, alignment, image=None):
self.landmarks_xy = alignment["landmarks_xy"]
# Manual tool does not know the final hash so default to None
self.hash = alignment.get("hash", None)
# Manual tool and legacy alignments will not have a mask
self.mask = alignment.get("mask", None)
if image is not None and image.any():
self.image = image
self._image_to_face(image)
Expand All @@ -144,7 +156,7 @@ def _image_to_face(self, image):
""" set self.image to be the cropped face from detected bounding box """
logger.trace("Cropping face from image")
self.face = image[self.top: self.bottom,
self.left: self.right]
self.left: self.right]

# <<< Aligned Face methods and properties >>> #
def load_aligned(self, image, size=256, coverage_ratio=1.0, dtype=None):
Expand Down Expand Up @@ -199,7 +211,8 @@ def load_aligned(self, image, size=256, coverage_ratio=1.0, dtype=None):
for k, v in self.aligned.items()
if k != "face"})

def _padding_from_coverage(self, size, coverage_ratio):
@staticmethod
def _padding_from_coverage(size, coverage_ratio):
""" Return the image padding for a face from coverage_ratio set against a
pre-padded training image """
padding = int((size * (coverage_ratio - 0.625)) / 2)
Expand Down Expand Up @@ -240,7 +253,7 @@ def load_feed_face(self, image, size=64, coverage_ratio=0.625, dtype=None):
self.feed["face"] = face if dtype is None else face.astype(dtype)

logger.trace("Loaded feed face. (face_shape: %s, matrix: %s)",
self.feed_face.shape, self._feed_matrix)
self.feed_face.shape, self.feed_matrix)

def load_reference_face(self, image, size=64, coverage_ratio=0.625, dtype=None):
""" Align a face in the correct dimensions for reference against the output from a model.
Expand Down Expand Up @@ -354,7 +367,7 @@ def feed_landmarks(self):
return landmarks

@property
def _feed_matrix(self):
def feed_matrix(self):
""" numpy.ndarray: The adjusted matrix face sized for feeding into a model. Only available
after :func:`load_feed_face` has been called with an image, otherwise returns ``None`` """
if not self.feed:
Expand All @@ -372,7 +385,7 @@ def feed_interpolators(self):
``None``"""
if not self.feed:
return None
return get_matrix_scaling(self._feed_matrix)
return get_matrix_scaling(self.feed_matrix)

@property
def reference_face(self):
Expand Down
17 changes: 17 additions & 0 deletions lib/model/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging

import tensorflow as tf
from keras.layers import Activation
from keras.models import load_model as k_load_model, Model
import numpy as np

Expand Down Expand Up @@ -165,3 +166,19 @@ def load_model_weights(self):
with self._session.as_default(): # pylint: disable=not-context-manager
with self._session.graph.as_default():
self._model.load_weights(self._model_path)

def append_softmax_activation(self, layer_index=-1):
""" Append a softmax activation layer to a model
Occasionally a softmax activation layer needs to be added to a model's output.
This is a convenience fuction to append this layer to the loaded model.
Parameters
----------
layer_index: int, optional
The layer index of the model to select the output from to use as an input to the
softmax activation layer. Default: -1 (The final layer of the model)
"""
logger.debug("Appending Softmax Activation to model: (layer_index: %s)", layer_index)
softmax = Activation("softmax", name="softmax")(self._model.layers[layer_index].output)
self._model = Model(inputs=self._model.input, outputs=[softmax])
Loading

0 comments on commit 468e270

Please sign in to comment.