Commit 07059f88 authored by Olegs NIKISINS's avatar Olegs NIKISINS
Browse files

Merge branch 'autoencoder' into 'master'

HLDI for the CelebA database and quality estimation script

See merge request !67
parents 0e3a9d70 8c5c27b8
Pipeline #20587 passed with stages
in 15 minutes and 33 seconds
......@@ -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
#!/usr/bin/env python
"""`CELEBA`_ is a face makeup spoofing database adapted for face PAD experiments.
You can download the raw data of the `CELEBA`_ database by following
the link.
.. include:: links.rst
"""
from bob.pad.face.database.celeb_a import CELEBAPadDatabase
# Directory where the data files are stored.
# This directory is given in the .bob_bio_databases.txt file located in your home directory
ORIGINAL_DIRECTORY = "[YOUR_CELEB_A_DATABASE_DIRECTORY]"
"""Value of ``~/.bob_bio_databases.txt`` for this database"""
ORIGINAL_EXTENSION = "" # extension of the data files
database = CELEBAPadDatabase(
protocol='grandtest',
original_directory=ORIGINAL_DIRECTORY,
original_extension=ORIGINAL_EXTENSION,
training_depends_on_protocol=True
)
"""The :py:class:`bob.pad.base.database.PadDatabase` derivative with CELEBA
database settings.
.. warning::
This class only provides a programmatic interface to load data in an orderly
manner, respecting usage protocols. It does **not** contain the raw
data files. You should procure those yourself.
Notice that ``original_directory`` is set to ``[YOUR_CELEBA_DATABASE_DIRECTORY]``.
You must make sure to create ``${HOME}/.bob_bio_databases.txt`` setting this
value to the place where you actually installed the CELEBA Database, as
explained in the section :ref:`bob.pad.face.baselines`.
"""
protocol = 'grandtest'
"""The default protocol to use for reproducing the baselines.
You may modify this at runtime by specifying the option ``--protocol`` on the
command-line of ``spoof.py`` or using the keyword ``protocol`` on a
configuration file that is loaded **after** this configuration resource.
"""
groups = ["train", "dev", "eval"]
"""The default groups to use for reproducing the baselines.
You may modify this at runtime by specifying the option ``--groups`` on the
command-line of ``spoof.py`` or using the keyword ``groups`` on a
configuration file that is loaded **after** this configuration resource.
"""
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from ..preprocessor import FaceCropAlign
from bob.pad.face.preprocessor import FaceCropAlign
from bob.bio.video.preprocessor import Wrapper
......@@ -42,3 +42,25 @@ _image_preprocessor = FaceCropAlign(face_size=FACE_SIZE,
rgb_face_detector_mtcnn = Wrapper(preprocessor = _image_preprocessor,
frame_selector = _frame_selector)
# =======================================================================================
FACE_SIZE = 64 # The size of the resulting face
RGB_OUTPUT_FLAG = False # Gray-scale output
USE_FACE_ALIGNMENT = True # detect face landmarks locally and align the face
MAX_IMAGE_SIZE = 1920 # the largest possible dimension of the input image
FACE_DETECTION_METHOD = "mtcnn" # face landmarks detection method
MIN_FACE_SIZE = 50 # skip faces smaller than this value
NORMALIZATION_FUNCTION = None # no normalization
NORMALIZATION_FUNCTION_KWARGS = 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,
normalization_function=NORMALIZATION_FUNCTION,
normalization_function_kwargs=NORMALIZATION_FUNCTION_KWARGS)
bw_face_detect_mtcnn = Wrapper(preprocessor=_image_preprocessor,
frame_selector=_frame_selector)
#!/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
This diff is collapsed.
......@@ -5,6 +5,7 @@ from .msu_mfsd import MsuMfsdPadDatabase
from .aggregated_db import AggregatedDbPadDatabase
from .mifs import MIFSPadDatabase
from .batl import BatlPadDatabase
from .celeb_a import CELEBAPadDatabase
# gets sphinx autodoc done right - don't remove it
......@@ -31,6 +32,7 @@ __appropriate__(
AggregatedDbPadDatabase,
MIFSPadDatabase,
BatlPadDatabase,
CELEBAPadDatabase
)
__all__ = [_ for _ in dir() if not _.startswith('_')]
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#!/usr/bin/env python
#==============================================================================
import bob.bio.video # Used in CELEBAPadFile class
import bob.io.base
import numpy as np
from bob.pad.base.database import PadFile # Used in ReplayPadFile class
from bob.pad.base.database import FileListPadDatabase
#==============================================================================
class CELEBAPadFile(PadFile):
"""
A high level implementation of the File class for the CELEBA database.
"""
def __init__(self, client_id, path, attack_type=None, file_id=None):
super(CELEBAPadFile, self).__init__(client_id, path, attack_type, file_id)
# ==========================================================================
def load(self, directory=None, extension=None,
frame_selector=bob.bio.video.FrameSelector(selection_style='all')):
"""
Overridden version of the load method defined in the ``PadFile``.
**Parameters:**
``directory`` : :py:class:`str`
String containing the path to the CELEBA database.
Default: None
``extension`` : :py:class:`str`
Extension of the video files in the CELEBA database.
Default: None
``frame_selector`` : :any:`bob.bio.video.FrameSelector`, optional
Specifying the frames to be selected.
**Returns:**
``video_data`` : FrameContainer
Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer``
for further details.
"""
path = self.make_path(directory=directory, extension=extension) # path to the file
data = bob.io.base.load(path)
data = np.expand_dims(data, axis=0) # upgrade to 4D (video)
video_data = frame_selector(data) # video data
return video_data # video data
#==============================================================================
class CELEBAPadDatabase(FileListPadDatabase):
"""
A high level implementation of the Database class for the CELEBA database.
"""
def __init__(
self,
protocol='grandtest', # grandtest is the default protocol for this database
original_directory='[YOUR_CELEB_A_DATABASE_DIRECTORY]',
original_extension='.jpg',
**kwargs):
from pkg_resources import resource_filename
folder = resource_filename(__name__, '../lists/celeb_a/')
super(CELEBAPadDatabase, self).__init__(folder, 'celeb_a',
pad_file_class=CELEBAPadFile,
protocol = protocol,
original_directory=original_directory,
original_extension=original_extension)
#==========================================================================
def annotations(self, f):
"""
Return annotations for a given file object ``f``, which is an instance
of ``CELEBAPadFile``.
**Parameters:**
``f`` : :py:class:`object`
An instance of ``CELEBAPadFile`` defined above.
**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.
"""
annotations = {} # dictionary to return
return annotations
\ No newline at end of file
132/132987.jpg 132/132987 attack
132/132959.jpg 132/132959 attack
132/132933.jpg 132/132933 attack
132/132932.jpg 132/132932 attack
This diff is collapsed.
202/202019.jpg 202/202019 attack
202/202054.jpg 202/202054 attack
202/202597.jpg 202/202597 attack
202/202588.jpg 202/202588 attack
This diff is collapsed.
075/075052.jpg 075/075052 attack
075/075079.jpg 075/075079 attack
075/075942.jpg 075/075942 attack
075/075974.jpg 075/075974 attack
This diff is collapsed.
#!/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 data loading and quality assessment functionality are defined in the
configuration file. 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 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="Only -v level is currently supported.")
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.
5. Data is loaded and quality is computed for each data sample.
6. Good quality samples are coppied to <save_folder>/good_quality_data
folder, low quality to <save_folder>/low_quality_data.
NOTE:
The config file used in this function 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.
"""
# Parse the command line arguments:
data_folder, save_folder, file_extension, relative_mod_name, config_group, verbosity = \