From 19dfa1e21a6b8c38d7c459500ea016020ceb7529 Mon Sep 17 00:00:00 2001 From: Amir Mohammadi <183.amir@gmail.com> Date: Mon, 13 Feb 2017 14:21:24 +0100 Subject: [PATCH] Add the MSU MFSD modified verification protocol --- bob/bio/face/config/database/msu_mfsd_mod.py | 17 ++++ .../config/preprocessor/face_crop_eyes.py | 18 +++- bob/bio/face/database/__init__.py | 41 +++++---- bob/bio/face/database/mobio.py | 8 +- bob/bio/face/database/msu_mfsd_mod.py | 83 +++++++++++++++++ bob/bio/face/test/test_databases.py | 90 +++++++++++++++---- databases.txt | 11 --- develop.cfg | 3 + doc/implementation.rst | 8 ++ doc/implemented.rst | 1 + setup.py | 2 + test-requirements.txt | 1 + 12 files changed, 229 insertions(+), 54 deletions(-) create mode 100644 bob/bio/face/config/database/msu_mfsd_mod.py create mode 100644 bob/bio/face/database/msu_mfsd_mod.py delete mode 100644 databases.txt diff --git a/bob/bio/face/config/database/msu_mfsd_mod.py b/bob/bio/face/config/database/msu_mfsd_mod.py new file mode 100644 index 00000000..ad4e9d4e --- /dev/null +++ b/bob/bio/face/config/database/msu_mfsd_mod.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +from bob.bio.face.database import MsuMfsdModBioDatabase + +msu_mfsd_mod_directory = "[YOUR_MSU_MFSD_MOD_DIRECTORY]" + +msu_mfsd_mod_licit = MsuMfsdModBioDatabase( + original_directory=msu_mfsd_mod_directory, + original_extension=".mov", + protocol='grandtest-licit', +) + +msu_mfsd_mod_spoof = MsuMfsdModBioDatabase( + original_directory=msu_mfsd_mod_directory, + original_extension=".mov", + protocol='grandtest-spoof', +) diff --git a/bob/bio/face/config/preprocessor/face_crop_eyes.py b/bob/bio/face/config/preprocessor/face_crop_eyes.py index a2a2ecb3..99ffba04 100644 --- a/bob/bio/face/config/preprocessor/face_crop_eyes.py +++ b/bob/bio/face/config/preprocessor/face_crop_eyes.py @@ -4,14 +4,24 @@ import bob.bio.face # Cropping CROPPED_IMAGE_HEIGHT = 80 -CROPPED_IMAGE_WIDTH = CROPPED_IMAGE_HEIGHT * 4 // 5 +CROPPED_IMAGE_WIDTH = CROPPED_IMAGE_HEIGHT * 4 // 5 # eye positions for frontal images RIGHT_EYE_POS = (CROPPED_IMAGE_HEIGHT // 5, CROPPED_IMAGE_WIDTH // 4 - 1) -LEFT_EYE_POS = (CROPPED_IMAGE_HEIGHT // 5, CROPPED_IMAGE_WIDTH // 4 * 3) +LEFT_EYE_POS = (CROPPED_IMAGE_HEIGHT // 5, CROPPED_IMAGE_WIDTH // 4 * 3) # define the preprocessor preprocessor = bob.bio.face.preprocessor.FaceCrop( - cropped_image_size = (CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH), - cropped_positions = {'leye' : LEFT_EYE_POS, 'reye' : RIGHT_EYE_POS} + cropped_image_size=(CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH), + cropped_positions={'leye': LEFT_EYE_POS, 'reye': RIGHT_EYE_POS} +) + +# top left and bottom right positions +TOP_LEFT_POS = (0, 0) +BOTTOM_RIGHT_POS = (CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH) + +# define the preprocessor +preprocessor_head = bob.bio.face.preprocessor.FaceCrop( + cropped_image_size=(CROPPED_IMAGE_HEIGHT, CROPPED_IMAGE_WIDTH), + cropped_positions={'topleft': TOP_LEFT_POS, 'bottomright': BOTTOM_RIGHT_POS} ) diff --git a/bob/bio/face/database/__init__.py b/bob/bio/face/database/__init__.py index ede18cda..e0c91f0a 100644 --- a/bob/bio/face/database/__init__.py +++ b/bob/bio/face/database/__init__.py @@ -17,8 +17,11 @@ from .frgc import FRGCBioDatabase from .cuhk_cufs import CUHK_CUFSBioDatabase from .scface import SCFaceBioDatabase from .replaymobile import ReplayMobileBioDatabase +from .msu_mfsd_mod import MsuMfsdModBioDatabase # gets sphinx autodoc done right - don't remove it + + def __appropriate__(*args): """Says object was actually declared here, and not in the import module. Fixing sphinx warnings of not being able to find classes, when path is shortened. @@ -30,24 +33,26 @@ def __appropriate__(*args): <https://github.com/sphinx-doc/sphinx/issues/3048>` """ - for obj in args: obj.__module__ = __name__ + for obj in args: + obj.__module__ = __name__ __appropriate__( - FaceBioFile, - MobioBioDatabase, - ReplayBioDatabase, - AtntBioDatabase, - BancaBioDatabase, - GBUBioDatabase, - ARFaceBioDatabase, - CaspealBioDatabase, - LFWBioDatabase, - MultipieBioDatabase, - IJBABioDatabase, - XM2VTSBioDatabase, - FRGCBioDatabase, - CUHK_CUFSBioDatabase, - SCFaceBioDatabase, - ReplayMobileBioDatabase, - ) + FaceBioFile, + MobioBioDatabase, + ReplayBioDatabase, + AtntBioDatabase, + BancaBioDatabase, + GBUBioDatabase, + ARFaceBioDatabase, + CaspealBioDatabase, + LFWBioDatabase, + MultipieBioDatabase, + IJBABioDatabase, + XM2VTSBioDatabase, + FRGCBioDatabase, + CUHK_CUFSBioDatabase, + SCFaceBioDatabase, + ReplayMobileBioDatabase, + MsuMfsdModBioDatabase, +) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/bio/face/database/mobio.py b/bob/bio/face/database/mobio.py index 900f4936..17a6b248 100644 --- a/bob/bio/face/database/mobio.py +++ b/bob/bio/face/database/mobio.py @@ -4,7 +4,7 @@ # Wed 13 Jul 16:43:22 CEST 2016 """ - MOBIO database implementation of bob.bio.base.database.ZTDatabase interface. + MOBIO database implementation of bob.bio.base.database.ZTBioDatabase interface. It is an extension of an SQL-based database interface, which directly talks to Mobio database, for verification experiments (good to use in bob.bio.base framework). """ @@ -15,7 +15,7 @@ from bob.bio.base.database import ZTBioDatabase class MobioBioFile(FaceBioFile): - """FaceBioFile implementation of the Replay Mobile Database""" + """FaceBioFile implementation of the Mobio Database""" def __init__(self, f): super(MobioBioFile, self).__init__(client_id=f.client_id, path=f.path, file_id=f.id) @@ -24,7 +24,9 @@ class MobioBioFile(FaceBioFile): class MobioBioDatabase(ZTBioDatabase): """ - Implements verification API for querying Mobio database. + MOBIO database implementation of bob.bio.base.database.ZTBioDatabase interface. + It is an extension of an SQL-based database interface, which directly talks to Mobio database, for + verification experiments (good to use in bob.bio.base framework). """ def __init__( diff --git a/bob/bio/face/database/msu_mfsd_mod.py b/bob/bio/face/database/msu_mfsd_mod.py new file mode 100644 index 00000000..b2f1dd9d --- /dev/null +++ b/bob/bio/face/database/msu_mfsd_mod.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : +# Amir Mohammadi <amir.mohammadi@idiap.ch> + +from .database import FaceBioFile +from bob.bio.base.database import BioDatabase + + +class MsuMfsdModBioFile(FaceBioFile): + """FaceBioFile implementation of the MSU_MFSD_MOD Database""" + + def __init__(self, f): + super(MsuMfsdModBioFile, self).__init__( + client_id=f.client_id, path=f.path, file_id=f.id) + self._f = f + + def load(self, directory=None, extension=None): + if extension in (None, '.mov', '.mp4'): + return self._f.load(directory, extension) + else: + return super(MsuMfsdModBioFile, self).load(directory, extension) + + +class MsuMfsdModBioDatabase(BioDatabase): + + """ + MsuMfsdMod database implementation of + :py:class:`bob.bio.base.database.BioDatabase` interface. It is an extension + of an SQL-based database interface, which directly talks to MsuMfsdMod + database, for verification experiments (good to use in bob.bio.base + framework). + """ + + def __init__(self, max_number_of_frames=None, **kwargs): + # call base class constructors to open a session to the database + super(MsuMfsdModBioDatabase, self).__init__( + name='msu-mfsd-mod', + max_number_of_frames=max_number_of_frames, **kwargs) + + from bob.db.msu_mfsd_mod.verificationprotocol import Database \ + as LowLevelDatabase + self._db = LowLevelDatabase(max_number_of_frames) + + def protocol_names(self): + return self._db.protocols() + + def groups(self): + return self._db.groups() + + def annotations(self, myfile): + """ + Will return the bounding box annotation of nth frame of the video. + """ + fn = myfile._f.framen + annots = myfile._f._f.bbx(directory=self.original_directory) + # convert width and height to bottomright coordinates + # bob uses the (y, x) format + topleft = (annots[fn][1], annots[fn][0]) + bottomright = (annots[fn][1] + annots[fn][3], + annots[fn][0] + annots[fn][2]) + annotations = {'topleft': topleft, 'bottomright': bottomright} + return annotations + + def model_ids_with_protocol(self, groups=None, protocol=None, **kwargs): + return self._db.model_ids_with_protocol(groups, protocol, **kwargs) + + def objects(self, groups=None, protocol=None, purposes=None, + model_ids=None, **kwargs): + retval = self._db.objects( + groups, protocol, purposes, model_ids, **kwargs) + return [MsuMfsdModBioFile(f) for f in retval] + + def arrange_by_client(self, files): + client_files = {} + for file in files: + if str(file.client_id) not in client_files: + client_files[str(file.client_id)] = [] + client_files[str(file.client_id)].append(file) + + files_by_clients = [] + for client in sorted(client_files.keys()): + files_by_clients.append(client_files[client]) + return files_by_clients diff --git a/bob/bio/face/test/test_databases.py b/bob/bio/face/test/test_databases.py index 14c7b885..8f90148e 100644 --- a/bob/bio/face/test/test_databases.py +++ b/bob/bio/face/test/test_databases.py @@ -56,7 +56,8 @@ def _check_annotations(database, topleft=False, required=True, limit_files=None, @db_available('arface') def test_arface(): - database = bob.bio.base.load_resource('arface', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'arface', 'database', preferred_package='bob.bio.face') try: check_database(database, groups=('dev', 'eval')) except IOError as e: @@ -71,7 +72,8 @@ def test_arface(): @db_available('atnt') def test_atnt(): - database = bob.bio.base.load_resource('atnt', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'atnt', 'database', preferred_package='bob.bio.face') try: check_database(database) except IOError as e: @@ -81,7 +83,8 @@ def test_atnt(): @db_available('banca') def test_banca(): - database = bob.bio.base.load_resource('banca', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'banca', 'database', preferred_package='bob.bio.face') try: check_database_zt(database) except IOError as e: @@ -96,7 +99,8 @@ def test_banca(): @db_available('caspeal') def test_caspeal(): - database = bob.bio.base.load_resource('caspeal', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'caspeal', 'database', preferred_package='bob.bio.face') try: check_database(database) check_database(database, protocol='aging') @@ -124,7 +128,8 @@ def test_caspeal(): @db_available('cuhk_cufs') def test_cuhk_cufs(): - database = bob.bio.base.load_resource('cuhk_cufs', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'cuhk_cufs', 'database', preferred_package='bob.bio.face') try: check_database(database) except IOError as e: @@ -139,7 +144,8 @@ def test_cuhk_cufs(): @db_available('gbu') def test_gbu(): - database = bob.bio.base.load_resource('gbu', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'gbu', 'database', preferred_package='bob.bio.face') try: check_database(database, models_depend=True) check_database(database, protocol='Bad', models_depend=True) @@ -156,7 +162,8 @@ def test_gbu(): @db_available('ijba') def test_ijba(): - database = bob.bio.base.load_resource('ijba', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'ijba', 'database', preferred_package='bob.bio.face') try: check_database(database) except IOError as e: @@ -171,10 +178,12 @@ def test_ijba(): @db_available('lfw') def test_lfw(): - database = bob.bio.base.load_resource('lfw-restricted', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'lfw-restricted', 'database', preferred_package='bob.bio.face') try: check_database(database, training_depends=True, models_depend=True) - check_database(database, groups=('dev', 'eval'), protocol='fold1', training_depends=True, models_depend=True) + check_database(database, groups=('dev', 'eval'), + protocol='fold1', training_depends=True, models_depend=True) check_database(bob.bio.base.load_resource('lfw-unrestricted', 'database', preferred_package='bob.bio.face'), training_depends=True, models_depend=True) except IOError as e: @@ -189,7 +198,8 @@ def test_lfw(): @db_available('mobio') def test_mobio(): - database = bob.bio.base.load_resource('mobio-image', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'mobio-image', 'database', preferred_package='bob.bio.face') try: check_database_zt(database, models_depend=True) check_database_zt(database, protocol='female', models_depend=True) @@ -210,7 +220,8 @@ def test_mobio(): @db_available('multipie') def test_multipie(): - database = bob.bio.base.load_resource('multipie', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'multipie', 'database', preferred_package='bob.bio.face') try: check_database_zt(database, training_depends=True) check_database_zt(bob.bio.base.load_resource('multipie-pose', 'database', preferred_package='bob.bio.face'), @@ -231,7 +242,8 @@ def test_multipie(): @db_available('scface') def test_scface(): - database = bob.bio.base.load_resource('scface', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'scface', 'database', preferred_package='bob.bio.face') try: check_database_zt(database) except IOError as e: @@ -246,10 +258,12 @@ def test_scface(): @db_available('xm2vts') def test_xm2vts(): - database = bob.bio.base.load_resource('xm2vts', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'xm2vts', 'database', preferred_package='bob.bio.face') try: check_database(database, groups=('dev', 'eval')) - check_database(database, groups=('dev', 'eval'), protocol='darkened-lp1') + check_database(database, groups=('dev', 'eval'), + protocol='darkened-lp1') except IOError as e: raise SkipTest( "The database could not be queried; probably the db.sql3 file is missing. Here is the error: '%s'" % e) @@ -262,7 +276,8 @@ def test_xm2vts(): @db_available('replay') def test_replay_licit(): - database = bob.bio.base.load_resource('replay-img-licit', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'replay-img-licit', 'database', preferred_package='bob.bio.face') try: check_database(database, groups=('dev', 'eval')) except IOError as e: @@ -277,7 +292,8 @@ def test_replay_licit(): @db_available('replay') def test_replay_spoof(): - database = bob.bio.base.load_resource('replay-img-spoof', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'replay-img-spoof', 'database', preferred_package='bob.bio.face') try: check_database(database, groups=('dev', 'eval')) except IOError as e: @@ -292,7 +308,8 @@ def test_replay_spoof(): @db_available('replaymobile') def test_replaymobile_licit(): - database = bob.bio.base.load_resource('replaymobile-img-licit', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'replaymobile-img-licit', 'database', preferred_package='bob.bio.face') try: check_database(database, groups=('dev', 'eval')) except IOError as e: @@ -307,7 +324,8 @@ def test_replaymobile_licit(): @db_available('replaymobile') def test_replaymobile_spoof(): - database = bob.bio.base.load_resource('replaymobile-img-spoof', 'database', preferred_package='bob.bio.face') + database = bob.bio.base.load_resource( + 'replaymobile-img-spoof', 'database', preferred_package='bob.bio.face') try: check_database(database, groups=('dev', 'eval')) except IOError as e: @@ -318,3 +336,39 @@ def test_replaymobile_spoof(): except IOError as e: raise SkipTest( "The annotations could not be queried; probably the annotation files are missing. Here is the error: '%s'" % e) + + +@db_available('msu_mfsd_mod') +def test_msu_mfsd_mod_licit(): + database = bob.bio.base.load_resource( + 'msu-mfsd-mod-licit', 'database', preferred_package='bob.bio.face') + try: + check_database(database, groups=('dev', 'eval')) + except IOError as e: + raise SkipTest( + "The database could not be queried; probably the db.sql3 file is " + "missing. Here is the error: '%s'" % e) + try: + _check_annotations(database, topleft=True, limit_files=20) + except IOError as e: + raise SkipTest( + "The annotations could not be queried; probably the annotation " + "files are missing. Here is the error: '%s'" % e) + + +@db_available('msu_mfsd_mod') +def test_msu_mfsd_mod_spoof(): + database = bob.bio.base.load_resource( + 'msu-mfsd-mod-spoof', 'database', preferred_package='bob.bio.face') + try: + check_database(database, groups=('dev', 'eval')) + except IOError as e: + raise SkipTest( + "The database could not be queried; probably the db.sql3 file is " + "missing. Here is the error: '%s'" % e) + try: + _check_annotations(database, topleft=True, limit_files=20) + except IOError as e: + raise SkipTest( + "The annotations could not be queried; probably the annotation " + "files are missing. Here is the error: '%s'" % e) diff --git a/databases.txt b/databases.txt deleted file mode 100644 index 492ce304..00000000 --- a/databases.txt +++ /dev/null @@ -1,11 +0,0 @@ -bob.db.arface -bob.db.banca -bob.db.caspeal -bob.db.frgc -bob.db.gbu -bob.db.lfw -bob.db.mobio -bob.db.multipie -bob.db.scface -bob.db.xm2vts -bob.db.replay diff --git a/develop.cfg b/develop.cfg index faa22780..627b5d1d 100644 --- a/develop.cfg +++ b/develop.cfg @@ -32,6 +32,7 @@ eggs = bob.extension bob.db.gbu bob.db.lfw bob.db.mobio + bob.db.msu_mfsd_mod bob.db.multipie bob.db.replay bob.db.replaymobile @@ -73,6 +74,7 @@ develop = src/bob.extension src/bob.db.gbu src/bob.db.lfw src/bob.db.mobio + src/bob.db.msu_mfsd_mod src/bob.db.multipie src/bob.db.replay src/bob.db.replaymobile @@ -118,6 +120,7 @@ bob.db.frgc = git https://gitlab.idiap.ch/bob/bob.db.frgc bob.db.gbu = git https://gitlab.idiap.ch/bob/bob.db.gbu bob.db.lfw = git https://gitlab.idiap.ch/bob/bob.db.lfw bob.db.mobio = git https://gitlab.idiap.ch/bob/bob.db.mobio +bob.db.msu_mfsd_mod = git https://gitlab.idiap.ch/bob/bob.db.msu_mfsd_mod bob.db.multipie = git https://gitlab.idiap.ch/bob/bob.db.multipie bob.db.replay = git https://gitlab.idiap.ch/bob/bob.db.replay bob.db.replaymobile = git https://gitlab.idiap.ch/bob/bob.db.replaymobile diff --git a/doc/implementation.rst b/doc/implementation.rst index d0c8a90d..ab86ee84 100644 --- a/doc/implementation.rst +++ b/doc/implementation.rst @@ -155,6 +155,14 @@ Here is the list of files and replacement strings for all databases that are reg - Complete directory: ``[YOUR_REPLAY_ATTACK_DIRECTORY]`` +* Replay Mobile ``'replaymobile-img-licit'``, ``'replaymobile-img-spoof'`` + + - Complete directory: ``[YOUR_REPLAY_MOBILE_DIRECTORY]`` + +* MSU MFSD Modified ``'msu-mfsd-mod-licit'``, ``'msu-mfsd-mod-spoof'`` + + - Complete directory: ``[YOUR_MSU_MFSD_MOD_DIRECTORY]`` + * SC face: ``'scface'`` - Images: ``[YOUR_SC_FACE_DIRECTORY]`` diff --git a/doc/implemented.rst b/doc/implemented.rst index 23ec72a5..4480535e 100644 --- a/doc/implemented.rst +++ b/doc/implemented.rst @@ -18,6 +18,7 @@ Databases bob.bio.face.database.CaspealBioDatabase bob.bio.face.database.ReplayBioDatabase bob.bio.face.database.ReplayMobileBioDatabase + bob.bio.face.database.MsuMfsdModBioDatabase bob.bio.face.database.GBUBioDatabase bob.bio.face.database.LFWBioDatabase bob.bio.face.database.MultipieBioDatabase diff --git a/setup.py b/setup.py index abe4c79a..e55963a8 100644 --- a/setup.py +++ b/setup.py @@ -119,6 +119,8 @@ setup( 'mobio-image = bob.bio.face.config.database.mobio:mobio_image', 'mobio-male = bob.bio.face.config.database.mobio:mobio_male', # MOBIO gender-dependent training 'mobio-female = bob.bio.face.config.database.mobio:mobio_female', # MOBIO gender-dependent training + 'msu-mfsd-mod-licit = bob.bio.face.config.database.msu_mfsd_mod:msu_mfsd_mod_licit', + 'msu-mfsd-mod-spoof = bob.bio.face.config.database.msu_mfsd_mod:msu_mfsd_mod_spoof', 'multipie = bob.bio.face.config.database.multipie:database', 'multipie-pose = bob.bio.face.config.database.multipie_pose:database', 'scface = bob.bio.face.config.database.scface:database', diff --git a/test-requirements.txt b/test-requirements.txt index 1786ccb2..64a2a50c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,6 +9,7 @@ bob.db.gbu bob.db.ijba bob.db.lfw bob.db.mobio +bob.db.msu_mfsd_mod bob.db.multipie bob.db.replay bob.db.replaymobile -- GitLab