diff --git a/bob/pad/face/algorithm/VideoCascadeSvmPadAlgorithm.py b/bob/pad/face/algorithm/VideoCascadeSvmPadAlgorithm.py index c5955171ad728f346d5e4aa40a206b76743c04a9..2f04d931bdf935016dce6da21d9781f3c9d49d33 100644 --- a/bob/pad/face/algorithm/VideoCascadeSvmPadAlgorithm.py +++ b/bob/pad/face/algorithm/VideoCascadeSvmPadAlgorithm.py @@ -15,6 +15,8 @@ import numpy as np import bob.learn.libsvm +import bob.learn.linear + import bob.io.base import os @@ -759,13 +761,13 @@ class VideoCascadeSvmPadAlgorithm(Algorithm): resulting_file_name = os.path.join( os.path.split(projector_file)[0], projector_file_name + extension ) # name of the file - f = bob.io.base.HDF5File(resulting_file_name, 'a') # file to read the machine from + f = bob.io.base.HDF5File(resulting_file_name, 'r') # file to read the machine from - if "pca_" in resulting_file_name: + if "pca_" in projector_file_name: machine = bob.learn.linear.Machine(f) - if "svm_" in resulting_file_name: + if "svm_" in projector_file_name: machine = bob.learn.libsvm.Machine(f) @@ -1014,57 +1016,26 @@ class VideoCascadeSvmPadAlgorithm(Algorithm): **Returns:** - ``score`` : :py:class:`float` or a 1D :py:class:`numpy.ndarray` + ``score`` : [:py:class:`float`] If ``frame_level_scores_flag = False`` a single score is returned. - One score per video. - Score is a probability of a sample being a real class. - If ``frame_level_scores_flag = True`` a 1D array of scores is returned. - One score per frame. + One score per video. This score is placed into a list, because + the ``score`` must be an iterable. Score is a probability of a sample being a real class. + If ``frame_level_scores_flag = True`` a list of scores is returned. + One score per frame/sample. """ if self.frame_level_scores_flag: - score = toscore # here score is a 1D array containing scores for each frame + score = list(toscore) else: - score = np.mean( toscore ) # compute a single score per video + score = [np.mean( toscore )] # compute a single score per video return score - #========================================================================== - def score_for_multiple_projections(self, toscore): - """ - Returns a list of scores computed by the score method of this class. - - **Parameters:** - - ``toscore`` : 1D or 2D :py:class:`numpy.ndarray` - 2D in the case of two-class SVM. - An array containing class probabilities for each frame. - First column contains probabilities for each frame being a real class. - Second column contains probabilities for each frame being an attack class. - 1D in the case of one-class SVM. - Vector with scores for each frame defining belonging to the real class. - - **Returns:** - - ``list_of_scores`` : [:py:class:`float`] - A list containing the scores. - """ - - scores = self.score(toscore) # returns float score or 1D array of scores - - if isinstance(scores, np.float): # if a single score - - list_of_scores = [scores] - - else: - - list_of_scores = list(scores) - return list_of_scores diff --git a/bob/pad/face/algorithm/VideoLRPadAlgorithm.py b/bob/pad/face/algorithm/VideoLRPadAlgorithm.py new file mode 100644 index 0000000000000000000000000000000000000000..a078fb6b9ad3cd0bac4445a293e59da3abcba276 --- /dev/null +++ b/bob/pad/face/algorithm/VideoLRPadAlgorithm.py @@ -0,0 +1,512 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +""" +Created on Fri Aug 25 09:29:02 2017 + +@author: Olegs Nikisins +""" + +#============================================================================== +# Import what is needed here: + +from bob.pad.base.algorithm import Algorithm + +from bob.bio.video.utils import FrameContainer + +import numpy as np + +from sklearn import linear_model + +import bob.io.base + + +#============================================================================== +# Main body : + +class VideoLRPadAlgorithm(Algorithm): + """ + This class is designed to Logistic Regression classifier given Frame Containers + with features of real and attack classes. The procedure is the following: + + 1. First, the input data is mean-std normalized using mean and std of the + real class only. + + 2. Second, the Logistic Regression classifier is trained on normalized + input features. + + 3. The input features are next classified using pre-trained LR machine. + + **Parameters:** + + ``C`` : :py:class:`float` + Inverse of regularization strength in LR classifier; must be a positive. + Like in support vector machines, smaller values specify stronger + regularization. Default: 1.0 . + + ``frame_level_scores_flag`` : :py:class:`bool` + Return scores for each frame individually if True. Otherwise, return a + single score per video. Default: False. + """ + + def __init__(self, + C = 1, + frame_level_scores_flag = False): + + + Algorithm.__init__(self, + C = C, + frame_level_scores_flag = frame_level_scores_flag, + performs_projection=True, + requires_projector_training=True) + + self.C = C + + self.frame_level_scores_flag = frame_level_scores_flag + + self.lr_machine = None # this argument will be updated with pretrained LR machine + + self.features_mean = None # this argument will be updated with features mean + self.features_std = None # this argument will be updated with features std + + # names of the arguments of the pretrained LR machine to be saved/loaded to/from HDF5 file: + self.lr_param_keys = ["C", "classes_", "coef_", "intercept_"] + + + #========================================================================== + def convert_frame_cont_to_array(self, frame_container): + """ + This function converts a single Frame Container into an array of features. + The rows are samples, the columns are features. + + **Parameters:** + + ``frame_container`` : object + A Frame Container conteining the features of an individual, + see ``bob.bio.video.utils.FrameContainer``. + + **Returns:** + + ``features_array`` : 2D :py:class:`numpy.ndarray` + An array containing features for all frames. + The rows are samples, the columns are features. + """ + + feature_vectors = [] + + frame_dictionary = {} + + for frame in frame_container: + + frame_dictionary[frame[0]] = frame[1] + + for idx, _ in enumerate(frame_container): + + # Frames are stored in a mixed order, therefore we get them using incrementing frame index: + feature_vectors.append(frame_dictionary[str(idx)]) + + features_array = np.vstack(feature_vectors) + + return features_array + + + #========================================================================== + def convert_list_of_frame_cont_to_array(self, frame_containers): + """ + This function converts a list of Frame containers into an array of features. + Features from different frame containers (individuals) are concatenated into the + same list. This list is then converted to an array. The rows are samples, + the columns are features. + + **Parameters:** + + ``frame_containers`` : [FrameContainer] + A list of Frame Containers, , see ``bob.bio.video.utils.FrameContainer``. + Each frame Container contains feature vectors for the particular individual/person. + + **Returns:** + + ``features_array`` : 2D :py:class:`numpy.ndarray` + An array containing features for all frames of all individuals. + """ + + feature_vectors = [] + + for frame_container in frame_containers: + + video_features_array = self.convert_frame_cont_to_array(frame_container) + + feature_vectors.append( video_features_array ) + + features_array = np.vstack(feature_vectors) + + return features_array + + + #========================================================================== + def mean_std_normalize(self, features, features_mean= None, features_std = None): + """ + The features in the input 2D array are mean-std normalized. + The rows are samples, the columns are features. If ``features_mean`` + and ``features_std`` are provided, then these vectors will be used for + normalization. Otherwise, the mean and std of the features is + computed on the fly. + + **Parameters:** + + ``features`` : 2D :py:class:`numpy.ndarray` + Array of features to be normalized. + + ``features_mean`` : 1D :py:class:`numpy.ndarray` + Mean of the features. Default: None. + + ``features_std`` : 2D :py:class:`numpy.ndarray` + Standart deviation of the features. Default: None. + + **Returns:** + + ``features_norm`` : 2D :py:class:`numpy.ndarray` + Normalized array of features. + + ``features_mean`` : 1D :py:class:`numpy.ndarray` + Mean of the features. + + ``features_std`` : 1D :py:class:`numpy.ndarray` + Standart deviation of the features. + """ + + features = np.copy(features) + + # Compute mean and std if not given: + if features_mean is None: + + features_mean = np.mean(features, axis=0) + + features_std = np.std(features, axis=0) + + row_norm_list = [] + + for row in features: # row is a sample + + row_norm = (row - features_mean) / features_std + + row_norm_list.append(row_norm) + + features_norm = np.vstack(row_norm_list) + + return features_norm, features_mean, features_std + + + #========================================================================== + def norm_train_data(self, real, attack): + """ + Mean-std normalization of input data arrays. The mean and std normalizers + are computed using real class only. + + **Parameters:** + + ``real`` : 2D :py:class:`numpy.ndarray` + Training features for the real class. + + ``attack`` : 2D :py:class:`numpy.ndarray` + Training features for the attack class. + + **Returns:** + + ``real_norm`` : 2D :py:class:`numpy.ndarray` + Mean-std normalized training features for the real class. + + ``attack_norm`` : 2D :py:class:`numpy.ndarray` + Mean-std normalized training features for the attack class. + Or an empty list if ``one_class_flag = True``. + + ``features_mean`` : 1D :py:class:`numpy.ndarray` + Mean of the features. + + ``features_std`` : 1D :py:class:`numpy.ndarray` + Standart deviation of the features. + """ + + real_norm, features_mean, features_std = self.mean_std_normalize(real) + + attack_norm, _, _ = self.mean_std_normalize(attack, features_mean, features_std) + + return real_norm, attack_norm, features_mean, features_std + + + #========================================================================== + def train_lr(self, real, attack, C): + """ + Train LR classifier given real and attack classes. Prior to training + the data is mean-std normalized. + + **Parameters:** + + ``real`` : 2D :py:class:`numpy.ndarray` + Training features for the real class. + + ``attack`` : 2D :py:class:`numpy.ndarray` + Training features for the attack class. + + ``C`` : :py:class:`float` + Inverse of regularization strength in LR classifier; must be a positive. + Like in support vector machines, smaller values specify stronger + regularization. Default: 1.0 . + + **Returns:** + + ``machine`` : object + A trained LR machine. The mean-std normalizers are also set in the + machine. + + ``features_mean`` : 1D :py:class:`numpy.ndarray` + Mean of the features. + + ``features_std`` : 1D :py:class:`numpy.ndarray` + Standart deviation of the features. + """ + + real, attack, features_mean, features_std = self.norm_train_data(real, attack) + # real and attack - are now mean-std normalized + + X = np.vstack([real, attack]) + + Y = np.hstack( [ np.zeros(len(real) ), np.ones(len(attack) ) ] ) + + machine = linear_model.LogisticRegression( C = C ) + + machine.fit(X, Y) + + return machine, features_mean, features_std + + + #========================================================================== + def save_lr_machine_and_mean_std(self, projector_file, machine, features_mean, features_std): + """ + Saves the LR machine, features mean and std to the hdf5 file. + The absolute name of the file is specified in ``projector_file`` string. + + **Parameters:** + + ``projector_file`` : :py:class:`str` + Absolute name of the file to save the data to, as returned by + ``bob.pad.base`` framework. + + ``machine`` : object + The LR machine to be saved. As returned by sklearn.linear_model + module. + + ``features_mean`` : 1D :py:class:`numpy.ndarray` + Mean of the features. + + ``features_std`` : 1D :py:class:`numpy.ndarray` + Standart deviation of the features. + """ + + f = bob.io.base.HDF5File(projector_file, 'w') # open hdf5 file to save to + + for key in self.lr_param_keys: # ["C", "classes_", "coef_", "intercept_"] + + data = getattr( machine, key ) + + f.set( key, data ) + + f.set( "features_mean", features_mean ) + + f.set( "features_std", features_std ) + + del f + + + #========================================================================== + def train_projector(self, training_features, projector_file): + """ + Train LR for feature projection and save them to files. + The ``requires_projector_training = True`` flag must be set to True + to enable this function. + + **Parameters:** + + ``training_features`` : [[FrameContainer], [FrameContainer]] + A list containing two elements: [0] - a list of Frame Containers with + feature vectors for the real class; [1] - a list of Frame Containers with + feature vectors for the attack class. + + ``projector_file`` : :py:class:`str` + The file to save the trained projector to, as returned by the + ``bob.pad.base`` framework. + """ + + # training_features[0] - training features for the REAL class. + real = self.convert_list_of_frame_cont_to_array(training_features[0]) # output is array + # training_features[1] - training features for the ATTACK class. + attack = self.convert_list_of_frame_cont_to_array(training_features[1]) # output is array + + # Train the LR machine and get normalizers: + machine, features_mean, features_std = self.train_lr(real = real, + attack = attack, + C = self.C) + + # Save the LR machine and normalizers: + self.save_lr_machine_and_mean_std(projector_file, machine, features_mean, features_std) + + + #========================================================================== + def load_lr_machine_and_mean_std(self, projector_file): + """ + Loads the machine, features mean and std from the hdf5 file. + The absolute name of the file is specified in ``projector_file`` string. + + **Parameters:** + + ``projector_file`` : :py:class:`str` + Absolute name of the file to load the trained projector from, as + returned by ``bob.pad.base`` framework. + + **Returns:** + + ``machine`` : object + The loaded LR machine. As returned by sklearn.linear_model module. + + ``features_mean`` : 1D :py:class:`numpy.ndarray` + Mean of the features. + + ``features_std`` : 1D :py:class:`numpy.ndarray` + Standart deviation of the features. + """ + + f = bob.io.base.HDF5File(projector_file, 'r') # file to read the machine from + + # initialize the machine: + machine = linear_model.LogisticRegression() + + # set the params of the machine: + for key in self.lr_param_keys: # ["C", "classes_", "coef_", "intercept_"] + + data = f.read(key) + + setattr(machine, key, data) + + features_mean = f.read("features_mean") + + features_std = f.read("features_std") + + del f + + return machine, features_mean, features_std + + + #========================================================================== + def load_projector(self, projector_file): + """ + Loads the machine, features mean and std from the hdf5 file. + The absolute name of the file is specified in ``projector_file`` string. + + This function sets the arguments ``self.lr_machine``, ``self.features_mean`` + and ``self.features_std`` of this class with loaded machines. + + The function must be capable of reading the data saved with the + :py:meth:`train_projector` method of this class. + + Please register `performs_projection = True` in the constructor to + enable this function. + + **Parameters:** + + ``projector_file`` : :py:class:`str` + The file to read the projector from, as returned by the + ``bob.pad.base`` framework. In this class the names of the files to + read the projectors from are modified, see ``load_machine`` and + ``load_cascade_of_machines`` methods of this class for more details. + """ + + lr_machine, features_mean, features_std = self.load_lr_machine_and_mean_std(projector_file) + + self.lr_machine = lr_machine + + self.features_mean = features_mean + + self.features_std = features_std + + + #========================================================================== + def project(self, feature): + """ + This function computes a vector of scores for each sample in the input + array of features. The following steps are apllied: + + 1. First, the input data is mean-std normalized using mean and std of the + real class only. + + 2. The input features are next classified using pre-trained LR machine. + + Set ``performs_projection = True`` in the constructor to enable this function. + It is assured that the :py:meth:`load_projector` was **called before** the + ``project`` function is executed. + + **Parameters:** + + ``feature`` : FrameContainer or 2D :py:class:`numpy.ndarray` + Two types of inputs are accepted. + A Frame Container conteining the features of an individual, + see ``bob.bio.video.utils.FrameContainer``. + Or a 2D feature array of the size (N_samples x N_features). + + **Returns:** + + ``scores`` : 1D :py:class:`numpy.ndarray` + Vector of scores. Scores for the real class are expected to be + higher, than the scores of the negative / attack class. + In this case scores are probabilities. + """ + + # 1. Convert input array to numpy array if necessary. + if isinstance(feature, FrameContainer): # if FrameContainer convert to 2D numpy array + + features_array = self.convert_frame_cont_to_array(feature) + + else: + + features_array = feature + + features_array_norm, _, _ = self.mean_std_normalize(features_array, self.features_mean, self.features_std) + + scores = self.lr_machine.predict_proba( features_array_norm )[:,0] + + return scores + + + #========================================================================== + def score(self, toscore): + """ + Returns a probability of a sample being a real class. + + **Parameters:** + + ``toscore`` : 1D :py:class:`numpy.ndarray` + Vector with scores for each frame/sample defining the probability + of the frame being a sample of the real class. + + **Returns:** + + ``score`` : [:py:class:`float`] + If ``frame_level_scores_flag = False`` a single score is returned. + One score per video. This score is placed into a list, because + the ``score`` must be an iterable. + Score is a probability of a sample being a real class. + If ``frame_level_scores_flag = True`` a list of scores is returned. + One score per frame/sample. + """ + + if self.frame_level_scores_flag: + + score = list(toscore) + + else: + + score = [np.mean( toscore )] # compute a single score per video + + return score + + + + diff --git a/bob/pad/face/algorithm/__init__.py b/bob/pad/face/algorithm/__init__.py index c763a329a4bf9771995439461666917d3bcc0de6..3ef1ea1c5da23998f47a172c21121658933c4e3e 100644 --- a/bob/pad/face/algorithm/__init__.py +++ b/bob/pad/face/algorithm/__init__.py @@ -1,5 +1,6 @@ from .VideoSvmPadAlgorithm import VideoSvmPadAlgorithm from .VideoCascadeSvmPadAlgorithm import VideoCascadeSvmPadAlgorithm +from .VideoLRPadAlgorithm import VideoLRPadAlgorithm def __appropriate__(*args): """Says object was actually declared here, and not in the import module. @@ -22,5 +23,6 @@ def __appropriate__(*args): __appropriate__( VideoSvmPadAlgorithm, VideoCascadeSvmPadAlgorithm, + VideoLRPadAlgorithm, ) __all__ = [_ for _ in dir() if not _.startswith('_')] diff --git a/bob/pad/face/config/algorithm/video_cascade_svm_pad_algorithm.py b/bob/pad/face/config/algorithm/video_cascade_svm_pad_algorithm.py new file mode 100644 index 0000000000000000000000000000000000000000..edd34b0123b256c720ba66d6fcd3ebc208f56c7d --- /dev/null +++ b/bob/pad/face/config/algorithm/video_cascade_svm_pad_algorithm.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python + +from bob.pad.face.algorithm import VideoCascadeSvmPadAlgorithm + + +#======================================================================================= +# Define instances here: + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.2} +N = 2 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n2_gamma_02 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.1} +N = 2 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n2_gamma_01 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.05} +N = 2 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n2_gamma_005 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.01} +N = 2 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n2_gamma_001 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + + +#======================================================================================= + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.1} +N = 10 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n10_gamma_01 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.05} +N = 10 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n10_gamma_005 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.01} +N = 10 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n10_gamma_001 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.005} +N = 10 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n10_gamma_0005 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + + +#======================================================================================= + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.5} +N = 20 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n20_gamma_05 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.2} +N = 20 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n20_gamma_02 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.1} +N = 20 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n20_gamma_01 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.05} +N = 20 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n20_gamma_005 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.01} +N = 20 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n20_gamma_001 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.005} +N = 20 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n20_gamma_0005 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.001} +N = 20 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = True + +algorithm_n20_gamma_0001 = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + + +#======================================================================================= + +MACHINE_TYPE = 'ONE_CLASS' +KERNEL_TYPE = 'RBF' +SVM_KWARGS = {'nu': 0.001, 'gamma': 0.1} +N = 2 +POS_SCORES_SLOPE = 0.01 +FRAME_LEVEL_SCORES_FLAG = False + +algorithm_n2_gamma_01_video_level = VideoCascadeSvmPadAlgorithm(machine_type = MACHINE_TYPE, + kernel_type = KERNEL_TYPE, + svm_kwargs = SVM_KWARGS, + N = N, + pos_scores_slope = POS_SCORES_SLOPE, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + + diff --git a/bob/pad/face/config/qm_lr_aggregated_db.py b/bob/pad/face/config/qm_lr_aggregated_db.py new file mode 100644 index 0000000000000000000000000000000000000000..986afb3d4fefc16a81d2dc215514300fd96b63ff --- /dev/null +++ b/bob/pad/face/config/qm_lr_aggregated_db.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +""" +This file contains configurations to run Image Quality Measures (IQM) and SVM based face PAD baseline. +The settings of the preprocessor and extractor are tuned for the Replay-attack database. +In the SVM algorithm the amount of training data is reduced speeding-up the training for +large data sets, such as Aggregated PAD database. +The IQM features used in this algorithm/resource are introduced in the following papers: [WHJ15]_ and [CBVM16]_. +""" + + +#======================================================================================= +sub_directory = 'qm_svm_aggregated_db' +""" +Sub-directory where results will be placed. + +You may change this setting using the ``--sub-directory`` command-line option +or the attribute ``sub_directory`` in a configuration file loaded **after** +this resource. +""" + + +#======================================================================================= +# define preprocessor: + +from ..preprocessor import VideoFaceCrop + +CROPPED_IMAGE_SIZE = (64, 64) # The size of the resulting face +CROPPED_POSITIONS = {'topleft' : (0,0) , 'bottomright' : CROPPED_IMAGE_SIZE} +FIXED_POSITIONS = None +MASK_SIGMA = None # The sigma for random values areas outside image +MASK_NEIGHBORS = 5 # The number of neighbors to consider while extrapolating +MASK_SEED = None # The seed for generating random values during extrapolation +CHECK_FACE_SIZE_FLAG = True # Check the size of the face +MIN_FACE_SIZE = 50 +USE_LOCAL_CROPPER_FLAG = True # Use the local face cropping class (identical to Ivana's paper) +RGB_OUTPUT_FLAG = True # Return RGB cropped face using local cropper + +preprocessor = VideoFaceCrop(cropped_image_size = CROPPED_IMAGE_SIZE, + cropped_positions = CROPPED_POSITIONS, + fixed_positions = FIXED_POSITIONS, + mask_sigma = MASK_SIGMA, + mask_neighbors = MASK_NEIGHBORS, + mask_seed = None, + check_face_size_flag = CHECK_FACE_SIZE_FLAG, + min_face_size = MIN_FACE_SIZE, + use_local_cropper_flag = USE_LOCAL_CROPPER_FLAG, + rgb_output_flag = RGB_OUTPUT_FLAG) +""" +In the preprocessing stage the face is cropped in each frame of the input video given facial annotations. +The size of the face is normalized to ``cropped_image_size`` dimensions. The faces of the size +below ``min_face_size`` threshold are discarded. The preprocessor is similar to the one introduced in +[CAM12]_, which is defined by ``use_local_cropper_flag = True``. The preprocessed frame is the RGB +facial image, which is defined by ``RGB_OUTPUT_FLAG = True``. +""" + + +#======================================================================================= +# define extractor: + +from ..extractor import VideoQualityMeasure + +GALBALLY=True +MSU=True +DTYPE=None + +extractor = VideoQualityMeasure(galbally=GALBALLY, + msu=MSU, + dtype=DTYPE) +""" +In the feature extraction stage the Image Quality Measures are extracted from each frame of the preprocessed RGB video. +The features to be computed are introduced in the following papers: [WHJ15]_ and [CBVM16]_. +""" + + +#======================================================================================= +# define algorithm: + +from ..algorithm import VideoLRPadAlgorithm + +C = 1. # The regularization parameter for the LR classifier +FRAME_LEVEL_SCORES_FLAG = True # Return one score per frame + +algorithm = VideoLRPadAlgorithm(C = C, + frame_level_scores_flag = FRAME_LEVEL_SCORES_FLAG) + +""" +The Logistic Regression is used to classify the data into *real* and *attack* classes. +One score is produced for each frame of the input video, ``frame_level_scores_flag = True``. +""" + + diff --git a/requirements.txt b/requirements.txt index 795e4100ff0332650da93fc9470405eefac9649e..c02452136f9beef322f603a69149aedc88dfa911 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,4 +12,7 @@ bob.bio.video bob.io.image bob.ip.color bob.ip.qualitymeasure -bob.learn.libsvm \ No newline at end of file +bob.learn.libsvm +bob.learn.linear +sklearn + diff --git a/setup.py b/setup.py index 01fbf4963d02758a9a0d5460150c1d6dbdd1abe5..9d1c453f075068e2173678eaad4dc717eca4273f 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ setup( 'msu-mfsd = bob.pad.face.config.msu_mfsd', 'aggregated-db = bob.pad.face.config.aggregated_db', - # baselines: + # baselines using SVM: 'lbp-svm = bob.pad.face.config.lbp_svm', 'lbp-svm-aggregated-db = bob.pad.face.config.lbp_svm_aggregated_db', @@ -91,6 +91,9 @@ setup( 'frame-diff-svm-aggregated-db = bob.pad.face.config.frame_diff_svm_aggregated_db', 'frame-diff-one-class-svm = bob.pad.face.config.frame_diff_one_class_svm', + + # baselines using LR: + 'qm-lr-aggregated-db = bob.pad.face.config.qm_lr_aggregated_db', ], # registered preprocessors: @@ -110,6 +113,28 @@ setup( 'video-svm-pad-algorithm-10k-grid-mean-std = bob.pad.face.config.algorithm.video_svm_pad_algorithm:video_svm_pad_algorithm_10k_grid_mean_std', 'video-svm-pad-algorithm-10k-grid-mean-std-frame-level = bob.pad.face.config.algorithm.video_svm_pad_algorithm:video_svm_pad_algorithm_10k_grid_mean_std_frame_level', 'video-svm-pad-algorithm-default-svm-param-mean-std-frame-level = bob.pad.face.config.algorithm.video_svm_pad_algorithm:video_svm_pad_algorithm_default_svm_param_mean_std_frame_level', + + # for grid search experiments with cascade of SVMs N = 2 + 'algorithm-n2-gamma-02 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n2_gamma_02', + 'algorithm-n2-gamma-01 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n2_gamma_01', + 'algorithm-n2-gamma-005 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n2_gamma_005', + 'algorithm-n2-gamma-001 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n2_gamma_001', + 'algorithm-n2-gamma-01-video-level = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n2_gamma_01_video_level', + + # for grid search experiments with cascade of SVMs N = 10 + 'algorithm-n10-gamma-01 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n10_gamma_01', + 'algorithm-n10-gamma-005 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n10_gamma_005', + 'algorithm-n10-gamma-001 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n10_gamma_001', + 'algorithm-n10-gamma-0005 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n10_gamma_0005', + + # for grid search experiments with cascade of SVMs N = 20 + 'algorithm-n20-gamma-05 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n20_gamma_05', + 'algorithm-n20-gamma-02 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n20_gamma_02', + 'algorithm-n20-gamma-01 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n20_gamma_01', + 'algorithm-n20-gamma-005 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n20_gamma_005', + 'algorithm-n20-gamma-001 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n20_gamma_001', + 'algorithm-n20-gamma-0005 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n20_gamma_0005', + 'algorithm-n20-gamma-0001 = bob.pad.face.config.algorithm.video_cascade_svm_pad_algorithm:algorithm_n20_gamma_0001', ], # registered grid configurations: