diff --git a/bob/pad/face/config/database/msu_mfsd.py b/bob/pad/face/config/database/msu_mfsd.py new file mode 100644 index 0000000000000000000000000000000000000000..ec2cd20a614af2becd29a89120b05994f3de4968 --- /dev/null +++ b/bob/pad/face/config/database/msu_mfsd.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +"""`MSU MFSD`_ is a database for face PAD experiments. + +Database created at MSU, for face-PAD experiments. The public version of the database contains +280 videos corresponding to 35 clients. The videos are grouped as 'genuine' and 'attack'. +The attack videos have been constructed from the genuine ones, +and consist of three kinds: print, iPad (video-replay), and iPhone (video-replay). +Face-locations are also provided for each frame of each video, but some (6 videos) face-locations are not reliable, +because the videos are not correctly oriented. +The reference citation is [WHJ15]_. + +You can download the raw data of the `MSU MFSD`_ database by following +the link. + +.. include:: links.rst +""" + +from bob.pad.face.database import MsuMfsdPadDatabase + + +# Directory where the data files are stored. +# This directory is given in the .bob_bio_databases.txt file located in your home directory +original_directory = "[YOUR_MSU_MFSD_DIRECTORY]" +"""Value of ``~/.bob_bio_databases.txt`` for this database""" + +original_extension = "none" # extension is not used to load the data in the HLDI of this database + +database = MsuMfsdPadDatabase( + protocol='grandtest', + original_directory=original_directory, + original_extension=original_extension, + training_depends_on_protocol=True, +) +"""The :py:class:`bob.pad.base.database.PadDatabase` derivative with MSU MFSD +database settings. + +.. warning:: + + This class only provides a programmatic interface to load data in an orderly + manner, respecting usage protocols. It does **not** contain the raw + data files. You should procure those yourself. + +Notice that ``original_directory`` is set to ``[YOUR_MSU_MFSD_DIRECTORY]``. +You must make sure to create ``${HOME}/.bob_bio_databases.txt`` setting this +value to the place where you actually installed the Replay-Mobile Database, as +explained in the section :ref:`bob.pad.face.baselines`. +""" \ No newline at end of file diff --git a/bob/pad/face/database/__init__.py b/bob/pad/face/database/__init__.py index 7b20112adff6a563706c7f182ec773726816ccd0..abf2f69122cb549489bf3f043f6c66e8c160ed88 100644 --- a/bob/pad/face/database/__init__.py +++ b/bob/pad/face/database/__init__.py @@ -1,5 +1,6 @@ from .replay import ReplayPadDatabase from .replay_mobile import ReplayMobilePadDatabase +from .msu_mfsd import MsuMfsdPadDatabase # gets sphinx autodoc done right - don't remove it def __appropriate__(*args): @@ -18,6 +19,7 @@ def __appropriate__(*args): __appropriate__( ReplayPadDatabase, ReplayMobilePadDatabase, + MsuMfsdPadDatabase, ) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/pad/face/database/msu_mfsd.py b/bob/pad/face/database/msu_mfsd.py new file mode 100644 index 0000000000000000000000000000000000000000..7b183441c2128e222c17725268c6e117328ef8dc --- /dev/null +++ b/bob/pad/face/database/msu_mfsd.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + + +#============================================================================== +import bob.bio.video # Used in MsuMfsdPadFile class + +from bob.pad.base.database import PadFile # Used in MsuMfsdPadFile class + +from bob.pad.base.database import PadDatabase + +import os + +import numpy as np + +#============================================================================== +class MsuMfsdPadFile(PadFile): + """ + A high level implementation of the File class for the MSU MFSD database. + """ + + def __init__(self, f): + """ + **Parameters:** + + ``f`` : :py:class:`object` + An instance of the File class defined in the low level db interface + of the MSU MFSD database, in the bob.db.msu_mfsd_mod.models.py file. + """ + + self.f = f + # this f is actually an instance of the File class that is defined in + # bob.db.msu_mfsd_mod.models and the PadFile class here needs + # client_id, path, attack_type, file_id for initialization. We have to + # convert information here and provide them to PadFile. attack_type is a + # little tricky to get here. Based on the documentation of PadFile: + # In cased of a spoofed data, this parameter should indicate what kind of spoofed attack it is. + # The default None value is interpreted that the PadFile is a genuine or real sample. + if f.is_real(): + attack_type = None + else: + attack_type = 'attack' + # attack_type is a string and I decided to make it like this for this + # particular database. You can do whatever you want for your own database. + + super(MsuMfsdPadFile, self).__init__(client_id=f.client_id, path=f.path, + attack_type=attack_type, file_id=f.id) + + + #========================================================================== + def convert_arr_to_frame_cont(self, data): + """ + This function converts an input 4D array with frames into FrameContainer, + where each frame is an RGB image. The dimensionality of the input array + is [N_frames, 3, N_rows, N_cols]. + + **Parameters:** + + ``data`` : 4D :py:class:`numpy.ndarray` + An input 4D array with frames of the dimensionality: + [N_frames, 3, N_rows, N_cols]. + + **Returns:** + + ``frames`` : FrameContainer + Resulting FrameContainer containing RGB frames. + """ + + frames = bob.bio.video.FrameContainer() # initialize the FrameContainer + + for idx, sample in enumerate(data): + + frames.add(idx, sample) + + return frames + + + #========================================================================== + def load(self, directory=None, extension=None): + """ + Overridden version of the load method defined in the ``PadFile``. + + **Parameters:** + + ``directory`` : :py:class:`str` + String containing the path to the MSU MFSD database. + Default: None + + ``extension`` : :py:class:`str` + Extension of the video files in the MSU MFSD database. + Note: ``extension`` value is not used in the code of this method. + Default: None + + **Returns:** + + ``video_data`` : FrameContainer + Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer`` + for further details. + """ + + _, extension = os.path.splitext(self.f.videofile()) # get file extension + + video_data_array = self.f.load(directory = directory, + extension = extension) + + video_data = self.convert_arr_to_frame_cont(video_data_array) # the result is now a FrameContainer + + return video_data + + +#============================================================================== +class MsuMfsdPadDatabase(PadDatabase): + """ + A high level implementation of the Database class for the MSU MFSD database. + """ + + def __init__( + self, + protocol='grandtest', # grandtest is the default protocol for this database + original_directory=None, + original_extension=None, + **kwargs): + """ + **Parameters:** + + ``protocol`` : :py:class:`str` or ``None`` + The name of the protocol that defines the default experimental setup for this database. + + ``original_directory`` : :py:class:`str` + The directory where the original data of the database are stored. + + ``original_extension`` : :py:class:`str` + The file name extension of the original data. + + ``kwargs`` + The arguments of the :py:class:`bob.bio.base.database.BioDatabase` base class constructor. + """ + + from bob.db.msu_mfsd_mod import Database as LowLevelDatabase + + self.db = LowLevelDatabase() + + # Since the high level API expects different group names than what the low + # level API offers, you need to convert them when necessary + self.low_level_group_names = ('train', 'devel', 'test') # group names in the low-level database interface + self.high_level_group_names = ('train', 'dev', 'eval') # names are expected to be like that in objects() function + + # Always use super to call parent class methods. + super(MsuMfsdPadDatabase, self).__init__( + name = 'msu-mfsd', + protocol = protocol, + original_directory = original_directory, + original_extension = original_extension, + **kwargs) + + + #========================================================================== + def objects(self, groups=None, protocol=None, purposes=None, model_ids=None, **kwargs): + """ + This function returns lists of MsuMfsdPadFile objects, which fulfill the given restrictions. + + Keyword parameters: + + ``groups`` : :py:class:`str` + OR a list of strings. + The groups of which the clients should be returned. + Usually, groups are one or more elements of ('train', 'dev', 'eval') + + ``protocol`` : :py:class:`str` + The protocol for which the clients should be retrieved. + Note: this argument is not used in the code, because ``objects`` method of the + low-level BD interface of the MSU MFSD doesn't have ``protocol`` argument. + + ``purposes`` : :py:class:`str` + OR a list of strings. + The purposes for which File objects should be retrieved. + Usually it is either 'real' or 'attack'. + + ``model_ids`` + This parameter is not supported in PAD databases yet. + + **Returns:** + + ``files`` : :py:class:`str` + A list of MsuMfsdPadFile objects. + """ + + # Convert group names to low-level group names here. + groups = self.convert_names_to_lowlevel(groups, self.low_level_group_names, self.high_level_group_names) + # Since this database was designed for PAD experiments, nothing special + # needs to be done here. + files = self.db.objects(group=groups, cls=purposes, **kwargs) + + files = [MsuMfsdPadFile(f) for f in files] + + return files + + + #========================================================================== + def annotations(self, f): + """ + Return annotations for a given file object ``f``, which is an instance + of ``MsuMfsdPadFile`` defined in the HLDI of the MSU MFSD DB. + The ``load()`` method of ``MsuMfsdPadFile`` class (see above) + returns a video, therefore this method returns bounding-box annotations + for each video frame. The annotations are returned as dictionary of dictionaries. + + **Parameters:** + + ``f`` : :py:class:`object` + An instance of ``MsuMfsdPadFile`` defined above. + + **Returns:** + + ``annotations`` : :py:class:`dict` + A dictionary containing the annotations for each frame in the video. + Dictionary structure: ``annotations = {'1': frame1_dict, '2': frame1_dict, ...}``. + Where ``frameN_dict = {'topleft': (row, col), 'bottomright': (row, col)}`` + is the dictionary defining the coordinates of the face bounding box in frame N. + """ + + annots = f.f.bbx(directory=self.original_directory) # numpy array containing the face bounding box data for each video frame, returned data format described in the f.bbx() method of the low level interface + + annotations = {} # dictionary to return + + for frame_annots in annots: + + topleft = ( np.int( frame_annots[2] ), np.int( frame_annots[1] ) ) + bottomright = ( np.int( frame_annots[2] + frame_annots[4] ), np.int( frame_annots[1] + frame_annots[3] ) ) + + annotations[str( np.int( frame_annots[0] ) )] = {'topleft': topleft, 'bottomright': bottomright} + + return annotations + + diff --git a/bob/pad/face/preprocessor/FrameDifference.py b/bob/pad/face/preprocessor/FrameDifference.py index 696b90e0a1f79df52977f381b8dce666f8f0c881..2f3cfcf28f39600f5abbe7b90d412c7d2bbadc94 100644 --- a/bob/pad/face/preprocessor/FrameDifference.py +++ b/bob/pad/face/preprocessor/FrameDifference.py @@ -300,6 +300,57 @@ class FrameDifference(Preprocessor, object): return diff + #========================================================================== + def select_annotated_frames(self, frames, annotations): + """ + Select only annotated frames in the input FrameContainer ``frames``. + + **Parameters:** + + ``frames`` : FrameContainer + Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer`` + for further details. + + ``annotations`` : :py:class:`dict` + A dictionary containing the annotations for each frame in the video. + Dictionary structure: ``annotations = {'1': frame1_dict, '2': frame1_dict, ...}``. + Where ``frameN_dict = {'topleft': (row, col), 'bottomright': (row, col)}`` + is the dictionary defining the coordinates of the face bounding box in frame N. + + **Returns:** + + ``cleaned_frame_container`` : FrameContainer + FrameContainer containing the annotated frames only. + + ``cleaned_annotations`` : :py:class:`dict` + A dictionary containing the annotations for each frame in the output video. + Dictionary structure: ``annotations = {'1': frame1_dict, '2': frame1_dict, ...}``. + Where ``frameN_dict = {'topleft': (row, col), 'bottomright': (row, col)}`` + is the dictionary defining the coordinates of the face bounding box in frame N. + """ + + annotated_frames = np.sort( [np.int(item) for item in annotations.keys()] ) # annotated frame numbers + + available_frames = range(0,len(frames)) # frame numbers in the input video + + valid_frames = list(set(annotated_frames).intersection(available_frames)) # valid and annotated frames + + cleaned_frame_container = bob.bio.video.FrameContainer() # initialize the FrameContainer + + cleaned_annotations = {} + + for idx, valid_frame_num in enumerate(valid_frames): + ## valid_frame_num - is the number of the original frame having annotations + + cleaned_annotations[str(idx)] = annotations[str(valid_frame_num)] # correct the frame numbers + + selected_frame = frames[valid_frame_num][1] # get current frame + + cleaned_frame_container.add(idx, selected_frame) # add current frame to FrameContainer + + return cleaned_frame_container, cleaned_annotations + + #========================================================================== def __call__(self, frames, annotations): """ @@ -329,6 +380,11 @@ class FrameDifference(Preprocessor, object): The second column contains frame differences of non-facial/background regions. """ + if len(frames) != len(annotations): # if some annotations are missing + + ## Select only annotated frames: + frames, annotations = self.select_annotated_frames(frames, annotations) + if self.check_face_size_flag: selected_frames, selected_annotations = self.check_face_size(frames, annotations, self.min_face_size) diff --git a/bob/pad/face/preprocessor/VideoFaceCrop.py b/bob/pad/face/preprocessor/VideoFaceCrop.py index 02a4371afec104036954e946bd02fd68ae9b44f1..fd9756766aea41ad1de85a1f9ae398ac2809f7f3 100644 --- a/bob/pad/face/preprocessor/VideoFaceCrop.py +++ b/bob/pad/face/preprocessor/VideoFaceCrop.py @@ -159,13 +159,19 @@ class VideoFaceCrop(Preprocessor, object): ``min_face_size`` : :py:class:`int` The minimal size of the face in pixels. + + **Returns:** + + ``cleaned_frame_container`` : FrameContainer + FrameContainer containing the frames with faces of the size + overcoming the specified threshold. """ cleaned_frame_container = bob.bio.video.FrameContainer() # initialize the FrameContainer selected_frame_idx = 0 - for idx in range(0, len(annotations)): # idx - frame index + for idx in range(0, np.min( [len(annotations), len(frame_container)] )): # idx - frame index frame_annotations = annotations[str(idx)] # annotations for particular frame @@ -183,6 +189,57 @@ class VideoFaceCrop(Preprocessor, object): return cleaned_frame_container + #========================================================================== + def select_annotated_frames(self, frames, annotations): + """ + Select only annotated frames in the input FrameContainer ``frames``. + + **Parameters:** + + ``frames`` : FrameContainer + Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer`` + for further details. + + ``annotations`` : :py:class:`dict` + A dictionary containing the annotations for each frame in the video. + Dictionary structure: ``annotations = {'1': frame1_dict, '2': frame1_dict, ...}``. + Where ``frameN_dict = {'topleft': (row, col), 'bottomright': (row, col)}`` + is the dictionary defining the coordinates of the face bounding box in frame N. + + **Returns:** + + ``cleaned_frame_container`` : FrameContainer + FrameContainer containing the annotated frames only. + + ``cleaned_annotations`` : :py:class:`dict` + A dictionary containing the annotations for each frame in the output video. + Dictionary structure: ``annotations = {'1': frame1_dict, '2': frame1_dict, ...}``. + Where ``frameN_dict = {'topleft': (row, col), 'bottomright': (row, col)}`` + is the dictionary defining the coordinates of the face bounding box in frame N. + """ + + annotated_frames = np.sort( [np.int(item) for item in annotations.keys()] ) # annotated frame numbers + + available_frames = range(0,len(frames)) # frame numbers in the input video + + valid_frames = list(set(annotated_frames).intersection(available_frames)) # valid and annotated frames + + cleaned_frame_container = bob.bio.video.FrameContainer() # initialize the FrameContainer + + cleaned_annotations = {} + + for idx, valid_frame_num in enumerate(valid_frames): + ## valid_frame_num - is the number of the original frame having annotations + + cleaned_annotations[str(idx)] = annotations[str(valid_frame_num)] # correct the frame numbers + + selected_frame = frames[valid_frame_num][1] # get current frame + + cleaned_frame_container.add(idx, selected_frame) # add current frame to FrameContainer + + return cleaned_frame_container, cleaned_annotations + + #========================================================================== def __call__(self, frames, annotations): """ @@ -206,6 +263,11 @@ class VideoFaceCrop(Preprocessor, object): Cropped faces stored in the FrameContainer. """ + if len(frames) != len(annotations): # if some annotations are missing + + ## Select only annotated frames: + frames, annotations = self.select_annotated_frames(frames, annotations) + preprocessed_video = self.video_preprocessor(frames = frames, annotations = annotations) if self.check_face_size_flag: diff --git a/bob/pad/face/test/test_databases.py b/bob/pad/face/test/test_databases.py index 68a61e4e0762b59be1d2b5441a0bfa66437faf8a..6c628bd2b28e310221186721254cacc434da5f48 100644 --- a/bob/pad/face/test/test_databases.py +++ b/bob/pad/face/test/test_databases.py @@ -38,4 +38,23 @@ def test_replaymobile(): 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) \ No newline at end of file + "The database could not be queried; probably the db.sql3 file is missing. Here is the error: '%s'" % e) + + +@db_available('msu_mfsd_mod') # the name of the package defining low-level interface of MSU MFSD +def test_msu_mfsd(): + msu_mfsd = bob.bio.base.load_resource('msu-mfsd', 'database', preferred_package='bob.pad.face', package_prefix='bob.pad.') + try: + + assert len( msu_mfsd.objects(groups=['train', 'dev', 'eval']) )== 280 + assert len( msu_mfsd.objects(groups=['train', 'dev']) ) == 160 + assert len( msu_mfsd.objects(groups=['train']) ) == 80 + assert len( msu_mfsd.objects(groups=['train', 'dev', 'eval'], protocol = 'grandtest') )== 280 + assert len( msu_mfsd.objects(groups=['train', 'dev', 'eval'], protocol = 'grandtest', purposes='real') ) == 70 + assert len( msu_mfsd.objects(groups=['train', 'dev', 'eval'], protocol = 'grandtest', purposes='attack') ) == 210 + + 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) + + diff --git a/doc/api.rst b/doc/api.rst index 4d29355e650ea5fb22acf9ce2fe438ddd99b2df4..8f94169abe5406ee6bfa772b71ff72ff0c5e9153 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -16,7 +16,20 @@ Database Interfaces REPLAY-ATTACK Database ======================== -.. automodule:: bob.pad.face.database.replay +.. autoclass:: bob.pad.face.database.replay.ReplayPadFile +.. autoclass:: bob.pad.face.database.replay.ReplayPadDatabase + +REPLAY-MOBILE Database +======================== + +.. autoclass:: bob.pad.face.database.replay_mobile.ReplayMobilePadFile +.. autoclass:: bob.pad.face.database.replay_mobile.ReplayMobilePadDatabase + +MSU MFSD Database +======================== + +.. autoclass:: bob.pad.face.database.msu_mfsd.MsuMfsdPadFile +.. autoclass:: bob.pad.face.database.msu_mfsd.MsuMfsdPadDatabase Pre-processors @@ -34,4 +47,4 @@ Feature Extractors Matching Algorithms ------------------------------ -.. automodule:: bob.pad.face.algorithm \ No newline at end of file +.. automodule:: bob.pad.face.algorithm diff --git a/doc/baselines.rst b/doc/baselines.rst index d9236f053fa6f1d00a66c58d445c585796661872..80057a25df0a54a75ddaf3ec93105368fd2acabe 100644 --- a/doc/baselines.rst +++ b/doc/baselines.rst @@ -141,9 +141,9 @@ The ROC curves for the particular experiment can be downloaded from here: :download:`ROC curve <img/ROC_lbp_svm_replay_attack.pdf>` - ------------ + Image Quality Measures as features of facial region + SVM classifier ======================================================================== @@ -249,6 +249,55 @@ The description of the database instance, which can be used to run face PAD expe here :ref:`bob.pad.face.resources.databases.replay_mobile`. +LBP features of facial region + SVM classifier +======================================================================== + +Detailed description of this PAD pipe-line is given at :ref:`bob.pad.face.resources.face_pad.lbp_svm_replayattack`. +Note, that the same PAD pipe-line was used to run experiments on the Replay-Attack database. + +To run this baseline on the `Replay-Mobile`_ database, using the ``grandtest`` protocol, execute the following: + +.. code-block:: sh + + $ ./bin/spoof.py lbp-svm \ + --database replay-mobile --protocol grandtest --groups train dev eval \ + --sub-directory <PATH_TO_STORE_THE_RESULTS> + +.. tip:: + + Similarly to the tip above you can run this baseline in parallel. + +To understand the settings of this baseline PAD experiment you can check the +corresponding configuration file: ``bob/pad/face/config/lbp_svm.py`` + +To evaluate the results computing EER, HTER and plotting ROC you can use the +following command: + +.. code-block:: sh + + ./bin/evaluate.py \ + --dev-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-dev \ + --eval-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-eval \ + --legends "LBP features of facial region + SVM classifier + Replay-Mobile database" \ + -F 7 \ + --criterion EER \ + --roc <PATH_TO_STORE_THE_RESULTS>/ROC.pdf + +The EER/HTER errors for the `Replay-Mobile`_ database are summarized in the Table below: + ++-------------------+----------+----------+ +| Protocol | EER,\% | HTER,\% | ++===================+==========+==========+ +| ``grandtest`` | 13.814 | 17.174 | ++-------------------+----------+----------+ + +The ROC curves for the particular experiment can be downloaded from here: + +:download:`ROC curve <img/ROC_lbp_svm_replay_mobile.pdf>` + +------------ + + Image Quality Measures as features of facial region + SVM classifier ======================================================================== @@ -298,4 +347,210 @@ The ROC curves for the particular experiment can be downloaded from here: ------------ +Frame differences based features (motion analysis) + SVM classifier +======================================================================== + +Detailed description of this PAD pipe-line is given at :ref:`bob.pad.face.resources.face_pad.frame_diff_svm_replayattack`. +Note, that the same PAD pipe-line was used to run experiments on the Replay-Attack database. + +To run this baseline on the `Replay-Mobile`_ database, using the ``grandtest`` protocol, execute the following: + +.. code-block:: sh + + $ ./bin/spoof.py frame-diff-svm \ + --database replay-mobile --protocol grandtest --groups train dev eval \ + --sub-directory <PATH_TO_STORE_THE_RESULTS> + +.. tip:: + + Similarly to the tip above you can run this baseline in parallel. + +To understand the settings of this baseline PAD experiment you can check the +corresponding configuration file: ``bob/pad/face/config/frame_diff_svm.py`` + +To evaluate the results computing EER, HTER and plotting ROC you can use the +following command: + +.. code-block:: sh + + ./bin/evaluate.py \ + --dev-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-dev \ + --eval-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-eval \ + --legends "10 features for each window in Frame Differences + SVM classifier + Replay-Mobile database" \ + -F 7 \ + --criterion EER \ + --roc <PATH_TO_STORE_THE_RESULTS>/ROC.pdf + +The EER/HTER errors for the `Replay-Mobile`_ database are summarized in the Table below: + ++-------------------+----------+----------+ +| Protocol | EER,\% | HTER,\% | ++===================+==========+==========+ +| ``grandtest`` | 10.801 | 10.414 | ++-------------------+----------+----------+ + +The ROC curves for the particular experiment can be downloaded from here: + +:download:`ROC curve <img/ROC_frame_diff_svm_replay_mobile.pdf>` + +------------ + +.. _bob.pad.face.baselines.msu_mfsd: + +Baselines on MSU MFSD database +-------------------------------------- + +This section summarizes the results of baseline face PAD experiments on the `MSU MFSD`_ database. +The description of the database instance, which can be used to run face PAD experiments on the MSU MFSD is given +here :ref:`bob.pad.face.resources.databases.msu_mfsd`. + + +LBP features of facial region + SVM classifier +======================================================================== + +Detailed description of this PAD pipe-line is given at :ref:`bob.pad.face.resources.face_pad.lbp_svm_replayattack`. +Note, that the same PAD pipe-line was used to run experiments on the Replay-Attack database. + +To run this baseline on the `MSU MFSD`_ database, using the ``grandtest`` protocol, execute the following: + +.. code-block:: sh + + $ ./bin/spoof.py lbp-svm \ + --database msu-mfsd --protocol grandtest --groups train dev eval \ + --sub-directory <PATH_TO_STORE_THE_RESULTS> + +.. tip:: + + Similarly to the tip above you can run this baseline in parallel. + +To understand the settings of this baseline PAD experiment you can check the +corresponding configuration file: ``bob/pad/face/config/lbp_svm.py`` + +To evaluate the results computing EER, HTER and plotting ROC you can use the +following command: + +.. code-block:: sh + + ./bin/evaluate.py \ + --dev-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-dev \ + --eval-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-eval \ + --legends "LBP features of facial region + SVM classifier + MSU MFSD database" \ + -F 7 \ + --criterion EER \ + --roc <PATH_TO_STORE_THE_RESULTS>/ROC.pdf + +The EER/HTER errors for the `MSU MFSD`_ database are summarized in the Table below: + ++-------------------+----------+----------+ +| Protocol | EER,\% | HTER,\% | ++===================+==========+==========+ +| ``grandtest`` | 27.402 | 21.399 | ++-------------------+----------+----------+ + +The ROC curves for the particular experiment can be downloaded from here: + +:download:`ROC curve <img/ROC_lbp_svm_msu_mfsd.pdf>` + +------------ + + +Image Quality Measures as features of facial region + SVM classifier +======================================================================== + +Detailed description of this PAD pipe-line is given at :ref:`bob.pad.face.resources.face_pad.qm_svm_replayattack`. +Note, that the same PAD pipe-line was used to run experiments on the Replay-Attack database. + +To run this baseline on the `MSU MFSD`_ database, using the ``grandtest`` protocol, execute the following: + +.. code-block:: sh + + $ ./bin/spoof.py qm-svm \ + --database msu-mfsd --protocol grandtest --groups train dev eval \ + --sub-directory <PATH_TO_STORE_THE_RESULTS> + +.. tip:: + + Similarly to the tip above you can run this baseline in parallel. + +To understand the settings of this baseline PAD experiment you can check the +corresponding configuration file: ``bob/pad/face/config/qm_svm.py`` + +To evaluate the results computing EER, HTER and plotting ROC you can use the +following command: + +.. code-block:: sh + + ./bin/evaluate.py \ + --dev-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-dev \ + --eval-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-eval \ + --legends "IQM features of facial region + SVM classifier + MSU MFSD database" \ + -F 7 \ + --criterion EER \ + --roc <PATH_TO_STORE_THE_RESULTS>/ROC.pdf + +The EER/HTER errors for the `MSU MFSD`_ database are summarized in the Table below: + ++-------------------+----------+----------+ +| Protocol | EER,\% | HTER,\% | ++===================+==========+==========+ +| ``grandtest`` | 4.115 | 5.564 | ++-------------------+----------+----------+ + +The ROC curves for the particular experiment can be downloaded from here: + +:download:`ROC curve <img/ROC_iqm_svm_msu_mfsd.pdf>` + +------------ + + +Frame differences based features (motion analysis) + SVM classifier +======================================================================== + +Detailed description of this PAD pipe-line is given at :ref:`bob.pad.face.resources.face_pad.frame_diff_svm_replayattack`. +Note, that the same PAD pipe-line was used to run experiments on the Replay-Attack database. + +To run this baseline on the `MSU MFSD`_ database, using the ``grandtest`` protocol, execute the following: + +.. code-block:: sh + + $ ./bin/spoof.py frame-diff-svm \ + --database msu-mfsd --protocol grandtest --groups train dev eval \ + --sub-directory <PATH_TO_STORE_THE_RESULTS> + +.. tip:: + + Similarly to the tip above you can run this baseline in parallel. + +To understand the settings of this baseline PAD experiment you can check the +corresponding configuration file: ``bob/pad/face/config/frame_diff_svm.py`` + +To evaluate the results computing EER, HTER and plotting ROC you can use the +following command: + +.. code-block:: sh + + ./bin/evaluate.py \ + --dev-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-dev \ + --eval-files <PATH_TO_STORE_THE_RESULTS>/grandtest/scores/scores-eval \ + --legends "10 features for each window in Frame Differences + SVM classifier + MSU MFSD database" \ + -F 7 \ + --criterion EER \ + --roc <PATH_TO_STORE_THE_RESULTS>/ROC.pdf + +The EER/HTER errors for the `MSU MFSD`_ database are summarized in the Table below: + ++-------------------+----------+----------+ +| Protocol | EER,\% | HTER,\% | ++===================+==========+==========+ +| ``grandtest`` | 25.839 | 17.050 | ++-------------------+----------+----------+ + +The ROC curves for the particular experiment can be downloaded from here: + +:download:`ROC curve <img/ROC_frame_diff_svm_msu_mfsd.pdf>` + +------------ + + .. include:: links.rst + diff --git a/doc/img/ROC_frame_diff_svm_msu_mfsd.pdf b/doc/img/ROC_frame_diff_svm_msu_mfsd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..18b1354e54dc9e64c57f345ba9a15cdc1f7410b1 Binary files /dev/null and b/doc/img/ROC_frame_diff_svm_msu_mfsd.pdf differ diff --git a/doc/img/ROC_frame_diff_svm_replay_mobile.pdf b/doc/img/ROC_frame_diff_svm_replay_mobile.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fa6048a5a55f3abeb9d6b3fd4d1ab075914b839f Binary files /dev/null and b/doc/img/ROC_frame_diff_svm_replay_mobile.pdf differ diff --git a/doc/img/ROC_iqm_svm_msu_mfsd.pdf b/doc/img/ROC_iqm_svm_msu_mfsd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..740123c376d98fdee99f260e5afa59effe258c36 Binary files /dev/null and b/doc/img/ROC_iqm_svm_msu_mfsd.pdf differ diff --git a/doc/img/ROC_lbp_svm_msu_mfsd.pdf b/doc/img/ROC_lbp_svm_msu_mfsd.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6b4e7c083a14deb090f1c65c61a260c500a9f079 Binary files /dev/null and b/doc/img/ROC_lbp_svm_msu_mfsd.pdf differ diff --git a/doc/img/ROC_lbp_svm_replay_mobile.pdf b/doc/img/ROC_lbp_svm_replay_mobile.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d31c0e0d2247664593b98147ebe62fa8c5d9da69 Binary files /dev/null and b/doc/img/ROC_lbp_svm_replay_mobile.pdf differ diff --git a/doc/links.rst b/doc/links.rst index 32f077c3900fd70ccb8468758ef3f3773c01d510..44ef1f87ef310f7e3bf869f70152c569dfb00ee9 100644 --- a/doc/links.rst +++ b/doc/links.rst @@ -3,7 +3,7 @@ .. This file contains all links we use for documentation in a centralized place -.. _idiap: http://www.idiap. +.. _idiap: http://www.idiap.ch .. _bob: http://www.idiap.ch/software/bob .. _buildout: http://www.buildout.org .. _pypi: http://pypi.python.org @@ -12,4 +12,4 @@ .. _replayattack: https://www.idiap.ch/dataset/replayattack .. _replay-mobile: https://www.idiap.ch/dataset/replay-mobile .. _dependencies: https://gitlab.idiap.ch/bob/bob/wikis/Dependencies - +.. _MSU MFSD: http://biometrics.cse.msu.edu/Publications/Databases/MSUMobileFaceSpoofing/index.htm \ No newline at end of file diff --git a/doc/resources.rst b/doc/resources.rst index b761a780b49247a1665bcaf187f0b56cf5e9c82e..9edada2e3441f26eedb7e24e27074caa51cd7715 100644 --- a/doc/resources.rst +++ b/doc/resources.rst @@ -40,6 +40,15 @@ Replay-Mobile Database :members: +.. _bob.pad.face.resources.databases.msu_mfsd: + +MSU MFSD Database +================================================================================ + +.. automodule:: bob.pad.face.config.database.msu_mfsd + :members: + + --------------------------------- diff --git a/setup.py b/setup.py index 27af5799b31136418c062267cff5970c76e52574..db586b2bc847b47e41069bfba8c598b3403f7fb2 100644 --- a/setup.py +++ b/setup.py @@ -97,6 +97,7 @@ setup( 'bob.pad.database': [ 'replay = bob.pad.face.config.database.replay:database', 'replay-mobile = bob.pad.face.config.database.replay_mobile:database', + 'msu-mfsd = bob.pad.face.config.database.msu_mfsd:database', ], # registered configurations: diff --git a/test-requirements.txt b/test-requirements.txt index 6a55b2f913576b9a6cd1ae96aec700e3d7931a2e..f7509181fd4249e73b5187b198e77f5881677ea2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,2 +1,3 @@ bob.db.replay bob.db.replaymobile +bob.db.msu_mfsd_mod \ No newline at end of file