diff --git a/bob/pad/face/config/database/__init__.py b/bob/pad/face/config/database/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bob/pad/face/config/database/batl/__init__.py b/bob/pad/face/config/database/batl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bob/pad/face/config/batl_db.py b/bob/pad/face/config/database/batl/batl_db.py similarity index 100% rename from bob/pad/face/config/batl_db.py rename to bob/pad/face/config/database/batl/batl_db.py diff --git a/bob/pad/face/config/batl_db_depth.py b/bob/pad/face/config/database/batl/batl_db_depth.py similarity index 100% rename from bob/pad/face/config/batl_db_depth.py rename to bob/pad/face/config/database/batl/batl_db_depth.py diff --git a/bob/pad/face/config/batl_db_infrared.py b/bob/pad/face/config/database/batl/batl_db_infrared.py similarity index 100% rename from bob/pad/face/config/batl_db_infrared.py rename to bob/pad/face/config/database/batl/batl_db_infrared.py diff --git a/bob/pad/face/config/database/batl/batl_db_rgb_ir_d_grandtest.py b/bob/pad/face/config/database/batl/batl_db_rgb_ir_d_grandtest.py new file mode 100644 index 0000000000000000000000000000000000000000..6fc2381bca64f7327b7f632458c574061af054f0 --- /dev/null +++ b/bob/pad/face/config/database/batl/batl_db_rgb_ir_d_grandtest.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +""" +Idiap BATL DB is a database for face PAD experiments. +""" + +from bob.pad.face.database import BatlPadDatabase + +# 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_BATL_DB_DIRECTORY]" +"""Value of ``~/.bob_bio_databases.txt`` for this database""" + +ORIGINAL_EXTENSION = ".h5" # extension of the data files + +ANNOTATIONS_TEMP_DIR = "" # NOTE: this variable is NOT assigned in the instance of the BatlPadDatabase, thus "rc" functionality defined in bob.extension will be involved + +PROTOCOL = 'grandtest-color*infrared*depth-10' # use 10 frames for PAD experiments + +database = BatlPadDatabase( + protocol=PROTOCOL, + original_directory=ORIGINAL_DIRECTORY, + original_extension=ORIGINAL_EXTENSION, + landmark_detect_method="mtcnn", # detect annotations using mtcnn + exclude_attacks_list=['makeup'], + exclude_pai_all_sets=True, # exclude makeup from all the sets, which is the default behavior for grandtest protocol + append_color_face_roi_annot=False) # do not append annotations, defining ROI in the cropped face image, to the dictionary of annotations + +"""The :py:class:`bob.pad.base.database.BatlPadDatabase` derivative with BATL Db +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_BATL_DB_DIRECTORY]``. +You must make sure to create ``${HOME}/.bob_bio_databases.txt`` file setting this +value to the places where you actually installed the BATL Govt database. +""" + +protocol = PROTOCOL +""" +You may modify this at runtime by specifying the option ``--protocol`` on the +command-line of ``spoof.py`` or using the keyword ``protocol`` on a +configuration file that is loaded **after** this configuration resource. +""" + +groups = ["train", "dev", "eval"] +"""The default groups to use for reproducing the baselines. + +You may modify this at runtime by specifying the option ``--groups`` on the +command-line of ``spoof.py`` or using the keyword ``groups`` on a +configuration file that is loaded **after** this configuration resource. +""" diff --git a/bob/pad/face/config/batl_db_thermal.py b/bob/pad/face/config/database/batl/batl_db_thermal.py similarity index 100% rename from bob/pad/face/config/batl_db_thermal.py rename to bob/pad/face/config/database/batl/batl_db_thermal.py diff --git a/bob/pad/face/config/preprocessor/video_face_crop_align_block_patch.py b/bob/pad/face/config/preprocessor/video_face_crop_align_block_patch.py new file mode 100644 index 0000000000000000000000000000000000000000..aa96bdf5b04834e4e550f122166c10050c1b7a3b --- /dev/null +++ b/bob/pad/face/config/preprocessor/video_face_crop_align_block_patch.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# ============================================================================= +# Import here: +from bob.pad.face.preprocessor import VideoFaceCropAlignBlockPatch + +from bob.pad.face.preprocessor import FaceCropAlign + +from bob.bio.video.preprocessor import Wrapper + +from bob.bio.video.utils import FrameSelector + +from bob.pad.face.preprocessor.FaceCropAlign import auto_norm_image as _norm_func + +from bob.pad.face.preprocessor import BlockPatch + + +# ============================================================================= +# names of the channels to process: +_channel_names = ['color', 'infrared', 'depth'] + + +# ============================================================================= +# dictionary containing preprocessors for all channels: +_preprocessors = {} + +""" +Preprocessor to be used for Color channel. +""" +FACE_SIZE = 128 # The size of the resulting face +RGB_OUTPUT_FLAG = False # BW output +USE_FACE_ALIGNMENT = True # use annotations +MAX_IMAGE_SIZE = None # no limiting here +FACE_DETECTION_METHOD = None # use ANNOTATIONS +MIN_FACE_SIZE = 50 # skip small faces + +_image_preprocessor = FaceCropAlign(face_size = FACE_SIZE, + rgb_output_flag = RGB_OUTPUT_FLAG, + use_face_alignment = USE_FACE_ALIGNMENT, + max_image_size = MAX_IMAGE_SIZE, + face_detection_method = FACE_DETECTION_METHOD, + min_face_size = MIN_FACE_SIZE) + +_frame_selector = FrameSelector(selection_style = "all") + +_preprocessor_rgb = Wrapper(preprocessor = _image_preprocessor, + frame_selector = _frame_selector) + +_preprocessors[_channel_names[0]] = _preprocessor_rgb + +""" +Preprocessor to be used for Infrared (or Thermal) channels: +""" +FACE_SIZE = 128 # The size of the resulting face +RGB_OUTPUT_FLAG = False # Gray-scale output +USE_FACE_ALIGNMENT = True # use annotations +MAX_IMAGE_SIZE = None # no limiting here +FACE_DETECTION_METHOD = None # use annotations +MIN_FACE_SIZE = 50 # skip small faces +NORMALIZATION_FUNCTION = _norm_func +NORMALIZATION_FUNCTION_KWARGS = {} +NORMALIZATION_FUNCTION_KWARGS = {'n_sigma':3.0, 'norm_method':'MAD'} + +_image_preprocessor_ir = FaceCropAlign(face_size = FACE_SIZE, + rgb_output_flag = RGB_OUTPUT_FLAG, + use_face_alignment = USE_FACE_ALIGNMENT, + max_image_size = MAX_IMAGE_SIZE, + face_detection_method = FACE_DETECTION_METHOD, + min_face_size = MIN_FACE_SIZE, + normalization_function = NORMALIZATION_FUNCTION, + normalization_function_kwargs = NORMALIZATION_FUNCTION_KWARGS) + +_preprocessor_ir = Wrapper(preprocessor = _image_preprocessor_ir, + frame_selector = _frame_selector) + +_preprocessors[_channel_names[1]] = _preprocessor_ir + +""" +Preprocessor to be used for Depth channel: +""" +FACE_SIZE = 128 # The size of the resulting face +RGB_OUTPUT_FLAG = False # Gray-scale output +USE_FACE_ALIGNMENT = True # use annotations +MAX_IMAGE_SIZE = None # no limiting here +FACE_DETECTION_METHOD = None # use annotations +MIN_FACE_SIZE = 50 # skip small faces +NORMALIZATION_FUNCTION = _norm_func +NORMALIZATION_FUNCTION_KWARGS = {} +NORMALIZATION_FUNCTION_KWARGS = {'n_sigma':6.0, 'norm_method':'MAD'} + +_image_preprocessor_d = FaceCropAlign(face_size = FACE_SIZE, + rgb_output_flag = RGB_OUTPUT_FLAG, + use_face_alignment = USE_FACE_ALIGNMENT, + max_image_size = MAX_IMAGE_SIZE, + face_detection_method = FACE_DETECTION_METHOD, + min_face_size = MIN_FACE_SIZE, + normalization_function = NORMALIZATION_FUNCTION, + normalization_function_kwargs = NORMALIZATION_FUNCTION_KWARGS) + +_preprocessor_d = Wrapper(preprocessor = _image_preprocessor_d, + frame_selector = _frame_selector) + +_preprocessors[_channel_names[2]] = _preprocessor_d + + +# ============================================================================= +# define parameters and an instance of the patch extractor: +PATCH_SIZE = 128 +STEP = 1 + +_block_patch_128x128 = BlockPatch(patch_size = PATCH_SIZE, + step = STEP, + use_annotations_flag = False) + + +# ============================================================================= +""" +Define an instance for extraction of one (**whole face**) multi-channel +(BW-NIR-D) face patch of the size (3 x 128 x 128). +""" +video_face_crop_align_bw_ir_d_channels_3x128x128 = VideoFaceCropAlignBlockPatch(preprocessors = _preprocessors, + channel_names = _channel_names, + return_multi_channel_flag = True, + block_patch_preprocessor = _block_patch_128x128) + diff --git a/bob/pad/face/preprocessor/VideoFaceCropAlignBlockPatch.py b/bob/pad/face/preprocessor/VideoFaceCropAlignBlockPatch.py index 4aa913090e9272739d879a184f077637511a0884..9ca1e5cfa1fc124e55d59f78cb24b0d1bc0ce83c 100644 --- a/bob/pad/face/preprocessor/VideoFaceCropAlignBlockPatch.py +++ b/bob/pad/face/preprocessor/VideoFaceCropAlignBlockPatch.py @@ -240,6 +240,8 @@ class VideoFaceCropAlignBlockPatch(Preprocessor, object): facial region. ROI is annotated as follows: ``annotations['face_roi'][0] = [x_top_left, y_top_left]`` ``annotations['face_roi'][1] = [x_bottom_right, y_bottom_right]`` + If ``face_roi`` annotations are undefined, the patches will be + extracted from an entire cropped facial image. **Returns:** diff --git a/bob/pad/face/test/test.py b/bob/pad/face/test/test.py index cf10dcab76da3c08d8f8e27954b8a60babf8bc77..ed6c8d2c9fca05e9e24424daa2e55d2ebae31d9d 100644 --- a/bob/pad/face/test/test.py +++ b/bob/pad/face/test/test.py @@ -52,6 +52,8 @@ from bob.pad.face.config.preprocessor.face_feature_crop_quality_check import fac from bob.pad.face.utils.patch_utils import reshape_flat_patches +from bob.pad.face.config.preprocessor.video_face_crop_align_block_patch import video_face_crop_align_bw_ir_d_channels_3x128x128 as mc_preprocessor + def test_detect_face_landmarks_in_image_mtcnn(): @@ -335,6 +337,42 @@ def test_preproc_with_quality_check(): assert data_preprocessed is None +# ============================================================================= +def test_multi_channel_preprocessing(): + """ + Test video_face_crop_align_bw_ir_d_channels_3x128x128 preprocessor. + """ + + # ========================================================================= + # prepare the test data: + + image = load(datafile('test_image.png', 'bob.pad.face.test')) + + # annotations must be known for this preprocessor, so compute them: + annotations = detect_face_landmarks_in_image(image, method="mtcnn") + + video_color, annotations = convert_image_to_video_data(image, annotations, 2) + + video_bw, _ = convert_image_to_video_data(image[0], annotations, 2) + + mc_video = {} + mc_video["color"] = video_color + mc_video["infrared"] = video_bw + mc_video["depth"] = video_bw + + # ========================================================================= + # test the preprocessor: + + data_preprocessed = mc_preprocessor(mc_video, annotations) + + assert len(data_preprocessed) == 2 + assert data_preprocessed[0][1].shape == (3, 128, 128) + + # chanenls are preprocessed differently, thus this should apply: + assert np.any(data_preprocessed[0][1][0] != data_preprocessed[0][1][1]) + assert np.any(data_preprocessed[0][1][0] != data_preprocessed[0][1][2]) + + # ============================================================================= def test_reshape_flat_patches(): """ diff --git a/doc/mc_autoencoder_pad.rst b/doc/mc_autoencoder_pad.rst index f59cbdf19bc1726e218e6b5eb81fdeb34f9c300c..7cd6d3bee6947d2d43dd2f4a09f61bce7af5b7fb 100644 --- a/doc/mc_autoencoder_pad.rst +++ b/doc/mc_autoencoder_pad.rst @@ -83,6 +83,25 @@ The training procedure is explained in the **Convolutional autoencoder** section .. include:: links.rst +2. Fine-tune N AEs on multi-channel data from WMCA (legacy name BATL) database +================================================================================= +Following the training procedure of [NGM19]_, the autoencoders are next fine-tuned on the multi-channel (**MC**) data from WMCA. +In this example, MC training data is a stack of gray-scale, NIR, and Depth (BW-NIR-D) facial images. +To prepare the training data one can use the following command: + + +.. code-block:: sh + + ./bin/spoof.py \ # spoof.py is used to run the preprocessor + batl-db-rgb-ir-d-grandtest \ # WMCA database instance allowing to load RGB-NIR-D channels + lbp-svm \ # required by spoof.py, but unused + --skip-extractor-training --skip-extraction --skip-projector-training --skip-projection --skip-score-computation --allow-missing-files \ # execute only preprocessing step + --grid idiap \ # use grid, only for Idiap users, remove otherwise + --preprocessor video-face-crop-align-bw-ir-d-channels-3x128x128 \ # preprocessor entry point + --sub-directory <PATH_TO_STORE_THE_RESULTS> # define your path here + +Once above script is completed, the MC data suitable for autoencoder fine-tuning is located in the folder ``<PATH_TO_STORE_THE_RESULTS>/preprocessed/``. +Now the autoencoder can be fine-tuned. Again, the fine-tuning procedure is explained in the **Convolutional autoencoder** section in the documentation of the ``bob.learn.pytorch`` package. diff --git a/setup.py b/setup.py index b9b97d29d3ad99ddfac96f79354377505194ae15..bdd290b8954e7a34de517cdd0ff11462021d1ff4 100644 --- a/setup.py +++ b/setup.py @@ -68,10 +68,11 @@ setup( 'msu-mfsd = bob.pad.face.config.msu_mfsd:database', 'aggregated-db = bob.pad.face.config.aggregated_db:database', 'mifs = bob.pad.face.config.mifs:database', - 'batl-db = bob.pad.face.config.batl_db:database', - 'batl-db-infrared = bob.pad.face.config.batl_db_infrared:database', - 'batl-db-depth = bob.pad.face.config.batl_db_depth:database', - 'batl-db-thermal = bob.pad.face.config.batl_db_thermal:database', + 'batl-db = bob.pad.face.config.database.batl.batl_db:database', + 'batl-db-infrared = bob.pad.face.config.database.batl.batl_db_infrared:database', + 'batl-db-depth = bob.pad.face.config.database.batl.batl_db_depth:database', + 'batl-db-thermal = bob.pad.face.config.database.batl.batl_db_thermal:database', + 'batl-db-rgb-ir-d-grandtest = bob.pad.face.config.database.batl.batl_db_rgb_ir_d_grandtest:database', 'celeb-a = bob.pad.face.config.celeb_a:database', 'maskattack = bob.pad.face.config.maskattack:database', ], @@ -84,10 +85,11 @@ setup( 'msu-mfsd = bob.pad.face.config.msu_mfsd', 'aggregated-db = bob.pad.face.config.aggregated_db', 'mifs = bob.pad.face.config.mifs', - 'batl-db = bob.pad.face.config.batl_db', - 'batl-db-infrared = bob.pad.face.config.batl_db_infrared', - 'batl-db-depth = bob.pad.face.config.batl_db_depth', - 'batl-db-thermal = bob.pad.face.config.batl_db_thermal', + 'batl-db = bob.pad.face.config.database.batl.batl_db', + 'batl-db-infrared = bob.pad.face.config.database.batl.batl_db_infrared', + 'batl-db-depth = bob.pad.face.config.database.batl.batl_db_depth', + 'batl-db-thermal = bob.pad.face.config.database.batl.batl_db_thermal', + 'batl-db-rgb-ir-d-grandtest = bob.pad.face.config.database.batl.batl_db_rgb_ir_d_grandtest', 'celeb-a = bob.pad.face.config.celeb_a', 'maskattack = bob.pad.face.config.maskattack', @@ -118,6 +120,7 @@ setup( 'rgb-face-detect-mtcnn = bob.pad.face.config.preprocessor.video_face_crop:rgb_face_detector_mtcnn', # detect faces locally replacing database annotations 'bw-face-detect-mtcnn = bob.pad.face.config.preprocessor.video_face_crop:bw_face_detect_mtcnn', # detect faces locally, return BW image 'rgb-face-detect-check-quality-128x128 = bob.pad.face.config.preprocessor.face_feature_crop_quality_check:face_feature_0_128x128_crop_rgb', # detect faces locally replacing database annotations, also check face quality by trying to detect the eyes in cropped face. + 'video-face-crop-align-bw-ir-d-channels-3x128x128 = bob.pad.face.config.preprocessor.video_face_crop_align_block_patch:video_face_crop_align_bw_ir_d_channels_3x128x128', # Extract a BW-NIR-D patch of size (3 x 128 x 128) containing aligned face ], # registered extractors: