Commit 1897e934 authored by Amir MOHAMMADI's avatar Amir MOHAMMADI

Merge branch 'dask-pipelines'

parents 2d768c77 80e1c3dc
Pipeline #44917 failed with stages
in 41 seconds
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
""" """
This file contains configurations to run LBP and SVM based face PAD baseline. This file contains configurations to run LBP and SVM based face PAD baseline.
The settings are tuned for the Replay-attack database. The settings are tuned for the Replay-attack database.
...@@ -7,8 +5,8 @@ The idea of the algorithm is introduced in the following paper: [CAM12]_. ...@@ -7,8 +5,8 @@ The idea of the algorithm is introduced in the following paper: [CAM12]_.
However some settings are different from the ones introduced in the paper. However some settings are different from the ones introduced in the paper.
""" """
#======================================================================================= # =======================================================================================
sub_directory = 'lbp_svm' sub_directory = "lbp_svm"
""" """
Sub-directory where results will be placed. Sub-directory where results will be placed.
...@@ -17,7 +15,7 @@ or the attribute ``sub_directory`` in a configuration file loaded **after** ...@@ -17,7 +15,7 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource. this resource.
""" """
#======================================================================================= # =======================================================================================
# define preprocessor: # define preprocessor:
from ..preprocessor import FaceCropAlign from ..preprocessor import FaceCropAlign
...@@ -26,24 +24,23 @@ from bob.bio.video.preprocessor import Wrapper ...@@ -26,24 +24,23 @@ from bob.bio.video.preprocessor import Wrapper
from bob.bio.video.utils import FrameSelector from bob.bio.video.utils import FrameSelector
FACE_SIZE = 64 # The size of the resulting face # check if a database is loaded first
RGB_OUTPUT_FLAG = False # Gray-scale output if "database" in locals():
USE_FACE_ALIGNMENT = False # use annotations annotation_type = database.annotation_type
MAX_IMAGE_SIZE = None # no limiting here fixed_positions = database.fixed_positions
FACE_DETECTION_METHOD = None # use annotations else:
MIN_FACE_SIZE = 50 # skip small faces annotation_type = None
fixed_positions = None
_image_preprocessor = FaceCropAlign(face_size = FACE_SIZE, cropped_image_size = 64 # The size of the resulting face
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") _image_preprocessor = face_crop_solver(
cropped_image_size=64, cropped_positions=annotation_type, color_channel="gray"
)
preprocessor = Wrapper(preprocessor = _image_preprocessor, _frame_selector = FrameSelector(selection_style="all")
frame_selector = _frame_selector)
preprocessor = Wrapper(preprocessor=_image_preprocessor, frame_selector=_frame_selector)
""" """
In the preprocessing stage the face is cropped in each frame of the input video given facial annotations. 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 ``FACE_SIZE`` dimensions. The faces with the size The size of the face is normalized to ``FACE_SIZE`` dimensions. The faces with the size
...@@ -51,44 +48,47 @@ below ``MIN_FACE_SIZE`` threshold are discarded. The preprocessor is similar to ...@@ -51,44 +48,47 @@ below ``MIN_FACE_SIZE`` threshold are discarded. The preprocessor is similar to
[CAM12]_, which is defined by ``FACE_DETECTION_METHOD = None``. [CAM12]_, which is defined by ``FACE_DETECTION_METHOD = None``.
""" """
#======================================================================================= # =======================================================================================
# define extractor: # define extractor:
from ..extractor import LBPHistogram from ..extractor import LBPHistogram
from bob.bio.video.extractor import Wrapper from bob.bio.video.extractor import Wrapper
LBPTYPE = 'uniform' LBPTYPE = "uniform"
ELBPTYPE = 'regular' ELBPTYPE = "regular"
RAD = 1 RAD = 1
NEIGHBORS = 8 NEIGHBORS = 8
CIRC = False CIRC = False
DTYPE = None DTYPE = None
extractor = Wrapper(LBPHistogram( extractor = Wrapper(
lbptype=LBPTYPE, LBPHistogram(
elbptype=ELBPTYPE, lbptype=LBPTYPE,
rad=RAD, elbptype=ELBPTYPE,
neighbors=NEIGHBORS, rad=RAD,
circ=CIRC, neighbors=NEIGHBORS,
dtype=DTYPE)) circ=CIRC,
dtype=DTYPE,
)
)
""" """
In the feature extraction stage the LBP histograms are extracted from each frame of the preprocessed video. In the feature extraction stage the LBP histograms are extracted from each frame of the preprocessed video.
The parameters are similar to the ones introduced in [CAM12]_. The parameters are similar to the ones introduced in [CAM12]_.
""" """
#======================================================================================= # =======================================================================================
# define algorithm: # define algorithm:
from bob.pad.base.algorithm import SVM from bob.pad.base.algorithm import SVM
MACHINE_TYPE = 'C_SVC' MACHINE_TYPE = "C_SVC"
KERNEL_TYPE = 'RBF' KERNEL_TYPE = "RBF"
N_SAMPLES = 10000 N_SAMPLES = 10000
TRAINER_GRID_SEARCH_PARAMS = { TRAINER_GRID_SEARCH_PARAMS = {
'cost': [2**P for P in range(-3, 14, 2)], "cost": [2 ** P for P in range(-3, 14, 2)],
'gamma': [2**P for P in range(-15, 0, 2)] "gamma": [2 ** P for P in range(-15, 0, 2)],
} }
MEAN_STD_NORM_FLAG = True # enable mean-std normalization MEAN_STD_NORM_FLAG = True # enable mean-std normalization
FRAME_LEVEL_SCORES_FLAG = True # one score per frame(!) in this case FRAME_LEVEL_SCORES_FLAG = True # one score per frame(!) in this case
...@@ -99,7 +99,8 @@ algorithm = SVM( ...@@ -99,7 +99,8 @@ algorithm = SVM(
n_samples=N_SAMPLES, n_samples=N_SAMPLES,
trainer_grid_search_params=TRAINER_GRID_SEARCH_PARAMS, trainer_grid_search_params=TRAINER_GRID_SEARCH_PARAMS,
mean_std_norm_flag=MEAN_STD_NORM_FLAG, mean_std_norm_flag=MEAN_STD_NORM_FLAG,
frame_level_scores_flag=FRAME_LEVEL_SCORES_FLAG) frame_level_scores_flag=FRAME_LEVEL_SCORES_FLAG,
)
""" """
The SVM algorithm with RBF kernel is used to classify the data into *real* and *attack* classes. The SVM algorithm with RBF kernel 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``. One score is produced for each frame of the input video, ``frame_level_scores_flag = True``.
......
# Legacy imports
from bob.bio.face.helpers import face_crop_solver
from bob.bio.video import VideoLikeContainer
from bob.bio.video.transformer import Wrapper as TransformerWrapper
from bob.pad.face.extractor import ImageQualityMeasure
# new imports
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import make_pipeline
from bob.pad.base.pipelines.vanilla_pad import FrameContainersToFrames
import bob.pipelines as mario
database = globals().get("database")
if database is not None:
annotation_type = database.annotation_type
fixed_positions = database.fixed_positions
else:
annotation_type = None
fixed_positions = None
# Preprocessor #
cropper = face_crop_solver(cropped_image_size=64, cropped_positions=annotation_type)
preprocessor = TransformerWrapper(cropper)
preprocessor = mario.wrap(
["sample", "checkpoint"],
preprocessor,
transform_extra_arguments=(("annotations", "annotations"),),
features_dir="temp/faces-64",
save_func=VideoLikeContainer.save,
load_func=VideoLikeContainer.load,
)
# Legacy extractor #
extractor = TransformerWrapper(ImageQualityMeasure(galbally=True, msu=True, dtype=None))
extractor = mario.wrap(
["sample", "checkpoint"],
extractor,
features_dir="temp/iqm-features",
save_func=VideoLikeContainer.save,
load_func=VideoLikeContainer.load,
)
# new stuff #
frame_cont_to_array = FrameContainersToFrames()
param_grid = [
{"C": [1, 10, 100, 1000], "kernel": ["linear"]},
{"C": [1, 10, 100, 1000], "gamma": [0.001, 0.0001], "kernel": ["rbf"]},
]
classifier = GridSearchCV(SVC(), param_grid=param_grid, cv=3)
classifier = mario.wrap(
["sample", "checkpoint"],
classifier,
fit_extra_arguments=[("y", "is_bonafide")],
model_path="temp/svm.pkl",
)
# pipeline #
# stateless_pipeline = mario.transformers.StatelessPipeline(
# [
# ("preprocessor", preprocessor),
# ("extractor", extractor),
# ("frame_cont_to_array", frame_cont_to_array),
# ]
# )
frames_classifier = make_pipeline(frame_cont_to_array, classifier)
pipeline = make_pipeline(preprocessor, extractor, frames_classifier)
from bob.pad.face.database import ReplayPadDatabase
from bob.pad.base.pipelines.vanilla_pad import DatabaseConnector
from bob.extension import rc
database = DatabaseConnector(
ReplayPadDatabase(
protocol="grandtest",
original_directory=rc.get("bob.db.replay.directory"),
original_extension=".mov",
)
)
...@@ -9,25 +9,38 @@ class VideoPadFile(PadFile): ...@@ -9,25 +9,38 @@ class VideoPadFile(PadFile):
use in face PAD experiments. use in face PAD experiments.
""" """
def __init__(self, attack_type, client_id, path, file_id=None): def __init__(
super(VideoPadFile, self).__init__( self,
attack_type=attack_type, client_id=client_id, path=path, file_id=file_id attack_type,
client_id,
path,
file_id=None,
original_directory=None,
original_extension=".avi",
annotation_directory=None,
annotation_extension=None,
annotation_type=None,
):
super().__init__(
attack_type=attack_type,
client_id=client_id,
path=path,
file_id=file_id,
original_directory=original_directory,
original_extension=original_extension,
annotation_directory=annotation_directory,
annotation_extension=annotation_extension,
annotation_type=annotation_type,
) )
def load( def load(
self, self,
directory=None,
extension=".avi",
frame_selector=bob.bio.video.FrameSelector(selection_style="all"), frame_selector=bob.bio.video.FrameSelector(selection_style="all"),
): ):
"""Loads the video file and returns in a :any:`bob.bio.video.FrameContainer`. """Loads the video file and returns in a `bob.bio.video.FrameContainer`.
Parameters Parameters
---------- ----------
directory : :obj:`str`, optional
The directory to load the data from.
extension : :obj:`str`, optional
The extension of the file to load.
frame_selector : :any:`bob.bio.video.FrameSelector`, optional frame_selector : :any:`bob.bio.video.FrameSelector`, optional
Which frames to select. Which frames to select.
...@@ -36,19 +49,16 @@ class VideoPadFile(PadFile): ...@@ -36,19 +49,16 @@ class VideoPadFile(PadFile):
:any:`bob.bio.video.FrameContainer` :any:`bob.bio.video.FrameContainer`
The loaded frames inside a frame container. The loaded frames inside a frame container.
""" """
return frame_selector(self.make_path(directory, extension)) path = self.make_path(
self.original_directory, self.original_extension
def check_original_directory_and_extension(self): )
if not hasattr(self, "original_directory"): video = bob.bio.video.VideoAsArray(
raise RuntimeError( path,
"Please set the original_directory attribute of files in your " selection_style=frame_selector.selection_style,
"database's object method." max_number_of_frames=frame_selector.max_number_of_frames,
) step_size=frame_selector.step_size,
if not hasattr(self, "original_extension"): )
raise RuntimeError( return video
"Please set the original_extension attribute of files in your "
"database's object method."
)
@property @property
def frames(self): def frames(self):
...@@ -60,15 +70,7 @@ class VideoPadFile(PadFile): ...@@ -60,15 +70,7 @@ class VideoPadFile(PadFile):
------- -------
collection.Iterator collection.Iterator
An iterator returning frames of the video. An iterator returning frames of the video.
Raises
------
RuntimeError
In your database implementation, the original_directory and
original_extension attributes of the files need to be set when database's
object method is called.
""" """
self.check_original_directory_and_extension()
path = self.make_path( path = self.make_path(
directory=self.original_directory, extension=self.original_extension directory=self.original_directory, extension=self.original_extension
) )
...@@ -76,7 +78,6 @@ class VideoPadFile(PadFile): ...@@ -76,7 +78,6 @@ class VideoPadFile(PadFile):
@property @property
def number_of_frames(self): def number_of_frames(self):
self.check_original_directory_and_extension()
path = self.make_path( path = self.make_path(
directory=self.original_directory, extension=self.original_extension directory=self.original_directory, extension=self.original_extension
) )
...@@ -85,7 +86,7 @@ class VideoPadFile(PadFile): ...@@ -85,7 +86,7 @@ class VideoPadFile(PadFile):
@property @property
def frame_shape(self): def frame_shape(self):
"""Returns the size of each frame in this database. """Returns the size of each frame in this database.
This implementation assumes all videos and frames have the same shape. This implementation assumes all frames have the same shape.
It's best to override this method in your database implementation and return It's best to override this method in your database implementation and return
a constant. a constant.
...@@ -94,7 +95,6 @@ class VideoPadFile(PadFile): ...@@ -94,7 +95,6 @@ class VideoPadFile(PadFile):
(int, int, int) (int, int, int)
The (Channels, Height, Width) sizes. The (Channels, Height, Width) sizes.
""" """
self.check_original_directory_and_extension()
path = self.make_path( path = self.make_path(
directory=self.original_directory, extension=self.original_extension directory=self.original_directory, extension=self.original_extension
) )
...@@ -113,17 +113,6 @@ class VideoPadFile(PadFile): ...@@ -113,17 +113,6 @@ class VideoPadFile(PadFile):
dict dict
The annotations as a dictionary. The annotations as a dictionary.
""" """
if not (
hasattr(self, "annotation_directory")
and hasattr(self, "annotation_extension")
and hasattr(self, "annotation_type")
):
raise RuntimeError(
"For this property to work, you need to set ``annotation_directory``, "
"``annotation_extension``, and ``annotation_type`` attributes of the "
"files when database's object method is called."
)
if self.annotation_directory is None: if self.annotation_directory is None:
return None return None
......
#!/usr/bin/env python from bob.pad.base.database import PadDatabase
# -*- coding: utf-8 -*- from bob.pad.face.database import VideoPadFile
from bob.pad.base.database import PadDatabase # Used in ReplayMobilePadFile class
from bob.pad.face.database import VideoPadFile # Used in ReplayPadFile class
from bob.extension import rc from bob.extension import rc
from bob.ip.facedetect import expected_eye_positions, BoundingBox from bob.ip.facedetect import expected_eye_positions, BoundingBox
from bob.db.base.annotations import read_annotation_file from bob.db.base.annotations import read_annotation_file
from bob.db.base.utils import convert_names_to_lowlevel
REPLAY_ATTACK_FRAME_SHAPE = (3, 240, 320) REPLAY_ATTACK_FRAME_SHAPE = (3, 240, 320)
...@@ -16,7 +14,11 @@ class ReplayPadFile(VideoPadFile): ...@@ -16,7 +14,11 @@ class ReplayPadFile(VideoPadFile):
database. database.
""" """
def __init__(self, f): def __init__(
self,
f,
**kwargs,
):
""" """
Parameters Parameters
---------- ----------
...@@ -43,7 +45,11 @@ class ReplayPadFile(VideoPadFile): ...@@ -43,7 +45,11 @@ class ReplayPadFile(VideoPadFile):
# database. # database.
super(ReplayPadFile, self).__init__( super(ReplayPadFile, self).__init__(
client_id=f.client_id, path=f.path, attack_type=attack_type, file_id=f.id client_id=f.client_id,
path=f.path,
attack_type=attack_type,
file_id=f.id,
**kwargs,
) )
@property @property
...@@ -76,12 +82,8 @@ class ReplayPadFile(VideoPadFile): ...@@ -76,12 +82,8 @@ class ReplayPadFile(VideoPadFile):
is the dictionary defining the coordinates of the face bounding box is the dictionary defining the coordinates of the face bounding box
in frame N. in frame N.
""" """
if ( if self.annotation_directory is not None:
hasattr(self, "annotation_directory") return super().annotations
and self.annotation_directory is not None
):
path = self.make_path(self.annotation_directory, extension=".json")
return read_annotation_file(path, annotation_type="json")
# numpy array containing the face bounding box data for each video # numpy array containing the face bounding box data for each video
# frame, returned data format described in the f.bbx() method of the # frame, returned data format described in the f.bbx() method of the
...@@ -116,10 +118,10 @@ class ReplayPadDatabase(PadDatabase): ...@@ -116,10 +118,10 @@ class ReplayPadDatabase(PadDatabase):
self, self,
# grandtest is the default protocol for this database # grandtest is the default protocol for this database
protocol="grandtest", protocol="grandtest",
original_directory=rc["bob.db.replay.directory"], original_directory=rc.get("bob.db.replay.directory"),
original_extension=".mov", original_extension=".mov",
annotation_directory=None, annotation_directory=rc.get("bob.db.replay.annotation_dir"),
**kwargs **kwargs,
): ):
""" """
Parameters Parameters
...@@ -164,7 +166,7 @@ class ReplayPadDatabase(PadDatabase): ...@@ -164,7 +166,7 @@ class ReplayPadDatabase(PadDatabase):
original_directory=original_directory, original_directory=original_directory,
original_extension=original_extension, original_extension=original_extension,
annotation_directory=annotation_directory, annotation_directory=annotation_directory,
**kwargs **kwargs,
) )
@property @property
...@@ -209,7 +211,7 @@ class ReplayPadDatabase(PadDatabase): ...@@ -209,7 +211,7 @@ class ReplayPadDatabase(PadDatabase):
""" """
# Convert group names to low-level group names here. # Convert group names to low-level group names here.
groups = self.convert_names_to_lowlevel( groups = convert_names_to_lowlevel(
groups, self.low_level_group_names, self.high_level_group_names groups, self.low_level_group_names, self.high_level_group_names
) )
# Since this database was designed for PAD experiments, nothing special # Since this database was designed for PAD experiments, nothing special
...@@ -217,77 +219,15 @@ class ReplayPadDatabase(PadDatabase): ...@@ -217,77 +219,15 @@ class ReplayPadDatabase(PadDatabase):
files = self.db.objects( files = self.db.objects(
protocol=protocol, groups=groups, cls=purposes, **kwargs protocol=protocol, groups=groups, cls=purposes, **kwargs
) )
files = [ReplayPadFile(f) for f in files] files = [
# set the attributes ReplayPadFile(
for f in files: f,
f.original_directory = self.original_directory original_directory=self.original_directory,
f.original_extension = self.original_extension original_extension=self.original_extension,
f.annotation_directory = self.annotation_directory annotation_directory=self.annotation_directory,
annotation_extension=self.annotation_extension,
annotation_type=self.annotation_type,
)
for f in files
]
return files return files
def annotations(self, f):
"""
Return annotations for a given file object ``f``, which is an instance
of ``ReplayPadFile`` defined in the HLDI of the Replay-Attack DB. The
``load()`` method of ``ReplayPadFile`` class (see above) returns a
video, therefore this method returns bounding-box annotations for each
video frame. The annotations are returned as a dictionary of
dictionaries.
Parameters
----------
f : :any:`ReplayPadFile`
An instance of :any:`ReplayPadFile`.
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.
"""
return f.annotations
def frames(self, padfile):
"""Yields the frames of the padfile one by one.
Parameters
----------
padfile : :any:`ReplayPadFile`
The high-level replay pad file
Yields
------
:any:`numpy.array`
A frame of the video. The size is (3, 240, 320).
"""
return padfile.frames
def number_of_frames(self, padfile):
"""Returns the number of frames in a video file.
Parameters
----------
padfile : :any:`ReplayPadFile`
The high-level pad file
Returns
-------
int
The number of frames.
"""
return padfile.number_of_frames
@property
def frame_shape(self):
"""Returns the size of each frame in this database.
Returns
-------
(int, int, int)
The (#Channels, Height, Width) which is (3, 240, 320).
"""
return REPLAY_ATTACK_FRAME_SHAPE
...@@ -5,102 +5,148 @@ ...@@ -5,102 +5,148 @@
# Import what is needed here: # Import what is needed here:
from __future__ import division from __future__ import division
from bob.bio.base.extractor import Extractor
from bob.ip.qualitymeasure import galbally_iqm_features as iqm from bob.ip.qualitymeasure import galbally_iqm_features as iqm
from bob.ip.qualitymeasure import msu_iqa_features as iqa from bob.ip.qualitymeasure import msu_iqa_features as iqa
import numpy
import logging import logging
import numpy as np
from sklearn.preprocessing import FunctionTransformer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
#============================================================================== def iqm_features(images, galbally=True, msu=True, dtype=None):
# Main body: if not (galbally or msu):
raise ValueError("At least galbally or msu needs to be True.")
all_features = []
for data in images:
assert isinstance(data, np.ndarray)
class ImageQualityMeasure(Extractor): features = []
"""
This class is designed to extract Image Quality Measures given input RGB
image. For further documentation and description of features,
see "bob.ip.qualitymeasure".
**Parameters:** if galbally:
``galbally`` : :py:class:`bool` try:
If ``True``, galbally features will be added to the features.
Default: ``True``.
``msu`` : :py:class:`bool` gf_set = iqm.compute_quality_features(data)
If ``True``, MSU features will be added to the features. gf_set = np.nan_to_num(gf_set)
Default: ``True``. features = np.hstack((features, gf_set))
``dtype`` : numpy.dtype except Exception:
The data type of the resulting feature vector.
Default: ``None``.
"""
#========================================================================== logger.error(
def __init__(self, galbally=True, msu=True, dtype=None, **kwargs): "Failed to extract galbally features.", exc_info=True)
Extractor.__init__( features = np.zeros((18,))
self, galbally=galbally, msu=msu, dtype=dtype, **kwargs)
self.dtype = dtype if msu:
self.galbally = galbally
self.msu = msu
#========================================================================== try:
def __call__(self, data):
"""
Compute Image Quality Measures given input RGB image.