From 8866cdc3ce29cac18bcafdc427382ecea7cb0e2f Mon Sep 17 00:00:00 2001
From: Olegs NIKISINS <onikisins@italix03.idiap.ch>
Date: Wed, 31 May 2017 10:28:27 +0200
Subject: [PATCH] Added preprocessor identical to Ivana's paper

---
 .../face/algorithm/VideoSvmPadAlgorithm.py    |  14 ++-
 .../algorithm/video_svm_pad_algorithm.py      |  43 +++++--
 .../config/preprocessor/video_face_crop.py    |  46 ++++++-
 bob/pad/face/preprocessor/ImageFaceCrop.py    | 116 ++++++++++++++++++
 bob/pad/face/preprocessor/VideoFaceCrop.py    | 107 +++++++++++++---
 bob/pad/face/preprocessor/__init__.py         |   2 +
 setup.py                                      |   4 +
 7 files changed, 295 insertions(+), 37 deletions(-)
 create mode 100644 bob/pad/face/preprocessor/ImageFaceCrop.py

diff --git a/bob/pad/face/algorithm/VideoSvmPadAlgorithm.py b/bob/pad/face/algorithm/VideoSvmPadAlgorithm.py
index ef921338..e99f7572 100644
--- a/bob/pad/face/algorithm/VideoSvmPadAlgorithm.py
+++ b/bob/pad/face/algorithm/VideoSvmPadAlgorithm.py
@@ -57,7 +57,7 @@ class VideoSvmPadAlgorithm(Algorithm):
     ``mean_std_norm_flag`` : :py:class:`bool`
         Perform mean-std normalization of data if set to True. Default: False.
 
-    ``frame_level_scores`` : :py:class:`bool`
+    ``frame_level_scores_flag`` : :py:class:`bool`
         Return scores for each frame individually if True. Otherwise, return a
         single score per video. Default: False.
     """
@@ -68,7 +68,7 @@ class VideoSvmPadAlgorithm(Algorithm):
                  n_samples = 10000,
                  trainer_grid_search_params = { 'cost': [2**p for p in range(-5, 16, 2)], 'gamma': [2**p for p in range(-15, 4, 2)]},
                  mean_std_norm_flag = False,
-                 frame_level_scores = False):
+                 frame_level_scores_flag = False):
 
 
         Algorithm.__init__(self,
@@ -77,7 +77,7 @@ class VideoSvmPadAlgorithm(Algorithm):
                            n_samples = n_samples,
                            trainer_grid_search_params = trainer_grid_search_params,
                            mean_std_norm_flag = mean_std_norm_flag,
-                           frame_level_scores = frame_level_scores,
+                           frame_level_scores_flag = frame_level_scores_flag,
                            performs_projection=True,
                            requires_projector_training=True)
 
@@ -86,7 +86,7 @@ class VideoSvmPadAlgorithm(Algorithm):
         self.n_samples = n_samples
         self.trainer_grid_search_params = trainer_grid_search_params
         self.mean_std_norm_flag = mean_std_norm_flag
-        self.frame_level_scores = frame_level_scores
+        self.frame_level_scores_flag = frame_level_scores_flag
         self.machine = None
 
 
@@ -668,6 +668,8 @@ class VideoSvmPadAlgorithm(Algorithm):
 
         probabilities = self.machine.predict_class_and_probabilities(features_array)[1]
 
+#        probabilities = self.machine.predict_class_and_scores(features_array)[1]
+
         return probabilities
 
 
@@ -691,7 +693,7 @@ class VideoSvmPadAlgorithm(Algorithm):
             A probability of a sample being a real class.
         """
 
-        if self.frame_level_scores:
+        if self.frame_level_scores_flag:
 
             score = toscore[:,0] # here score is a list containing scores for each frame
 
@@ -720,7 +722,7 @@ class VideoSvmPadAlgorithm(Algorithm):
 
 #        import ipdb; ipdb.set_trace()
 
-        if self.frame_level_scores:
+        if self.frame_level_scores_flag:
 
             list_of_scores = self.score(toscore)
 
diff --git a/bob/pad/face/config/algorithm/video_svm_pad_algorithm.py b/bob/pad/face/config/algorithm/video_svm_pad_algorithm.py
index 6fe78da7..cd6fc1ad 100644
--- a/bob/pad/face/config/algorithm/video_svm_pad_algorithm.py
+++ b/bob/pad/face/config/algorithm/video_svm_pad_algorithm.py
@@ -9,20 +9,41 @@ from bob.pad.face.algorithm import VideoSvmPadAlgorithm
 machine_type = 'C_SVC'
 kernel_type = 'RBF'
 n_samples = 10000
