Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
bob.bio.vein
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
bob
bob.bio.vein
Commits
72135a20
Commit
72135a20
authored
Jun 30, 2017
by
André Anjos
💬
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
More preprocessor changes
parent
4f0f8a5c
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
109 additions
and
538 deletions
+109
-538
bob/bio/vein/preprocessor/FingerCrop.py
bob/bio/vein/preprocessor/FingerCrop.py
+0
-538
bob/bio/vein/preprocessor/filters.py
bob/bio/vein/preprocessor/filters.py
+109
-0
No files found.
bob/bio/vein/preprocessor/FingerCrop.py
deleted
100644 → 0
View file @
4f0f8a5c
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
import
math
import
numpy
from
PIL
import
Image
import
bob.io.base
from
bob.bio.base.preprocessor
import
Preprocessor
from
..
import
utils
class
FingerCrop
(
Preprocessor
):
"""
Extracts the mask heuristically and pre-processes fingervein images.
Based on the implementation: E.C. Lee, H.C. Lee and K.R. Park. Finger vein
recognition using minutia-based alignment and local binary pattern-based
feature extraction. International Journal of Imaging Systems and
Technology. Vol. 19, No. 3, pp. 175-178, September 2009.
Finger orientation is 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.
The ``konomask`` option is 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).
In this implementation, the finger image is (in this order):
1. The mask is extracted (if ``annotation`` is not chosen as a parameter to
``fingercontour``). Other mask extraction options correspond to
heuristics developed by Lee et al. (2009) or Kono et al. (2002)
2. The finger is normalized (made horizontal), via a least-squares
normalization procedure concerning the center of the annotated area,
width-wise. Before normalization, the image is padded to avoid loosing
pixels corresponding to veins during the rotation
3. (optionally) Post processed with histogram-equalization to enhance vein
information. Notice that only the area inside the mask is used for
normalization. Areas outside of the mask (where the mask is ``False``
are set to black)
Parameters:
mask_h (:py:obj:`int`, optional): Height of contour mask in pixels, must
be an even number (used by the methods ``leemaskMod`` or
``leemaskMatlab``)
mask_w (:py:obj:`int`, optional): Width of the contour mask in pixels
(used by the methods ``leemaskMod`` or ``leemaskMatlab``)
padding_width (:py:obj:`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). This parameter is always used before
normalizing the finger orientation.
padding_constant (:py:obj:`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). This parameter is
always used before normalizing the finger orientation.
fingercontour (:py:obj:`str`, optional): Select between three finger
contour implementations: ``"leemaskMod"``, ``"leemaskMatlab"``,
``"konomask"`` or ``annotation``. (From Pedro Tome: the option
``leemaskMatlab`` was just implemented for testing purposes so we could
compare with MAT files generated from Matlab code of other authors. He
only used it with the UTFVP database, using ``leemaskMod`` with that
database yields slight worse results.)
postprocessing (:py:obj:`str`, optional): Select between ``HE`` (histogram
equalization, as with :py:func:`skimage.exposure.equalize_hist`) or
``None`` (the default).
"""
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_width
=
padding_width
,
padding_constant
=
padding_constant
,
fingercontour
=
fingercontour
,
postprocessing
=
postprocessing
,
**
kwargs
)
self
.
mask_h
=
mask_h
self
.
mask_w
=
mask_w
self
.
fingercontour
=
fingercontour
self
.
postprocessing
=
postprocessing
self
.
padding_width
=
padding_width
self
.
padding_constant
=
padding_constant
def
__konomask__
(
self
,
image
,
sigma
):
"""
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).
"""
padded_image
=
numpy
.
pad
(
image
,
self
.
padding_width
,
'constant'
,
constant_values
=
self
.
padding_constant
)
sigma
=
5
img_h
,
img_w
=
padded_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
)
#Construct filter kernel
winsize
=
numpy
.
ceil
(
4
*
sigma
)
x
=
numpy
.
arange
(
-
winsize
,
winsize
+
1
)
y
=
numpy
.
arange
(
-
winsize
,
winsize
+
1
)
X
,
Y
=
numpy
.
meshgrid
(
x
,
y
)
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
(
padded_image
,
hy
)
# Upper part of filtred image
img_filt_up
=
fy
[
0
:
half_img_h
,:]
y_up
=
img_filt_up
.
argmax
(
axis
=
0
)
# Lower part of filtred image
img_filt_lo
=
fy
[
half_img_h
-
1
:,:]
y_lo
=
img_filt_lo
.
argmin
(
axis
=
0
)
# Fill region between upper and lower edges
finger_mask
=
numpy
.
ndarray
(
padded_image
.
shape
,
numpy
.
bool
)
finger_mask
[:,:]
=
False
for
i
in
range
(
0
,
img_w
):
finger_mask
[
y_up
[
i
]:
y_lo
[
i
]
+
padded_image
.
shape
[
0
]
-
half_img_h
+
2
,
i
]
=
True
if
not
self
.
padding_width
:
return
finger_mask
else
:
w
=
self
.
padding_width
return
finger_mask
[
w
:
-
w
,
w
:
-
w
]
def
__leemaskMod__
(
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 a variant of the Matlab implementation by Bram Ton, available
at:
https://nl.mathworks.com/matlabcentral/fileexchange/35752-finger-region-localisation/content/lee_region.m
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:**
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).
"""
padded_image
=
numpy
.
pad
(
image
,
self
.
padding_width
,
'constant'
,
constant_values
=
self
.
padding_constant
)
img_h
,
img_w
=
padded_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
[
int
(
self
.
mask_h
/
2
):,:]
=
-
1.0
img_filt
=
utils
.
imfilter
(
padded_image
,
mask
)
# Upper part of filtred image
img_filt_up
=
img_filt
[:
int
(
half_img_h
),:]
y_up
=
img_filt_up
.
argmax
(
axis
=
0
)
# Lower part of filtred image
img_filt_lo
=
img_filt
[
int
(
half_img_h
):,:]
y_lo
=
img_filt_lo
.
argmin
(
axis
=
0
)
img_filt
=
utils
.
imfilter
(
padded_image
,
mask
.
T
)
# Left part of filtered image
img_filt_lf
=
img_filt
[:,:
int
(
half_img_w
)]
y_lf
=
img_filt_lf
.
argmax
(
axis
=
1
)
# Right part of filtred image
img_filt_rg
=
img_filt
[:,
int
(
half_img_w
):]
y_rg
=
img_filt_rg
.
argmin
(
axis
=
1
)
finger_mask
=
numpy
.
zeros
(
padded_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
# Left region
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
finger_mask
[:,
int
(
numpy
.
median
(
y_rg
)
+
img_filt_rg
.
shape
[
1
]):]
=
False
if
not
self
.
padding_width
:
return
finger_mask
else
:
w
=
self
.
padding_width
return
finger_mask
[
w
:
-
w
,
w
:
-
w
]
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).
"""
padded_image
=
numpy
.
pad
(
image
,
self
.
padding_width
,
'constant'
,
constant_values
=
self
.
padding_constant
)
img_h
,
img_w
=
padded_image
.
shape
# Determine lower half starting point
half_img_h
=
int
(
img_h
/
2
)
# Construct mask for filtering
mask
=
numpy
.
ones
((
self
.
mask_h
,
self
.
mask_w
),
dtype
=
'float64'
)
mask
[
int
(
self
.
mask_h
/
2
):,:]
=
-
1.0
img_filt
=
utils
.
imfilter
(
padded_image
,
mask
)
# Upper part of filtered image
img_filt_up
=
img_filt
[:
half_img_h
,:]
y_up
=
img_filt_up
.
argmax
(
axis
=
0
)
# Lower part of filtered image
img_filt_lo
=
img_filt
[
half_img_h
:,:]
y_lo
=
img_filt_lo
.
argmin
(
axis
=
0
)
# 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
(
padded_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
if
not
self
.
padding_width
:
return
finger_mask
else
:
w
=
self
.
padding_width
return
finger_mask
[
w
:
-
w
,
w
:
-
w
]
def
__huangnormalization__
(
self
,
image
,
mask
):
"""
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
**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
# Calculates the mask edges along the columns
edges
=
numpy
.
zeros
((
2
,
mask
.
shape
[
1
]),
dtype
=
int
)
edges
[
0
,:]
=
mask
.
argmax
(
axis
=
0
)
# get upper edges
edges
[
1
,:]
=
len
(
mask
)
-
numpy
.
flipud
(
mask
).
argmax
(
axis
=
0
)
-
1
bl
=
edges
.
mean
(
axis
=
0
)
#baseline
x
=
numpy
.
arange
(
0
,
edges
.
shape
[
1
])
A
=
numpy
.
vstack
([
x
,
numpy
.
ones
(
len
(
x
))]).
T
# Fit a straight line through the base line points
w
=
numpy
.
linalg
.
lstsq
(
A
,
bl
)[
0
]
# obtaining the parameters
angle
=
-
1
*
math
.
atan
(
w
[
0
])
# Rotation
tr
=
img_h
/
2
-
w
[
1
]
# Translation
scale
=
1.0
# Scale
#Affine transformation parameters
sx
=
sy
=
scale
cosine
=
math
.
cos
(
angle
)
sine
=
math
.
sin
(
angle
)
a
=
cosine
/
sx
b
=
-
sine
/
sy
#b = sine/sx
c
=
0
#Translation in x
d
=
sine
/
sx
e
=
cosine
/
sy
f
=
tr
#Translation in y
#d = -sine/sy
#e = cosine/sy
#f = 0
g
=
0
h
=
0
#h=tr
i
=
1
T
=
numpy
.
matrix
([[
a
,
b
,
c
],[
d
,
e
,
f
],[
g
,
h
,
i
]])
Tinv
=
numpy
.
linalg
.
inv
(
T
)
Tinvtuple
=
(
Tinv
[
0
,
0
],
Tinv
[
0
,
1
],
Tinv
[
0
,
2
],
Tinv
[
1
,
0
],
Tinv
[
1
,
1
],
Tinv
[
1
,
2
])
def
_afftrans
(
img
):
'''Applies the affine transform on the resulting image'''
t
=
Image
.
fromarray
(
img
.
astype
(
'uint8'
))
w
,
h
=
t
.
size
#pillow image is encoded w, h
w
+=
2
*
self
.
padding_width
h
+=
2
*
self
.
padding_width
t
=
t
.
transform
(
(
w
,
h
),
Image
.
AFFINE
,
Tinvtuple
,
resample
=
Image
.
BICUBIC
,
fill
=
self
.
padding_constant
)
return
numpy
.
array
(
t
).
astype
(
img
.
dtype
)
return
_afftrans
(
image
),
_afftrans
(
mask
)
def
__HE__
(
self
,
image
,
mask
):
"""
Applies histogram equalization on the input image inside the mask.
In this implementation, only the pixels that lie inside the mask will be
used to calculate the histogram equalization parameters. Because of this
particularity, we don't use Bob's implementation for histogram equalization
and have one based exclusively on scikit-image.
**Parameters:**
image (numpy.ndarray): raw image to be filtered, as 2D array of
unsigned 8-bit integers
mask (numpy.ndarray): mask of the same size of the image, but composed
of boolean values indicating which values should be considered for
the histogram equalization
**Returns:**
numpy.ndarray: normalized image as a 2D array of unsigned 8-bit integers
"""
from
skimage.exposure
import
equalize_hist
from
skimage.exposure
import
rescale_intensity
retval
=
rescale_intensity
(
equalize_hist
(
image
,
mask
=
mask
),
out_range
=
(
0
,
255
))
# make the parts outside the mask totally black
retval
[
~
mask
]
=
0
return
retval
def
__call__
(
self
,
data
,
annotations
=
None
):
"""Reads the input image or (image, mask) and prepares for fex.
Parameters:
data (numpy.ndarray, tuple): Either a :py:class:`numpy.ndarray`
containing a gray-scaled image with dtype ``uint8`` or a 2-tuple
containing both the gray-scaled image and a mask, with the same size of
the image, with dtype ``bool`` containing the points which should be
considered part of the finger
Returns:
numpy.ndarray: The image, preprocessed and normalized
numpy.ndarray: A mask, of the same size of the image, indicating where
the valid data for the object is.
"""
if
isinstance
(
data
,
numpy
.
ndarray
):
image
=
data
mask
=
None
else
:
image
,
mask
=
data
## Finger edges and contour extraction:
if
self
.
fingercontour
==
'none'
:
#image is sufficiently clean
mask
=
numpy
.
ones
(
image
.
shape
,
dtype
=
'bool'
)
elif
self
.
fingercontour
==
'leemaskMatlab'
:
mask
=
self
.
__leemaskMatlab__
(
image
)
#for UTFVP
elif
self
.
fingercontour
==
'leemaskMod'
:
mask
=
self
.
__leemaskMod__
(
image
)
#for VERA
elif
self
.
fingercontour
==
'konomask'
:
mask
=
self
.
__konomask__
(
image
,
sigma
=
5
)
elif
self
.
fingercontour
==
'annotation'
:
if
mask
is
None
:
raise
RuntimeError
(
"Cannot use fingercontour=annotation - the "
\
"current sample being processed does not provide a mask"
)
else
:
raise
RuntimeError
(
"Please choose between leemaskMod, leemaskMatlab, "
\
"konomask, annotation or none for parameter 'fingercontour'. %s "
\
"is not valid"
%
self
.
fingercontour
)
## Finger region normalization:
if
self
.
fingercontour
==
'none'
:
#don't normalize
image_norm
,
mask_norm
=
image
,
mask
else
:
image_norm
,
mask_norm
=
self
.
__huangnormalization__
(
image
,
mask
)
## veins enhancement:
if
self
.
postprocessing
==
'HE'
:
image_norm
=
self
.
__HE__
(
image_norm
,
mask_norm
)
## returns the normalized image and the finger mask
return
image_norm
,
mask_norm
def
write_data
(
self
,
data
,
filename
):
'''Overrides the default method implementation to handle our tuple'''
f
=
bob
.
io
.
base
.
HDF5File
(
filename
,
'w'
)
f
.
set
(
'image'
,
data
[
0
])
f
.
set
(
'mask'
,
data
[
1
])
def
read_data
(
self
,
filename
):
'''Overrides the default method implementation to handle our tuple'''
f
=
bob
.
io
.
base
.
HDF5File
(
filename
,
'r'
)
return
f
.
read
(
'image'
),
f
.
read
(
'mask'
)
bob/bio/vein/preprocessor/filters.py
0 → 100644
View file @
72135a20
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
'''Base utilities for post-filtering vein images'''
import
numpy
class
Filter
(
object
):
'''Objects of this class filter the input image'''
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
,
mask
):
'''Inputs image and mask and outputs a filtered version of the image
Parameters:
image (numpy.ndarray): raw image to filter as 2D array of unsigned
8-bit integers
mask (numpy.ndarray): mask to normalize as 2D array of booleans
Returns:
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input image representing the filtered image.
'''
raise
NotImplemented
(
'You must implement the __call__ slot'
)
class
NoFilter
(
Filter
):
'''Applies no filtering on the input image, returning it without changes'''
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
,
mask
):
'''Inputs image and mask and outputs the image, without changes
Parameters:
image (numpy.ndarray): raw image to filter as 2D array of unsigned
8-bit integers
mask (numpy.ndarray): mask to normalize as 2D array of booleans
Returns:
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input image representing the filtered image.
'''
return
image
class
HistogramEqualization
(
Filter
):
'''Applies histogram equalization on the input image inside the mask.
In this implementation, only the pixels that lie inside the mask will be
used to calculate the histogram equalization parameters. Because of this
particularity, we don't use Bob's implementation for histogram equalization
and have one based exclusively on scikit-image.
'''
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
,
mask
):
'''Applies histogram equalization on the input image, returns filtered
Parameters:
image (numpy.ndarray): raw image to filter as 2D array of unsigned
8-bit integers
mask (numpy.ndarray): mask to normalize as 2D array of booleans
Returns:
numpy.ndarray: A 2D boolean array with the same shape and data type of
the input image representing the filtered image.
'''
from
skimage.exposure
import
equalize_hist
from
skimage.exposure
import
rescale_intensity
retval
=
rescale_intensity
(
equalize_hist
(
image
,
mask
=
mask
),
out_range
=
(
0
,
255
))
# make the parts outside the mask totally black
retval
[
~
mask
]
=
0
return
retval
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment