From 1e8e19f219be53ca3b561766a9c3eaed0e71f139 Mon Sep 17 00:00:00 2001 From: Amir MOHAMMADI <amir.mohammadi@idiap.ch> Date: Sun, 11 Mar 2018 12:26:09 +0100 Subject: [PATCH] Add more documentation and write tests --- bob/bio/face/annotator/__init__.py | 33 +++++++++++++-- bob/bio/face/annotator/bobipfacedetect.py | 16 ++++++-- bob/bio/face/annotator/bobipflandmark.py | 17 +++++++- bob/bio/face/config/annotator/__init__.py | 0 bob/bio/face/config/annotator/dlib.py | 3 ++ bob/bio/face/config/annotator/facedetect.py | 3 ++ .../annotator/facedetect_eye_estimate.py | 6 +++ bob/bio/face/config/annotator/flandmark.py | 6 +++ bob/bio/face/config/annotator/mtcnn.py | 3 ++ bob/bio/face/preprocessor/utils.py | 5 ++- bob/bio/face/test/test_annotators.py | 40 +++++++++++++++++-- conda/meta.yaml | 2 + doc/annotators.rst | 25 ++++++++++++ doc/extra-intersphinx.txt | 3 +- doc/implemented.rst | 21 +++++++++- doc/index.rst | 1 + requirements.txt | 1 + setup.py | 8 ++++ 18 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 bob/bio/face/config/annotator/__init__.py create mode 100644 bob/bio/face/config/annotator/dlib.py create mode 100644 bob/bio/face/config/annotator/facedetect.py create mode 100644 bob/bio/face/config/annotator/facedetect_eye_estimate.py create mode 100644 bob/bio/face/config/annotator/flandmark.py create mode 100644 bob/bio/face/config/annotator/mtcnn.py create mode 100644 doc/annotators.rst diff --git a/bob/bio/face/annotator/__init__.py b/bob/bio/face/annotator/__init__.py index 83adf1d8..ce553638 100644 --- a/bob/bio/face/annotator/__init__.py +++ b/bob/bio/face/annotator/__init__.py @@ -1,8 +1,19 @@ -from bob.ip.facedetect import bounding_box_from_annotation -from .Base import Base +import bob.ip.facedetect def bounding_box_to_annotations(bbx): + """Converts :any:`bob.ip.facedetect.BoundingBox` to dictionary annotations. + + Parameters + ---------- + bbx : :any:`bob.ip.facedetect.BoundingBox` + The given bounding box. + + Returns + ------- + dict + A dictionary with topleft and bottomright keys. + """ landmarks = { 'topleft': bbx.topleft, 'bottomright': bbx.bottomright, @@ -17,7 +28,7 @@ def min_face_size_validator(annotations, min_face_size=(32, 32)): ---------- annotations : dict The annotations in dictionary format. - min_face_size : (int, int), optional + min_face_size : (:obj:`int`, :obj:`int`), optional The minimal size of a face. Returns @@ -25,12 +36,21 @@ def min_face_size_validator(annotations, min_face_size=(32, 32)): bool True, if the face is large enough. """ - bbx = bounding_box_from_annotation(source='direct', **annotations) + bbx = bob.ip.facedetect.bounding_box_from_annotations( + source='direct', **annotations) if bbx.size < min_face_size: return False return True +# These imports should be here to avoid circular dependencies +from .Base import Base +from .bobipfacedetect import BobIpFacedetect, BoundingBoxToEyes +from .bobipflandmark import BobIpFlandmark +from .bobipdlib import BobIpDlib +from .bobipmtcnn import BobIpMTCNN + + # gets sphinx autodoc done right - don't remove it def __appropriate__(*args): """Says object was actually declared here, and not in the import module. @@ -52,6 +72,11 @@ def __appropriate__(*args): __appropriate__( Base, + BobIpFacedetect, + BoundingBoxToEyes, + BobIpFlandmark, + BobIpDlib, + BobIpMTCNN, ) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/bio/face/annotator/bobipfacedetect.py b/bob/bio/face/annotator/bobipfacedetect.py index 46bd27cd..afae0492 100644 --- a/bob/bio/face/annotator/bobipfacedetect.py +++ b/bob/bio/face/annotator/bobipfacedetect.py @@ -42,7 +42,8 @@ class BobIpFacedetect(Base): The annotations in a dictionary. The keys are topleft, bottomright, quality, leye, reye. """ - image = rgb_to_gray(image) + if image.ndim == 3: + image = rgb_to_gray(image) bounding_box, quality = detect_single_face( image, self.cascade, self.sampler, self.detection_overlap) landmarks = bounding_box_to_annotations(bounding_box) @@ -53,6 +54,17 @@ class BobIpFacedetect(Base): class BoundingBoxToEyes(Base): """Converts bounding box annotations to eye locations. The bounding box's annotations is expected to have come from :any:`BobIpFacedetect`. + + Example usage: + + .. doctest:: + + >>> from bob.bio.base.annotator import FailSafe + >>> from bob.bio.face.annotator import ( + ... BobIpFacedetect, BoundingBoxToEyes) + >>> annotator = FailSafe( + ... [BobIpFacedetect(), BoundingBoxToEyes()], + ... required_keys=('reye', 'leye')) """ def annotate(self, image, annotations, **kwargs): @@ -73,7 +85,5 @@ class BoundingBoxToEyes(Base): The annotations with reye and leye locations added. """ bbx = bounding_box_from_annotation(source='direct', **annotations) - # make a copy of the annotations - annotations = dict(annotations) annotations.update(expected_eye_positions(bbx)) return annotations diff --git a/bob/bio/face/annotator/bobipflandmark.py b/bob/bio/face/annotator/bobipflandmark.py index cd1552af..3d099fa0 100644 --- a/bob/bio/face/annotator/bobipflandmark.py +++ b/bob/bio/face/annotator/bobipflandmark.py @@ -5,7 +5,19 @@ from bob.ip.flandmark import Flandmark class BobIpFlandmark(Base): """Annotator using bob.ip.flandmark. - This annotator needs the topleft and bottomright annotations provided.""" + This annotator needs the topleft and bottomright annotations provided. + + Example usage: + + .. doctest:: + + >>> from bob.bio.base.annotator import FailSafe + >>> from bob.bio.face.annotator import ( + ... BobIpFacedetect, BobIpFlandmark) + >>> annotator = FailSafe( + ... [BobIpFacedetect(), BobIpFlandmark()], + ... required_keys=('reye', 'leye')) + """ def __init__(self, **kwargs): super(BobIpFlandmark, self).__init__(**kwargs) @@ -28,7 +40,8 @@ class BobIpFlandmark(Base): dict Annotations with reye and leye keys or None if it fails. """ - image = rgb_to_gray(image) + if image.ndim == 3: + image = rgb_to_gray(image) top, left = annotations['topleft'] top, left = int(max(top, 0)), int(max(left, 0)) height = annotations['bottomright'][0] - top diff --git a/bob/bio/face/config/annotator/__init__.py b/bob/bio/face/config/annotator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bob/bio/face/config/annotator/dlib.py b/bob/bio/face/config/annotator/dlib.py new file mode 100644 index 00000000..5475604c --- /dev/null +++ b/bob/bio/face/config/annotator/dlib.py @@ -0,0 +1,3 @@ +from bob.bio.face.annotator import BobIpDlib + +annotator = BobIpDlib() diff --git a/bob/bio/face/config/annotator/facedetect.py b/bob/bio/face/config/annotator/facedetect.py new file mode 100644 index 00000000..f39c8a11 --- /dev/null +++ b/bob/bio/face/config/annotator/facedetect.py @@ -0,0 +1,3 @@ +from bob.bio.face.annotator import BobIpFacedetect + +annotator = BobIpFacedetect() diff --git a/bob/bio/face/config/annotator/facedetect_eye_estimate.py b/bob/bio/face/config/annotator/facedetect_eye_estimate.py new file mode 100644 index 00000000..a42e1911 --- /dev/null +++ b/bob/bio/face/config/annotator/facedetect_eye_estimate.py @@ -0,0 +1,6 @@ +from bob.bio.base.annotator import FailSafe +from bob.bio.face.annotator import BobIpFacedetect, BoundingBoxToEyes + +annotator = FailSafe( + [BobIpFacedetect(), BoundingBoxToEyes()], + required_keys=('reye', 'leye')) diff --git a/bob/bio/face/config/annotator/flandmark.py b/bob/bio/face/config/annotator/flandmark.py new file mode 100644 index 00000000..e473b8d8 --- /dev/null +++ b/bob/bio/face/config/annotator/flandmark.py @@ -0,0 +1,6 @@ +from bob.bio.base.annotator import FailSafe +from bob.bio.face.annotator import BobIpFacedetect, BobIpFlandmark + +annotator = FailSafe( + [BobIpFacedetect(), BobIpFlandmark()], + required_keys=('reye', 'leye')) diff --git a/bob/bio/face/config/annotator/mtcnn.py b/bob/bio/face/config/annotator/mtcnn.py new file mode 100644 index 00000000..6dcfb2a5 --- /dev/null +++ b/bob/bio/face/config/annotator/mtcnn.py @@ -0,0 +1,3 @@ +from bob.bio.face.annotator import BobIpMTCNN + +annotator = BobIpMTCNN() diff --git a/bob/bio/face/preprocessor/utils.py b/bob/bio/face/preprocessor/utils.py index 01c35054..7be6bfe6 100644 --- a/bob/bio/face/preprocessor/utils.py +++ b/bob/bio/face/preprocessor/utils.py @@ -1,4 +1,5 @@ import bob.bio.base +import six def load_cropper(face_cropper): @@ -6,7 +7,7 @@ def load_cropper(face_cropper): from .FaceDetect import FaceDetect if face_cropper is None: cropper = None - elif isinstance(face_cropper, str): + 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 @@ -21,7 +22,7 @@ def load_cropper_only(face_cropper): from .FaceCrop import FaceCrop if face_cropper is None: cropper = None - elif isinstance(face_cropper, str): + elif isinstance(face_cropper, six.string_types): cropper = bob.bio.base.load_resource(face_cropper, 'preprocessor') elif isinstance(face_cropper, FaceCrop): cropper = face_cropper diff --git a/bob/bio/face/test/test_annotators.py b/bob/bio/face/test/test_annotators.py index db4a8e66..4f71886e 100644 --- a/bob/bio/face/test/test_annotators.py +++ b/bob/bio/face/test/test_annotators.py @@ -1,16 +1,48 @@ import bob.io.base import bob.io.base.test_utils import bob.io.image -import bob.ip.facedetect +from bob.bio.face.annotator import ( + BobIpFacedetect, BoundingBoxToEyes, BobIpFlandmark) +from bob.bio.base.annotator import FailSafe import numpy face_image = bob.io.base.load(bob.io.base.test_utils.datafile( 'testimage.jpg', 'bob.ip.facedetect')) -def test_bob_ip_facedetect(): - from bob.bio.face.annotator.bobipfacedetect import BobIpFacedetect - annot = BobIpFacedetect()(face_image) +def _assert_bob_ip_facedetect(annot): assert annot['topleft'] == (110, 82), annot assert annot['bottomright'] == (334, 268), annot assert numpy.allclose(annot['quality'], 39.209601948013685), annot + + +def test_bob_ip_facedetect(): + from bob.bio.face.annotator.bobipfacedetect import BobIpFacedetect + annot = BobIpFacedetect()(face_image) + _assert_bob_ip_facedetect(annot) + + +def test_bob_ip_facedetect_eyes(): + annotator = FailSafe( + [BobIpFacedetect(), BoundingBoxToEyes()], + required_keys=('reye', 'leye'), + ) + + annot = annotator(face_image) + + _assert_bob_ip_facedetect(annot) + assert [int(x) for x in annot['reye']] == [175, 128], annot + assert [int(x) for x in annot['leye']] == [175, 221], annot + + +def test_bob_ip_flandmark(): + annotator = FailSafe( + [BobIpFacedetect(), BobIpFlandmark()], + required_keys=('reye', 'leye'), + ) + + annot = annotator(face_image) + + _assert_bob_ip_facedetect(annot) + assert [int(x) for x in annot['reye']] == [183, 127], annot + assert [int(x) for x in annot['leye']] == [174, 223], annot diff --git a/conda/meta.yaml b/conda/meta.yaml index 2a7b011e..4e408a42 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -45,10 +45,12 @@ requirements: - bob.ip.facedetect - bob.ip.flandmark - matplotlib {{ matplotlib }} + - six {{ six }} run: - python - setuptools - matplotlib + - six test: imports: diff --git a/doc/annotators.rst b/doc/annotators.rst new file mode 100644 index 00000000..b7cb0abf --- /dev/null +++ b/doc/annotators.rst @@ -0,0 +1,25 @@ +.. vim: set fileencoding=utf-8 : + +.. _bob.bio.face.annotators: + +================= + Face Annotators +================= + +This packages provides several face annotators (using RGB images) that you can +use to annotate biometric databases. See :ref:`bob.bio.base.annotations` for +a guide on the general usage of this feature. + +.. warning:: + + The annotators are named after the package that provides this annotator. + Their respective |project| packages need to be installed if you want to use + them. + +See :doc:`implemented` for a list of available annotators. Here is an example +on how to use :any:`bob.bio.face.annotator.BobIpFlandmark` to annotate the ATNT +database: + +.. code-block:: sh + + $ bob bio annotate -vvv -d atnt -a flandmark -o /tmp/annotations diff --git a/doc/extra-intersphinx.txt b/doc/extra-intersphinx.txt index 03938bdd..c82f95f8 100644 --- a/doc/extra-intersphinx.txt +++ b/doc/extra-intersphinx.txt @@ -4,7 +4,6 @@ bob.bio.base bob.io.base bob.ip.gabor bob.ip.base -bob.bio.speaker bob.bio.gmm bob.bio.video bob.bio.csu @@ -14,4 +13,4 @@ bob.ip.facedetect bob.ip.flandmark bob.learn.linear gridtk -bob.db.youtube \ No newline at end of file +bob.db.youtube diff --git a/doc/implemented.rst b/doc/implemented.rst index c5f98bee..c0019209 100644 --- a/doc/implemented.rst +++ b/doc/implemented.rst @@ -27,6 +27,19 @@ Databases bob.bio.face.database.FRGCBioDatabase bob.bio.face.database.SCFaceBioDatabase + +Face Image Annotators +~~~~~~~~~~~~~~~~~~~~~ + +.. autosummary:: + bob.bio.face.annotator.Base + bob.bio.face.annotator.BobIpFacedetect + bob.bio.face.annotator.BoundingBoxToEyes + bob.bio.face.annotator.BobIpFlandmark + bob.bio.face.annotator.BobIpDlib + bob.bio.face.annotator.BobIpMTCNN + + Image Preprocessors ~~~~~~~~~~~~~~~~~~~ @@ -41,7 +54,6 @@ Image Preprocessors bob.bio.face.preprocessor.INormLBP - Image Feature Extractors ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -59,10 +71,16 @@ Face Recognition Algorithms bob.bio.face.algorithm.GaborJet bob.bio.face.algorithm.Histogram + Databases --------- .. automodule:: bob.bio.face.database +Annotators +---------- + +.. automodule:: bob.bio.face.annotator + Preprocessors ------------- @@ -78,4 +96,5 @@ Algorithms .. automodule:: bob.bio.face.algorithm + .. include:: links.rst diff --git a/doc/index.rst b/doc/index.rst index 1f0b9b57..361114f3 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -37,6 +37,7 @@ Users Guide baselines implementation references + annotators Reference Manual ================ diff --git a/requirements.txt b/requirements.txt index 35383328..4dc31984 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,4 @@ bob.learn.boosting bob.ip.facedetect bob.ip.flandmark matplotlib # for plotting +six diff --git a/setup.py b/setup.py index e0e5768d..05079868 100644 --- a/setup.py +++ b/setup.py @@ -134,6 +134,14 @@ setup( 'replaymobile-img-spoof = bob.bio.face.config.database.replaymobile:replaymobile_spoof', ], + 'bob.bio.annotator': [ + 'facedetect = bob.bio.face.config.annotator.facedetect:annotator', + 'facedetect-eye-estimate = bob.bio.face.config.annotator.facedetect_eye_estimate:annotator', + 'flandmark = bob.bio.face.config.annotator.flandmark:annotator', + 'dlib = bob.bio.face.config.annotator.dlib:annotator', + 'mtcnn = bob.bio.face.config.annotator.mtcnn:annotator', + ], + 'bob.bio.preprocessor': [ 'base = bob.bio.face.config.preprocessor.base:preprocessor', # simple color conversion 'face-crop-eyes = bob.bio.face.config.preprocessor.face_crop_eyes:preprocessor', # face crop -- GitLab