-trainer_grid_search_params = { 'cost': [2**p for p in range(-5, 16, 2)], 'gamma': [2**p for p in range(-15, 4, 2)]}
-# trainer_grid_search_params = { 'cost': [32768], 'gamma': [2] }
+# trainer_grid_search_params = {'cost': [2**p for p in range(-5, 16, 2)], 'gamma': [2**p for p in range(-15, 4, 2)]}
+trainer_grid_search_params = {'cost': [2**p for p in range(-3, 14, 2)], 'gamma': [2**p for p in range(-15, 0, 2)]}
 mean_std_norm_flag = False
+frame_level_scores_flag = False # one score per video(!) in this case
 
 video_svm_pad_algorithm_10k_grid = VideoSvmPadAlgorithm(machine_type = machine_type,
-                                               kernel_type = kernel_type,
-                                               n_samples = n_samples,
-                                               trainer_grid_search_params = trainer_grid_search_params,
-                                               mean_std_norm_flag = mean_std_norm_flag)
+                                                        kernel_type = kernel_type,
+                                                        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)
 
-mean_std_norm_flag = True
+mean_std_norm_flag = True # enable mean-std normalization
 
 video_svm_pad_algorithm_10k_grid_mean_std = VideoSvmPadAlgorithm(machine_type = machine_type,
-                                               kernel_type = kernel_type,
-                                               n_samples = n_samples,
-                                               trainer_grid_search_params = trainer_grid_search_params,
-                                               mean_std_norm_flag = mean_std_norm_flag)
\ No newline at end of file
+                                                                 kernel_type = kernel_type,
+                                                                 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 = True # one score per frame(!) in this case
+
+video_svm_pad_algorithm_10k_grid_mean_std_frame_level = VideoSvmPadAlgorithm(machine_type = machine_type,
+                                                                             kernel_type = kernel_type,
+                                                                             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)
+
+trainer_grid_search_params = {'cost': [1], 'gamma': [0]} # set the default LibSVM parameters
+
+video_svm_pad_algorithm_default_svm_param_mean_std_frame_level = VideoSvmPadAlgorithm(machine_type = machine_type,
+                                                                                      kernel_type = kernel_type,
+                                                                                      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)
\ No newline at end of file
diff --git a/bob/pad/face/config/preprocessor/video_face_crop.py b/bob/pad/face/config/preprocessor/video_face_crop.py
index 61eaf725..c4d00efc 100644
--- a/bob/pad/face/config/preprocessor/video_face_crop.py
+++ b/bob/pad/face/config/preprocessor/video_face_crop.py
@@ -12,12 +12,46 @@ fixed_positions = None
 mask_sigma = None             # The sigma for random values areas outside image
 mask_neighbors = 5            # The number of neighbors to consider while extrapolating
 mask_seed = None              # The seed for generating random values during extrapolation
