diff --git a/bob/bio/face/helpers.py b/bob/bio/face/helpers.py index 8cd4e5b9b1a195debdbed2709cbdaf656b310a06..d74e7a4208d7b522b1f8de579c09711d0f6aa711 100644 --- a/bob/bio/face/helpers.py +++ b/bob/bio/face/helpers.py @@ -1,37 +1,31 @@ #!/usr/bin/env python # vim: set fileencoding=utf-8 : -from bob.bio.face.preprocessor import FaceDetect, FaceCrop, Scale -from skimage.transform import resize -import numpy as np +from bob.bio.face.preprocessor import FaceCrop, Scale + def face_crop_solver( cropped_image_size, color_channel="rgb", cropped_positions=None, fixed_positions=None, - use_face_detector=False, - dtype=np.uint8 + annotator=None, + dtype="uint8", ): """ Decide which face cropper to use. """ - - if use_face_detector: - return FaceDetect( - face_cropper="face-crop-eyes", use_flandmark=True - ) + # If there's not cropped positions, just resize + if cropped_positions is None: + return Scale(cropped_image_size) else: - # If there's not cropped positions, just resize - if cropped_positions is None: - return Scale(cropped_image_size) - else: - # Detects the face and crops it without eye detection - return FaceCrop( - cropped_image_size=cropped_image_size, - cropped_positions=cropped_positions, - color_channel=color_channel, - fixed_positions=fixed_positions, - dtype=dtype - ) + # Detects the face and crops it without eye detection + return FaceCrop( + cropped_image_size=cropped_image_size, + cropped_positions=cropped_positions, + color_channel=color_channel, + fixed_positions=fixed_positions, + dtype=dtype, + annotator=annotator, + ) diff --git a/bob/bio/face/preprocessor/FaceDetect.py b/bob/bio/face/preprocessor/FaceDetect.py deleted file mode 100644 index 479a0eab61e96cb160c7f5bef498392ece2e5813..0000000000000000000000000000000000000000 --- a/bob/bio/face/preprocessor/FaceDetect.py +++ /dev/null @@ -1,241 +0,0 @@ -import math -import numpy - -import bob.ip.facedetect -import bob.ip.flandmark - -import bob.ip.base -import numpy - -from .Base import Base -from .utils import load_cropper_only -from sklearn.utils import check_array -from bob.pipelines.sample import SampleBatch -import logging - -logger = logging.getLogger("bob.bio.face") - - -class FaceDetect(Base): - """Performs a face detection (and facial landmark localization) in the given image and crops the face. - - This class is designed to perform a geometric normalization of the face based on the detected face. - Face detection is performed using :ref:`bob.ip.facedetect <bob.ip.facedetect>`. - Particularly, the function :py:func:`bob.ip.facedetect.detect_single_face` is executed, which will *always* return *exactly one* bounding box, even if the image contains more than one face, or no face at all. - The speed of the face detector can be regulated using the ``cascade``, ``distance` ``scale_base`` and ``lowest_scale`` parameters. - The number of overlapping detected bounding boxes that should be joined can be selected by ``detection_overlap``. - Please see the documentation of :ref:`bob.ip.facedetect <bob.ip.facedetect>` for more details about these parameters. - - Additionally, facial landmarks can be detected using the :ref:`bob.ip.flandmark`. - If enabled using ``use_flandmark = True`` in the constructor, it is tried to obtain the facial landmarks inside the detected facial area. - If landmarks are found, these are used to geometrically normalize the face. - Otherwise, the eye locations are estimated based on the bounding box. - This is also applied, when ``use_flandmark = False.`` - - The face cropping itself is done by the given ``face_cropper``. - This cropper can either be an instance of :py:class:`FaceCrop` (or any other class that provides a similar ``crop_face`` function), or it can be the resource name of a face cropper, such as ``'face-crop-eyes'``. - - **Parameters:** - - face_cropper : :py:class:`bob.bio.face.preprocessor.FaceCrop` or str - The face cropper to be used to crop the detected face. - Might be an instance of a :py:class:`FaceCrop` or the name of a face cropper resource. - - cascade : str or ``None`` - The file name, where a face detector cascade can be found. - If ``None``, the default cascade for frontal faces :py:func:`bob.ip.facedetect.default_cascade` is used. - - use_flandmark : bool - If selected, :py:class:`bob.ip.flandmark.Flandmark` is used to detect the eye locations. - Otherwise, the eye locations are estimated based on the detected bounding box. - - detection_overlap : float - See :py:func:`bob.ip.facedetect.detect_single_face`. - - distance : int - See the Sampling section in the :ref:`Users Guide of bob.ip.facedetect <bob.ip.facedetect>`. - - scale_base : float - See the Sampling section in the :ref:`Users Guide of bob.ip.facedetect <bob.ip.facedetect>`. - - lowest_scale : float - See the Sampling section in the :ref:`Users Guide of bob.ip.facedetect <bob.ip.facedetect>`. - - kwargs - Remaining keyword parameters passed to the :py:class:`Base` constructor, such as ``color_channel`` or ``dtype``. - """ - - def __init__( - self, - face_cropper, - cascade=None, - use_flandmark=False, - detection_overlap=0.2, - distance=2, - scale_base=math.pow(2.0, -1.0 / 16.0), - lowest_scale=0.125, - **kwargs - ): - # call base class constructors - Base.__init__(self, **kwargs) - - self.face_cropper = face_cropper - self.cascade=cascade - self.use_flandmark=use_flandmark - self.detection_overlap=detection_overlap - self.distance=distance - self.scale_base=scale_base - self.lowest_scale=lowest_scale - - assert face_cropper is not None - - self.scale_base = scale_base - self.lowest_scale = lowest_scale - self.distance = distance - self.cascade = cascade - self.use_flandmark = use_flandmark - - self.detection_overlap = detection_overlap - self.quality = None - - self.cropper = load_cropper_only(face_cropper) - - self._init_non_pickables() - - def _init_non_pickables(self): - self.sampler = bob.ip.facedetect.Sampler( - scale_factor=self.scale_base, - lowest_scale=self.lowest_scale, - distance=self.distance, - ) - - if self.cascade is None: - self.cascade_classifier = bob.ip.facedetect.default_cascade() - else: - self.cascade_classifier = bob.ip.facedetect.Cascade( - bob.io.base.HDF5File(self.cascade) - ) - - self.flandmark = bob.ip.flandmark.Flandmark() if self.use_flandmark else None - - def _landmarks(self, image, bounding_box): - """Try to detect the landmarks in the given bounding box, and return the eye locations.""" - # get the landmarks in the face - if self.flandmark is not None: - # use the flandmark detector - - # make the bounding box square shape by extending the horizontal position by 2 pixels times width/20 - bb = bob.ip.facedetect.BoundingBox( - topleft=( - bounding_box.top_f, - bounding_box.left_f - bounding_box.size[1] / 10.0, - ), - size=bounding_box.size, - ) - - top = max(bb.top, 0) - left = max(bb.left, 0) - bottom = min(bb.bottom, image.shape[0]) - right = min(bb.right, image.shape[1]) - landmarks = self.flandmark.locate( - image, top, left, bottom - top, right - left - ) - - if landmarks is not None and len(landmarks): - return { - "reye": ( - (landmarks[1][0] + landmarks[5][0]) / 2.0, - (landmarks[1][1] + landmarks[5][1]) / 2.0, - ), - "leye": ( - (landmarks[2][0] + landmarks[6][0]) / 2.0, - (landmarks[2][1] + landmarks[6][1]) / 2.0, - ), - } - else: - logger.warn("Could not detect landmarks -- using estimated landmarks") - - # estimate from default locations - return bob.ip.facedetect.expected_eye_positions(bounding_box) - - def crop_face(self, image, annotations=None): - """crop_face(image, annotations = None) -> face - - Detects the face (and facial landmarks), and used the ``face_cropper`` given in the constructor to crop the face. - - **Parameters:** - - image : 2D or 3D :py:class:`numpy.ndarray` - The face image to be processed. - - annotations : any - Ignored. - - **Returns:** - - face : 2D or 3D :py:class:`numpy.ndarray` (float) - The detected and cropped face. - """ - uint8_image = image.astype(numpy.uint8) - if uint8_image.ndim == 3: - uint8_image = bob.ip.color.rgb_to_gray(uint8_image) - - # detect the face - bounding_box, self.quality = bob.ip.facedetect.detect_single_face( - uint8_image, self.cascade_classifier, self.sampler, self.detection_overlap - ) - - # get the eye landmarks - annotations = self._landmarks(uint8_image, bounding_box) - - # apply face cropping - return self.cropper.crop_face(image, annotations) - - def transform(self, X, annotations=None): - """__call__(image, annotations = None) -> face - - Aligns the given image according to the detected face bounding box or the detected facial features. - - First, the desired color channel is extracted from the given image. - Afterward, the face is detected and cropped, see :py:meth:`crop_face`. - Finally, the resulting face is converted to the desired data type. - - **Parameters:** - - image : 2D or 3D :py:class:`numpy.ndarray` - The face image to be processed. - - annotations : any - Ignored. - - **Returns:** - - face : 2D :py:class:`numpy.ndarray` - The cropped face. - """ - def _crop(image, annotation): - - # convert to the desired color channel - image = self.color_channel(image) - - # detect face and crop it - image = self.crop_face(image) - - # convert data type - return self.data_type(image) - - if annotations is None: - return [_crop(data) for data in X] - else: - return [_crop(data, annot) for data, annot in zip(X, annotations)] - - def __getstate__(self): - d = dict(self.__dict__) - d.pop("sampler") - d.pop("cascade_classifier") - d.pop("flandmark") - return d - - def __setstate__(self, d): - self.__dict__ = d - self._init_non_pickables() diff --git a/bob/bio/face/preprocessor/__init__.py b/bob/bio/face/preprocessor/__init__.py index d7e0bcc4576e74fbfd89fb04de6ffba8c173a386..31e02f0cf4151eb82cc725911c4caebcd6042e89 100644 --- a/bob/bio/face/preprocessor/__init__.py +++ b/bob/bio/face/preprocessor/__init__.py @@ -1,6 +1,5 @@ from .Base import Base from .FaceCrop import FaceCrop -from .FaceDetect import FaceDetect from .TanTriggs import TanTriggs from .INormLBP import INormLBP diff --git a/bob/bio/face/preprocessor/utils.py b/bob/bio/face/preprocessor/utils.py index 9ea3eea51e5025b202ee2817458c0438db91d712..ffd5602632cb909a465d2238a3f58e0b74325e0c 100644 --- a/bob/bio/face/preprocessor/utils.py +++ b/bob/bio/face/preprocessor/utils.py @@ -4,14 +4,11 @@ import six def load_cropper(face_cropper): from .FaceCrop import FaceCrop - from .FaceDetect import FaceDetect if face_cropper is None: cropper = None elif isinstance(face_cropper, six.string_types): cropper = bob.bio.base.load_resource(face_cropper, "preprocessor") - # elif isinstance(face_cropper, (FaceCrop, FaceDetect)): - # cropper = face_cropper else: cropper = face_cropper diff --git a/bob/bio/face/test/test_picklability.py b/bob/bio/face/test/test_picklability.py index 799d3de8db6c4936bfaad3fa99da05922b00ce26..b5c60ae4c3f7359686a8d18c8afc92642f38fd39 100644 --- a/bob/bio/face/test/test_picklability.py +++ b/bob/bio/face/test/test_picklability.py @@ -68,26 +68,6 @@ def test_face_crop(): assert assert_picklable_with_exceptions(cropper) -def test_face_detect(): - face_cropper = bob.bio.face.preprocessor.FaceCrop( - cropped_image_size=(CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH), - cropped_positions={'leye': LEFT_EYE_POS, 'reye': RIGHT_EYE_POS} - ) - - face_detect = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = False - ) - - assert_picklable_with_exceptions(face_detect) - - face_detect = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = True - ) - assert assert_picklable_with_exceptions(face_detect) - - def test_INormLBP(): face_cropper = bob.bio.face.preprocessor.FaceCrop( cropped_image_size=(CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH), @@ -126,7 +106,7 @@ def test_SQI(): def test_HistogramEqualization(): - + face_cropper = bob.bio.face.preprocessor.FaceCrop( cropped_image_size=(CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH), cropped_positions={'leye': LEFT_EYE_POS, 'reye': RIGHT_EYE_POS} diff --git a/bob/bio/face/test/test_preprocessors.py b/bob/bio/face/test/test_preprocessors.py index 4c88442af87e75f0c0936033f3a753efb1082db2..5ce379fd4fba39786447482796553323a04207e9 100644 --- a/bob/bio/face/test/test_preprocessors.py +++ b/bob/bio/face/test/test_preprocessors.py @@ -127,7 +127,7 @@ def test_face_crop(): cropper.cropped_positions, fixed_positions={"reye": annotation["reye"], "leye": annotation["leye"]}, ) - # result must be identical to the original face cropper (same eyes are used) + # result must be identical to the original face cropper (same eyes are used) _compare(fixed_cropper.transform([image]), reference) # check color cropping @@ -152,41 +152,6 @@ def test_face_crop(): cropper.channel = "gray" -def test_face_detect(): - image, annotation = _image(), None - face_cropper = bob.bio.face.preprocessor.FaceCrop( - cropped_image_size=(CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH), - cropped_positions={'leye': LEFT_EYE_POS, 'reye': RIGHT_EYE_POS} - ) - - cropper = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = False - ) - - assert isinstance(cropper, bob.bio.face.preprocessor.FaceDetect) - assert isinstance(cropper, bob.bio.face.preprocessor.Base) - assert cropper.flandmark is None - - # execute face detector - reference = pkg_resources.resource_filename( - "bob.bio.face.test", "data/detected.hdf5" - ) - _compare(cropper.transform([image], [annotation]), reference) - assert abs(cropper.quality - 39.209601948013685) < 1e-5 - - # execute face detector with flandmark - cropper = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = True - ) - reference = pkg_resources.resource_filename( - "bob.bio.face.test", "data/flandmark.hdf5" - ) - _compare(cropper.transform([image], [annotation]), reference) - assert abs(cropper.quality - 39.209601948013685) < 1e-5 - - def test_tan_triggs(): # read input image, annotation = _image(), _annotation() @@ -199,7 +164,7 @@ def test_tan_triggs(): preprocessor = bob.bio.face.preprocessor.TanTriggs(face_cropper=face_cropper) assert isinstance(preprocessor, bob.bio.face.preprocessor.TanTriggs) - assert isinstance(preprocessor, bob.bio.face.preprocessor.Base) + assert isinstance(preprocessor, bob.bio.face.preprocessor.Base) assert isinstance(preprocessor.cropper, bob.bio.face.preprocessor.FaceCrop) # execute face cropper @@ -221,14 +186,6 @@ def test_tan_triggs(): ) ) - face_detector = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = True - ) - preprocessor = bob.bio.face.preprocessor.TanTriggs(face_cropper=face_detector) - assert isinstance(preprocessor.cropper, bob.bio.face.preprocessor.FaceDetect) - assert preprocessor.cropper.flandmark is not None - def test_inorm_lbp(): # read input @@ -256,20 +213,9 @@ def test_inorm_lbp(): # load the preprocessor without cropping preprocessor = bob.bio.face.preprocessor.INormLBP( - face_cropper = None, + face_cropper = None, ) assert preprocessor.cropper is None - # load the preprocessor landmark detection - face_detector = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = True - ) - - preprocessor = bob.bio.face.preprocessor.INormLBP( - face_cropper = face_detector, - dtype = numpy.float64 - ) - assert isinstance(preprocessor.cropper, bob.bio.face.preprocessor.FaceDetect) def test_heq(): @@ -285,7 +231,7 @@ def test_heq(): ) assert isinstance(preprocessor, bob.bio.face.preprocessor.HistogramEqualization) - assert isinstance(preprocessor, bob.bio.face.preprocessor.Base) + assert isinstance(preprocessor, bob.bio.face.preprocessor.Base) assert isinstance(preprocessor.cropper, bob.bio.face.preprocessor.FaceCrop) # execute preprocessor _compare( @@ -302,18 +248,6 @@ def test_heq(): assert preprocessor.cropper is None # load the preprocessor landmark detection - face_detector = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = True - ) - - preprocessor = bob.bio.face.preprocessor.HistogramEqualization( - face_cropper = face_detector, - dtype = numpy.float64 - ) - - assert isinstance(preprocessor.cropper, bob.bio.face.preprocessor.FaceDetect) - def test_sqi(): # read input @@ -328,7 +262,7 @@ def test_sqi(): ) assert isinstance(preprocessor, bob.bio.face.preprocessor.SelfQuotientImage) - assert isinstance(preprocessor, bob.bio.face.preprocessor.Base) + assert isinstance(preprocessor, bob.bio.face.preprocessor.Base) assert isinstance(preprocessor.cropper, bob.bio.face.preprocessor.FaceCrop) # execute preprocessor _compare( @@ -343,14 +277,3 @@ def test_sqi(): face_cropper = None ) assert preprocessor.cropper is None - # load the preprocessor landmark detection - face_detector = bob.bio.face.preprocessor.FaceDetect( - face_cropper = face_cropper, - use_flandmark = True - ) - - preprocessor = bob.bio.face.preprocessor.SelfQuotientImage( - face_cropper = face_detector - ) - - assert isinstance(preprocessor.cropper, bob.bio.face.preprocessor.FaceDetect) diff --git a/doc/implementation.rst b/doc/implementation.rst index 05e023392879e2c351f0bb551140877d369f9fd3..2fd65693d3cdbb145dca86ec8e54a0482bac2dd7 100644 --- a/doc/implementation.rst +++ b/doc/implementation.rst @@ -67,14 +67,16 @@ However, we want to use the same algorithms on those images as well, so we have So, image preprocessing becomes a three stage algorithm. How to combine the two stages, image alignment and photometric enhancement, we have seen before. -Fortunately, the same technique can be applied for the :py:class:`bob.bio.face.preprocessor.FaceDetect`. The face detector takes as an input a ``face_cropper``, where we can use the same options to select a face cropper, just that we cannot pass ``None``. Interestingly, the face detector itself can be used as a ``face_cropper`` inside the photometric enhancement classes. Hence, to generate a TanTriggs preprocessor that performs face detection, crops the face and performs photometric enhancement, you can create: .. code-block:: py - preprocessor = bob.bio.face.preprocessor.TanTriggs(face_cropper = bob.bio.face.preprocessor.FaceDetect(face_cropper = 'face-crop-eyes', use_flandmark = True) ) + face_cropper = bob.bio.base.load_resource("face-crop-eyes", "preprocessor") + annotator = bob.bio.base.load_resource("facedetect-eye-estimate", "annotator") + face_cropper.annotator = annotator + preprocessor = bob.bio.face.preprocessor.TanTriggs(face_cropper=face_cropper) Or simply (using the face detector :ref:`Resource <bob.bio.face.preprocessors>`): diff --git a/doc/implemented.rst b/doc/implemented.rst index 9b442a5955dfd7147c91050c11e9374e04c1ec6e..481d8f9ed4c473cdfe126383a601a770d75ded63 100644 --- a/doc/implemented.rst +++ b/doc/implemented.rst @@ -43,7 +43,6 @@ Image Preprocessors .. autosummary:: bob.bio.face.preprocessor.Base bob.bio.face.preprocessor.FaceCrop - bob.bio.face.preprocessor.FaceDetect bob.bio.face.preprocessor.TanTriggs bob.bio.face.preprocessor.HistogramEqualization