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
8f6cd5ec
Commit
8f6cd5ec
authored
Jun 07, 2019
by
Amir MOHAMMADI
Browse files
Merge branch 'casia_fasd' into 'master'
Add casia fasd and an optical flow extractor See merge request
!98
parents
1ae85bfe
2c0b25cd
Pipeline
#30798
passed with stages
in 40 minutes and 11 seconds
Changes
12
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
bob/pad/face/config/casiafasd.py
0 → 100644
View file @
8f6cd5ec
#!/usr/bin/env python
"""Config file for the CASIA FASD dataset.
Please run ``bob config set bob.db.casia_fasd.directory /path/to/casia_fasd_files``
in terminal to point to the original files of the dataset on your computer."""
from
bob.pad.face.database
import
CasiaFasdPadDatabase
database
=
CasiaFasdPadDatabase
()
bob/pad/face/config/extractor/optical_flow.py
0 → 100644
View file @
8f6cd5ec
from
bob.bio.base.extractor
import
CallableExtractor
from
bob.pad.face.extractor
import
OpticalFlow
extractor
=
CallableExtractor
(
OpticalFlow
())
bob/pad/face/config/preprocessor/optical_flow.py
0 → 100644
View file @
8f6cd5ec
from
bob.bio.base.preprocessor
import
CallablePreprocessor
from
bob.pad.face.extractor
import
OpticalFlow
def
_read_original_data
(
biofile
,
directory
,
extension
):
return
biofile
.
frames
preprocessor
=
CallablePreprocessor
(
OpticalFlow
(),
accepts_annotations
=
False
)
preprocessor
.
read_original_data
=
_read_original_data
bob/pad/face/database/__init__.py
View file @
8f6cd5ec
...
...
@@ -8,6 +8,8 @@ from .batl import BatlPadDatabase
from
.celeb_a
import
CELEBAPadDatabase
from
.maskattack
import
MaskAttackPadDatabase
from
.casiasurf
import
CasiaSurfPadDatabase
from
.casiafasd
import
CasiaFasdPadDatabase
# gets sphinx autodoc done right - don't remove it
def
__appropriate__
(
*
args
):
...
...
@@ -35,7 +37,8 @@ __appropriate__(
BatlPadDatabase
,
CELEBAPadDatabase
,
MaskAttackPadDatabase
,
CasiaSurfPadDatabase
CasiaSurfPadDatabase
,
CasiaFasdPadDatabase
,
)
__all__
=
[
_
for
_
in
dir
()
if
not
_
.
startswith
(
'_'
)]
bob/pad/face/database/casiafasd.py
0 → 100644
View file @
8f6cd5ec
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from
bob.bio.video
import
FrameSelector
from
bob.extension
import
rc
from
bob.io.video
import
reader
from
bob.pad.base.database
import
PadDatabase
from
bob.pad.face.database
import
VideoPadFile
from
bob.db.base.utils
import
(
check_parameter_for_validity
,
check_parameters_for_validity
)
import
numpy
import
os
CASIA_FASD_FRAME_SHAPE
=
(
3
,
1280
,
720
)
class
CasiaFasdPadFile
(
VideoPadFile
):
"""
A high level implementation of the File class for the CASIA_FASD database.
"""
def
__init__
(
self
,
f
,
original_directory
=
None
):
"""
Parameters
----------
f : object
An instance of the File class defined in the low level db interface
of the CasiaFasd database, in bob.db.casia_fasd.models
"""
self
.
f
=
f
self
.
original_directory
=
original_directory
if
f
.
is_real
():
attack_type
=
None
else
:
attack_type
=
'attack/{}/{}'
.
format
(
f
.
get_type
(),
f
.
get_quality
())
super
(
CasiaFasdPadFile
,
self
).
__init__
(
client_id
=
str
(
f
.
get_clientid
()),
path
=
f
.
filename
,
attack_type
=
attack_type
,
file_id
=
f
.
filename
)
@
property
def
frames
(
self
):
"""Yields the frames of the biofile one by one.
Yields
------
:any:`numpy.array`
A frame of the video. The size is :any:`CASIA_FASD_FRAME_SHAPE`.
"""
vfilename
=
self
.
make_path
(
directory
=
self
.
original_directory
,
extension
=
'.avi'
)
for
frame
in
reader
(
vfilename
):
# pad frames to 1280 x 720 so they all have the same size
h
,
w
=
frame
.
shape
[
1
:]
H
,
W
=
CASIA_FASD_FRAME_SHAPE
[
1
:]
assert
h
<=
H
assert
w
<=
W
frame
=
numpy
.
pad
(
frame
,
((
0
,
0
),
(
0
,
H
-
h
),
(
0
,
W
-
w
)),
mode
=
'constant'
,
constant_values
=
0
)
yield
frame
@
property
def
number_of_frames
(
self
):
"""Returns the number of frames in a video file.
Returns
-------
int
The number of frames.
"""
vfilename
=
self
.
make_path
(
directory
=
self
.
original_directory
,
extension
=
'.avi'
)
return
reader
(
vfilename
).
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
:any:`CASIA_FASD_FRAME_SHAPE`.
"""
return
CASIA_FASD_FRAME_SHAPE
@
property
def
annotations
(
self
):
"""Reads the annotations
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.
"""
annots
=
self
.
f
.
bbx
()
annotations
=
{}
for
i
,
v
in
enumerate
(
annots
):
topleft
=
(
v
[
2
],
v
[
1
])
bottomright
=
(
v
[
2
]
+
v
[
4
],
v
[
1
]
+
v
[
3
])
annotations
[
str
(
i
)]
=
{
'topleft'
:
topleft
,
'bottomright'
:
bottomright
}
return
annotations
def
load
(
self
,
directory
=
None
,
extension
=
'.avi'
,
frame_selector
=
FrameSelector
(
selection_style
=
'all'
)):
"""Loads the video file and returns in a
:any:`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.
Returns
-------
:any:`bob.bio.video.FrameContainer`
The loaded frames inside a frame container.
"""
directory
=
directory
or
self
.
original_directory
return
frame_selector
(
self
.
make_path
(
directory
,
extension
))
class
CasiaFasdPadDatabase
(
PadDatabase
):
"""
A high level implementation of the Database class for the CASIA_FASD
database. Please run ``bob config set bob.db.casia_fasd.directory
/path/to/casia_fasd_files`` in a terminal to point to the original files on
your computer. This interface is different from the one implemented in
``bob.db.casia_fasd.Database``.
"""
def
__init__
(
self
,
# grandtest is the new modified protocol for this database
protocol
=
'grandtest'
,
original_directory
=
rc
[
'bob.db.casia_fasd.directory'
],
**
kwargs
):
"""
Parameters
----------
protocol : str or None
The name of the protocol that defines the default experimental
setup for this database. Only grandtest is supported for now.
original_directory : str
The directory where the original data of the database are stored.
kwargs
The arguments of the :py:class:`bob.pad.base.database.PadDatabase`
base class constructor.
"""
return
super
(
CasiaFasdPadDatabase
,
self
).
__init__
(
name
=
'casiafasd'
,
protocol
=
protocol
,
original_directory
=
original_directory
,
original_extension
=
'.avi'
,
training_depends_on_protocol
=
True
,
**
kwargs
)
def
objects
(
self
,
groups
=
None
,
protocol
=
None
,
purposes
=
None
,
model_ids
=
None
,
**
kwargs
):
"""
This function returns lists of CasiaFasdPadFile objects, which fulfill
the given restrictions.
Parameters
----------
groups : :obj:`str` or [:obj:`str`]
The groups of which the clients should be returned.
Usually, groups are one or more elements of
('train', 'dev', 'eval')
protocol : str
The protocol for which the clients should be retrieved.
Only 'grandtest' is supported for now. This protocol modifies the
'Overall Test' protocol and adds some ids to dev set.
purposes : :obj:`str` or [:obj:`str`]
The purposes for which File objects should be retrieved either
'real' or 'attack' or both.
model_ids
Ignored.
**kwargs
Ignored.
Returns
-------
files : [CasiaFasdPadFile]
A list of CasiaFasdPadFile objects.
"""
groups
=
check_parameters_for_validity
(
groups
,
'groups'
,
(
'train'
,
'dev'
,
'eval'
),
(
'train'
,
'dev'
,
'eval'
))
protocol
=
check_parameter_for_validity
(
protocol
,
'protocol'
,
(
'grandtest'
),
'grandtest'
)
purposes
=
check_parameters_for_validity
(
purposes
,
'purposes'
,
(
'real'
,
'attack'
),
(
'real'
,
'attack'
))
qualities
=
(
'low'
,
'normal'
,
'high'
)
types
=
(
'warped'
,
'cut'
,
'video'
)
from
bob.db.casia_fasd.models
import
File
files
=
[]
db_mappings
=
{
'real_normal'
:
'1'
,
'real_low'
:
'2'
,
'real_high'
:
'HR_1'
,
'warped_normal'
:
'3'
,
'warped_low'
:
'4'
,
'warped_high'
:
'HR_2'
,
'cut_normal'
:
'5'
,
'cut_low'
:
'6'
,
'cut_high'
:
'HR_3'
,
'video_normal'
:
'7'
,
'video_low'
:
'8'
,
'video_high'
:
'HR_4'
}
# identitites 1-15 are for train, 16-20 are dev, and 21-50 for eval
grp_id_map
=
{
'train'
:
list
(
range
(
1
,
16
)),
'dev'
:
list
(
range
(
16
,
21
)),
'eval'
:
list
(
range
(
21
,
51
)),
}
grp_map
=
{
'train'
:
'train'
,
'dev'
:
'train'
,
'eval'
:
'test'
,
}
for
g
in
groups
:
ids
=
grp_id_map
[
g
]
for
i
in
ids
:
cur_id
=
i
if
g
==
'eval'
:
cur_id
=
i
-
20
# the id within the group subset
# this group name (grp) is only train and test
grp
=
grp_map
[
g
]
folder_name
=
grp
+
'_release'
for
q
in
qualities
:
for
c
in
purposes
:
# the class real doesn't have any different types, only
# the attacks can be of different type
if
c
==
'real'
:
filename
=
os
.
path
.
join
(
folder_name
,
"%d"
%
cur_id
,
db_mappings
[
'real_'
+
q
])
files
.
append
(
CasiaFasdPadFile
(
File
(
filename
,
c
,
grp
),
self
.
original_directory
))
else
:
for
t
in
types
:
filename
=
os
.
path
.
join
(
folder_name
,
"%d"
%
cur_id
,
db_mappings
[
t
+
'_'
+
q
])
files
.
append
(
CasiaFasdPadFile
(
File
(
filename
,
c
,
grp
),
self
.
original_directory
))
return
files
def
annotations
(
self
,
padfile
):
return
padfile
.
annotations
def
frames
(
self
,
padfile
):
return
padfile
.
frames
def
number_of_frames
(
self
,
padfile
):
return
padfile
.
number_of_frames
@
property
def
frame_shape
(
self
):
return
CASIA_FASD_FRAME_SHAPE
bob/pad/face/database/replay.py
View file @
8f6cd5ec
#!/usr/bin/env python
2
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Used in ReplayMobilePadFile class
...
...
bob/pad/face/extractor/OpticalFlow.py
0 → 100644
View file @
8f6cd5ec
from
bob.bio.base
import
vstack_features
from
bob.bio.video
import
FrameContainer
from
bob.io.base
import
HDF5File
from
bob.ip.optflow.liu.cg
import
flow
from
collections
import
Iterator
from
functools
import
partial
import
logging
logger
=
logging
.
getLogger
(
__name__
)
def
_check_frame
(
frame
):
if
frame
.
dtype
==
"uint8"
:
return
frame
.
astype
(
"float64"
)
/
255.0
return
frame
.
astype
(
"float64"
)
class
_Reader
:
def
__init__
(
self
,
i1
,
flow_method
):
self
.
i1
=
_check_frame
(
i1
)
self
.
flow_method
=
flow_method
def
__call__
(
self
,
i2
):
i2
=
_check_frame
(
i2
)
flows
=
self
.
flow_method
(
self
.
i1
,
i2
)[:
2
]
self
.
i1
=
i2
return
flows
class
OpticalFlow
(
object
):
"""An optical flow extractor
For more information see :any:`bob.ip.optflow.liu.cg.flow`.
Attributes
----------
alpha : float
Regularization weight
inner : int
The number of inner fixed point iterations
iterations : int
The number of conjugate-gradient (CG) iterations
min_width : int
Width of the coarsest level
outer : int
The number of outer fixed point iterations
ratio : float
Downsample ratio
"""
def
__init__
(
self
,
alpha
=
0.02
,
ratio
=
0.75
,
min_width
=
30
,
outer
=
20
,
inner
=
1
,
iterations
=
50
,
**
kwargs
):
super
().
__init__
(
**
kwargs
)
self
.
alpha
=
alpha
self
.
ratio
=
ratio
self
.
min_width
=
min_width
self
.
outer
=
outer
self
.
inner
=
inner
self
.
iterations
=
iterations
def
__call__
(
self
,
video
):
"""Computes optical flows on a video
Please note that the video should either be uint8 or float64 with values from 0
to 1.
Parameters
----------
video : numpy.ndarray
The video. Can be a FrameContainer, generator, bob.io.video.reader, or a
numpy array.
Returns
-------
numpy.ndarray
The flows calculated for each pixel. The output shape will be
[number_of_frames - 1, 2, height, width].
"""
if
isinstance
(
video
,
FrameContainer
):
video
=
video
.
as_array
()
if
not
isinstance
(
video
,
Iterator
):
video
=
iter
(
video
)
i1
=
next
(
video
)
reader
=
_Reader
(
i1
,
partial
(
flow
,
alpha
=
self
.
alpha
,
ratio
=
self
.
ratio
,
min_width
=
self
.
min_width
,
n_outer_fp_iterations
=
self
.
outer
,
n_inner_fp_iterations
=
self
.
inner
,
n_cg_iterations
=
self
.
iterations
,
),
)
flows
=
vstack_features
(
reader
,
video
)
shape
=
list
(
flows
.
shape
)
shape
[
0
]
=
2
shape
.
insert
(
0
,
-
1
)
return
flows
.
reshape
(
shape
)
def
write_feature
(
self
,
feature
,
feature_file
):
if
not
isinstance
(
feature_file
,
HDF5File
):
feature_file
=
HDF5File
(
feature_file
,
"w"
)
feature_file
.
set
(
"uv"
,
feature
)
feature_file
.
set_attribute
(
"method"
,
"liu.cg"
,
"uv"
)
feature_file
.
set_attribute
(
"alpha"
,
self
.
alpha
,
"uv"
)
feature_file
.
set_attribute
(
"ratio"
,
self
.
ratio
,
"uv"
)
feature_file
.
set_attribute
(
"min_width"
,
self
.
min_width
,
"uv"
)
feature_file
.
set_attribute
(
"n_outer_fp_iterations"
,
self
.
outer
,
"uv"
)
feature_file
.
set_attribute
(
"n_inner_fp_iterations"
,
self
.
inner
,
"uv"
)
feature_file
.
set_attribute
(
"n_iterations"
,
self
.
iterations
,
"uv"
)
def
read_feature
(
self
,
feature_file
):
if
not
isinstance
(
feature_file
,
HDF5File
):
feature_file
=
HDF5File
(
feature_file
,
"r"
)
return
feature_file
[
"uv"
]
bob/pad/face/test/test_databases.py
View file @
8f6cd5ec
...
...
@@ -119,14 +119,17 @@ def test_maskattack():
package_prefix
=
'bob.pad.'
)
try
:
# all real sequences: 2 sessions, 5 recordings for 17 individuals
assert
len
(
maskattack
.
objects
(
groups
=
[
'train'
,
'dev'
,
'eval'
],
purposes
=
'real'
))
==
170
assert
len
(
maskattack
.
objects
(
groups
=
[
'train'
,
'dev'
,
'eval'
],
purposes
=
'real'
))
==
170
# all attacks: 1 session, 5 recordings for 17 individuals
assert
len
(
maskattack
.
objects
(
groups
=
[
'train'
,
'dev'
,
'eval'
],
purposes
=
'attack'
))
==
85
assert
len
(
maskattack
.
objects
(
groups
=
[
'train'
,
'dev'
,
'eval'
],
purposes
=
'attack'
))
==
85
# training real: 7 subjects, 2 sessions, 5 recordings
assert
len
(
maskattack
.
objects
(
groups
=
[
'train'
],
purposes
=
'real'
))
==
70
# training real: 7 subjects, 1 session, 5 recordings
assert
len
(
maskattack
.
objects
(
groups
=
[
'train'
],
purposes
=
'attack'
))
==
35
assert
len
(
maskattack
.
objects
(
groups
=
[
'train'
],
purposes
=
'attack'
))
==
35
# dev and test contains the same number of sequences:
# real: 5 subjects, 2 sessions, 5 recordings
...
...
@@ -134,7 +137,8 @@ def test_maskattack():
assert
len
(
maskattack
.
objects
(
groups
=
[
'dev'
],
purposes
=
'real'
))
==
50
assert
len
(
maskattack
.
objects
(
groups
=
[
'eval'
],
purposes
=
'real'
))
==
50
assert
len
(
maskattack
.
objects
(
groups
=
[
'dev'
],
purposes
=
'attack'
))
==
25
assert
len
(
maskattack
.
objects
(
groups
=
[
'eval'
],
purposes
=
'attack'
))
==
25
assert
len
(
maskattack
.
objects
(
groups
=
[
'eval'
],
purposes
=
'attack'
))
==
25
except
IOError
as
e
:
raise
SkipTest
(
...
...
@@ -142,6 +146,8 @@ def test_maskattack():
%
e
)
# Test the Aggregated database, which doesn't have a package
def
test_aggregated_db
():
aggregated_db
=
bob
.
bio
.
base
.
load_resource
(
'aggregated-db'
,
...
...
@@ -210,147 +216,61 @@ def test_casiasurf():
preferred_package
=
'bob.pad.face'
,
package_prefix
=
'bob.pad.'
)
try
:
assert
len
(
casiasurf
.
objects
(
groups
=
[
'train'
],
purposes
=
'real'
))
==
8942
assert
len
(
casiasurf
.
objects
(
groups
=
[
'train'
],
purposes
=
'real'
))
==
8942
assert
len
(
casiasurf
.
objects
(
groups
=
[
'train'
],
purposes
=
'attack'
))
==
20324
assert
len
(
casiasurf
.
objects
(
groups
=
(
'dev'
,),
purposes
=
(
'real'
,)))
==
2994
assert
len
(
casiasurf
.
objects
(
groups
=
(
'dev'
,),
purposes
=
(
'attack'
,)))
==
6614
assert
len
(
casiasurf
.
objects
(
groups
=
(
'dev'
,),
purposes
=
(
'real'
,
'attack'
)))
==
9608
assert
len
(
casiasurf
.
objects
(
groups
=
(
'eval'
,),
purposes
=
(
'attack'
,)))
==
57710
except
IOError
as
e
:
raise
SkipTest
(
"The database could not be queried; probably the db.sql3 file is missing. Here is the error: '%s'"
%
e
)
# # Test the BATL database
# @db_available('batl-db')
# def test_aggregated_db():
# batl_db = bob.bio.base.load_resource(
# 'batl-db',
# 'database',
# preferred_package='bob.pad.face',
# package_prefix='bob.pad.')
# try:
# assert len(
# batl_db.objects(groups=['train', 'dev', 'eval'])) == 1679
# assert len(batl_db.objects(groups=['train', 'dev'])) == 1122
# assert len(batl_db.objects(groups=['train'])) == 565
# assert len(batl_db.objects(groups='train')) == 565
# assert len(batl_db.objects(groups='dev')) == 557
# assert len(batl_db.objects(groups='eval')) == 557
# assert len(
# batl_db.objects(
# groups=['train', 'dev', 'eval'], protocol='grandtest')) == 1679
# assert len(
# batl_db.objects(
# groups=['train', 'dev', 'eval'],
# protocol='grandtest',
# purposes='real')) == 347
# assert len(
# batl_db.objects(
# groups=['train', 'dev', 'eval'],
# protocol='grandtest',
# purposes='attack')) == 1332
# #tests for join_train_dev protocols
# assert len(
# batl_db.objects(
# groups=['train', 'dev', 'eval'],
# protocol='grandtest-color-50-join_train_dev')) == 1679
# assert len(
# batl_db.objects(
# groups=['train', 'dev'], protocol='grandtest-color-50-join_train_dev')) == 1679
# assert len(
# batl_db.objects(groups='eval',
# protocol='grandtest-color-50-join_train_dev')) == 557
# # test for LOO_fakehead
# assert len(
# batl_db.objects(
# groups=['train', 'dev', 'eval'],
# protocol='grandtest-color-50-LOO_fakehead')) == 1149
# assert len(
# batl_db.objects(
# groups=['train', 'dev'], protocol='grandtest-color-50-LOO_fakehead')) == 1017
# assert len(
# batl_db.objects(groups='eval',
# protocol='grandtest-color-50-LOO_fakehead')) == 132
# # test for LOO_flexiblemask
# assert len(
# batl_db.objects(
# groups=['train', 'dev', 'eval'],
# protocol='grandtest-color-50-LOO_flexiblemask')) == 1132
# assert len(
# batl_db.objects(
# groups=['train', 'dev'], protocol='grandtest-color-50-LOO_flexiblemask')) == 880
# assert len(
# batl_db.objects(groups='eval',
# protocol='grandtest-color-50-LOO_flexiblemask')) == 252
# # test for LOO_glasses
# assert len(
# batl_db.objects(
# groups=['train', 'dev', 'eval'],
# protocol='grandtest-color-50-LOO_glasses')) == 1206
# assert len(
# batl_db.objects(
# groups=['train', 'dev'], protocol='grandtest-color-50-LOO_glasses')) == 1069