Commit a4fe3bf6 authored by André Anjos's avatar André Anjos 💬

More cleaning-up and test passing

parent ac24ea5d
......@@ -2,23 +2,4 @@
# vim: set fileencoding=utf-8 :
from ...preprocessors import FingerCrop
# Contour localization mask
CONTOUR_MASK_HEIGHT = 4 # Height of the mask
CONTOUR_MASK_WIDTH = 40 # Width of the mask
PADDING_OFFSET = 5
PADDING_THRESHOLD = 0.2 #Threshold for padding black zones
FINGERCONTOUR = 'leemaskMod' # Options: 'leemaskMatlab', 'konomask'
POSTPROCESSING = 'CircGabor' # Options: None, 'HE', 'HFE', 'CircGabor'
# define the preprocessor
preprocessor = FingerCrop(
mask_h=CONTOUR_MASK_HEIGHT,
mask_w=CONTOUR_MASK_WIDTH,
padding_offset=PADDING_OFFSET,
padding_threshold=PADDING_THRESHOLD,
fingercontour=FINGERCONTOUR,
postprocessing=POSTPROCESSING,
)
preprocessor = FingerCrop(postprocessing='CircGabor')
......@@ -2,24 +2,4 @@
# vim: set fileencoding=utf-8 :
from ...preprocessors import FingerCrop
# Contour localization mask
CONTOUR_MASK_HEIGHT = 4 # Height of the mask
CONTOUR_MASK_WIDTH = 40 # Width of the mask
PADDING_OFFSET = 5
PADDING_THRESHOLD = 0.2 #Threshold for padding black zones
PREPROCESSING = None
FINGERCONTOUR = 'leemaskMod' # Options: 'leemaskMatlab', 'konomask'
POSTPROCESSING = 'HE' # Options: None, 'HE', 'HFE', 'CircGabor'
# define the preprocessor
preprocessor = FingerCrop(
mask_h=CONTOUR_MASK_HEIGHT,
mask_w=CONTOUR_MASK_WIDTH,
padding_offset=PADDING_OFFSET,
padding_threshold=PADDING_THRESHOLD,
fingercontour=FINGERCONTOUR,
postprocessing=POSTPROCESSING,
)
preprocessor = FingerCrop(postprocessing='HFE')
......@@ -2,26 +2,4 @@
# vim: set fileencoding=utf-8 :
from ...preprocessors import FingerCrop
# Contour localization mask
CONTOUR_MASK_HEIGHT = 4 # Height of the mask
CONTOUR_MASK_WIDTH = 40 # Width of the mask
PADDING_OFFSET = 5
PADDING_THRESHOLD = 0.2 #Threshold for padding black zones
PREPROCESSING = None
FINGERCONTOUR = 'leemaskMod' # Options: 'leemaskMatlab', 'konomask'
POSTPROCESSING = 'HFE' # Options: None, 'HE', 'HFE', 'CircGabor'
# define the preprocessor
preprocessor = FingerCrop(
mask_h=CONTOUR_MASK_HEIGHT,
mask_w=CONTOUR_MASK_WIDTH,
padding_offset=PADDING_OFFSET,
padding_threshold=PADDING_THRESHOLD,
preprocessing=PREPROCESSING,
fingercontour=FINGERCONTOUR,
postprocessing=POSTPROCESSING,
)
preprocessor = FingerCrop(postprocessing='HFE')
......@@ -2,24 +2,4 @@
# vim: set fileencoding=utf-8 :
from ...preprocessors import FingerCrop
# Contour localization mask
CONTOUR_MASK_HEIGHT = 4 # Height of the mask
CONTOUR_MASK_WIDTH = 40 # Width of the mask
PADDING_OFFSET = 5
PADDING_THRESHOLD = 0.2 #Threshold for padding black zones
FINGERCONTOUR = 'leemaskMod' # Options: 'leemaskMod', leemaskMatlab', 'konomask'
POSTPROCESSING = None # Options: None, 'HE', 'HFE', 'CircGabor'
# define the preprocessor
preprocessor = FingerCrop(
mask_h=CONTOUR_MASK_HEIGHT,
mask_w=CONTOUR_MASK_WIDTH,
padding_offset=PADDING_OFFSET,
padding_threshold=PADDING_THRESHOLD,
fingercontour=FINGERCONTOUR,
postprocessing=POSTPROCESSING,
)
preprocessor = FingerCrop()
......@@ -57,11 +57,11 @@ class MaximumCurvature (Extractor):
# Do the actual filtering
fx = utils.imfilter(image, hx, conv=False)
fxx = utils.imfilter(image, hxx, conv=False)
fy = utils.imfilter(image, hy, conv=False)
fyy = utils.imfilter(image, hyy, conv=False)
fxy = utils.imfilter(image, hxy, conv=False)
fx = utils.imfilter(image, hx)
fxx = utils.imfilter(image, hxx)
fy = utils.imfilter(image, hy)
fyy = utils.imfilter(image, hyy)
fxy = utils.imfilter(image, hxy)
f1 = 0.5*numpy.sqrt(2)*(fx + fy) # \ #
f2 = 0.5*numpy.sqrt(2)*(fx - fy) # / #
......
......@@ -24,16 +24,30 @@ class FingerCrop (Preprocessor):
feature extraction. International Journal of Imaging Systems and
Technology. Vol. 19, No. 3, pp. 175-178, September 2009.
In this implementation, the finger image is (in this order):
1. Padded
2. The mask is extracted
3. The finger is normalized (made horizontal)
3. (optionally) Post processed
Parameters:
mask_h (int, Optional): Height of contour mask in pixels
mask_h (int, Optional): Height of contour mask in pixels, must be an even
number
mask_w (int, Optional): Width of the contour mask in pixels
padding_offset (int, Optional):
padding_width (int, Optional): How much padding (in pixels) to add around
the borders of the input image. We normally always keep this value on its
default (5 pixels).
padding_threshold (float, Optional):
padding_constant (int, Optional): What is the value of the pixels added
to the padding. This number should be a value between 0 and 255. (From
Pedro Tome: for UTFVP (high-quality samples), use 0. For the VERA
Fingervein database (low-quality samples), use 51 (that corresponds to
0.2 in a float image with values between 0 and 1).
fingercontour (str, Optional): Select between three finger contour
implementations: leemaskMod, leemaskMatlab or konomask. (From Pedro Tome:
......@@ -46,34 +60,21 @@ class FingerCrop (Preprocessor):
equalization, as with :py:func:`bob.ip.base.histogram_equalization`),
``HFE`` (high-frequency emphasis filter, with hard-coded parameters - see
implementation) or ``CircGabor`` (circular Gabor filter with band-width
1.12 octaves and standard deviation of 5 pixels (this is hard-coded). By
1.12 octaves and standard deviation of 5 pixels (this is hard-coded)). By
default, no postprocessing is applied on the image.
"""
def __init__(
self,
mask_h = 4, # Height of the mask
mask_w = 40, # Width of the mask
padding_offset = 5, #Always the same
padding_threshold = 0.2, #0 for UTFVP database (high quality), 0.2 for VERA database (low quality)
fingercontour = 'leemaskMod',
postprocessing = None,
**kwargs
):
"""Parameters of the constructor of this preprocessor:
"""
def __init__(self, mask_h = 4, mask_w = 40,
padding_width = 5, padding_constant = 51,
fingercontour = 'leemaskMod', postprocessing = None, **kwargs):
Preprocessor.__init__(self,
mask_h = mask_h,
mask_w = mask_w,
padding_offset = padding_offset,
padding_threshold = padding_threshold,
padding_width = padding_width,
padding_constant = padding_constant,
fingercontour = fingercontour,
postprocessing = postprocessing,
**kwargs
......@@ -85,13 +86,17 @@ class FingerCrop (Preprocessor):
self.fingercontour = fingercontour
self.postprocessing = postprocessing
self.padding_offset = padding_offset
self.padding_threshold = padding_threshold
self.padding_width = padding_width
self.padding_constant = padding_constant
def __konomask__(self, image, sigma):
""" M. Kono, H. Ueki and S. Umemura. Near-infrared finger vein patterns for personal identification,
Applied Optics, Vol. 41, Issue 35, pp. 7429-7436 (2002).
"""Finger vein mask extractor
Based on the work of M. Kono, H. Ueki and S. Umemura. Near-infrared finger
vein patterns for personal identification, Applied Optics, Vol. 41, Issue
35, pp. 7429-7436 (2002).
"""
sigma = 5
......@@ -113,7 +118,7 @@ class FingerCrop (Preprocessor):
hy = (-Y/(2*math.pi*sigma**4))*numpy.exp(-(X**2 + Y**2)/(2*sigma**2))
# Filter the image with the directional kernel
fy = utils.imfilter(image, hy, conv=False)
fy = utils.imfilter(image, hy)
# Upper part of filtred image
img_filt_up = fy[0:half_img_h,:]
......@@ -139,48 +144,72 @@ class FingerCrop (Preprocessor):
def __leemaskMod__(self, image):
"""A method to calculate the finger mask
img_h,img_w = image.shape
Based on the work of Finger vein recognition using minutia-based alignment
and local binary pattern-based feature extraction, E.C. Lee, H.C. Lee and
K.R. Park, International Journal of Imaging Systems and Technology, Volume
19, Issue 3, September 2009, Pages 175--178, doi: 10.1002/ima.20193
# Determine lower half starting point vertically
if numpy.mod(img_h,2) == 0:
half_img_h = img_h/2 + 1
else:
half_img_h = numpy.ceil(img_h/2)
This code is a variant of the Matlab implementation by Bram Ton, available
at:
# Determine lower half starting point horizontally
if numpy.mod(img_w,2) == 0:
half_img_w = img_w/2 + 1
else:
half_img_w = numpy.ceil(img_w/2)
https://nl.mathworks.com/matlabcentral/fileexchange/35752-finger-region-localisation/content/lee_region.m
# Construct mask for filtering
mask = numpy.zeros((self.mask_h,self.mask_w))
mask[0:self.mask_h/2,:] = -1
mask[self.mask_h/2:,:] = 1
In this variant from Pedro Tome, the technique of filtering the image with
a horizontal filter is also applied on the vertical axis.
Parameters:
image (numpy.ndarray): raw image to use for finding the mask, as 2D array
of unsigned 8-bit integers
Returns:
img_filt = utils.imfilter(image, mask, conv=True)
numpy.ndarray: A 2D boolean array with the same shape of the input image
representing the cropping mask. ``True`` values indicate where the
finger is.
numpy.ndarray: A 2D array with 64-bit floats indicating the indexes where
the mask, for each column, starts and ends on the original image. The
same of this array is (2, number of columns on input image).
"""
img_h,img_w = image.shape
# Determine lower half starting point
half_img_h = img_h/2
half_img_w = img_w/2
# Construct mask for filtering (up-bottom direction)
mask = numpy.ones((self.mask_h, self.mask_w), dtype='float64')
mask[(self.mask_h/2):,:] = -1.0
img_filt = utils.imfilter(image, mask)
# Upper part of filtred image
img_filt_up = img_filt[0:half_img_h-1,:]
img_filt_up = img_filt[:half_img_h,:]
y_up = img_filt_up.argmax(axis=0)
# Lower part of filtred image
img_filt_lo = img_filt[half_img_h-1:,:]
img_filt_lo = img_filt[half_img_h:,:]
y_lo = img_filt_lo.argmin(axis=0)
img_filt = utils.imfilter(image, mask.T, conv=True)
img_filt = utils.imfilter(image, mask.T)
# Left part of filtered image
img_filt_lf = img_filt[:,0:half_img_w]
img_filt_lf = img_filt[:,:half_img_w]
y_lf = img_filt_lf.argmax(axis=1)
# Right part of filtred image
img_filt_rg = img_filt[:,half_img_w:]
y_rg = img_filt_rg.argmin(axis=1)
finger_mask = numpy.ndarray(image.shape, numpy.bool)
finger_mask[:,:] = False
finger_mask = numpy.zeros(image.shape, dtype='bool')
for i in range(0,y_up.size):
finger_mask[y_up[i]:y_lo[i]+img_filt_lo.shape[0]+1,i] = True
......@@ -189,7 +218,8 @@ class FingerCrop (Preprocessor):
for i in range(0,y_lf.size):
finger_mask[i,0:y_lf[i]+1] = False
# Right region has always the finger ending, crop the padding with the meadian
# Right region has always the finger ending, crop the padding with the
# meadian
finger_mask[:,numpy.median(y_rg)+img_filt_rg.shape[1]:] = False
# Extract y-position of finger edges
......@@ -197,59 +227,130 @@ class FingerCrop (Preprocessor):
edges[0,:] = y_up
edges[0,0:round(numpy.mean(y_lf))+1] = edges[0,round(numpy.mean(y_lf))+1]
edges[1,:] = numpy.round(y_lo + img_filt_lo.shape[0])
edges[1,0:round(numpy.mean(y_lf))+1] = edges[1,round(numpy.mean(y_lf))+1]
return (finger_mask, edges)
return finger_mask, edges
def __leemaskMatlab__(self, image):
"""A method to calculate the finger mask
Based on the work of Finger vein recognition using minutia-based alignment
and local binary pattern-based feature extraction, E.C. Lee, H.C. Lee and
K.R. Park, International Journal of Imaging Systems and Technology, Volume
19, Issue 3, September 2009, Pages 175--178, doi: 10.1002/ima.20193
This code is based on the Matlab implementation by Bram Ton, available at:
https://nl.mathworks.com/matlabcentral/fileexchange/35752-finger-region-localisation/content/lee_region.m
In this method, we calculate the mask of the finger independently for each
column of the input image. Firstly, the image is convolved with a [1,-1]
filter of size ``(self.mask_h, self.mask_w)``. Then, the upper and lower
parts of the resulting filtered image are separated. The location of the
maxima in the upper part is located. The same goes for the location of the
minima in the lower part. The mask is then calculated, per column, by
considering it starts in the point where the maxima is in the upper part
and goes up to the point where the minima is detected on the lower part.
Parameters:
image (numpy.ndarray): raw image to use for finding the mask, as 2D array
of unsigned 8-bit integers
Returns:
numpy.ndarray: A 2D boolean array with the same shape of the input image
representing the cropping mask. ``True`` values indicate where the
finger is.
numpy.ndarray: A 2D array with 64-bit floats indicating the indexes where
the mask, for each column, starts and ends on the original image. The
same of this array is (2, number of columns on input image).
"""
img_h,img_w = image.shape
# Determine lower half starting point
if numpy.mod(img_h,2) == 0:
half_img_h = img_h/2 + 1
else:
half_img_h = numpy.ceil(img_h/2)
half_img_h = img_h/2
# Construct mask for filtering
mask = numpy.zeros((self.mask_h,self.mask_w))
mask[0:self.mask_h/2,:] = -1
mask[self.mask_h/2:,:] = 1
mask = numpy.ones((self.mask_h,self.mask_w), dtype='float64')
mask[(self.mask_h/2):,:] = -1.0
img_filt = utils.imfilter(image, mask, conv=True)
img_filt = utils.imfilter(image, mask)
# Upper part of filtred image
img_filt_up = img_filt[0:numpy.floor(img_h/2),:]
# Upper part of filtered image
img_filt_up = img_filt[:half_img_h,:]
y_up = img_filt_up.argmax(axis=0)
# Lower part of filtred image
img_filt_lo = img_filt[half_img_h-1:,:]
# Lower part of filtered image
img_filt_lo = img_filt[half_img_h:,:]
y_lo = img_filt_lo.argmin(axis=0)
for i in range(0,y_up.size):
img_filt[y_up[i]:y_lo[i]+img_filt_lo.shape[0],i]=1
finger_mask = numpy.ndarray(image.shape, numpy.bool)
finger_mask[:,:] = False
finger_mask[img_filt==1] = True
# Translation: for all columns of the input image, set to True all pixels
# of the mask from index where the maxima occurred in the upper part until
# the index where the minima occurred in the lower part.
finger_mask = numpy.zeros(image.shape, dtype='bool')
for i in range(img_filt.shape[1]):
finger_mask[y_up[i]:(y_lo[i]+img_filt_lo.shape[0]+1), i] = True
# Extract y-position of finger edges
edges = numpy.zeros((2,img_w))
edges = numpy.zeros((2,img_w), dtype='float64')
edges[0,:] = y_up
edges[1,:] = numpy.round(y_lo + img_filt_lo.shape[0])
return (finger_mask, edges)
return finger_mask, edges
def __huangnormalization__(self, image, mask, edges):
"""Simple finger normalization
Based on B. Huang, Y. Dai, R. Li, D. Tang and W. Li, Finger-vein
authentication based on wide line detector and pattern normalization,
Proceedings on 20th International Conference on Pattern Recognition (ICPR),
2010.
This implementation aligns the finger to the centre of the image using an
affine transformation. Elliptic projection which is described in the
referenced paper is not included.
In order to defined the affine transformation to be performed, the
algorithm first calculates the center for each edge (column wise) and
calculates the best linear fit parameters for a straight line passing
through those points.
Parameters:
image (numpy.ndarray): raw image to normalize as 2D array of unsigned
8-bit integers
mask (numpy.ndarray): mask to normalize as 2D array of booleans
edges (numpy.ndarray): edges of the mask, 2D array with 2 rows and as
many columns as the input image, containing the start of the mask and
the end of the mask.
Returns:
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input image representing the newly aligned image.
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input mask representing the newly aligned mask.
"""
img_h, img_w = image.shape
bl = (edges[0,:] + edges[1,:])/2 # Finger base line
bl = edges.mean(axis=0) #baseline
x = numpy.arange(0,img_w)
A = numpy.vstack([x, numpy.ones(len(x))]).T
......@@ -288,41 +389,16 @@ class FingerCrop (Preprocessor):
img=Image.fromarray(image)
image_norm = img.transform(img.size, Image.AFFINE, Tinvtuple, resample=Image.BICUBIC)
#image_norm = img.transform(img.size, Image.AFFINE, (a,b,c,d,e,f,g,h,i), resample=Image.BICUBIC)
image_norm = numpy.array(image_norm)
finger_mask = numpy.zeros(mask.shape)
finger_mask[mask == True] = 1
finger_mask[mask] = 1
img_mask=Image.fromarray(finger_mask)
mask_norm = img_mask.transform(img_mask.size, Image.AFFINE, Tinvtuple, resample=Image.BICUBIC)
#mask_norm = img_mask.transform(img_mask.size, Image.AFFINE, (a,b,c,d,e,f,g,h,i), resample=Image.BICUBIC)
mask_norm = numpy.array(mask_norm)
mask[:,:] = False
mask[mask_norm==1] = True
return (image_norm,mask)
def __padding_finger__(self, image):
image_new = bob.core.convert(image,numpy.float64,(0,1),(0,255))
img_h, img_w = image_new.shape
padding_w = self.padding_threshold * numpy.ones((self.padding_offset, img_w))
# up and down
image_new = numpy.concatenate((padding_w,image_new),axis=0)
image_new = numpy.concatenate((image_new,padding_w),axis=0)
img_h, img_w = image_new.shape
padding_h = self.padding_threshold * numpy.ones((img_h,self.padding_offset))
# left and right
image_new = numpy.concatenate((padding_h,image_new),axis=1)
image_new = numpy.concatenate((image_new,padding_h),axis=1)
mask_norm = numpy.array(mask_norm).astype('bool')
return bob.core.convert(image_new,numpy.uint8,(0,255),(0,1))
return (image_norm, mask_norm)
def __HE__(self, image):
......@@ -393,7 +469,7 @@ class FingerCrop (Preprocessor):
# Without normalisation
#gaborfilter = numpy.exp(-0.5*(X**2/sigma**2+Y**2/sigma**2))*numpy.cos(2*math.pi*fc*numpy.sqrt(X**2+Y**2))
imageEnhance = utils.imfilter(image, gaborfilter, conv=False)
imageEnhance = utils.imfilter(image, gaborfilter)
imageEnhance = numpy.abs(imageEnhance)
return bob.core.convert(imageEnhance,numpy.uint8, (0,255),
......@@ -445,8 +521,9 @@ class FingerCrop (Preprocessor):
"""Reads the input image, extract the mask of the fingervein, postprocesses
"""
# Padding array
image = self.__padding_finger__(image)
# 1. Pads the input image if any padding should be added
image = numpy.pad(image, self.padding_width, 'constant',
constant_values = self.padding_constant)
## Finger edges and contour extraction:
if self.fingercontour == 'leemaskMatlab':
......
......@@ -29,6 +29,27 @@ def F(parts):
return pkg_resources.resource_filename(__name__, os.path.join(*parts))
def _show_mask_over_image(image, mask, color='red'):
"""Plots the mask over the image of a finger, for debugging purposes
Parameters:
image (numpy.ndarray): A 2D numpy.ndarray compose of 8-bit unsigned
integers containing the original image
mask (numpy.ndarray): A 2D numpy.ndarray compose of boolean values
containing the calculated mask
"""
from PIL import Image
img = Image.fromarray(image).convert(mode='RGBA')
msk = Image.fromarray((~mask).astype('uint8')*80)
red = Image.new('RGBA', img.size, color=color)
img.paste(red, mask=msk)
img.show()
def test_finger_crop():
input_filename = F(('preprocessors', '0019_3_1_120509-160517.png'))
......@@ -40,17 +61,17 @@ def test_finger_crop():
img = bob.io.base.load(input_filename)
from bob.bio.vein.preprocessors.FingerCrop import FingerCrop
preprocess = FingerCrop(fingercontour='leemaskMatlab')
preprocess = FingerCrop(fingercontour='leemaskMatlab', padding_width=0)
output_img, finger_mask_norm = preprocess(img)
preproc, mask = preprocess(img)
#_show_mask_over_image(preproc, mask)
# Load Matlab reference
output_img_ref = bob.io.base.load(output_img_filename)
output_fvr_ref = bob.io.base.load(output_fvr_filename)
mask_ref = bob.io.base.load(output_fvr_filename)
preproc_ref = bob.io.base.load(output_img_filename)
# Compare output of python's implementation to matlab reference
# (loose comparison!)
assert numpy.mean(numpy.abs(output_img - output_img_ref)) < 1e2
# Very loose comparison!
assert numpy.mean(numpy.abs(mask.astype('float64') - mask_ref)) < 1e-2
assert numpy.mean(numpy.abs(preproc - preproc_ref)) < 1e2
def test_miuramax():
......@@ -67,7 +88,7 @@ def test_miuramax():
# Apply Python implementation
from bob.bio.vein.extractors.MaximumCurvature import MaximumCurvature
MC = MaximumCurvature(5, False)
MC = MaximumCurvature(5)
output_img = MC((input_img, input_fvr))
# Load Matlab reference
......
......@@ -8,10 +8,10 @@ import bob.sp
import bob.core
def imfilter(a, b, conv=True):
def imfilter(a, b):
"""Applies a 2D filtering between images
This implementation was created to work exactly like the Matlab one.
This implementation was created to work similarly like the Matlab one.
Parameters:
......@@ -23,20 +23,16 @@ def imfilter(a, b, conv=True):
with :py:func:`bob.core.convert` and the range reset to ``[0.0, 1.0]``.
b (numpy.ndarray): A 64-bit float 2-dimensional :py:class:`numpy.ndarray`
which represents the filter to be applied to the image
conv (bool, Optional): If set, then rotates the filter ``b`` by 180 degrees
before applying it to the image ``a``, with
:py:func:`bob.ip.base.rotate`.
which represents the filter to be applied to the image. The input filter
has to be rotated by 180 degrees as we use
:py:func:`scipy.signal.convolve2d` to apply it. You can rotate your
filter ``b`` with the help of :py:func:`bob.ip.base.rotate`.
"""
if a.dtype == numpy.uint8:
a = bob.core.convert(a, numpy.float64, (0,1))
if conv:
b = bob.ip.base.rotate(b, 180)
shape = (a.shape[0] + b.shape[0] - 1, a.shape[1] + b.shape[1] - 1)
a_ext = numpy.ndarray(shape=shape, dtype=numpy.float64)
bob.sp.extrapolate_nearest(a, a_ext)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment