Commit d77ef348 authored by Manuel Günther's avatar Manuel Günther
Browse files

Added extractors from FaceRecLib

parent 97ef6a7c
from . import preprocessor
from . import extractor
from . import algorithm
from . import test
......
#!/usr/bin/env python
import bob.bio.face
extractor = bob.bio.face.extractor.DCTBlocks(
block_size = 12,
block_overlap = 11,
number_of_dct_coefficients = 45
)
#!/usr/bin/env python
import bob.bio.face
# compute eigenfaces using the training database
extractor = bob.bio.face.extractor.Eigenface(
subspace_dimension = 100
)
#!/usr/bin/env python
import bob.bio.base
import bob.bio.face
import math
# load the face cropping parameters
cropper = bob.bio.base.load_resource("face-crop-eyes", "preprocessor")
extractor = bob.bio.face.extractor.GridGraph(
# Gabor parameters
gabor_sigma = math.sqrt(2.) * math.pi,
# what kind of information to extract
normalize_gabor_jets = True,
# setup of the fixed grid
node_distance = (4, 4),
first_node = (6, 6),
image_resolution = cropper.cropped_image_size
)
#!/usr/bin/env python
import bob.bio.face
import math
# feature extraction
extractor = bob.bio.face.extractor.LGBPHS(
# block setup
block_size = 10,
block_overlap = 4,
# Gabor parameters
gabor_sigma = math.sqrt(2.) * math.pi,
# LBP setup (we use the defaults)
# histogram setup
sparse_histogram = True
)
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Laurent El Shafey <Laurent.El-Shafey@idiap.ch>
"""Features for face recognition"""
import bob.ip.base
import numpy
from bob.bio.base.extractor import Extractor
class DCTBlocks (Extractor):
"""Extracts DCT blocks"""
def __init__(
self,
block_size = 12, # 1 or two parameters for block size
block_overlap = 11, # 1 or two parameters for block overlap
number_of_dct_coefficients = 45,
normalize_blocks = True,
normalize_dcts = True,
auto_reduce_coefficients = False
):
# call base class constructor
Extractor.__init__(
self,
block_size = block_size,
block_overlap = block_overlap,
number_of_dct_coefficients = number_of_dct_coefficients,
normalize_blocks = normalize_blocks,
normalize_dcts = normalize_dcts,
auto_reduce_coefficients = auto_reduce_coefficients
)
# block parameters
block_size = block_size if isinstance(block_size, (tuple, list)) else (block_size, block_size)
block_overlap = block_overlap if isinstance(block_overlap, (tuple, list)) else (block_overlap, block_overlap)
if block_size[0] < block_overlap[0] or block_size[1] < block_overlap[1]:
raise ValueError("The overlap '%s' is bigger than the block size '%s'. This won't work. Please check your setup!"%(block_overlap, block_size))
if block_size[0] * block_size[1] <= number_of_dct_coefficients:
if auto_reduce_coefficients:
number_of_dct_coefficients = block_size[0] * block_size[1] - 1
else:
raise ValueError("You selected more coefficients %d than your blocks have %d. This won't work. Please check your setup!"%(number_of_dct_coefficients, block_size[0] * block_size[1]))
self.dct_features = bob.ip.base.DCTFeatures(number_of_dct_coefficients, block_size, block_overlap, normalize_blocks, normalize_dcts)
def __call__(self, image):
"""Computes and returns DCT blocks for the given input image"""
assert isinstance(image, numpy.ndarray)
assert image.ndim == 2
assert image.dtype == numpy.float64
# Computes DCT features
return self.dct_features(image)
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Manuel Guenther <Manuel.Guenther@idiap.ch>
import numpy
import bob.learn.linear
import bob.io.base
from bob.bio.base.extractor import Extractor
import logging
logger = logging.getLogger("bob.bio.face")
class Eigenface (Extractor):
"""Extracts grid graphs from the images"""
def __init__(self, subspace_dimension):
# We have to register that this function will need a training step
Extractor.__init__(self, requires_training = True, subspace_dimension = subspace_dimension)
self.subspace_dimension = subspace_dimension
def _check_data(self, data):
assert isinstance(data, numpy.ndarray)
assert data.ndim == 2
assert data.dtype == numpy.float64
def train(self, image_list, extractor_file):
"""Trains the eigenface extractor using the given list of training images"""
[self._check_data(image) for image in image_list]
# Initializes an array for the data
data = numpy.vstack([image.flatten() for image in image_list])
logger.info(" -> Training LinearMachine using PCA (SVD)")
t = bob.learn.linear.PCATrainer()
self.machine, __eig_vals = t.train(data)
# Machine: get shape, then resize
self.machine.resize(self.machine.shape[0], self.subspace_dimension)
self.machine.save(bob.io.base.HDF5File(extractor_file, "w"))
def load(self, extractor_file):
# read PCA projector
self.machine = bob.learn.linear.Machine(bob.io.base.HDF5File(extractor_file))
def __call__(self, image):
"""Projects the data using the stored covariance matrix"""
self._check_data(image)
# Projects the data
return self.machine(image.flatten())
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Manuel Guenther <Manuel.Guenther@idiap.ch>
import bob.ip.gabor
import bob.io.base
import numpy
import math
from bob.bio.base.extractor import Extractor
class GridGraph (Extractor):
"""Extracts grid graphs from the images"""
def __init__(
self,
# Gabor parameters
gabor_directions = 8,
gabor_scales = 5,
gabor_sigma = 2. * math.pi,
gabor_maximum_frequency = math.pi / 2.,
gabor_frequency_step = math.sqrt(.5),
gabor_power_of_k = 0,
gabor_dc_free = True,
# what kind of information to extract
normalize_gabor_jets = True,
# setup of the aligned grid
eyes = None, # if set, the grid setup will be aligned to the eye positions {'leye' : LEFT_EYE_POS, 'reye' : RIGHT_EYE_POS},
nodes_between_eyes = 4,
nodes_along_eyes = 2,
nodes_above_eyes = 3,
nodes_below_eyes = 7,
# setup of static grid
node_distance = None, # one or two integral values
image_resolution = None, # always two integral values
first_node = None, # one or two integral values, or None -> automatically determined
):
# call base class constructor
Extractor.__init__(
self,
gabor_directions = gabor_directions,
gabor_scales = gabor_scales,
gabor_sigma = gabor_sigma,
gabor_maximum_frequency = gabor_maximum_frequency,
gabor_frequency_step = gabor_frequency_step,
gabor_power_of_k = gabor_power_of_k,
gabor_dc_free = gabor_dc_free,
normalize_gabor_jets = normalize_gabor_jets,
eyes = eyes,
nodes_between_eyes = nodes_between_eyes,
nodes_along_eyes = nodes_along_eyes,
nodes_above_eyes = nodes_above_eyes,
nodes_below_eyes = nodes_below_eyes,
node_distance = node_distance,
image_resolution = image_resolution,
first_node = first_node
)
# create Gabor wavelet transform class
self.gwt = bob.ip.gabor.Transform(
number_of_scales = gabor_scales,
number_of_directions = gabor_directions,
sigma = gabor_sigma,
k_max = gabor_maximum_frequency,
k_fac = gabor_frequency_step,
power_of_k = gabor_power_of_k,
dc_free = gabor_dc_free
)
# create graph extractor
if eyes is not None:
self.graph = bob.ip.gabor.Graph(
righteye = [int(e) for e in eyes['reye']],
lefteye = [int(e) for e in eyes['leye']],
between = int(nodes_between_eyes),
along = int(nodes_along_eyes),
above = int(nodes_above_eyes),
below = int(nodes_below_eyes)
)
else:
if node_distance is None or image_resolution is None:
raise ValueError("Please specify either 'eyes' or the grid parameters 'first_node', 'last_node', and 'node_distance'!")
if isinstance(node_distance, (int, float)):
node_distance = (int(node_distance), int(node_distance))
if first_node is None:
first_node = [0,0]
for i in (0,1):
offset = int((image_resolution[i] - int(image_resolution[i]/node_distance[i])*node_distance[i]) / 2)
if offset < node_distance[i]//2: # This is not tested, but should ALWAYS be the case.
offset += node_distance[i]//2
first_node[i] = offset
last_node = tuple([int(image_resolution[i] - max(first_node[i],1)) for i in (0,1)])
# take the specified nodes
self.graph = bob.ip.gabor.Graph(
first = first_node,
last = last_node,
step = node_distance
)
self.normalize_jets = normalize_gabor_jets
self.trafo_image = None
def __call__(self, image):
assert image.ndim == 2
assert isinstance(image, numpy.ndarray)
assert image.dtype == numpy.float64
if self.trafo_image is None or self.trafo_image.shape[1:3] != image.shape:
# create trafo image
self.trafo_image = numpy.ndarray((self.gwt.number_of_wavelets, image.shape[0], image.shape[1]), numpy.complex128)
# perform Gabor wavelet transform
self.gwt.transform(image, self.trafo_image)
# extract face graph
jets = self.graph.extract(self.trafo_image)
# normalize the Gabor jets of the graph only
if self.normalize_jets:
[j.normalize() for j in jets]
# return the extracted face graph
return jets
def write_feature(self, feature, feature_file):
feature_file = feature_file if isinstance(feature_file, bob.io.base.HDF5File) else bob.io.base.HDF5File(feature_file, 'w')
bob.ip.gabor.save_jets(feature, feature_file)
def read_feature(self, feature_file):
return bob.ip.gabor.load_jets(bob.io.base.HDF5File(feature_file))
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
# Manuel Guenther <Manuel.Guenther@idiap.ch>
import bob.ip.gabor
import bob.ip.base
import numpy
import math
from bob.bio.base.extractor import Extractor
class LGBPHS (Extractor):
"""Extractor for local Gabor binary pattern histogram sequences"""
def __init__(
self,
# Block setup
block_size, # one or two parameters for block size
block_overlap = 0, # one or two parameters for block overlap
# Gabor parameters
gabor_directions = 8,
gabor_scales = 5,
gabor_sigma = 2. * math.pi,
gabor_maximum_frequency = math.pi / 2.,
gabor_frequency_step = math.sqrt(.5),
gabor_power_of_k = 0,
gabor_dc_free = True,
use_gabor_phases = False,
# LBP parameters
lbp_radius = 2,
lbp_neighbor_count = 8,
lbp_uniform = True,
lbp_circular = True,
lbp_rotation_invariant = False,
lbp_compare_to_average = False,
lbp_add_average = False,
# histogram options
sparse_histogram = False,
split_histogram = None
):
"""Initializes the local Gabor binary pattern histogram sequence tool chain with the given file selector object"""
# call base class constructor
Extractor.__init__(
self,
block_size = block_size,
block_overlap = block_overlap,
gabor_directions = gabor_directions,
gabor_scales = gabor_scales,
gabor_sigma = gabor_sigma,
gabor_maximum_frequency = gabor_maximum_frequency,
gabor_frequency_step = gabor_frequency_step,
gabor_power_of_k = gabor_power_of_k,
gabor_dc_free = gabor_dc_free,
use_gabor_phases = use_gabor_phases,
lbp_radius = lbp_radius,
lbp_neighbor_count = lbp_neighbor_count,
lbp_uniform = lbp_uniform,
lbp_circular = lbp_circular,
lbp_rotation_invariant = lbp_rotation_invariant,
lbp_compare_to_average = lbp_compare_to_average,
lbp_add_average = lbp_add_average,
sparse_histogram = sparse_histogram,
split_histogram = split_histogram
)
# block parameters
self.block_size = block_size if isinstance(block_size, (tuple, list)) else (block_size, block_size)
self.block_overlap = block_overlap if isinstance(block_overlap, (tuple, list)) else (block_overlap, block_overlap)
if self.block_size[0] < self.block_overlap[0] or self.block_size[1] < self.block_overlap[1]:
raise ValueError("The overlap is bigger than the block size. This won't work. Please check your setup!")
# Gabor wavelet transform class
self.gwt = bob.ip.gabor.Transform(
number_of_scales = gabor_scales,
number_of_directions = gabor_directions,
sigma = gabor_sigma,
k_max = gabor_maximum_frequency,
k_fac = gabor_frequency_step,
power_of_k = gabor_power_of_k,
dc_free = gabor_dc_free
)
self.trafo_image = None
self.use_phases = use_gabor_phases
self.lbp = bob.ip.base.LBP(
neighbors = lbp_neighbor_count,
radius = float(lbp_radius),
circular = lbp_circular,
to_average = lbp_compare_to_average,
add_average_bit = lbp_add_average,
uniform = lbp_uniform,
rotation_invariant = lbp_rotation_invariant,
border_handling = 'wrap'
)
self.split = split_histogram
self.sparse = sparse_histogram
if self.sparse and self.split:
raise ValueError("Sparse histograms cannot be split! Check your setup!")
def _fill(self, lgbphs_array, lgbphs_blocks, j):
"""Copies the given array into the given blocks"""
# fill array in the desired shape
if self.split is None:
start = j * self.n_bins * self.n_blocks
for b in range(self.n_blocks):
lgbphs_array[start + b * self.n_bins : start + (b+1) * self.n_bins] = lgbphs_blocks[b][:]
elif self.split == 'blocks':
for b in range(self.n_blocks):
lgbphs_array[b, j * self.n_bins : (j+1) * self.n_bins] = lgbphs_blocks[b][:]
elif self.split == 'wavelets':
for b in range(self.n_blocks):
lgbphs_array[j, b * self.n_bins : (b+1) * self.n_bins] = lgbphs_blocks[b][:]
elif self.split == 'both':
for b in range(self.n_blocks):
lgbphs_array[j * self.n_blocks + b, 0 : self.n_bins] = lgbphs_blocks[b][:]
def _sparsify(self, array):
"""This function generates a sparse histogram from a non-sparse one."""
if not self.sparse:
return array
if len(array.shape) == 2 and array.shape[0] == 2:
# already sparse
return array
assert len(array.shape) == 1
indices = []
values = []
for i in range(array.shape[0]):
if array[i] != 0.:
indices.append(i)
values.append(array[i])
return numpy.array([indices, values], dtype = numpy.float64)
def __call__(self, image):
"""Extracts the local Gabor binary pattern histogram sequence from the given image"""
assert image.ndim == 2
assert isinstance(image, numpy.ndarray)
assert image.dtype == numpy.float64
# perform GWT on image
if self.trafo_image is None or self.trafo_image.shape[1:3] != image.shape:
# create trafo image
self.trafo_image = numpy.ndarray((self.gwt.number_of_wavelets, image.shape[0], image.shape[1]), numpy.complex128)
# perform Gabor wavelet transform
self.gwt.transform(image, self.trafo_image)
jet_length = self.gwt.number_of_wavelets * (2 if self.use_phases else 1)
lgbphs_array = None
# iterate through the layers of the trafo image
for j in range(self.gwt.number_of_wavelets):
# compute absolute part of complex response
abs_image = numpy.abs(self.trafo_image[j])
# Computes LBP histograms
abs_blocks = bob.ip.base.lbphs(abs_image, self.lbp, self.block_size, self.block_overlap)
# Converts to Blitz array (of different dimensionalities)
self.n_bins = abs_blocks.shape[1]
self.n_blocks = abs_blocks.shape[0]
if self.split is None:
shape = (self.n_blocks * self.n_bins * jet_length,)
elif self.split == 'blocks':
shape = (self.n_blocks, self.n_bins * jet_length)
elif self.split == 'wavelets':
shape = (jet_length, self.n_bins * self.n_blocks)
elif self.split == 'both':
shape = (jet_length * self.n_blocks, self.n_bins)
else:
raise ValueError("The split parameter must be one of ['blocks', 'wavelets', 'both'] or None")
# create new array if not done yet
if lgbphs_array is None:
lgbphs_array = numpy.ndarray(shape, 'float64')
# fill the array with the absolute values of the Gabor wavelet transform
self._fill(lgbphs_array, abs_blocks, j)
if self.use_phases:
# compute phase part of complex response
phase_image = numpy.angle(self.trafo_image[j])
# Computes LBP histograms
phase_blocks = bob.ip.base.lbphs(phase_image, self.lbp, self.block_size, self.block_overlap)
# fill the array with the phases at the end of the blocks
self._fill(lgbphs_array, phase_blocks, j + self.gwt.number_of_wavelets)
# return the concatenated list of all histograms
return self._sparsify(lgbphs_array)
from .DCTBlocks import DCTBlocks
from .GridGraph import GridGraph
from .LGBPHS import LGBPHS
from .Eigenface import Eigenface
#!/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.bio.base
import bob.bio.face
import unittest
import os
import numpy
import math
from nose.plugins.skip import SkipTest
import bob.io.base.test_utils
from bob.bio.base.test import utils
import pkg_resources
regenerate_refs = False
def _compare(data, reference, write_function = bob.bio.base.save, read_function = bob.bio.base.load):
# write reference?
if regenerate_refs:
write_function(data, reference)
# compare reference
reference = read_function(reference)
assert numpy.allclose(data, reference, atol=1e-5)
def _data():
return bob.bio.base.load(pkg_resources.resource_filename('bob.bio.face.test', 'data/cropped.hdf5'))
def test_dct_blocks():
# read input
data = _data()
dct = bob.bio.base.load_resource('dct-blocks', 'extractor')
assert isinstance(dct, bob.bio.face.extractor.DCTBlocks)
assert isinstance(dct, bob.bio.base.extractor.Extractor)
assert not dct.requires_training
# generate smaller extractor, using mixed tuple and int input for the block size and overlap
dct = bob.bio.face.extractor.DCTBlocks(8, (0,0), 15)
# extract features
feature = dct(data)
assert feature.ndim == 2
# feature dimension is one lower than the block size, since blocks are normalized by default
assert feature.shape == (80, 14)