diff --git a/bob/bio/face/annotator/__init__.py b/bob/bio/face/annotator/__init__.py index 83adf1d8d9d1ae3ab7d5572edeb72abc38e85553..ce553638a891bb84b554f9c323834e1931e060a1 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 46bd27cdce528f0fd0b8e6b06dba3d59997667b3..afae049212c06cb8314054bda5337f0e8978acf5 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 cd1552af8157aa0460a8f66d04c4d42d8412bfbc..3d099fa099809202318786b0835861c14ddbc4e1 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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bob/bio/face/config/annotator/dlib.py b/bob/bio/face/config/annotator/dlib.py new file mode 100644 index 0000000000000000000000000000000000000000..5475604c9bc580fe416efda6ddabbbca91989df2 --- /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 0000000000000000000000000000000000000000..f39c8a118128dc44068482b4965bcc226d20016b --- /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 0000000000000000000000000000000000000000..a42e19116a087a0fbec07d5b9e5b214b06b48b87 --- /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 0000000000000000000000000000000000000000..e473b8d8506cf770f5134c8bb1001bbb7c588711 --- /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 0000000000000000000000000000000000000000..6dcfb2a5ea9bbaac33a3793dd8b0c6ffeb126b2b --- /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 01c350549d8774170d4f420b22f49daf6b31aca1..7be6bfe6ce5d9498d3a88a9b53a8f56aea210cae 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 db4a8e665b4664310a6ef267627f3c6b179b358f..4f71886e0124738751270f132f9b042fb8a9af35 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 2a7b011ec064e6ad8882777779cf4e65de593ffa..4e408a42b6fbc9415bb1a6ddf1115348fefa3c52 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 0000000000000000000000000000000000000000..b7cb0abf8051202fed2ee56924bb6df2863e5050 --- /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 03938bdd20d5e43e2e1b18dc1d16365e57f5eb20..c82f95f8fd7a78b61d0116246d7a6f2ff9a69a65 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 c5f98bee1b8fa742ac5112fc1ae3b25727881189..c00192097f092b0fee8a7ba7fcd96be6e5108bcf 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 1f0b9b5758a3c38a53a89c96760f0b611fd85b55..361114f346038cc84fba6ef2377f65e823ac6a9c 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 35383328ae4c7e7c459168579874a4471141693a..4dc31984ef4954f5d97cf1a33972148d2169222c 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 e0e5768d9cc0d6326553634cf47a3e230eea933d..05079868fb561109918f32b5b1b53a12f20e4a82 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