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.
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]_.
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.
......@@ -17,7 +15,7 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource.
"""
#=======================================================================================
# =======================================================================================
# define preprocessor:
from ..preprocessor import FaceCropAlign
......@@ -26,24 +24,23 @@ from bob.bio.video.preprocessor import Wrapper
from bob.bio.video.utils import FrameSelector
FACE_SIZE = 64 # The size of the resulting face
RGB_OUTPUT_FLAG = False # Gray-scale output
USE_FACE_ALIGNMENT = False # use annotations
MAX_IMAGE_SIZE = None # no limiting here
FACE_DETECTION_METHOD = None # use annotations
MIN_FACE_SIZE = 50 # skip small faces
# check if a database is loaded first
if "database" in locals():
annotation_type = database.annotation_type
fixed_positions = database.fixed_positions
else:
annotation_type = None
fixed_positions = None
_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)
cropped_image_size = 64 # The size of the resulting face
_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 = _frame_selector)
_frame_selector = FrameSelector(selection_style="all")
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.
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
[CAM12]_, which is defined by ``FACE_DETECTION_METHOD = None``.
"""
#=======================================================================================
# =======================================================================================
# define extractor:
from ..extractor import LBPHistogram
from bob.bio.video.extractor import Wrapper
LBPTYPE = 'uniform'
ELBPTYPE = 'regular'
LBPTYPE = "uniform"
ELBPTYPE = "regular"
RAD = 1
NEIGHBORS = 8
CIRC = False
DTYPE = None
extractor = Wrapper(LBPHistogram(
lbptype=LBPTYPE,
elbptype=ELBPTYPE,
rad=RAD,
neighbors=NEIGHBORS,
circ=CIRC,
dtype=DTYPE))
extractor = Wrapper(
LBPHistogram(
lbptype=LBPTYPE,
elbptype=ELBPTYPE,
rad=RAD,
neighbors=NEIGHBORS,
circ=CIRC,
dtype=DTYPE,
)
)
"""
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]_.
"""
#=======================================================================================
# =======================================================================================
# define algorithm:
from bob.pad.base.algorithm import SVM
MACHINE_TYPE = 'C_SVC'
KERNEL_TYPE = 'RBF'
MACHINE_TYPE = "C_SVC"
KERNEL_TYPE = "RBF"
N_SAMPLES = 10000
TRAINER_GRID_SEARCH_PARAMS = {
'cost': [2**P for P in range(-3, 14, 2)],
'gamma': [2**P for P in range(-15, 0, 2)]
"cost": [2 ** P for P in range(-3, 14, 2)],
"gamma": [2 ** P for P in range(-15, 0, 2)],
}
MEAN_STD_NORM_FLAG = True # enable mean-std normalization
FRAME_LEVEL_SCORES_FLAG = True # one score per frame(!) in this case
......@@ -99,7 +99,8 @@ algorithm = SVM(
n_samples=N_SAMPLES,
trainer_grid_search_params=TRAINER_GRID_SEARCH_PARAMS,
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.
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):
use in face PAD experiments.
"""
def __init__(self, attack_type, client_id, path, file_id=None):
super(VideoPadFile, self).__init__(
attack_type=attack_type, client_id=client_id, path=path, file_id=file_id
def __init__(
self,
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(
self,
directory=None,
extension=".avi",
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
----------
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
Which frames to select.
......@@ -36,19 +49,16 @@ class VideoPadFile(PadFile):
:any:`bob.bio.video.FrameContainer`
The loaded frames inside a frame container.
"""
return frame_selector(self.make_path(directory, extension))
def check_original_directory_and_extension(self):
if not hasattr(self, "original_directory"):
raise RuntimeError(
"Please set the original_directory attribute of files in your "
"database's object method."
)
if not hasattr(self, "original_extension"):
raise RuntimeError(
"Please set the original_extension attribute of files in your "
"database's object method."
)
path = self.make_path(
self.original_directory, self.original_extension
)
video = bob.bio.video.VideoAsArray(
path,
selection_style=frame_selector.selection_style,
max_number_of_frames=frame_selector.max_number_of_frames,
step_size=frame_selector.step_size,
)
return video
@property
def frames(self):
......@@ -60,15 +70,7 @@ class VideoPadFile(PadFile):
-------
collection.Iterator
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(
directory=self.original_directory, extension=self.original_extension
)
......@@ -76,7 +78,6 @@ class VideoPadFile(PadFile):
@property
def number_of_frames(self):
self.check_original_directory_and_extension()
path = self.make_path(
directory=self.original_directory, extension=self.original_extension
)
......@@ -85,7 +86,7 @@ class VideoPadFile(PadFile):
@property
def frame_shape(self):
"""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
a constant.
......@@ -94,7 +95,6 @@ class VideoPadFile(PadFile):
(int, int, int)
The (Channels, Height, Width) sizes.
"""
self.check_original_directory_and_extension()
path = self.make_path(
directory=self.original_directory, extension=self.original_extension
)
......@@ -113,17 +113,6 @@ class VideoPadFile(PadFile):
dict
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:
return None
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from bob.pad.base.database import PadDatabase # Used in ReplayMobilePadFile class
from bob.pad.face.database import VideoPadFile # Used in ReplayPadFile class
from bob.pad.base.database import PadDatabase
from bob.pad.face.database import VideoPadFile
from bob.extension import rc
from bob.ip.facedetect import expected_eye_positions, BoundingBox
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)
......@@ -16,7 +14,11 @@ class ReplayPadFile(VideoPadFile):
database.
"""
def __init__(self, f):
def __init__(
self,
f,
**kwargs,
):
"""
Parameters
----------
......@@ -43,7 +45,11 @@ class ReplayPadFile(VideoPadFile):
# database.
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
......@@ -76,12 +82,8 @@ class ReplayPadFile(VideoPadFile):
is the dictionary defining the coordinates of the face bounding box
in frame N.
"""
if (
hasattr(self, "annotation_directory")
and self.annotation_directory is not None
):
path = self.make_path(self.annotation_directory, extension=".json")
return read_annotation_file(path, annotation_type="json")
if self.annotation_directory is not None:
return super().annotations
# numpy array containing the face bounding box data for each video
# frame, returned data format described in the f.bbx() method of the
......@@ -116,10 +118,10 @@ class ReplayPadDatabase(PadDatabase):
self,
# grandtest is the default protocol for this database
protocol="grandtest",
original_directory=rc["bob.db.replay.directory"],
original_directory=rc.get("bob.db.replay.directory"),
original_extension=".mov",
annotation_directory=None,
**kwargs
annotation_directory=rc.get("bob.db.replay.annotation_dir"),
**kwargs,
):
"""
Parameters
......@@ -164,7 +166,7 @@ class ReplayPadDatabase(PadDatabase):
original_directory=original_directory,
original_extension=original_extension,
annotation_directory=annotation_directory,
**kwargs
**kwargs,
)
@property
......@@ -209,7 +211,7 @@ class ReplayPadDatabase(PadDatabase):
"""
# 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
)
# Since this database was designed for PAD experiments, nothing special
......@@ -217,77 +219,15 @@ class ReplayPadDatabase(PadDatabase):
files = self.db.objects(
protocol=protocol, groups=groups, cls=purposes, **kwargs
)
files = [ReplayPadFile(f) for f in files]
# set the attributes
for f in files:
f.original_directory = self.original_directory
f.original_extension = self.original_extension
f.annotation_directory = self.annotation_directory
files = [
ReplayPadFile(
f,
original_directory=self.original_directory,
original_extension=self.original_extension,
annotation_directory=self.annotation_directory,
annotation_extension=self.annotation_extension,
annotation_type=self.annotation_type,
)
for f in 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 @@
# Import what is needed here:
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 msu_iqa_features as iqa
import numpy
import logging
import numpy as np
from sklearn.preprocessing import FunctionTransformer
logger = logging.getLogger(__name__)
#==============================================================================
# Main body:
def iqm_features(images, galbally=True, msu=True, dtype=None):
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):
"""
This class is designed to extract Image Quality Measures given input RGB
image. For further documentation and description of features,
see "bob.ip.qualitymeasure".
features = []
**Parameters:**
if galbally:
``galbally`` : :py:class:`bool`
If ``True``, galbally features will be added to the features.
Default: ``True``.
try:
``msu`` : :py:class:`bool`
If ``True``, MSU features will be added to the features.
Default: ``True``.
gf_set = iqm.compute_quality_features(data)
gf_set = np.nan_to_num(gf_set)
features = np.hstack((features, gf_set))
``dtype`` : numpy.dtype
The data type of the resulting feature vector.
Default: ``None``.
"""
except Exception:
#==========================================================================
def __init__(self, galbally=True, msu=True, dtype=None, **kwargs):
logger.error(
"Failed to extract galbally features.", exc_info=True)
Extractor.__init__(
self, galbally=galbally, msu=msu, dtype=dtype, **kwargs)
features = np.zeros((18,))
self.dtype = dtype
self.galbally = galbally
self.msu = msu
if msu:
#==========================================================================
def __call__(self, data):
"""
Compute Image Quality Measures given input RGB image.
try:
**Parameters:**
msuf_set = iqa.compute_msu_iqa_features(data)
msuf_set = np.nan_to_num(msuf_set)
features = np.hstack((features, msuf_set))
``data`` : 3D :py:class:`numpy.ndarray`
Input RGB image of the dimensionality (3, Row, Col), as returned
by Bob image loading routines.
except Exception:
**Returns:**
logger.error("Failed to extract MSU features.", exc_info=True)
``features`` : 1D :py:class:`numpy.ndarray`
Feature vector containing Image Quality Measures.
"""
features = np.zeros((121,))
assert isinstance(data, numpy.ndarray)
assert self.galbally or self.msu
if dtype is not None:
features = []
features = features.astype(dtype)
all_features.append(features)
if self.galbally:
return np.array(all_features)
try:
def ImageQualityMeasure(galbally=True, msu=True, dtype=None, **kwargs):
kw_args = dict(galbally=galbally, msu=msu, dtype=dtype)
return FunctionTransformer(iqm_features, validate=False, kw_args=kw_args)
gf_set = iqm.compute_quality_features(data)
gf_set = numpy.nan_to_num(gf_set)
features = numpy.hstack((features, gf_set))
# class ImageQualityMeasure(Extractor):
# """
# This class is designed to extract Image Quality Measures given input RGB
# image. For further documentation and description of features,
# see "bob.ip.qualitymeasure".
except Exception as e:
# **Parameters:**
logger.error(
"Failed to extract galbally features.", exc_info=e)
# ``galbally`` : :py:class:`bool`
# If ``True``, galbally features will be added to the features.
# Default: ``True``.
return None
# ``msu`` : :py:class:`bool`
# If ``True``, MSU features will be added to the features.
# Default: ``True``.
if self.msu:
# ``dtype`` : np.dtype
# The data type of the resulting feature vector.
# Default: ``None``.
# """
try:
# #==========================================================================
# def __init__(self, galbally=True, msu=True, dtype=None, **kwargs):
msuf_set = iqa.compute_msu_iqa_features(data)
msuf_set = numpy.nan_to_num(msuf_set)
features = numpy.hstack((features, msuf_set))
# Extractor.__init__(
# self, galbally=galbally, msu=msu, dtype=dtype, **kwargs)
# self.dtype = dtype
# self.galbally = galbally
# self.msu = msu
# #==========================================================================
# def __call__(self, data):
# """
# Compute Image Quality Measures given input RGB image.
# **Parameters:**
# ``data`` : 3D :py:class:`np.ndarray`
# Input RGB image of the dimensionality (3, Row, Col), as returned
# by Bob image loading routines.
# **Returns:**
# ``features`` : 1D :py:class:`np.ndarray`
# Feature vector containing Image Quality Measures.
# """
# assert isinstance(data, np.ndarray)
# assert self.galbally or self.msu
# features = []
# if self.galbally:
# try:
# gf_set = iqm.compute_quality_features(data)
# gf_set = np.nan_to_num(gf_set)
# features = np.hstack((features, gf_set))
# except Exception as e:
# logger.error(
# "Failed to extract galbally features.", exc_info=e)
# return None
# if self.msu:
# try:
# msuf_set = iqa.compute_msu_iqa_features(data)
# msuf_set = np.nan_to_num(msuf_set)