Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
bob
bob.pad.face
Commits
85db25fc
Commit
85db25fc
authored
May 28, 2018
by
Olegs NIKISINS
Browse files
Added a quality assessment script and config for CelebA quality
parent
c243d2ce
Pipeline
#20561
passed with stage
in 13 minutes and 34 seconds
Changes
7
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
MANIFEST.in
View file @
85db25fc
...
...
@@ -3,3 +3,5 @@ recursive-include bob/pad/face/lists *.*
recursive-include bob/pad/face/config/preprocessor/dictionaries *.hdf5
recursive-include doc *.py *.rst *.ico *.png
recursive-include bob/pad/face/test/data *.hdf5 *.png
recursive-include bob/pad/face/config *.xml
bob/pad/face/config/quality_assessment/__init__.py
0 → 100644
View file @
85db25fc
bob/pad/face/config/quality_assessment/celeb_a/__init__.py
0 → 100644
View file @
85db25fc
bob/pad/face/config/quality_assessment/celeb_a/quality_assessment_config.py
0 → 100644
View file @
85db25fc
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Quality assessment configuration file for the CelebA database to be used
with quality assessment script.
Note: this config checks the quality of the preprocessed(!) data. Here the
preprocessed data is sored in ``.hdf5`` files, as a frame container with
one frame. Frame contains a BW image of the facial regions of the size
64x64 pixels.
The config file MUST contain at least the following functions:
``load_datafile(file_name)`` - returns the ``data`` given ``file_name``, and
``assess_quality(data, **assess_quality_kwargs)`` - returns ``True`` for good
quality ``data``, and ``False`` for low quality data, and
``assess_quality_kwargs`` - a dictionary with kwargs for ``assess_quality()``
function.
@author: Olegs Nikisins
"""
# =============================================================================
# Import here:
import
pkg_resources
import
cv2
from
bob.bio.video.preprocessor
import
Wrapper
import
numpy
as
np
# =============================================================================
def
detect_eyes_in_bw_image
(
image
):
"""
Detect eyes in the image using OpenCV.
**Parameters:**
``image`` : 2D :py:class:`numpy.ndarray`
A BW image to detect the eyes in.
**Returns:**
``eyes`` : 2D :py:class:`numpy.ndarray`
An array containing coordinates of the bounding boxes of detected eyes.
The dimensionality of the array:
``num_of_detected_eyes x coordinates_of_bbx``
"""
eye_model
=
pkg_resources
.
resource_filename
(
'bob.pad.face.config'
,
'quality_assessment/models/eye_detector.xml'
)
eye_cascade
=
cv2
.
CascadeClassifier
(
eye_model
)
eyes
=
eye_cascade
.
detectMultiScale
(
image
)
return
eyes
# =============================================================================
def
load_datafile
(
file_name
):
"""
Load data from file given filename. Here the data file is an hdf5 file
containing a framecontainer with one frame. The data in the frame is
a BW image of the facial region.
**Parameters:**
``file_name`` : str
Absolute name of the file.
**Returns:**
``data`` : 2D :py:class:`numpy.ndarray`
Data array containing the image of the facial region.
"""
frame_container
=
Wrapper
().
read_data
(
file_name
)
data
=
frame_container
[
0
][
1
]
return
data
# =============================================================================
face_size
=
64
eyes_distance
=
((
face_size
+
1
)
/
2.
)
eyes_center
=
(
face_size
/
4.
,
(
face_size
-
0.5
)
/
2.
)
eyes_expected
=
[[
eyes_center
[
0
],
eyes_center
[
1
]
-
eyes_distance
/
2.
],
[
eyes_center
[
0
],
eyes_center
[
1
]
+
eyes_distance
/
2.
]]
assess_quality_kwargs
=
{}
assess_quality_kwargs
[
"eyes_expected"
]
=
eyes_expected
assess_quality_kwargs
[
"threshold"
]
=
7
# =============================================================================
def
assess_quality
(
data
,
eyes_expected
,
threshold
):
"""
Assess the quality of the data sample, which in this case is an image of
the face of the size 64x64 pixels. The quality assessment is based on the
eye detection. If two eyes are detected, and they are located in the
pre-defined positions, then quality is good, otherwise the quality is low.
**Parameters:**
``data`` : 2D :py:class:`numpy.ndarray`
Data array containing the image of the facial region. The size of the
image is 64x64.
``eyes_expected`` : list
A list containing expected coordinates of the eyes. The format is
as follows:
[ [left_y, left_x], [right_y, right_x] ]
``threshold`` : int
A maximum allowed distance between expected and detected centers of
the eyes.
**Returns:**
``quality_flag`` : bool
``True`` for good quality data, ``False`` otherwise.
"""
quality_flag
=
False
eyes
=
detect_eyes_in_bw_image
(
data
)
if
isinstance
(
eyes
,
np
.
ndarray
):
if
eyes
.
shape
[
0
]
==
2
:
# only consider the images with two eyes detected
# coordinates of detected centers of the eyes: [ [left_y, left_x], [right_y, right_x] ]:
eyes_detected
=
[]
for
(
ex
,
ey
,
ew
,
eh
)
in
eyes
:
eyes_detected
.
append
(
[
ey
+
eh
/
2.
,
ex
+
ew
/
2.
]
)
dists
=
[]
# dits between detected and expected:
for
a
,
b
in
zip
(
eyes_detected
,
eyes_expected
):
dists
.
append
(
np
.
linalg
.
norm
(
np
.
array
(
a
)
-
np
.
array
(
b
))
)
max_dist
=
np
.
max
(
dists
)
if
max_dist
<
threshold
:
quality_flag
=
True
return
quality_flag
bob/pad/face/config/quality_assessment/models/eye_detector.xml
0 → 100644
View file @
85db25fc
This diff is collapsed.
Click to expand it.
bob/pad/face/script/quality_assessment.py
0 → 100644
View file @
85db25fc
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This script is designed to assess the quality of the data(images, videos, etc.)
in the user-defined folder and split/copy the data into two folders
according to their quality.
Good quality data will be copied to ``<save_path>/good_quality_data/`` folder,
respectively "low" quality to ``<save_path>/low_quality_data/`` folder.
The function determining the quality of the data must be implemented in the
config file.
@author: Olegs Nikisins
"""
# =============================================================================
# Import here:
import
argparse
import
importlib
import
os
from
shutil
import
copyfile
# =============================================================================
def
parse_arguments
(
cmd_params
=
None
):
"""
Parse command line arguments.
**Parameters:**
``cmd_params``: []
An optional list of command line arguments. Default: None.
**Returns:**
``data_folder``: py:class:`string`
A directory containing the data to be used in quality assessment.
``save_folder``: py:class:`string`
A directory to save the results to. Two sub-folders will be created
here: ``good_quality_data``, and ``low_quality_data``.
``file_extension``: py:class:`string`
An extension of the data files.
Default: ``.hdf5``.
``relative_mod_name``: py:class:`string`
Relative name of the module to import configurations from.
Default: ``celeb_a/quality_assessment_config.py``.
``config_group``: py:class:`string`
Group/package name containing the configuration file.
Default: ``bob.pad.face.config.quality_assessment``.
``verbosity``: py:class:`int`
Verbosity level.
"""
parser
=
argparse
.
ArgumentParser
(
description
=
__doc__
)
parser
.
add_argument
(
"data_folder"
,
type
=
str
,
help
=
"A directory containing the data to be used in quality assessment."
)
parser
.
add_argument
(
"save_folder"
,
type
=
str
,
help
=
"A directory to save the results to. "
"Two sub-folders will be created here: good_quality_data, and low_quality_data."
)
parser
.
add_argument
(
"-e"
,
"--file-extension"
,
type
=
str
,
help
=
"An extension of the data files."
,
default
=
".hdf5"
)
parser
.
add_argument
(
"-c"
,
"--config-file"
,
type
=
str
,
help
=
"Relative name of the config file containing "
"quality assessment function, and data loading function."
,
default
=
"celeb_a/quality_assessment_config.py"
)
parser
.
add_argument
(
"-cg"
,
"--config-group"
,
type
=
str
,
help
=
"Name of the group, where config file is stored."
,
default
=
"bob.pad.face.config.quality_assessment"
)
parser
.
add_argument
(
"-v"
,
"--verbosity"
,
action
=
"count"
,
default
=
0
,
help
=
"Currently not used."
)
if
cmd_params
is
not
None
:
args
=
parser
.
parse_args
(
cmd_params
)
else
:
args
=
parser
.
parse_args
()
data_folder
=
args
.
data_folder
save_folder
=
args
.
save_folder
file_extension
=
args
.
file_extension
config_file
=
args
.
config_file
config_group
=
args
.
config_group
verbosity
=
args
.
verbosity
relative_mod_name
=
'.'
+
os
.
path
.
splitext
(
config_file
)[
0
].
replace
(
os
.
path
.
sep
,
'.'
)
return
data_folder
,
save_folder
,
file_extension
,
relative_mod_name
,
config_group
,
verbosity
# =============================================================================
def
get_all_filenames_for_path_and_extension
(
path
,
extension
):
"""
Get all filenames with specific extension in all subdirectories of the
given path
**Parameters:**
``path`` : str
String containing the path to directory with files.
``extension`` : str
Extension of the files.
**Returns:**
``all_filenames`` : [str]
A list of selected absolute filenames.
"""
all_filenames
=
[]
for
root
,
dirs
,
files
in
os
.
walk
(
path
):
for
file
in
files
:
if
file
.
endswith
(
extension
):
all_filenames
.
append
(
os
.
path
.
join
(
root
,
file
))
return
all_filenames
# =============================================================================
def
copy_file
(
file_name
,
data_folder
,
save_folder
):
"""
Copy the file from from source to destanation.
**Parameters:**
``file_name`` : str
Absolute name of the file to be copied.
``data_folder`` : str
Folder containing all data files.
``save_folder`` : str
Folder to copy the results to.
"""
# absolute name of the destanation file:
save_filename
=
os
.
path
.
join
(
save_folder
,
file_name
.
replace
(
data_folder
,
""
))
# make the folders to save the file to:
dst_folder
=
os
.
path
.
split
(
save_filename
)[
0
]
if
not
os
.
path
.
exists
(
dst_folder
):
os
.
makedirs
(
dst_folder
)
copyfile
(
file_name
,
save_filename
)
# =============================================================================
def
main
(
cmd_params
=
None
):
"""
The following steps are performed in this function:
1. The command line arguments are first parsed.
2. Folder to save the results to is created.
3. Configuration file specifying the quality function, and data loading
functionality, is loaded.
4. All files in data folder with specified extension are obtained.
"""
# Parse the command line arguments:
data_folder
,
save_folder
,
file_extension
,
relative_mod_name
,
config_group
,
verbosity
=
\
parse_arguments
(
cmd_params
=
cmd_params
)
# Create the directories:
good_quality_folder
=
os
.
path
.
join
(
save_folder
,
"good_quality_data"
)
low_quality_folder
=
os
.
path
.
join
(
save_folder
,
"low_quality_data"
)
if
not
os
.
path
.
exists
(
good_quality_folder
):
os
.
makedirs
(
good_quality_folder
)
if
not
os
.
path
.
exists
(
low_quality_folder
):
os
.
makedirs
(
low_quality_folder
)
# Load the configuretion file:
config_module
=
importlib
.
import_module
(
relative_mod_name
,
config_group
)
# Obtain a list of data files:
all_filenames
=
get_all_filenames_for_path_and_extension
(
data_folder
,
file_extension
)
if
verbosity
>
0
:
print
(
"The number of files to process: {}"
.
format
(
len
(
all_filenames
)
)
)
for
idx
,
file_name
in
enumerate
(
all_filenames
):
data
=
config_module
.
load_datafile
(
file_name
)
quality_flag
=
config_module
.
assess_quality
(
data
,
**
config_module
.
assess_quality_kwargs
)
if
quality_flag
:
copy_file
(
file_name
,
data_folder
,
good_quality_folder
)
if
verbosity
>
0
:
print
(
"Good quality sample copied. {} out of {} samples processed."
.
format
(
idx
,
len
(
all_filenames
)))
else
:
copy_file
(
file_name
,
data_folder
,
low_quality_folder
)
if
verbosity
>
0
:
print
(
"Bad quality sample copied. {} out of {} samples processed."
.
format
(
idx
,
len
(
all_filenames
)))
if
verbosity
>
0
:
print
(
"Done!"
)
setup.py
View file @
85db25fc
...
...
@@ -56,6 +56,11 @@ setup(
# the version of bob.
entry_points
=
{
# scripts should be declared using this entry:
'console_scripts'
:
[
'quality-assessment.py = bob.pad.face.script.quality_assessment:main'
,
],
# registered databases:
'bob.pad.database'
:
[
'replay-attack = bob.pad.face.config.replay_attack:database'
,
...
...
Guillaume HEUSCH
@heusch
mentioned in merge request
!53 (merged)
·
Jul 03, 2018
mentioned in merge request
!53 (merged)
mentioned in merge request !53
Toggle commit list
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment