#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# @author: Manuel Guenther <Manuel.Guenther@idiap.ch>
# @date: Thu May 24 10:41:42 CEST 2012
#
# Copyright (C) 2011-2012 Idiap Research Institute, Martigny, Switzerland
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bob.ip.base
import numpy
import math
from .. import utils
from .Preprocessor import Preprocessor
[docs]class FaceCrop (Preprocessor):
"""Crops the face according to the eye positions"""
def __init__(
self,
cropped_image_size = None, # resolution of the cropped image, in order (HEIGHT,WIDTH); if not given, no face cropping will be performed
cropped_positions = None, # dictionary of the cropped positions, usually: {'reye':(RIGHT_EYE_Y, RIGHT_EYE_X) , 'leye':(LEFT_EYE_Y, LEFT_EYE_X)}
fixed_positions = None, # dictionary of FIXED positions in the original image; if specified, annotations from the database will be ignored
color_channel = 'gray', # the color channel to extract from colored images, if colored images are in the database
offset = 0, # if your feature extractor requires a specific offset, you might want to specify it here
supported_annotations = None, # The set of annotations that this cropper excepts; (('reye', 'leye'), ('eye', 'mouth')) by default
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
**kwargs # parameters to be written in the __str__ method
):
"""Parameters of the constructor of this preprocessor:
cropped_image_size
The size of the resulting cropped images.
cropped_positions
The coordinates in the cropped image, where the annotated points should be put to.
This parameter is a dictionary with usually two elements, e.g., {'reye':(RIGHT_EYE_Y, RIGHT_EYE_X) , 'leye':(LEFT_EYE_Y, LEFT_EYE_X)}.
fixed_positions
If specified, ignore the annotations from the database and use these fixed positions throughout.
color_channel
In case of color images, which color channel should be used?
offset
An offset for feature extraction; will affect the cropped_image_size and the cropped_image_positions
supported_annotations
A list of supported pairs of annotations.
If the database has different names for the annotations, they should be put here (used mainly for testing purposes).
If not specified, (('reye', 'leye'), ('eye', 'mouth')) is used.
mask_sigma
Fill the area outside of image boundaries with random pixels from the border, by adding noise to the pixel values.
To disable extrapolation, set this value to None.
To disable adding random noise, set it to a negative value or 0.
mask_neighbors
The number of neighbors used during mask extrapolation.
See :py:func:`bob.ip.base.extrapolate_mask` for details.
mask_seed
The random seed to apply for mask extrapolation.
.. warning::
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.
"""
# call base class constructor
Preprocessor.__init__(
self,
cropped_image_size = cropped_image_size,
cropped_positions = cropped_positions,
fixed_positions = fixed_positions,
color_channel = color_channel,
offset = offset,
supported_annotations = supported_annotations,
mask_sigma = mask_sigma,
mask_neighbors = mask_neighbors,
mask_seed = mask_seed,
**kwargs
)
self.m_cropped_image_size = cropped_image_size
self.m_cropped_positions = cropped_positions
self.m_fixed_postions = fixed_positions
self.m_color_channel = color_channel
self.m_offset = offset
self.m_supported_annotations = supported_annotations if supported_annotations is not None else (('reye', 'leye'), ('eye', 'mouth'))
self.m_mask_sigma = mask_sigma
self.m_mask_neighbors = mask_neighbors
self.m_mask_rng = bob.core.random.mt19937(mask_seed) if mask_seed is not None else bob.core.random.mt19937()
if fixed_positions:
assert len(fixed_positions) == 2
# define our set of functions
self.m_croppers = {}
self.m_original_masks = {}
self.m_perform_image_cropping = self.m_cropped_image_size is not None
if self.m_perform_image_cropping:
# define the preprocessed image once
self.m_cropped_image = numpy.ndarray((self.m_cropped_image_size[0] + 2 * self.m_offset, self.m_cropped_image_size[1] + 2 * self.m_offset), numpy.float64)
# define the mask; this mask can be used in derived classes to further process the image
self.m_cropped_mask = numpy.ndarray(self.m_cropped_image.shape, numpy.bool)
def __cropper__(self, pair):
key = (pair[0] + "+" + pair[1])
if pair[0] not in self.m_cropped_positions or pair[1] not in self.m_cropped_positions:
import ipdb; ipdb.set_trace()
raise KeyError("The given positions '%s' or '%s' are not found in the list of cropped positions: %s" % (pair[0], pair[1], self.m_cropped_positions))
if key not in self.m_croppers:
# generate cropper on the fly
cropper = bob.ip.base.FaceEyesNorm(
crop_size = (int(self.m_cropped_image_size[0] + 2 * self.m_offset), # cropped image height
int(self.m_cropped_image_size[1] + 2 * self.m_offset)), # cropped image width
right_eye = (self.m_cropped_positions[pair[0]][0] + self.m_offset, # Y of first position (usually: right eye)
self.m_cropped_positions[pair[0]][1] + self.m_offset), # X of first position (usually: right eye)
left_eye = (self.m_cropped_positions[pair[1]][0] + self.m_offset, # Y of second position (usually: left eye)
self.m_cropped_positions[pair[1]][1] + self.m_offset) # X of second position (usually: left eye)
)
self.m_croppers[key] = cropper
# return cropper for this type
return self.m_croppers[key]
def __mask__(self, shape):
key = (str(shape[0]) + "x" + str(shape[1]))
if key not in self.m_original_masks:
# generate mask for the given image resolution
mask = numpy.ndarray(shape, numpy.bool)
mask.fill(True)
self.m_original_masks[key] = mask
# return the stored mask for the given resolution
return self.m_original_masks[key]
[docs] def crop_face(self, image, annotations):
"""Executes the face cropping on the given image and returns the cropped version of it"""
# convert to the desired color channel
image = utils.gray_channel(image, self.m_color_channel)
if not self.m_perform_image_cropping:
return image
keys = None
# check, which type of annotations we have
if self.m_fixed_postions:
# get the cropper for the fixed positions
keys = self.m_fixed_postions.keys()
# take the fixed annotations
annotations = self.m_fixed_postions
if annotations:
# get cropper for given annotations
for pair in self.m_supported_annotations:
if pair[0] in annotations and pair[1] in annotations:
keys = pair
if keys is None:
raise ValueError("The given annoations '%s' did not contain the supported annotations '%s'" % (annotations, self.m_supported_annotations))
else:
# No annotations and no fixed positions: don't do any processing
return image.astype(numpy.float64)
cropper = self.__cropper__(keys)
mask = self.__mask__(image.shape)
# assure that the image is initialized with 0
self.m_cropped_image[:] = 0.
# perform the cropping
cropper(
image, # input image
mask, # full input mask
self.m_cropped_image, # cropped image
self.m_cropped_mask, # cropped mask
right_eye = annotations[keys[0]], # position of first annotation, usually left eye
left_eye = annotations[keys[1]] # position of second annotation, usually right eye
)
if self.m_mask_sigma is not None:
# extrapolate the mask so that pixels outside of the image original image region are filled with border pixels
bob.ip.base.extrapolate_mask(self.m_cropped_mask, self.m_cropped_image, self.m_mask_sigma, self.m_mask_neighbors, self.m_mask_rng)
return self.m_cropped_image
def __call__(self, image, annotations = None):
"""Reads the input image, normalizes it according to the eye positions, and writes the resulting image"""
return self.crop_face(image, annotations)