Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
bob
bob.pad.face
Commits
1897e934
Commit
1897e934
authored
Nov 05, 2020
by
Amir MOHAMMADI
Browse files
Merge branch 'dask-pipelines'
parents
2d768c77
80e1c3dc
Pipeline
#44917
failed with stages
in 41 seconds
Changes
9
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
bob/pad/face/config/lbp_svm.py
View file @
1897e934
#!/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``.
...
...
bob/pad/face/config/vanilla_pad/qm_svm.py
0 → 100644
View file @
1897e934
# 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
)
bob/pad/face/config/vanilla_pad/replay_attack.py
0 → 100644
View file @
1897e934
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"
,
)
)
bob/pad/face/database/database.py
View file @
1897e934
...
...
@@ -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
...
...
bob/pad/face/database/replay.py
View file @
1897e934
#!/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
bob/pad/face/extractor/ImageQualityMeasure.py
View file @
1897e934
...
...
@@ -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
,))