Skip to content

Commit

Permalink
major code rebase: rewriting everything under the feature extraction …
Browse files Browse the repository at this point in the history
…to be in a class based structure. This is now compatible to the pypi release 0.0.4
  • Loading branch information
Utkarsh-Deshmukh committed Jun 19, 2022
1 parent e4f92f7 commit c8facfb
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 219 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ The feature set for the image consists of the location of Terminations and Bifur
**Usage:**
```
import fingerprint_feature_extractor
img = cv2.imread('image_path', 0) # read the input image --> You can enhance the fingerprint image using the "fingerprint_enhancer" library
FeaturesTerminations, FeaturesBifurcations = fingerprint_feature_extractor.extract_minutiae_features(img, showResult=True)
```
FeaturesTerminations, FeaturesBifurcations = fingerprint_feature_extractor.extract_minutiae_features(img, spuriousMinutiaeThresh=10, invertImage=False, showResult=True, saveResult=True)
```
## method 2
- from the src folder, run the file "main.py"
- **the input image is stored in the folder "enhanced".**
Expand All @@ -39,3 +38,16 @@ Here are some of the outputs:

![1](https://user-images.githubusercontent.com/13918778/35665568-ae1fdb6c-06db-11e8-937b-33d7445c931d.jpg) ![enhanced_feat1](https://user-images.githubusercontent.com/13918778/35665578-baddaf82-06db-11e8-8638-d24de65acd31.jpg)


# How to match the extracted minutiae?
Various papers are published to perform minutiae matching.
Here are some good ones:
- "A MINUTIAE-BASED MATCHING ALGORITHMS IN FINGERPRINT RECOGNITION SYSTEMS" by Łukasz WIĘCŁAW
http://www.keia.ath.bielsko.pl/sites/default/files/publikacje/11-BIO-41-lukaszWieclawMIT_v2_popr2.pdf

"A Minutiae-based Fingerprint Matching Algorithm Using Phase Correlation" by Weiping Chen and Yongsheng Gao
https://core.ac.uk/download/pdf/143875633.pdf

"FINGERPRINT RECOGNITION USING MINUTIA SCORE MATCHING" by RAVI. J, K. B. RAJA, VENUGOPAL. K. R
https://arxiv.org/ftp/arxiv/papers/1001/1001.4186.pdf

7 changes: 7 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import cv2
import fingerprint_feature_extractor


if __name__ == '__main__':
img = cv2.imread('enhanced/3.jpg', 0) # read the input image --> You can enhance the fingerprint image using the "fingerprint_enhancer" library
FeaturesTerminations, FeaturesBifurcations = fingerprint_feature_extractor.extract_minutiae_features(img, spuriousMinutiaeThresh=10, invertImage = False, showResult=True, saveResult = True)
200 changes: 200 additions & 0 deletions fingerprint_feature_extractor/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import cv2
import numpy as np
import skimage.morphology
from skimage.morphology import convex_hull_image, erosion
from skimage.morphology import square
import math

class MinutiaeFeature(object):
def __init__(self, locX, locY, Orientation, Type):
self.locX = locX
self.locY = locY
self.Orientation = Orientation
self.Type = Type

class FingerprintFeatureExtractor(object):
def __init__(self):
self._mask = []
self._skel = []
self.minutiaeTerm = []
self.minutiaeBif = []
self._spuriousMinutiaeThresh = 10

def setSpuriousMinutiaeThresh(self, spuriousMinutiaeThresh):
self._spuriousMinutiaeThresh = spuriousMinutiaeThresh

def __skeletonize(self, img):
img = np.uint8(img > 128)
self._skel = skimage.morphology.skeletonize(img)
self._skel = np.uint8(self._skel) * 255
self._mask = img * 255

def __computeAngle(self, block, minutiaeType):
angle = []
(blkRows, blkCols) = np.shape(block)
CenterX, CenterY = (blkRows - 1) / 2, (blkCols - 1) / 2
if (minutiaeType.lower() == 'termination'):
sumVal = 0
for i in range(blkRows):
for j in range(blkCols):
if ((i == 0 or i == blkRows - 1 or j == 0 or j == blkCols - 1) and block[i][j] != 0):
angle.append(-math.degrees(math.atan2(i - CenterY, j - CenterX)))
sumVal += 1
if (sumVal > 1):
angle.append(float('nan'))
return (angle)

elif (minutiaeType.lower() == 'bifurcation'):
(blkRows, blkCols) = np.shape(block)
CenterX, CenterY = (blkRows - 1) / 2, (blkCols - 1) / 2
angle = []
sumVal = 0
for i in range(blkRows):
for j in range(blkCols):
if ((i == 0 or i == blkRows - 1 or j == 0 or j == blkCols - 1) and block[i][j] != 0):
angle.append(-math.degrees(math.atan2(i - CenterY, j - CenterX)))
sumVal += 1
if (sumVal != 3):
angle.append(float('nan'))
return (angle)

def __getTerminationBifurcation(self):
self._skel = self._skel == 255
(rows, cols) = self._skel.shape
self.minutiaeTerm = np.zeros(self._skel.shape)
self.minutiaeBif = np.zeros(self._skel.shape)

for i in range(1, rows - 1):
for j in range(1, cols - 1):
if (self._skel[i][j] == 1):
block = self._skel[i - 1:i + 2, j - 1:j + 2]
block_val = np.sum(block)
if (block_val == 2):
self.minutiaeTerm[i, j] = 1
elif (block_val == 4):
self.minutiaeBif[i, j] = 1

self._mask = convex_hull_image(self._mask > 0)
self._mask = erosion(self._mask, square(5)) # Structuing element for mask erosion = square(5)
self.minutiaeTerm = np.uint8(self._mask) * self.minutiaeTerm

def __removeSpuriousMinutiae(self, minutiaeList, img):
img = img * 0
SpuriousMin = []
numPoints = len(minutiaeList)
D = np.zeros((numPoints, numPoints))
for i in range(1,numPoints):
for j in range(0, i):
(X1,Y1) = minutiaeList[i]['centroid']
(X2,Y2) = minutiaeList[j]['centroid']

dist = np.sqrt((X2-X1)**2 + (Y2-Y1)**2)
D[i][j] = dist
if(dist < self._spuriousMinutiaeThresh):
SpuriousMin.append(i)
SpuriousMin.append(j)

SpuriousMin = np.unique(SpuriousMin)
for i in range(0,numPoints):
if(not i in SpuriousMin):
(X,Y) = np.int16(minutiaeList[i]['centroid'])
img[X,Y] = 1

img = np.uint8(img)
return(img)

def __cleanMinutiae(self, img):
self.minutiaeTerm = skimage.measure.label(self.minutiaeTerm, connectivity=2)
RP = skimage.measure.regionprops(self.minutiaeTerm)
self.minutiaeTerm = self.__removeSpuriousMinutiae(RP, np.uint8(img))

def __performFeatureExtraction(self):
FeaturesTerm = []
self.minutiaeTerm = skimage.measure.label(self.minutiaeTerm, connectivity=2)
RP = skimage.measure.regionprops(np.uint8(self.minutiaeTerm))

WindowSize = 2 # --> For Termination, the block size must can be 3x3, or 5x5. Hence the window selected is 1 or 2
FeaturesTerm = []
for num, i in enumerate(RP):
(row, col) = np.int16(np.round(i['Centroid']))
block = self._skel[row - WindowSize:row + WindowSize + 1, col - WindowSize:col + WindowSize + 1]
angle = self.__computeAngle(block, 'Termination')
if(len(angle) == 1):
FeaturesTerm.append(MinutiaeFeature(row, col, angle, 'Termination'))

FeaturesBif = []
self.minutiaeBif = skimage.measure.label(self.minutiaeBif, connectivity=2)
RP = skimage.measure.regionprops(np.uint8(self.minutiaeBif))
WindowSize = 1 # --> For Bifurcation, the block size must be 3x3. Hence the window selected is 1
for i in RP:
(row, col) = np.int16(np.round(i['Centroid']))
block = self._skel[row - WindowSize:row + WindowSize + 1, col - WindowSize:col + WindowSize + 1]
angle = self.__computeAngle(block, 'Bifurcation')
if(len(angle) == 3):
FeaturesBif.append(MinutiaeFeature(row, col, angle, 'Bifurcation'))
return (FeaturesTerm, FeaturesBif)

def extractMinutiaeFeatures(self, img):
self.__skeletonize(img)

self.__getTerminationBifurcation()

self.__cleanMinutiae(img)

FeaturesTerm, FeaturesBif = self.__performFeatureExtraction()
return(FeaturesTerm, FeaturesBif)

def showResults(self, FeaturesTerm, FeaturesBif):

(rows, cols) = self._skel.shape
DispImg = np.zeros((rows, cols, 3), np.uint8)
DispImg[:, :, 0] = 255*self._skel
DispImg[:, :, 1] = 255*self._skel
DispImg[:, :, 2] = 255*self._skel

for idx, curr_minutiae in enumerate(FeaturesTerm):
row, col = curr_minutiae.locX, curr_minutiae.locY
(rr, cc) = skimage.draw.circle_perimeter(row, col, 3)
skimage.draw.set_color(DispImg, (rr, cc), (0, 0, 255))

for idx, curr_minutiae in enumerate(FeaturesBif):
row, col = curr_minutiae.locX, curr_minutiae.locY
(rr, cc) = skimage.draw.circle_perimeter(row, col, 3)
skimage.draw.set_color(DispImg, (rr, cc), (255, 0, 0))

cv2.imshow('output', DispImg)
cv2.waitKey(0)

def saveResult(self, FeaturesTerm, FeaturesBif):
(rows, cols) = self._skel.shape
DispImg = np.zeros((rows, cols, 3), np.uint8)
DispImg[:, :, 0] = 255 * self._skel
DispImg[:, :, 1] = 255 * self._skel
DispImg[:, :, 2] = 255 * self._skel

for idx, curr_minutiae in enumerate(FeaturesTerm):
row, col = curr_minutiae.locX, curr_minutiae.locY
(rr, cc) = skimage.draw.circle_perimeter(row, col, 3)
skimage.draw.set_color(DispImg, (rr, cc), (0, 0, 255))

for idx, curr_minutiae in enumerate(FeaturesBif):
row, col = curr_minutiae.locX, curr_minutiae.locY
(rr, cc) = skimage.draw.circle_perimeter(row, col, 3)
skimage.draw.set_color(DispImg, (rr, cc), (255, 0, 0))
cv2.imwrite('result.png', DispImg)

def extract_minutiae_features(img, spuriousMinutiaeThresh=10, invertImage=False, showResult=False, saveResult=False):
feature_extractor = FingerprintFeatureExtractor()
feature_extractor.setSpuriousMinutiaeThresh(spuriousMinutiaeThresh)
if (invertImage):
img = 255 - img;

FeaturesTerm, FeaturesBif = feature_extractor.extractMinutiaeFeatures(img)

if (saveResult):
feature_extractor.saveResult(FeaturesTerm, FeaturesBif)

if(showResult):
feature_extractor.showResults(FeaturesTerm, FeaturesBif)

return(FeaturesTerm, FeaturesBif)
30 changes: 0 additions & 30 deletions src/CommonFunctions.py

This file was deleted.

64 changes: 0 additions & 64 deletions src/extractMinutiaeFeatures.py

This file was deleted.

31 changes: 0 additions & 31 deletions src/getTerminationBifurcation.py

This file was deleted.

Loading

0 comments on commit c8facfb

Please sign in to comment.