+check_face_size_flag = False  # Don't check the size of the face
+min_face_size = 50
+use_local_cropper_flag = False # Use the cropper of bob.bio.face
 color_channel = 'gray'        # Convert image to gray-scale format
 
 video_face_crop_preproc_64_64 = VideoFaceCrop(cropped_image_size = cropped_image_size,
-                                                cropped_positions = cropped_positions,
-                                                fixed_positions = fixed_positions,
-                                                mask_sigma = mask_sigma,
-                                                mask_neighbors = mask_neighbors,
-                                                mask_seed = None,
-                                                color_channel = color_channel)
+                                              cropped_positions = cropped_positions,
+                                              fixed_positions = fixed_positions,
+                                              mask_sigma = mask_sigma,
+                                              mask_neighbors = mask_neighbors,
+                                              mask_seed = None,
+                                              check_face_size_flag = check_face_size_flag,
+                                              min_face_size = min_face_size,
+                                              use_local_cropper_flag = use_local_cropper_flag,
+                                              color_channel = color_channel)
+
+check_face_size_flag = True  # Check the size of the face
+min_face_size = 50
+
+video_face_crop_preproc_64_64_face_50 = VideoFaceCrop(cropped_image_size = cropped_image_size,
+                                                      cropped_positions = cropped_positions,
+                                                      fixed_positions = fixed_positions,
+                                                      mask_sigma = mask_sigma,
+                                                      mask_neighbors = mask_neighbors,
+                                                      mask_seed = None,
+                                                      check_face_size_flag = check_face_size_flag,
+                                                      min_face_size = min_face_size,
+                                                      use_local_cropper_flag = use_local_cropper_flag,
+                                                      color_channel = color_channel)
+
+
+use_local_cropper_flag = True # Use the local face cropping class (identical to Ivana's paper)
+
+video_face_crop_preproc_64_64_face_50_local_cropper = VideoFaceCrop(cropped_image_size = cropped_image_size,
+                                                                    cropped_positions = cropped_positions,
+                                                                    fixed_positions = fixed_positions,
+                                                                    mask_sigma = mask_sigma,
+                                                                    mask_neighbors = mask_neighbors,
+                                                                    mask_seed = None,
+                                                                    check_face_size_flag = check_face_size_flag,
+                                                                    min_face_size = min_face_size,
+                                                                    use_local_cropper_flag = use_local_cropper_flag,
+                                                                    color_channel = color_channel)
\ No newline at end of file
diff --git a/bob/pad/face/preprocessor/ImageFaceCrop.py b/bob/pad/face/preprocessor/ImageFaceCrop.py
new file mode 100644
index 00000000..a59c362a
--- /dev/null
+++ b/bob/pad/face/preprocessor/ImageFaceCrop.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+"""
+Created on Tue May 30 14:11:16 2017
+
+@author: Olegs Nikisins
+"""
+
+#==============================================================================
+# Import what is needed here:
+
+from bob.bio.base.preprocessor import Preprocessor
+
+import numpy as np
+
+import bob.ip.color
+
+import bob.ip.base
+
+#==============================================================================
+# Main body:
+
+class ImageFaceCrop(Preprocessor):
+    """
+    This class crops the face in the input image given annotations defining
+    the face bounding box. The size of the face is also normalized to the
+    pre-defined dimensions. If input image is RGB it is first converted to the
+    gray-scale format.
+    The algorithm is identical to the following paper:
+    "On the Effectiveness of Local Binary Patterns in Face Anti-spoofing"
+
+    **Parameters:**
+
+    ``face_size`` : :py:class:`int`
+        The size of the face after normalization.
+    """
+    #==========================================================================
+    def __init__(self, face_size):
+
+
+        Preprocessor.__init__(self,
+                              face_size = face_size)
+
+        self.face_size = face_size
+
+
+    #==========================================================================
+    def normalize_image_size(self, image, annotations, face_size):
+        """
+        This function crops the face in the input image given annotations defining
+        the face bounding box. The size of the face is also normalized to the
+        pre-defined dimensions. If input image is RGB it is first converted to the
+        gray-scale format.
+        The algorithm is identical to the following paper:
+        "On the Effectiveness of Local Binary Patterns in Face Anti-spoofing"
+
+        **Parameters:**
+
+        ``image`` : 2D or 3D :py:class:`numpy.ndarray`
+            Input image (RGB or gray-scale).
+
+        ``annotations`` : :py:class:`dict`
+            A dictionary containing annotations of the face bounding box.
+            Dictionary must be as follows ``{'topleft': (row, col), 'bottomright': (row, col)}``
+
+        ``face_size`` : :py:class:`int`
+            The size of the face after normalization.
+
+        **Returns:**
+
+        ``normbbx`` : 2D :py:class:`numpy.ndarray`
+            An image of the cropped face of the size (face_size, face_size).
+        """
+
+        if len(image.shape) == 3:
+
+            image = bob.ip.color.rgb_to_gray(image)
+
+        cutframe = image[annotations['topleft'][0]:annotations['bottomright'][0],
+                         annotations['topleft'][1]:annotations['bottomright'][1]]
+
+        tempbbx = np.ndarray((face_size, face_size), 'float64')
+        normbbx = np.ndarray((face_size, face_size), 'uint8')
+        bob.ip.base.scale(cutframe, tempbbx) # normalization
+        tempbbx_ = tempbbx + 0.5
+        tempbbx_ = np.floor(tempbbx_)
+        normbbx = np.cast['uint8'](tempbbx_)
+
+        return normbbx
+
+
+    #==========================================================================
+    def __call__(self, image, annotations):
+        """
+        Call the ``normalize_image_size()`` method of this class.
+
+        **Parameters:**
+
+        ``image`` : 2D or 3D :py:class:`numpy.ndarray`
+            Input image (RGB or gray-scale).
+
+        ``annotations`` : :py:class:`dict`
+            A dictionary containing annotations of the face bounding box.
+            Dictionary must be as follows ``{'topleft': (row, col), 'bottomright': (row, col)}``
+
+        **Returns:**
+
+        ``norm_face_image`` : 2D :py:class:`numpy.ndarray`
+            An image of the cropped face of the size (self.face_size, self.face_size).
+        """
+
+        norm_face_image = self.normalize_image_size(image, annotations, self.face_size)
+
+        return norm_face_image
+
+
diff --git a/bob/pad/face/preprocessor/VideoFaceCrop.py b/bob/pad/face/preprocessor/VideoFaceCrop.py
index 2ef76572..48d50bb3 100644
--- a/bob/pad/face/preprocessor/VideoFaceCrop.py
+++ b/bob/pad/face/preprocessor/VideoFaceCrop.py
@@ -14,6 +14,10 @@ from bob.bio.face.preprocessor import FaceCrop
 
 import bob.bio.video
 
+import numpy as np
+
+from bob.pad.face.preprocessor.ImageFaceCrop import ImageFaceCrop
+
 #==============================================================================
 # Main body:
 
@@ -54,9 +58,21 @@ class VideoFaceCrop(Preprocessor, object):
           When run in parallel, the same random seed will be applied to all parallel processes.
           Hence, results of parallel execution will differ from the results in serial execution.
 
+    ``check_face_size_flag`` : :py:class:`bool`
+        If True, only return the frames containing faces of the size above the
+        specified threshold ``min_face_size``. Default: False.
+
+    ``min_face_size`` : :py:class:`int`
+        The minimal size of the face in pixels. Only valid when ``check_face_size_flag``
+        is set to True. Default: 50.
+
+    ``use_local_cropper_flag`` : :py:class:`bool`
+        If True, use the local ImageFaceCrop class to crop faces in the frames.
+        Otherwise, the FaceCrop preprocessor from bob.bio.face is used.
+        Default: False.
+
     ``kwargs``
         Remaining keyword parameters passed to the :py:class:`Base` constructor, such as ``color_channel`` or ``dtype``.
-
     """
 
     #==========================================================================
@@ -67,15 +83,21 @@ class VideoFaceCrop(Preprocessor, object):
                  mask_sigma = None,
                  mask_neighbors = 5,
                  mask_seed = None,
+                 check_face_size_flag = False,
+                 min_face_size = 50,
+                 use_local_cropper_flag = False,
                  **kwargs):
 
         super(VideoFaceCrop, self).__init__(cropped_image_size = cropped_image_size,
-                                             cropped_positions = cropped_positions,
-                                             fixed_positions = fixed_positions,
-                                             mask_sigma = mask_sigma,
-                                             mask_neighbors = mask_neighbors,
-                                             mask_seed = mask_seed,
-                                             **kwargs)
+                                            cropped_positions = cropped_positions,
+                                            fixed_positions = fixed_positions,
+                                            mask_sigma = mask_sigma,
+                                            mask_neighbors = mask_neighbors,
+                                            mask_seed = mask_seed,
+                                            check_face_size_flag = check_face_size_flag,
+                                            min_face_size = min_face_size,
+                                            use_local_cropper_flag = use_local_cropper_flag,
+                                            **kwargs)
 
         self.cropped_image_size = cropped_image_size
         self.cropped_positions = cropped_positions
@@ -83,22 +105,75 @@ class VideoFaceCrop(Preprocessor, object):
         self.mask_sigma = mask_sigma
         self.mask_neighbors = mask_neighbors
         self.mask_seed = mask_seed
+        self.check_face_size_flag = check_face_size_flag
+        self.min_face_size = min_face_size
+        self.use_local_cropper_flag = use_local_cropper_flag
 
         # Save also the data stored in the kwargs:
         for (k, v) in kwargs.items():
             setattr(self, k, v)
 
-        preprocessor = FaceCrop(cropped_image_size = cropped_image_size,
-                                cropped_positions = cropped_positions,
-                                fixed_positions = fixed_positions,
-                                mask_sigma = mask_sigma,
-                                mask_neighbors = mask_neighbors,
-                                mask_seed = mask_seed,
-                                **kwargs)
+        if self.use_local_cropper_flag:
+
+            preprocessor = ImageFaceCrop(face_size = self.cropped_image_size[0])
+
+        else:
+
+            preprocessor = FaceCrop(cropped_image_size = self.cropped_image_size,
+                                    cropped_positions = self.cropped_positions,
+                                    fixed_positions = self.fixed_positions,
+                                    mask_sigma = self.mask_sigma,
+                                    mask_neighbors = self.mask_neighbors,
+                                    mask_seed = self.mask_seed,
+                                    **kwargs)
 
         self.video_preprocessor = bob.bio.video.preprocessor.Wrapper(preprocessor)
 
 
+    #==========================================================================
+    def check_face_size(self, frame_container, annotations, min_face_size):
+        """
+        Return the FrameContainer containing the frames with faces of the
+        size overcoming the specified threshold.
+
+        **Parameters:**
+
+        ``frame_container`` : FrameContainer
+            Video data stored in the FrameContainer, see ``bob.bio.video.utils.FrameContainer``
+            for further details.
+
+        ``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.
+
+        ``min_face_size`` : :py:class:`int`
+            The minimal size of the face in pixels.
+        """
+
+        cleaned_frame_container = bob.bio.video.FrameContainer() # initialize the FrameContainer
+
+        selected_frame_idx = 0
+
+        for idx in range(0, len(annotations)): # idx - frame index
+
+            frame_annotations = annotations[str(idx)] # annotations for particular frame
+
+            # size of current face
+            face_size = np.min(np.array(frame_annotations['bottomright']) - np.array(frame_annotations['topleft']))
+
+            if face_size >= min_face_size: # check if face size is above the threshold
+
+                selected_frame = frame_container[idx][1] # get current frame
+
+                cleaned_frame_container.add(selected_frame_idx, selected_frame) # add current frame to FrameContainer
+
+                selected_frame_idx = selected_frame_idx + 1
+
+        return cleaned_frame_container
+
+
     #==========================================================================
     def __call__(self, frames, annotations):
         """
@@ -124,6 +199,10 @@ class VideoFaceCrop(Preprocessor, object):
 
         preprocessed_video = self.video_preprocessor(frames = frames, annotations = annotations)
 
+        if self.check_face_size_flag:
+
+            preprocessed_video = self.check_face_size(preprocessed_video, annotations, self.min_face_size)
+
         return preprocessed_video
 
 
diff --git a/bob/pad/face/preprocessor/__init__.py b/bob/pad/face/preprocessor/__init__.py
index 60586054..9f59b552 100644
--- a/bob/pad/face/preprocessor/__init__.py
+++ b/bob/pad/face/preprocessor/__init__.py
@@ -1,4 +1,5 @@
 from .VideoFaceCrop import VideoFaceCrop
+from .ImageFaceCrop import ImageFaceCrop
 
 
 def __appropriate__(*args):
@@ -21,5 +22,6 @@ def __appropriate__(*args):
 
 __appropriate__(
     VideoFaceCrop,
+    ImageFaceCrop,
 )
 __all__ = [_ for _ in dir() if not _.startswith('_')]
diff --git a/setup.py b/setup.py
index 791a68e8..8c0cf3dc 100644
--- a/setup.py
+++ b/setup.py
@@ -101,6 +101,8 @@ setup(
         # registered preprocessors:
         'bob.pad.preprocessor': [
             'video-face-crop-preproc-64 = bob.pad.face.config.preprocessor.video_face_crop:video_face_crop_preproc_64_64',
+            'video-face-crop-preproc-64-face-50 = bob.pad.face.config.preprocessor.video_face_crop:video_face_crop_preproc_64_64_face_50',
+            'video-face-crop-preproc-64-face-50-local-cropper = bob.pad.face.config.preprocessor.video_face_crop:video_face_crop_preproc_64_64_face_50_local_cropper',
             ],
 
         # registered preprocessors:
@@ -112,6 +114,8 @@ setup(
         'bob.pad.algorithm': [
             'video-svm-pad-algorithm-10k-grid = bob.pad.face.config.algorithm.video_svm_pad_algorithm:video_svm_pad_algorithm_10k_grid',
             'video-svm-pad-algorithm-10k-grid-mean-std = bob.pad.face.config.algorithm.video_svm_pad_algorithm:video_svm_pad_algorithm_10k_grid_mean_std',
+            'video-svm-pad-algorithm-10k-grid-mean-std-frame-level = bob.pad.face.config.algorithm.video_svm_pad_algorithm:video_svm_pad_algorithm_10k_grid_mean_std_frame_level',
+            'video-svm-pad-algorithm-default-svm-param-mean-std-frame-level = bob.pad.face.config.algorithm.video_svm_pad_algorithm:video_svm_pad_algorithm_default_svm_param_mean_std_frame_level',
             ],
 
         # registered grid configurations:
-- 
GitLab