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
b9af7a82
Commit
b9af7a82
authored
Jun 30, 2017
by
André Anjos
💬
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-estructured Preprocessor to simplify experimentation and configuration
parent
a0aed3f5
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
880 additions
and
64 deletions
+880
-64
bob/bio/vein/configurations/maximum_curvature.py
bob/bio/vein/configurations/maximum_curvature.py
+17
-2
bob/bio/vein/configurations/repeated_line_tracking.py
bob/bio/vein/configurations/repeated_line_tracking.py
+17
-2
bob/bio/vein/configurations/wide_line_detector.py
bob/bio/vein/configurations/wide_line_detector.py
+17
-2
bob/bio/vein/database/__init__.py
bob/bio/vein/database/__init__.py
+23
-0
bob/bio/vein/database/fv3d.py
bob/bio/vein/database/fv3d.py
+55
-42
bob/bio/vein/database/utfvp.py
bob/bio/vein/database/utfvp.py
+1
-1
bob/bio/vein/database/verafinger.py
bob/bio/vein/database/verafinger.py
+14
-14
bob/bio/vein/preprocessor/__init__.py
bob/bio/vein/preprocessor/__init__.py
+34
-1
bob/bio/vein/preprocessor/mask.py
bob/bio/vein/preprocessor/mask.py
+430
-0
bob/bio/vein/preprocessor/normalize.py
bob/bio/vein/preprocessor/normalize.py
+185
-0
bob/bio/vein/preprocessor/preprocessor.py
bob/bio/vein/preprocessor/preprocessor.py
+87
-0
No files found.
bob/bio/vein/configurations/maximum_curvature.py
View file @
b9af7a82
...
...
@@ -20,8 +20,23 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource.
"""
from
..preprocessor
import
FingerCrop
preprocessor
=
FingerCrop
()
from
..preprocessor
import
Padder
,
TomesLeeMask
,
HuangNormalization
,
NoFilter
from
..preprocessor
import
Preprocessor
# Filter sizes for the vertical "high-pass" filter
FILTER_HEIGHT
=
4
FILTER_WIDTH
=
40
# Padding (to create a buffer during normalization)
PAD_WIDTH
=
5
PAD_CONST
=
51
preprocessor
=
Preprocessor
(
mask
=
TomesLeeMask
(
filter_height
=
FILTER_HEIGHT
,
filter_width
=
FILTER_WIDTH
),
normalize
=
HuangNormalization
(
padding_width
=
PAD_WIDTH
,
padding_constant
=
PAD_CONST
),
filter
=
NoFilter
(),
)
"""Preprocessing using gray-level based finger cropping and no post-processing
"""
...
...
bob/bio/vein/configurations/repeated_line_tracking.py
View file @
b9af7a82
...
...
@@ -20,8 +20,23 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource.
"""
from
..preprocessor
import
FingerCrop
preprocessor
=
FingerCrop
()
from
..preprocessor
import
Padder
,
TomesLeeMask
,
HuangNormalization
,
NoFilter
from
..preprocessor
import
Preprocessor
# Filter sizes for the vertical "high-pass" filter
FILTER_HEIGHT
=
4
FILTER_WIDTH
=
40
# Padding (to create a buffer during normalization)
PAD_WIDTH
=
5
PAD_CONST
=
51
preprocessor
=
Preprocessor
(
mask
=
TomesLeeMask
(
filter_height
=
FILTER_HEIGHT
,
filter_width
=
FILTER_WIDTH
),
normalize
=
HuangNormalization
(
padding_width
=
PAD_WIDTH
,
padding_constant
=
PAD_CONST
),
filter
=
NoFilter
(),
)
"""Preprocessing using gray-level based finger cropping and no post-processing
"""
...
...
bob/bio/vein/configurations/wide_line_detector.py
View file @
b9af7a82
...
...
@@ -20,8 +20,23 @@ or the attribute ``sub_directory`` in a configuration file loaded **after**
this resource.
"""
from
..preprocessor
import
FingerCrop
preprocessor
=
FingerCrop
()
from
..preprocessor
import
Padder
,
TomesLeeMask
,
HuangNormalization
,
NoFilter
from
..preprocessor
import
Preprocessor
# Filter sizes for the vertical "high-pass" filter
FILTER_HEIGHT
=
4
FILTER_WIDTH
=
40
# Padding (to create a buffer during normalization)
PAD_WIDTH
=
5
PAD_CONST
=
51
preprocessor
=
Preprocessor
(
mask
=
TomesLeeMask
(
filter_height
=
FILTER_HEIGHT
,
filter_width
=
FILTER_WIDTH
),
normalize
=
HuangNormalization
(
padding_width
=
PAD_WIDTH
,
padding_constant
=
PAD_CONST
),
filter
=
NoFilter
(),
)
"""Preprocessing using gray-level based finger cropping and no post-processing
"""
...
...
bob/bio/vein/database/__init__.py
View file @
b9af7a82
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
'''Database definitions for Vein Recognition'''
import
numpy
class
AnnotatedArray
(
numpy
.
ndarray
):
"""Defines a numpy array subclass that can carry its own metadata
Copied from: https://docs.scipy.org/doc/numpy-1.12.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array
"""
def
__new__
(
cls
,
input_array
,
metadata
=
None
):
obj
=
numpy
.
asarray
(
input_array
).
view
(
cls
)
obj
.
metadata
=
metadata
if
metadata
is
not
None
else
dict
()
return
obj
def
__array_finalize__
(
self
,
obj
):
if
obj
is
None
:
return
self
.
metadata
=
getattr
(
obj
,
'metadata'
,
dict
())
bob/bio/vein/database/fv3d.py
View file @
b9af7a82
...
...
@@ -4,80 +4,93 @@
import
numpy
from
bob.bio.base.database
import
BioFile
,
BioDatabase
from
.
import
AnnotatedArray
class
File
(
BioFile
):
"""
Implements extra properties of vein files for the Vera Fingervein database
"""
Implements extra properties of vein files for the Vera Fingervein database
Parameters:
f (object): Low-level file (or sample) object that is kept inside
"""
def
__init__
(
self
,
f
):
Parameters:
super
(
File
,
self
).
__init__
(
client_id
=
f
.
finger
.
unique_name
,
path
=
f
.
path
,
file_id
=
f
.
id
)
self
.
__f
=
f
f (object): Low-level file (or sample) object that is kept inside
"""
def
mask
(
self
,
shape
):
"""Returns the binary mask from the ROI annotations available"""
def
__init__
(
self
,
f
):
return
poly_to_mask
(
shape
,
self
.
__f
.
roi
())
super
(
File
,
self
).
__init__
(
client_id
=
f
.
finger
.
unique_name
,
path
=
f
.
path
,
file_id
=
f
.
id
)
self
.
__f
=
f
def
load
(
self
,
*
args
,
**
kwargs
):
"""(Overrides base method) Loads both image and mask"""
def
load
(
self
,
*
args
,
**
kwargs
):
"""(Overrides base method) Loads both image and mask"""
image
=
super
(
File
,
self
).
load
(
*
args
,
**
kwargs
)
roi
=
self
.
__f
.
roi
()
image
=
super
(
File
,
self
).
load
(
*
args
,
**
kwargs
)
# calculates the 90 degrees anti-clockwise rotated RoI points
h
,
w
=
image
.
shape
roi
=
[(
x
,
-
y
+
h
)
for
k
in
roi
]
# image is upside, whereas this package requires fingers to be horizontal
return
numpy
.
rot90
(
image
)
return
ImageWithAnnotation
(
numpy
.
rot90
(
image
),
metadata
=
dict
(
mask
=
roi
))
class
Database
(
BioDatabase
):
"""
Implements verification API for querying Vera Fingervein database.
"""
"""
Implements verification API for querying Vera Fingervein database.
"""
def
__init__
(
self
,
**
kwargs
):
def
__init__
(
self
,
**
kwargs
):
super
(
Database
,
self
).
__init__
(
name
=
'fv3d'
,
**
kwargs
)
from
bob.db.fv3d.query
import
Database
as
LowLevelDatabase
self
.
__db
=
LowLevelDatabase
()
super
(
Database
,
self
).
__init__
(
name
=
'fv3d'
,
**
kwargs
)
from
bob.db.fv3d.query
import
Database
as
LowLevelDatabase
self
.
__db
=
LowLevelDatabase
()
self
.
low_level_group_names
=
(
'train'
,
'dev'
,
'eval'
)
self
.
high_level_group_names
=
(
'world'
,
'dev'
,
'eval'
)
self
.
low_level_group_names
=
(
'train'
,
'dev'
,
'eval'
)
self
.
high_level_group_names
=
(
'world'
,
'dev'
,
'eval'
)
def
groups
(
self
):
def
groups
(
self
):
return
self
.
convert_names_to_highlevel
(
self
.
__db
.
groups
(),
self
.
low_level_group_names
,
self
.
high_level_group_names
)
return
self
.
convert_names_to_highlevel
(
self
.
__db
.
groups
(),
self
.
low_level_group_names
,
self
.
high_level_group_names
)
def
client_id_from_model_id
(
self
,
model_id
,
group
=
'dev'
):
"""Required as ``model_id != client_id`` on this database"""
def
client_id_from_model_id
(
self
,
model_id
,
group
=
'dev'
):
"""Required as ``model_id != client_id`` on this database"""
return
self
.
__db
.
finger_name_from_model_id
(
model_id
)
return
self
.
__db
.
finger_name_from_model_id
(
model_id
)
def
model_ids_with_protocol
(
self
,
groups
=
None
,
protocol
=
None
,
**
kwargs
):
def
model_ids_with_protocol
(
self
,
groups
=
None
,
protocol
=
None
,
**
kwargs
):
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
return
self
.
__db
.
model_ids
(
groups
=
groups
,
protocol
=
protocol
)
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
return
self
.
__db
.
model_ids
(
groups
=
groups
,
protocol
=
protocol
)
def
objects
(
self
,
groups
=
None
,
protocol
=
None
,
purposes
=
None
,
model_ids
=
None
,
**
kwargs
):
def
objects
(
self
,
groups
=
None
,
protocol
=
None
,
purposes
=
None
,
model_ids
=
None
,
**
kwargs
):
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
retval
=
self
.
__db
.
objects
(
groups
=
groups
,
protocol
=
protocol
,
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
retval
=
self
.
__db
.
objects
(
groups
=
groups
,
protocol
=
protocol
,
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
return
[
File
(
f
)
for
f
in
retval
]
return
[
File
(
f
)
for
f
in
retval
]
def
annotations
(
self
,
file
):
return
None
def
annotations
(
self
,
file
):
return
None
bob/bio/vein/database/utfvp.py
View file @
b9af7a82
...
...
@@ -42,7 +42,7 @@ class Database(BioDatabase):
model_ids
=
None
,
**
kwargs
):
retval
=
self
.
_db
.
objects
(
groups
=
groups
,
protocol
=
protocol
,
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
return
[
File
(
f
)
for
f
in
retval
]
...
...
bob/bio/vein/database/verafinger.py
View file @
b9af7a82
...
...
@@ -5,6 +5,8 @@
from
bob.bio.base.database
import
BioFile
,
BioDatabase
from
.
import
AnnotatedArray
class
File
(
BioFile
):
"""
...
...
@@ -20,23 +22,17 @@ class File(BioFile):
def
__init__
(
self
,
f
):
super
(
File
,
self
).
__init__
(
client_id
=
f
.
unique_finger_name
,
path
=
f
.
path
,
file_id
=
f
.
id
)
file_id
=
f
.
id
)
self
.
__f
=
f
def
mask
(
self
):
"""Returns the binary mask from the ROI annotations available"""
from
..preprocessor.utils
import
poly_to_mask
# The size of images in this database is (250, 665) pixels (h, w)
return
poly_to_mask
((
250
,
665
),
self
.
__f
.
roi
())
def
load
(
self
,
*
args
,
**
kwargs
):
"""(Overrides base method) Loads both image and mask"""
image
=
super
(
File
,
self
).
load
(
*
args
,
**
kwargs
)
return
image
,
self
.
mask
()
roi
=
self
.
__f
.
roi
()
mask
=
poly_to_mask
(
image
.
shape
,
roi
)
return
AnnotatedArray
(
image
,
metadata
=
dict
(
mask
=
mask
,
roi
=
roi
))
class
Database
(
BioDatabase
):
...
...
@@ -56,28 +52,32 @@ class Database(BioDatabase):
def
groups
(
self
):
return
self
.
convert_names_to_highlevel
(
self
.
_db
.
groups
(),
self
.
low_level_group_names
,
self
.
high_level_group_names
)
self
.
low_level_group_names
,
self
.
high_level_group_names
)
def
client_id_from_model_id
(
self
,
model_id
,
group
=
'dev'
):
"""Required as ``model_id != client_id`` on this database"""
return
self
.
_db
.
finger_name_from_model_id
(
model_id
)
def
model_ids_with_protocol
(
self
,
groups
=
None
,
protocol
=
None
,
**
kwargs
):
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
self
.
low_level_group_names
,
self
.
high_level_group_names
)
return
self
.
_db
.
model_ids
(
groups
=
groups
,
protocol
=
protocol
)
def
objects
(
self
,
groups
=
None
,
protocol
=
None
,
purposes
=
None
,
model_ids
=
None
,
**
kwargs
):
groups
=
self
.
convert_names_to_lowlevel
(
groups
,
self
.
low_level_group_names
,
self
.
high_level_group_names
)
self
.
low_level_group_names
,
self
.
high_level_group_names
)
retval
=
self
.
_db
.
objects
(
groups
=
groups
,
protocol
=
protocol
,
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
purposes
=
purposes
,
model_ids
=
model_ids
,
**
kwargs
)
return
[
File
(
f
)
for
f
in
retval
]
def
annotations
(
self
,
file
):
return
None
bob/bio/vein/preprocessor/__init__.py
View file @
b9af7a82
from
.FingerCrop
import
FingerCrop
from
.mask
import
Padder
,
Masker
,
NoMask
,
AnnotatedRoIMask
from
.mask
import
KonoMask
,
LeeMask
,
TomesLeeMask
from
.normalize
import
Normalizer
,
NoNormalization
,
HuangNormalization
from
.filters
import
Filter
,
NoFilter
,
HistogramEqualization
from
.preprocessor
import
Preprocessor
# gets sphinx autodoc done right - don't remove it
def
__appropriate__
(
*
args
):
"""Says object was actually declared here, an not on the import module.
Parameters:
*args: An iterable of objects to modify
Resolves `Sphinx referencing issues
<https://github.com/sphinx-doc/sphinx/issues/3048>`
"""
for
obj
in
args
:
obj
.
__module__
=
__name__
__appropriate__
(
Padder
,
Masker
,
NoMask
,
AnnotatedRoIMask
,
KonoMask
,
LeeMask
,
TomesLeeMask
,
Normalizer
,
NoNormalization
,
HuangNormalization
,
Filter
,
NoFilter
,
HistogramEqualization
,
Preprocessor
,
)
__all__
=
[
_
for
_
in
dir
()
if
not
_
.
startswith
(
'_'
)]
bob/bio/vein/preprocessor/mask.py
0 → 100644
View file @
b9af7a82
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
'''Base utilities for mask processing'''
import
numpy
import
scipy.ndimage
from
.utils
import
poly_to_mask
class
Padder
(
object
):
"""A class that pads the input image returning a new object
Parameters:
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.
"""
def
__init__
(
self
,
padding_width
=
5
,
padding_constant
=
51
):
self
.
padding_width
=
padding_width
self
.
padding_constant
=
padding_constant
def
__call__
(
self
,
image
):
'''Inputs an image, returns a padded (larger) image
Parameters:
image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
input image
Returns:
numpy.ndarray: A 2D numpy array of the same type as the input, but with
the extra padding
'''
return
numpy
.
pad
(
image
,
self
.
padding_width
,
'constant'
,
constant_values
=
self
.
padding_constant
)
class
Masker
(
object
):
"""This is the base class for all maskers
It defines the minimum requirements for all derived masker classes.
"""
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
):
"""Overwrite this method to implement your masking method
Parameters:
image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
input image
Returns:
numpy.ndarray: A 2D numpy array of type boolean with the caculated
mask. ``True`` values correspond to regions where the finger is
situated
"""
raise
NotImplemented
(
'You must implement the __call__ slot'
)
class
NoMask
(
object
):
"""Implements no masking - i.e. returns a mask the same size as input
"""
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
):
"""Returns a big mask
Parameters:
image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
input image
Returns:
numpy.ndarray: A 2D numpy array of type boolean with the caculated
mask. ``True`` values correspond to regions where the finger is
situated
"""
return
numpy
.
ones
(
image
.
shape
,
dtype
=
'bool'
)
class
AnnotatedRoIMask
(
object
):
"""Devises the mask from the annotated RoI"""
def
__init__
(
self
):
pass
def
__call__
(
self
,
image
):
"""Returns a mask extrapolated from RoI annotations
Parameters:
image (bob.bio.vein.database.AnnotatedArray): A 2D numpy array of type
``uint8`` with the input image containing an attribute called
``metadata`` (a python dictionary). The ``metadata`` object just
contain a key called ``roi`` containing the annotated points
Returns:
numpy.ndarray: A 2D numpy array of type boolean with the caculated
mask. ``True`` values correspond to regions where the finger is
situated
"""
return
poly_to_mask
(
image
.
shape
,
image
.
metadata
[
'roi'
])
class
KonoMask
(
Masker
):
"""Estimates the finger region given an input NIR image using Kono et al.
This method 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).
Parameters:
sigma (:py:obj:`float`, optional): The standard deviation of the gaussian
blur filter to apply for low-passing the input image (background
extraction). Defaults to ``5``.
padder (:py:class:`Padder`, optional): If passed, will pad the image before
evaluating the mask. The returned value will have the padding removed and
is, therefore, of the exact size of the input image.
"""
def
__init__
(
self
,
sigma
=
5
,
padder
=
None
):
self
.
sigma
=
sigma
self
.
padder
=
padder
def
__call__
(
self
,
image
):
'''Inputs an image, returns a mask (numpy boolean array)
Parameters:
image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
input image
Returns:
numpy.ndarray: A 2D numpy array of type boolean with the caculated
mask. ``True`` values correspond to regions where the finger is
situated
'''
image
=
image
if
self
.
padder
is
None
else
self
.
padder
(
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
)
#Construct filter kernel
winsize
=
numpy
.
ceil
(
4
*
self
.
sigma
)
x
=
numpy
.
arange
(
-
winsize
,
winsize
+
1
)
y
=
numpy
.
arange
(
-
winsize
,
winsize
+
1
)
X
,
Y
=
numpy
.
meshgrid
(
x
,
y
)
hy
=
(
-
Y
/
(
2
*
math
.
pi
*
self
.
sigma
**
4
))
*
\
numpy
.
exp
(
-
(
X
**
2
+
Y
**
2
)
/
(
2
*
self
.
sigma
**
2
))
# Filter the image with the directional kernel
fy
=
scipy
.
ndimage
.
convolve
(
image
,
hy
,
mode
=
'nearest'
)
# 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
(
image
.
shape
,
numpy
.
bool
)
finger_mask
[:,:]
=
False
for
i
in
range
(
0
,
img_w
):
finger_mask
[
y_up
[
i
]:
y_lo
[
i
]
+
image
.
shape
[
0
]
-
half_img_h
+
2
,
i
]
=
True
if
not
self
.
padder
:
return
finger_mask
else
:
w
=
self
.
padder
.
padding_width
return
finger_mask
[
w
:
-
w
,
w
:
-
w
]
class
LeeMask
(
Masker
):
"""Estimates the finger region given an input NIR image using Lee et al.
This method is 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.filter_height, self.filter_width)``. 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:
filter_height (:py:obj:`int`, optional): Height of contour mask in pixels,
must be an even number
filter_width (:py:obj:`int`, optional): Width of the contour mask in pixels
"""
def
__init__
(
self
,
filter_height
=
4
,
filter_width
=
40
,
padder
=
None
):
self
.
filter_height
=
filter_height
self
.
filter_width
=
filter_width
self
.
padder
=
padder
def
__call__
(
self
,
image
):
'''Inputs an image, returns a mask (numpy boolean array)
Parameters:
image (numpy.ndarray): A 2D numpy array of type ``uint8`` with the
input image
Returns:
numpy.ndarray: A 2D numpy array of type boolean with the caculated
mask. ``True`` values correspond to regions where the finger is
situated
'''
image
=
image
if
self
.
padder
is
None
else
self
.
padder
(
image
)
img_h
,
img_w
=
image
.
shape
# Determine lower half starting point
half_img_h
=
int
(
img_h
/
2
)
# Construct mask for filtering
mask
=
numpy
.
ones
((
self
.
filter_height
,
self
.
filter_width
),
dtype
=
'float64'
)
mask
[
int
(
self
.
filter_height
/
2
):,:]
=
-
1.0
img_filt
=
scipy
.
ndimage
.
convolve
(
image
.
astype
(
numpy
.
float64
),
mask
,
mode
=
'nearest'
)
# 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
(
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
.
padder
:
return
finger_mask
else
:
w
=
self
.
padder
.
padding_width
return
finger_mask
[
w
:
-
w
,
w
:
-
w
]
class
TomesLeeMask
(
Masker
):
"""Estimates the finger region given an input NIR image using Lee et al.
This method is 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:
filter_height (:py:obj:`int`, optional): Height of contour mask in pixels,