Commited minutiae

This commit is contained in:
2026-02-13 13:17:41 +01:00
parent 0ee74cbcb8
commit 9595675cdb
40 changed files with 4125 additions and 0 deletions
@@ -0,0 +1,947 @@
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
https://arxiv.org/pdf/1712.09401.pdf
If you use whole or partial function in this code, please cite paper:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
"""
from __future__ import absolute_import
from __future__ import division
from time import time
from datetime import datetime
from CoarseNet_utils import *
from scipy import misc, ndimage, signal, sparse, io
import scipy.ndimage
import cv2
import sys,os
sys.path.append(os.path.realpath('../FineNet'))
from FineNet_model import FineNetmodel
from keras.models import Model
from keras.layers import Input
from keras import layers
from keras.layers.core import Flatten,Activation,Lambda, Dropout
from keras.layers.convolutional import Conv2D,MaxPooling2D,UpSampling2D,AveragePooling2D
from keras.layers.normalization import BatchNormalization
from keras.layers.advanced_activations import PReLU
from keras.regularizers import l2
from keras.optimizers import SGD, Adam
from keras.utils import plot_model
import tensorflow as tf
from MinutiaeNet_utils import *
from LossFunctions import *
def conv_bn(bottom, w_size, name, strides=(1,1), dilation_rate=(1,1)):
top = Conv2D(w_size[0], (w_size[1],w_size[2]),
kernel_regularizer=l2(5e-5),
padding='same',
strides=strides,
dilation_rate=dilation_rate,
name='conv-'+name)(bottom)
top = BatchNormalization(name='bn-'+name)(top)
return top
def conv_bn_prelu(bottom, w_size, name, strides=(1,1), dilation_rate=(1,1)):
if dilation_rate == (1,1):
conv_type = 'conv'
else:
conv_type = 'atrousconv'
top = Conv2D(w_size[0], (w_size[1],w_size[2]),
kernel_regularizer=l2(5e-5),
padding='same',
strides=strides,
dilation_rate=dilation_rate,
name=conv_type+name)(bottom)
top = BatchNormalization(name='bn-'+name)(top)
top = PReLU(alpha_initializer='zero', shared_axes=[1,2], name='prelu-'+name)(top)
# top = Dropout(0.25)(top)
return top
def CoarseNetmodel(input_shape=(400,400,1), weights_path=None, mode='train'):
# Change network architecture here!!
img_input=Input(input_shape)
bn_img=Lambda(img_normalization, name='img_normalized')(img_input)
# Main part
conv = conv_bn_prelu(bn_img, (64, 5, 5), '1_0')
conv = conv_bn_prelu(conv, (64, 3, 3), '1_1')
conv = conv_bn_prelu(conv, (64, 3, 3), '1_2')
conv = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(conv)
# =======Block 1 ========
conv1 = conv_bn_prelu(conv, (128, 3, 3), '2_1')
conv = conv_bn_prelu(conv1, (128, 3, 3), '2_2')
conv = conv_bn_prelu(conv, (128, 3, 3), '2_3')
conv = layers.add([conv, conv1])
conv1 = conv_bn_prelu(conv, (128, 3, 3), '2_1b')
conv = conv_bn_prelu(conv1, (128, 3, 3), '2_2b')
conv = conv_bn_prelu(conv, (128, 3, 3), '2_3b')
conv = layers.add([conv, conv1])
conv1 = conv_bn_prelu(conv, (128, 3, 3), '2_1c')
conv = conv_bn_prelu(conv1, (128, 3, 3), '2_2c')
conv = conv_bn_prelu(conv, (128, 3, 3), '2_3c')
conv = layers.add([conv, conv1])
conv_block1 = MaxPooling2D(pool_size=(2,2),strides=(2,2))(conv)
# ==========================
# =======Block 2 ========
conv1 = conv_bn_prelu(conv_block1, (256,3,3), '3_1')
conv = conv_bn_prelu(conv1, (256,3,3), '3_2')
conv = conv_bn_prelu(conv, (256,3,3), '3_3')
conv = layers.add([conv, conv1])
conv1 = conv_bn_prelu(conv, (256, 3, 3), '3_1b')
conv = conv_bn_prelu(conv1, (256, 3, 3), '3_2b')
conv = conv_bn_prelu(conv, (256, 3, 3), '3_3b')
conv = layers.add([conv, conv1])
conv_block2 = MaxPooling2D(pool_size=(2,2),strides=(2,2))(conv)
# ==========================
# =======Block 3 ========
conv1 = conv_bn_prelu(conv_block2, (512, 3, 3), '3_1c')
conv = conv_bn_prelu(conv1, (512, 3, 3), '3_2c')
conv = conv_bn_prelu(conv, (512, 3, 3), '3_3c')
conv = layers.add([conv, conv1])
conv_block3 = conv_bn_prelu(conv, (256, 3, 3), '3_4c')
#conv_block3 = MaxPooling2D(pool_size=(2,2),strides=(2,2))(conv)
# ==========================
# multi-scale ASPP
level_2=conv_bn_prelu(conv_block3, (256,3,3), '4_1', dilation_rate=(1,1))
ori_1=conv_bn_prelu(level_2, (128,1,1), 'ori_1_1')
ori_1=Conv2D(90, (1,1), padding='same', name='ori_1_2')(ori_1)
seg_1=conv_bn_prelu(level_2, (128,1,1), 'seg_1_1')
seg_1=Conv2D(1, (1,1), padding='same', name='seg_1_2')(seg_1)
level_3=conv_bn_prelu(conv_block2, (256,3,3), '4_2', dilation_rate=(4,4))
ori_2=conv_bn_prelu(level_3, (128,1,1), 'ori_2_1')
ori_2=Conv2D(90, (1,1), padding='same', name='ori_2_2')(ori_2)
seg_2=conv_bn_prelu(level_3, (128,1,1), 'seg_2_1')
seg_2=Conv2D(1, (1,1), padding='same', name='seg_2_2')(seg_2)
level_4=conv_bn_prelu(conv_block2, (256,3,3), '4_3', dilation_rate=(8,8))
ori_3=conv_bn_prelu(level_4, (128,1,1), 'ori_3_1')
ori_3=Conv2D(90, (1,1), padding='same', name='ori_3_2')(ori_3)
seg_3=conv_bn_prelu(level_4, (128,1,1), 'seg_3_1')
seg_3=Conv2D(1, (1,1), padding='same', name='seg_3_2')(seg_3)
# sum fusion for ori
ori_out=Lambda(merge_sum)([ori_1, ori_2, ori_3])
ori_out_1=Activation('sigmoid', name='ori_out_1')(ori_out)
ori_out_2=Activation('sigmoid', name='ori_out_2')(ori_out)
# sum fusion for segmentation
seg_out=Lambda(merge_sum)([seg_1, seg_2, seg_3])
seg_out=Activation('sigmoid', name='seg_out')(seg_out)
# ----------------------------------------------------------------------------
# enhance part
filters_cos, filters_sin = gabor_bank(stride=2, Lambda=8)
filter_img_real = Conv2D(filters_cos.shape[3],(filters_cos.shape[0],filters_cos.shape[1]),
weights=[filters_cos, np.zeros([filters_cos.shape[3]])], padding='same',
name='enh_img_real_1')(img_input)
filter_img_imag = Conv2D(filters_sin.shape[3],(filters_sin.shape[0],filters_sin.shape[1]),
weights=[filters_sin, np.zeros([filters_sin.shape[3]])], padding='same',
name='enh_img_imag_1')(img_input)
ori_peak = Lambda(ori_highest_peak)(ori_out_1)
ori_peak = Lambda(select_max)(ori_peak) # select max ori and set it to 1
# Use this function to upsample image
upsample_ori = UpSampling2D(size=(8,8))(ori_peak)
seg_round = Activation('softsign')(seg_out)
upsample_seg = UpSampling2D(size=(8,8))(seg_round)
mul_mask_real = Lambda(merge_mul)([filter_img_real, upsample_ori])
enh_img_real = Lambda(reduce_sum, name='enh_img_real_2')(mul_mask_real)
mul_mask_imag = Lambda(merge_mul)([filter_img_imag, upsample_ori])
enh_img_imag = Lambda(reduce_sum, name='enh_img_imag_2')(mul_mask_imag)
enh_img = Lambda(atan2, name='phase_img')([enh_img_imag, enh_img_real])
enh_seg_img = Lambda(merge_concat, name='phase_seg_img')([enh_img, upsample_seg])
# ----------------------------------------------------------------------------
# mnt part
# =======Block 1 ========
mnt_conv1 = conv_bn_prelu(enh_seg_img, (64, 9, 9), 'mnt_1_1')
mnt_conv = conv_bn_prelu(mnt_conv1, (64, 9, 9), 'mnt_1_2')
mnt_conv = conv_bn_prelu(mnt_conv, (64, 9, 9), 'mnt_1_3')
mnt_conv = layers.add([mnt_conv, mnt_conv1])
mnt_conv1 = conv_bn_prelu(mnt_conv, (64, 9, 9), 'mnt_1_1b')
mnt_conv = conv_bn_prelu(mnt_conv1, (64, 9, 9), 'mnt_1_2b')
mnt_conv = conv_bn_prelu(mnt_conv, (64, 9, 9), 'mnt_1_3b')
mnt_conv = layers.add([mnt_conv, mnt_conv1])
mnt_conv = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(mnt_conv)
# ==========================
# =======Block 2 ========
mnt_conv1 = conv_bn_prelu(mnt_conv, (128, 5, 5), 'mnt_2_1')
mnt_conv = conv_bn_prelu(mnt_conv1, (128, 5, 5), 'mnt_2_2')
mnt_conv = conv_bn_prelu(mnt_conv, (128, 5, 5), 'mnt_2_3')
mnt_conv = layers.add([mnt_conv, mnt_conv1])
mnt_conv1 = conv_bn_prelu(mnt_conv, (128, 5, 5), 'mnt_2_1b')
mnt_conv = conv_bn_prelu(mnt_conv1, (128, 5, 5), 'mnt_2_2b')
mnt_conv = conv_bn_prelu(mnt_conv, (128, 5, 5), 'mnt_2_3b')
mnt_conv = layers.add([mnt_conv, mnt_conv1])
mnt_conv = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(mnt_conv)
# ==========================
# =======Block 3 ========
mnt_conv1 = conv_bn_prelu(mnt_conv, (256, 3, 3), 'mnt_3_1')
mnt_conv2 = conv_bn_prelu(mnt_conv1, (256, 3, 3), 'mnt_3_2')
mnt_conv3 = conv_bn_prelu(mnt_conv2, (256, 3, 3), 'mnt_3_3')
mnt_conv3 = layers.add([mnt_conv3, mnt_conv1])
mnt_conv4 = conv_bn_prelu(mnt_conv3, (256, 3, 3), 'mnt_3_4')
mnt_conv4 = layers.add([mnt_conv4, mnt_conv2])
mnt_conv = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(mnt_conv4)
# ==========================
mnt_o_1=Lambda(merge_concat)([mnt_conv, ori_out_1])
mnt_o_2=conv_bn_prelu(mnt_o_1, (256,1,1), 'mnt_o_1_1')
mnt_o_3=Conv2D(180, (1,1), padding='same', name='mnt_o_1_2')(mnt_o_2)
mnt_o_out=Activation('sigmoid', name='mnt_o_out')(mnt_o_3)
mnt_w_1=conv_bn_prelu(mnt_conv, (256,1,1), 'mnt_w_1_1')
mnt_w_2=Conv2D(8, (1,1), padding='same', name='mnt_w_1_2')(mnt_w_1)
mnt_w_out=Activation('sigmoid', name='mnt_w_out')(mnt_w_2)
mnt_h_1=conv_bn_prelu(mnt_conv, (256,1,1), 'mnt_h_1_1')
mnt_h_2=Conv2D(8, (1,1), padding='same', name='mnt_h_1_2')(mnt_h_1)
mnt_h_out=Activation('sigmoid', name='mnt_h_out')(mnt_h_2)
mnt_s_1=conv_bn_prelu(mnt_conv, (256,1,1), 'mnt_s_1_1')
mnt_s_2=Conv2D(1, (1,1), padding='same', name='mnt_s_1_2')(mnt_s_1)
mnt_s_out=Activation('sigmoid', name='mnt_s_out')(mnt_s_2)
if mode == 'deploy':
model = Model(inputs=[img_input,], outputs=[enh_img, enh_img_imag, enh_img_real, ori_out_1, ori_out_2, seg_out, mnt_o_out, mnt_w_out, mnt_h_out, mnt_s_out])
else:
model = Model(inputs=[img_input,], outputs=[ori_out_1, ori_out_2, seg_out, mnt_o_out, mnt_w_out, mnt_h_out, mnt_s_out])
if weights_path != None:
model.load_weights(weights_path, by_name=True)
return model
def train(input_shape=(400,400), train_set = None,output_dir='../output_CoarseNet/'+datetime.now().strftime('%Y%m%d-%H%M%S'),
pretrain_dir=None,batch_size=1,test_set=None, learning_config=None, logging=None):
img_name, folder_name, img_size = get_maximum_img_size_and_names(train_set, None, max_size=input_shape)
main_net_model = CoarseNetmodel((img_size[0], img_size[1], 1), pretrain_dir, 'train')
# Save model architecture
plot_model(main_net_model, to_file=output_dir+'/model.png',show_shapes=True)
main_net_model.compile(optimizer=learning_config,
loss={'seg_out':segmentation_loss,
'mnt_o_out': orientation_output_loss, 'mnt_w_out': orientation_output_loss,
'mnt_h_out': orientation_output_loss, 'mnt_s_out': minutiae_score_loss
},
loss_weights={'seg_out': .5, 'mnt_w_out': .5, 'mnt_h_out': .5, 'mnt_o_out': 100., 'mnt_s_out': 50.},
metrics={'seg_out':[seg_acc_pos, seg_acc_neg, seg_acc_all],
'mnt_o_out': [mnt_acc_delta_10, ],
'mnt_w_out': [mnt_mean_delta, ],
'mnt_h_out': [mnt_mean_delta, ],
'mnt_s_out': [seg_acc_pos, seg_acc_neg, seg_acc_all]})
writer = tf.summary.FileWriter(output_dir)
Best_F1_result = 0
Best_loss = 10000000
for epoch in range(1000):
outdir = "%s/saved_best_loss/" % (output_dir)
mkdir(outdir)
for i, train in enumerate(load_data((img_name, folder_name, img_size), tra_ori_model, rand=True, aug=0.7, batch_size=batch_size)):
loss = main_net_model.train_on_batch(train[0],
{'seg_out': train[3],
'mnt_w_out': train[4], 'mnt_h_out': train[5], 'mnt_o_out': train[6],
'mnt_s_out': train[7]
})
# Save the lowest loss for easy converge
if Best_loss > loss[0]:
savedir = "%s%s_%d_%s" % (outdir, str(epoch),i,str(loss[0]))
main_net_model.save_weights(savedir, True)
Best_loss = loss[0]
# Write log on screen at every 20 epochs
if i%(2/batch_size) == 0:
logging.info("epoch=%d, step=%d", epoch, i)
# Write details loss
logging.info("%s", " ".join(["%s:%.4f\t"%(x) for x in zip(main_net_model.metrics_names, loss)]))
# logging.info("Loss = %f Best loss = %f",loss[0],Best_loss)
# Show in tensorboard
for name, value in zip(main_net_model.metrics_names, loss):
summary = tf.Summary(value=[tf.Summary.Value(tag=name,simple_value=value), ])
writer.add_summary(summary, i)
# Evaluate every 5 epoch: for faster training
if epoch%10 == 0:
outdir = "%s/saved_models/" % (output_dir)
mkdir(outdir)
savedir = "%s%s" % (outdir, str(epoch))
main_net_model.save_weights(savedir, True)
for folder in test_set:
precision_test, recall_test, F1_test, precision_test_location, recall_test_location, F1_test_location = evaluate_training(savedir, [folder, ], logging=logging)
summary = tf.Summary(value=[tf.Summary.Value(tag="Precision", simple_value=precision_test),
tf.Summary.Value(tag="Recall", simple_value=recall_test),
tf.Summary.Value(tag="F1", simple_value=F1_test),
tf.Summary.Value(tag="Location Precision", simple_value=precision_test_location),
tf.Summary.Value(tag="Location Recall", simple_value=recall_test_location),
tf.Summary.Value(tag="Location F1", simple_value=F1_test_location), ])
writer.add_summary(summary, epoch)
# Only save the best result
if F1_test > Best_F1_result:
Best_F1_result = F1_test
# else:
# os.remove(savedir)
writer.close()
return
def evaluate_training(model_dir, test_set, logging=None, FineNet_path=None):
logging.info("Evaluating %s:" % (test_set))
# Prepare input info
img_name, folder_name, img_size = get_maximum_img_size_and_names(test_set)
main_net_model = CoarseNetmodel((None, None, 1), model_dir, 'test')
ave_prf_nms,ave_prf_nms_location = [],[]
for j, test in enumerate(
load_data((img_name, folder_name, img_size), tra_ori_model, rand=False, aug=0.0, batch_size=1)):
# logging.info("%d / %d: %s"%(j+1, len(img_name), img_name[j]))
ori_out_1, ori_out_2, seg_out, mnt_o_out, mnt_w_out, mnt_h_out, mnt_s_out = main_net_model.predict(test[0])
mnt_gt = label2mnt(test[7], test[4], test[5], test[6])
original_image = test[0].copy()
mnt_s_out = mnt_s_out * seg_out
# Does not useful to use this while training
final_minutiae_score_threashold = 0.45
early_minutiae_thres = final_minutiae_score_threashold + 0.05
isHavingFineNet = False
# In cases of small amount of minutiae given, try adaptive threshold
while final_minutiae_score_threashold >= 0:
mnt = label2mnt(mnt_s_out, mnt_w_out, mnt_h_out, mnt_o_out, thresh=early_minutiae_thres)
# Previous exp: 0.2
mnt_nms_1 = py_cpu_nms(mnt, 0.5)
mnt_nms_2 = nms(mnt)
# Make sure good result is given
if mnt_nms_1.shape[0] > 4 and mnt_nms_2.shape[0] > 4:
break
else:
final_minutiae_score_threashold = final_minutiae_score_threashold - 0.05
early_minutiae_thres = early_minutiae_thres - 0.05
mnt_nms = fuse_nms(mnt_nms_1, mnt_nms_2)
mnt_nms = mnt_nms[mnt_nms[:, 3] > early_minutiae_thres, :]
mnt_refined = []
if isHavingFineNet == True:
# ======= Verify using FineNet ============
patch_minu_radio = 22
if FineNet_path != None:
for idx_minu in range(mnt_nms.shape[0]):
try:
# Extract patch from image
x_begin = int(mnt_nms[idx_minu, 1]) - patch_minu_radio
y_begin = int(mnt_nms[idx_minu, 0]) - patch_minu_radio
patch_minu = original_image[x_begin:x_begin + 2 * patch_minu_radio,
y_begin:y_begin + 2 * patch_minu_radio]
patch_minu = cv2.resize(patch_minu, dsize=(224, 224), interpolation=cv2.INTER_NEAREST)
ret = np.empty((patch_minu.shape[0], patch_minu.shape[1], 3), dtype=np.uint8)
ret[:, :, 0] = patch_minu
ret[:, :, 1] = patch_minu
ret[:, :, 2] = patch_minu
patch_minu = ret
patch_minu = np.expand_dims(patch_minu, axis=0)
# # Can use class as hard decision
# # 0: minu 1: non-minu
# [class_Minutiae] = np.argmax(model_FineNet.predict(patch_minu), axis=1)
#
# if class_Minutiae == 0:
# mnt_refined.append(mnt_nms[idx_minu,:])
# Use soft decision: merge FineNet score with CoarseNet score
[isMinutiaeProb] = model_FineNet.predict(patch_minu)
isMinutiaeProb = isMinutiaeProb[0]
# print isMinutiaeProb
tmp_mnt = mnt_nms[idx_minu, :].copy()
tmp_mnt[3] = (4 * tmp_mnt[3] + isMinutiaeProb) / 5
mnt_refined.append(tmp_mnt)
except:
mnt_refined.append(mnt_nms[idx_minu, :])
else:
mnt_refined = mnt_nms
mnt_nms = np.array(mnt_refined)
if mnt_nms.shape[0] > 0:
mnt_nms = mnt_nms[mnt_nms[:, 3] > final_minutiae_score_threashold, :]
p, r, f, l, o = metric_P_R_F(mnt_gt, mnt_nms, 16, np.pi/6)
ave_prf_nms.append([p, r, f, l, o])
p, r, f, l, o = metric_P_R_F(mnt_gt, mnt_nms, 16, np.pi)
ave_prf_nms_location.append([p, r, f, l, o])
logging.info("Average testing results:")
ave_prf_nms = np.mean(np.array(ave_prf_nms), 0)
ave_prf_nms_location = np.mean(np.array(ave_prf_nms_location), 0)
logging.info(
"Precision: %f\tRecall: %f\tF1-measure: %f\tLocation_dis: %f\tOrientation_delta:%f\n----------------\n" % (
ave_prf_nms[0], ave_prf_nms[1], ave_prf_nms[2], ave_prf_nms[3], ave_prf_nms[4]))
return ave_prf_nms[0], ave_prf_nms[1], ave_prf_nms[2], ave_prf_nms_location[0], ave_prf_nms_location[1], ave_prf_nms_location[2]
def fuse_minu_orientation(dir_map, mnt, mode=1,block_size=16):
# mode is the way to fuse output minutiae with orientation
# 1: use orientation; 2: use minutiae; 3: fuse average
blkH, blkW = dir_map.shape
dir_map = dir_map%(2*np.pi)
if mode == 1:
for k in range(mnt.shape[0]):
# Choose nearest orientation
ori_value = dir_map[int(mnt[k, 1]//block_size),int(mnt[k, 0]//block_size)]
if 0 < mnt[k, 2] and mnt[k, 2] <= np.pi/2:
if 0 < ori_value and ori_value <= np.pi / 2:
mnt[k, 2] = ori_value
if np.pi / 2 < ori_value and ori_value <= np.pi:
if (ori_value - mnt[k, 2]) < (np.pi - ori_value + mnt[k, 2]):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value + np.pi
if np.pi < ori_value and ori_value <= 3*np.pi/2:
mnt[k, 2] = ori_value - np.pi
if 3*np.pi/2 < ori_value and ori_value <= 2 * np.pi:
if (np.pi*2 - ori_value + mnt[k, 2]) < (ori_value - np.pi - mnt[k, 2]):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value - np.pi
if np.pi/2 < mnt[k, 2] and mnt[k, 2] <= np.pi:
if 0 < ori_value and ori_value <= np.pi / 2:
if (mnt[k, 2] - ori_value) < (np.pi - ori_value + mnt[k, 2]):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value + np.pi
if np.pi / 2 < ori_value and ori_value <= np.pi:
mnt[k, 2] = ori_value
if np.pi < ori_value and ori_value <= 3*np.pi/2:
if (ori_value - mnt[k, 2]) < (mnt[k, 2] - ori_value + np.pi):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value - np.pi
if 3*np.pi/2 < ori_value and ori_value <= 2 * np.pi:
mnt[k, 2] = ori_value - np.pi
if np.pi < mnt[k, 2] and mnt[k, 2] <= 3*np.pi/2:
if 0 < ori_value and ori_value <= np.pi / 2:
mnt[k, 2] = ori_value + np.pi
if np.pi / 2 < ori_value and ori_value <= np.pi:
if (mnt[k, 2] - ori_value) < (ori_value + np.pi - mnt[k, 2]):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value + np.pi
if np.pi < ori_value and ori_value <= 3*np.pi/2:
mnt[k, 2] = ori_value
if 3*np.pi/2 < ori_value and ori_value <= 2 * np.pi:
if (ori_value - mnt[k, 2]) < (mnt[k, 2] - ori_value + np.pi):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value - np.pi
if 3*np.pi/2 < mnt[k, 2] and mnt[k, 2] <= 2*np.pi:
if 0 < ori_value and ori_value <= np.pi / 2:
if (np.pi - mnt[k, 2] + ori_value) < (mnt[k, 2] - np.pi - ori_value):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value + np.pi
if np.pi / 2 < ori_value and ori_value <= np.pi:
mnt[k, 2] = ori_value + np.pi
if np.pi < ori_value and ori_value <= 3*np.pi/2:
if (mnt[k, 2] - ori_value) < (np.pi*2 - mnt[k, 2] + ori_value - np.pi):
mnt[k, 2] = ori_value
else:
mnt[k, 2] = ori_value - np.pi
if 3*np.pi/2 < ori_value and ori_value <= 2 * np.pi:
mnt[k, 2] = ori_value
elif mode == 2:
return
elif mode ==3:
for k in range(mnt.shape[0]):
# Choose nearest orientation
ori_value = dir_map[int(mnt[k, 1] // block_size), int(mnt[k, 0] // block_size)]
if 0 < mnt[k, 2] and mnt[k, 2] <= np.pi / 2:
if 0 < ori_value and ori_value <= np.pi / 2:
fixed_ori = ori_value
if np.pi / 2 < ori_value and ori_value <= np.pi:
if (ori_value - mnt[k, 2]) < (np.pi - ori_value + mnt[k, 2]):
fixed_ori = ori_value
else:
fixed_ori = ori_value + np.pi
if np.pi < ori_value and ori_value <= 3 * np.pi / 2:
fixed_ori = ori_value - np.pi
if 3 * np.pi / 2 < ori_value and ori_value <= 2 * np.pi:
if (np.pi * 2 - ori_value + mnt[k, 2]) < (ori_value - np.pi - mnt[k, 2]):
fixed_ori = ori_value
else:
fixed_ori = ori_value - np.pi
if np.pi / 2 < mnt[k, 2] and mnt[k, 2] <= np.pi:
if 0 < ori_value and ori_value <= np.pi / 2:
if (mnt[k, 2] - ori_value) < (np.pi - ori_value + mnt[k, 2]):
fixed_ori = ori_value
else:
fixed_ori = ori_value + np.pi
if np.pi / 2 < ori_value and ori_value <= np.pi:
fixed_ori = ori_value
if np.pi < ori_value and ori_value <= 3 * np.pi / 2:
if (ori_value - mnt[k, 2]) < (mnt[k, 2] - ori_value + np.pi):
fixed_ori = ori_value
else:
fixed_ori = ori_value - np.pi
if 3 * np.pi / 2 < ori_value and ori_value <= 2 * np.pi:
fixed_ori = ori_value - np.pi
if np.pi < mnt[k, 2] and mnt[k, 2] <= 3 * np.pi / 2:
if 0 < ori_value and ori_value <= np.pi / 2:
fixed_ori = ori_value + np.pi
if np.pi / 2 < ori_value and ori_value <= np.pi:
if (mnt[k, 2] - ori_value) < (ori_value + np.pi - mnt[k, 2]):
fixed_ori = ori_value
else:
fixed_ori = ori_value + np.pi
if np.pi < ori_value and ori_value <= 3 * np.pi / 2:
fixed_ori = ori_value
if 3 * np.pi / 2 < ori_value and ori_value <= 2 * np.pi:
if (ori_value - mnt[k, 2]) < (mnt[k, 2] - ori_value + np.pi):
fixed_ori = ori_value
else:
fixed_ori = ori_value - np.pi
if 3 * np.pi / 2 < mnt[k, 2] and mnt[k, 2] <= 2 * np.pi:
if 0 < ori_value and ori_value <= np.pi / 2:
if (np.pi - mnt[k, 2] + ori_value) < (mnt[k, 2] - np.pi - ori_value):
fixed_ori = ori_value
else:
fixed_ori = ori_value + np.pi
if np.pi / 2 < ori_value and ori_value <= np.pi:
fixed_ori = ori_value + np.pi
if np.pi < ori_value and ori_value <= 3 * np.pi / 2:
if (mnt[k, 2] - ori_value) < (np.pi * 2 - mnt[k, 2] + ori_value - np.pi):
fixed_ori = ori_value
else:
fixed_ori = ori_value - np.pi
if 3 * np.pi / 2 < ori_value and ori_value <= 2 * np.pi:
fixed_ori = ori_value
mnt[k, 2] = (mnt[k, 2] + fixed_ori)/2.0
else:
return
def deploy_with_GT(deploy_set, output_dir, model_path, FineNet_path=None, set_name=None):
if set_name is None:
set_name = deploy_set.split('/')[-2]
# Read image and GT
img_name, folder_name, img_size = get_maximum_img_size_and_names(deploy_set)
mkdir(output_dir + '/'+ set_name + '/')
mkdir(output_dir + '/' + set_name + '/mnt_results/')
mkdir(output_dir + '/'+ set_name + '/seg_results/')
mkdir(output_dir + '/' + set_name + '/OF_results/')
logging.info("Predicting %s:" % (set_name))
isHavingFineNet = False
main_net_model = CoarseNetmodel((None, None, 1), model_path, mode='deploy')
if isHavingFineNet == True:
# ====== Load FineNet to verify
model_FineNet = FineNetmodel(num_classes=2,
pretrained_path=FineNet_path,
input_shape=(224,224,3))
model_FineNet.compile(loss='categorical_crossentropy',
optimizer=Adam(lr=0),
metrics=['accuracy'])
time_c = []
ave_prf_nms=[]
for i, test in enumerate(
load_data((img_name, folder_name, img_size), tra_ori_model, rand=False, aug=0.0, batch_size=1)):
print i, img_name[i]
logging.info("%s %d / %d: %s" % (set_name, i + 1, len(img_name), img_name[i]))
time_start = time()
image = misc.imread(deploy_set + 'img_files/' + img_name[i] + '.bmp', mode='L')# / 255.0
mask = misc.imread(deploy_set + 'seg_files/' + img_name[i] + '.bmp', mode='L') / 255.0
img_size = image.shape
img_size = np.array(img_size, dtype=np.int32) // 8 * 8
image = image[:img_size[0], :img_size[1]]
mask = mask[:img_size[0], :img_size[1]]
original_image = image.copy()
# Generate OF
texture_img = FastEnhanceTexture(image, sigma=2.5, show=False)
dir_map, fre_map = get_maps_STFT(texture_img, patch_size=64, block_size=16, preprocess=True)
image = np.reshape(image, [1, image.shape[0], image.shape[1], 1])
enh_img, enh_img_imag, enhance_img, ori_out_1, ori_out_2, seg_out, mnt_o_out, mnt_w_out, mnt_h_out, mnt_s_out \
= main_net_model.predict(image)
time_afterconv = time()
# Use post processing to smooth image
round_seg = np.round(np.squeeze(seg_out))
seg_out = 1 - round_seg
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
seg_out = cv2.morphologyEx(seg_out, cv2.MORPH_CLOSE, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
seg_out = cv2.morphologyEx(seg_out, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
seg_out = cv2.dilate(seg_out, kernel)
# If use mask from outside
# seg_out = cv2.resize(mask, dsize=(seg_out.shape[1], seg_out.shape[0]))
mnt_gt = label2mnt(test[7], test[4], test[5], test[6])
final_minutiae_score_threashold = 0.45
early_minutiae_thres = final_minutiae_score_threashold + 0.05
# In cases of small amount of minutiae given, try adaptive threshold
while final_minutiae_score_threashold >= 0:
mnt = label2mnt(np.squeeze(mnt_s_out) * np.round(np.squeeze(seg_out)), mnt_w_out, mnt_h_out, mnt_o_out,
thresh=early_minutiae_thres)
# Previous exp: 0.2
mnt_nms_1 = py_cpu_nms(mnt, 0.5)
mnt_nms_2 = nms(mnt)
# Make sure good result is given
if mnt_nms_1.shape[0] > 4 and mnt_nms_2.shape[0] > 4:
break
else:
final_minutiae_score_threashold = final_minutiae_score_threashold - 0.05
early_minutiae_thres = early_minutiae_thres - 0.05
mnt_nms = fuse_nms(mnt_nms_1, mnt_nms_2)
mnt_nms = mnt_nms[mnt_nms[:, 3] > early_minutiae_thres, :]
mnt_refined = []
if isHavingFineNet == True:
# ======= Verify using FineNet ============
patch_minu_radio = 22
if FineNet_path != None:
for idx_minu in range(mnt_nms.shape[0]):
try:
# Extract patch from image
x_begin = int(mnt_nms[idx_minu, 1]) - patch_minu_radio
y_begin = int(mnt_nms[idx_minu, 0]) - patch_minu_radio
patch_minu = original_image[x_begin:x_begin + 2 * patch_minu_radio,
y_begin:y_begin + 2 * patch_minu_radio]
patch_minu = cv2.resize(patch_minu, dsize=(224, 224), interpolation=cv2.INTER_NEAREST)
ret = np.empty((patch_minu.shape[0], patch_minu.shape[1], 3), dtype=np.uint8)
ret[:, :, 0] = patch_minu
ret[:, :, 1] = patch_minu
ret[:, :, 2] = patch_minu
patch_minu = ret
patch_minu = np.expand_dims(patch_minu, axis=0)
# # Can use class as hard decision
# # 0: minu 1: non-minu
# [class_Minutiae] = np.argmax(model_FineNet.predict(patch_minu), axis=1)
#
# if class_Minutiae == 0:
# mnt_refined.append(mnt_nms[idx_minu,:])
# Use soft decision: merge FineNet score with CoarseNet score
[isMinutiaeProb] = model_FineNet.predict(patch_minu)
isMinutiaeProb = isMinutiaeProb[0]
# print isMinutiaeProb
tmp_mnt = mnt_nms[idx_minu, :].copy()
tmp_mnt[3] = (4*tmp_mnt[3] + isMinutiaeProb) / 5
mnt_refined.append(tmp_mnt)
except:
mnt_refined.append(mnt_nms[idx_minu, :])
else:
mnt_refined = mnt_nms
mnt_nms = np.array(mnt_refined)
if mnt_nms.shape[0] > 0:
mnt_nms = mnt_nms[mnt_nms[:, 3] > final_minutiae_score_threashold, :]
final_mask = ndimage.zoom(np.round(np.squeeze(seg_out)), [8, 8], order=0)
# Show the orientation
show_orientation_field(original_image, dir_map + np.pi, mask=final_mask,
fname="%s/%s/OF_results/%s_OF.jpg" % (output_dir, set_name, img_name[i]))
fuse_minu_orientation(dir_map, mnt_nms, mode=3)
time_afterpost = time()
mnt_writer(mnt_nms, img_name[i], img_size, "%s/%s/mnt_results/%s.mnt" % (output_dir, set_name, img_name[i]))
draw_minutiae_overlay_with_score(image, mnt_nms, mnt_gt[:, :3], "%s/%s/%s_minu.jpg"%(output_dir, set_name, img_name[i]),saveimage=True)
# misc.imsave("%s/%s/%s_score.jpg"%(output_dir, set_name, img_name[i]), np.squeeze(mnt_s_out_upscale))
misc.imsave("%s/%s/seg_results/%s_seg.jpg" % (output_dir, set_name, img_name[i]), final_mask)
time_afterdraw = time()
time_c.append([time_afterconv - time_start, time_afterpost - time_afterconv, time_afterdraw - time_afterpost])
logging.info(
"load+conv: %.3fs, seg-postpro+nms: %.3f, draw: %.3f" % (time_c[-1][0], time_c[-1][1], time_c[-1][2]))
# Metrics calculating
p, r, f, l, o = metric_P_R_F(mnt_gt, mnt_nms)
ave_prf_nms.append([p, r, f, l, o])
print p,r,f
time_c = np.mean(np.array(time_c), axis=0)
ave_prf_nms = np.mean(np.array(ave_prf_nms), 0)
print "Precision: %f\tRecall: %f\tF1-measure: %f" % (ave_prf_nms[0], ave_prf_nms[1], ave_prf_nms[2])
logging.info(
"Average: load+conv: %.3fs, oir-select+seg-post+nms: %.3f, draw: %.3f" % (time_c[0], time_c[1], time_c[2]))
return
def inference(deploy_set, output_dir, model_path, FineNet_path=None, set_name=None, file_ext='.bmp', isHavingFineNet = False):
if set_name is None:
set_name = deploy_set.split('/')[-2]
mkdir(output_dir + '/'+ set_name + '/')
mkdir(output_dir + '/' + set_name + '/mnt_results/')
mkdir(output_dir + '/'+ set_name + '/seg_results/')
mkdir(output_dir + '/' + set_name + '/OF_results/')
logging.info("Predicting %s:" % (set_name))
_, img_name = get_files_in_folder(deploy_set+ 'img_files/', file_ext)
print deploy_set
# ====== Load FineNet to verify
if isHavingFineNet == True:
model_FineNet = FineNetmodel(num_classes=2,
pretrained_path=FineNet_path,
input_shape=(224,224,3))
model_FineNet.compile(loss='categorical_crossentropy',
optimizer=Adam(lr=0),
metrics=['accuracy'])
time_c = []
main_net_model = CoarseNetmodel((None, None, 1), model_path, mode='deploy')
for i in xrange(0, len(img_name)):
print i
image = misc.imread(deploy_set + 'img_files/'+ img_name[i] + file_ext, mode='L') # / 255.0
img_size = image.shape
img_size = np.array(img_size, dtype=np.int32) // 8 * 8
# read the mask from files
try:
mask = misc.imread(deploy_set + 'seg_files/' + img_name[i] + '.jpg', mode='L') / 255.0
except:
mask = np.ones((img_size[0],img_size[1]))
image = image[:img_size[0], :img_size[1]]
mask = mask[:img_size[0], :img_size[1]]
original_image = image.copy()
texture_img = FastEnhanceTexture(image, sigma=2.5, show=False)
dir_map, fre_map = get_maps_STFT(texture_img, patch_size=64, block_size=16, preprocess=True)
image = image*mask
logging.info("%s %d / %d: %s" % (set_name, i + 1, len(img_name), img_name[i]))
time_start = time()
image = np.reshape(image, [1, image.shape[0], image.shape[1], 1])
enh_img, enh_img_imag, enhance_img, ori_out_1, ori_out_2, seg_out, mnt_o_out, mnt_w_out, mnt_h_out, mnt_s_out \
= main_net_model.predict(image)
time_afterconv = time()
# If use mask from model
round_seg = np.round(np.squeeze(seg_out))
seg_out = 1 - round_seg
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
seg_out = cv2.morphologyEx(seg_out, cv2.MORPH_CLOSE, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
seg_out = cv2.morphologyEx(seg_out, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
seg_out = cv2.dilate(seg_out, kernel)
# If use mask from outside
# seg_out = cv2.resize(mask, dsize=(seg_out.shape[1], seg_out.shape[0]))
max_num_minu = 20
min_num_minu = 6
early_minutiae_thres = 0.5
# New adaptive threshold
mnt = label2mnt(np.squeeze(mnt_s_out) * np.round(np.squeeze(seg_out)), mnt_w_out, mnt_h_out, mnt_o_out,
thresh=0)
# Previous exp: 0.2
mnt_nms_1 = py_cpu_nms(mnt, 0.5)
mnt_nms_2 = nms(mnt)
mnt_nms_1.view('f8,f8,f8,f8').sort(order=['f3'], axis=0)
mnt_nms_1 = mnt_nms_1[::-1]
mnt_nms_1_copy = mnt_nms_1.copy()
mnt_nms_2_copy = mnt_nms_2.copy()
# Adaptive threshold goes here
# Make sure the maximum number of minutiae is max_num_minu
# Sort minutiae by score
while early_minutiae_thres > 0:
mnt_nms_1 = mnt_nms_1_copy[mnt_nms_1_copy[:, 3] > early_minutiae_thres, :]
mnt_nms_2 = mnt_nms_2_copy[mnt_nms_2_copy[:, 3] > early_minutiae_thres, :]
if mnt_nms_1.shape[0]>max_num_minu or mnt_nms_2.shape[0]>max_num_minu:
mnt_nms_1 = mnt_nms_1[:max_num_minu,:]
mnt_nms_2 = mnt_nms_2[:max_num_minu, :]
if mnt_nms_1.shape[0] > min_num_minu and mnt_nms_2.shape[0] > min_num_minu:
break
early_minutiae_thres = early_minutiae_thres - 0.05
mnt_nms = fuse_nms(mnt_nms_1, mnt_nms_2)
final_minutiae_score_threashold = early_minutiae_thres - 0.05
print early_minutiae_thres, final_minutiae_score_threashold
mnt_refined = []
if isHavingFineNet == True:
# ======= Verify using FineNet ============
patch_minu_radio = 22
if FineNet_path != None:
for idx_minu in range(mnt_nms.shape[0]):
try:
# Extract patch from image
x_begin = int(mnt_nms[idx_minu, 1]) - patch_minu_radio
y_begin = int(mnt_nms[idx_minu, 0]) - patch_minu_radio
patch_minu = original_image[x_begin:x_begin + 2 * patch_minu_radio,
y_begin:y_begin + 2 * patch_minu_radio]
patch_minu = cv2.resize(patch_minu, dsize=(224, 224),interpolation=cv2.INTER_NEAREST)
ret = np.empty((patch_minu.shape[0], patch_minu.shape[1], 3), dtype=np.uint8)
ret[:, :, 0] = patch_minu
ret[:, :, 1] = patch_minu
ret[:, :, 2] = patch_minu
patch_minu = ret
patch_minu = np.expand_dims(patch_minu, axis=0)
# # Can use class as hard decision
# # 0: minu 1: non-minu
# [class_Minutiae] = np.argmax(model_FineNet.predict(patch_minu), axis=1)
#
# if class_Minutiae == 0:
# mnt_refined.append(mnt_nms[idx_minu,:])
# Use soft decision: merge FineNet score with CoarseNet score
[isMinutiaeProb] = model_FineNet.predict(patch_minu)
isMinutiaeProb = isMinutiaeProb[0]
#print isMinutiaeProb
tmp_mnt = mnt_nms[idx_minu, :].copy()
tmp_mnt[3] = (4*tmp_mnt[3] + isMinutiaeProb)/5
mnt_refined.append(tmp_mnt)
except:
mnt_refined.append(mnt_nms[idx_minu, :])
else:
mnt_refined = mnt_nms
mnt_nms_backup = mnt_nms.copy()
mnt_nms = np.array(mnt_refined)
if mnt_nms.shape[0] > 0:
mnt_nms = mnt_nms[mnt_nms[:,3]>final_minutiae_score_threashold,:]
final_mask = ndimage.zoom(np.round(np.squeeze(seg_out)), [8, 8], order=0)
# Show the orientation
show_orientation_field(original_image, dir_map + np.pi, mask=final_mask, fname="%s/%s/OF_results/%s_OF.jpg" % (output_dir, set_name, img_name[i]))
fuse_minu_orientation(dir_map, mnt_nms, mode=3)
time_afterpost = time()
mnt_writer(mnt_nms, img_name[i], img_size, "%s/%s/mnt_results/%s.mnt"%(output_dir, set_name, img_name[i]))
draw_minutiae(original_image, mnt_nms, "%s/%s/%s_minu.jpg"%(output_dir, set_name, img_name[i]),saveimage=True)
misc.imsave("%s/%s/seg_results/%s_seg.jpg" % (output_dir, set_name, img_name[i]), final_mask)
time_afterdraw = time()
time_c.append([time_afterconv - time_start, time_afterpost - time_afterconv, time_afterdraw - time_afterpost])
logging.info(
"load+conv: %.3fs, seg-postpro+nms: %.3f, draw: %.3f" % (time_c[-1][0], time_c[-1][1], time_c[-1][2]))
# time_c = np.mean(np.array(time_c), axis=0)
# logging.info(
# "Average: load+conv: %.3fs, oir-select+seg-post+nms: %.3f, draw: %.3f" % (time_c[0], time_c[1], time_c[2]))
return
@@ -0,0 +1,65 @@
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
https://arxiv.org/pdf/1712.09401.pdf
If you use whole or partial function in this code, please cite paper:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
"""
from __future__ import absolute_import
from __future__ import division
import os
os.environ['KERAS_BACKEND'] = 'tensorflow'
from datetime import datetime
from keras import backend as K
from MinutiaeNet_utils import *
from CoarseNet_utils import *
from CoarseNet_model import *
os.environ["CUDA_VISIBLE_DEVICES"] = '0'
config = K.tf.ConfigProto(gpu_options=K.tf.GPUOptions(allow_growth=True))
sess = K.tf.Session(config=config)
K.set_session(sess)
# mode = 'inference'
mode = 'deploy'
# Can use multiple folders for deploy, inference
deploy_set = ['../Dataset/CoarseNet_train/',]
inference_set = ['../Dataset/CoarseNet_test/',]
pretrain_dir = '../Models/CoarseNet.h5'
output_dir = '../output_CoarseNet/'+datetime.now().strftime('%Y%m%d-%H%M%S')
FineNet_dir = '../Models/FineNet.h5'
def main():
if mode == 'deploy':
output_dir = '../output_CoarseNet/deployResults/' +datetime.now().strftime('%Y%m%d-%H%M%S')
logging = init_log(output_dir)
for i, folder in enumerate(deploy_set):
deploy_with_GT(folder, output_dir=output_dir, model_path=pretrain_dir, FineNet_path=FineNet_dir)
# evaluate_training(model_dir=pretrain_dir, test_set=folder, logging=logging)
elif mode == 'inference':
output_dir = '../output_CoarseNet/inferenceResults/' +datetime.now().strftime('%Y%m%d-%H%M%S')
logging = init_log(output_dir)
for i, folder in enumerate(inference_set):
inference(folder, output_dir=output_dir, model_path=pretrain_dir, FineNet_path=FineNet_dir, file_ext='.bmp',
isHavingFineNet=False)
else:
pass
if __name__ =='__main__':
main()
@@ -0,0 +1,69 @@
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
https://arxiv.org/pdf/1712.09401.pdf
If you use whole or partial function in this code, please cite paper:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
"""
from __future__ import absolute_import
from __future__ import division
import os
os.environ['KERAS_BACKEND'] = 'tensorflow'
from datetime import datetime
from MinutiaeNet_utils import *
from keras import backend as K
from keras.optimizers import SGD, Adam
from CoarseNet_utils import *
from CoarseNet_model import *
import argparse
parser = argparse.ArgumentParser(description='Minutiae Net')
parser.add_argument('lr', type=str, default="0.005",
help='Setting learning rate')
parser.add_argument('GPU', type=str, default="0",
help='Choosing GPU')
args = parser.parse_args()
os.environ["CUDA_VISIBLE_DEVICES"] = args.GPU
config = K.tf.ConfigProto(gpu_options=K.tf.GPUOptions(allow_growth=True))
sess = K.tf.Session(config=config)
K.set_session(sess)
batch_size = 2
use_multiprocessing = False
input_size = 400
# Can use multiple folders for training
train_set = ['../Dataset/CoarseNet_train/',]
validate_set = ['../path/to/your/data/',]
pretrain_dir = '../Models/CoarseNet.h5'
output_dir = '../output_CoarseNet/'+datetime.now().strftime('%Y%m%d-%H%M%S')
FineNet_dir = '../Models/FineNet.h5'
if __name__ =='__main__':
output_dir = '../output_CoarseNet/trainResults/' + datetime.now().strftime('%Y%m%d-%H%M%S')
logging = init_log(output_dir)
logging.info("Learning rate = %s", args.lr)
logging.info("Pretrain dir = %s", pretrain_dir)
train(input_shape=(input_size, input_size), train_set=train_set, output_dir=output_dir,
pretrain_dir=pretrain_dir, batch_size=batch_size, test_set=validate_set,
learning_config=Adam(lr=float(args.lr), beta_1=0.9, beta_2=0.999, epsilon=1e-08, clipnorm=0.9),
logging=logging)
@@ -0,0 +1,386 @@
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
https://arxiv.org/pdf/1712.09401.pdf
If you use whole or partial function in this code, please cite paper:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
"""
from functools import partial
from multiprocessing import Pool
from MinutiaeNet_utils import *
from scipy import misc, ndimage, signal, sparse
import numpy as np
from keras import backend as K
from keras.models import Model
from keras.layers import Input
from keras.layers.core import Lambda
import tensorflow as tf
def sub_load_data(data, img_size, aug):
img_name, dataset = data
img = misc.imread(dataset+'img_files/'+img_name+'.bmp', mode='L')
try:
seg = misc.imread(dataset + 'seg_files/' + img_name + '.bmp', mode='L')
except:
seg = np.ones_like(img)
try:
ali = misc.imread(dataset+'ori_files/'+img_name+'.jpg', mode='L')
except:
ali = np.zeros_like(img)
mnt = np.array(mnt_reader(dataset+'mnt_files/'+img_name+'.mnt'), dtype=float)
if any(img.shape != img_size):
# random pad mean values to reach required shape
if np.random.rand()<aug:
tra = np.int32(np.random.rand(2)*(np.array(img_size)-np.array(img.shape)))
else:
tra = np.int32(0.5*(np.array(img_size)-np.array(img.shape)))
img_t = np.ones(img_size)*np.mean(img)
seg_t = np.zeros(img_size)
ali_t = np.ones(img_size)*np.mean(ali)
img_t[tra[0]:tra[0]+img.shape[0],tra[1]:tra[1]+img.shape[1]] = img
seg_t[tra[0]:tra[0]+img.shape[0],tra[1]:tra[1]+img.shape[1]] = seg
ali_t[tra[0]:tra[0]+img.shape[0],tra[1]:tra[1]+img.shape[1]] = ali
img = img_t
seg = seg_t
ali = ali_t
mnt = mnt+np.array([tra[1],tra[0],0])
if np.random.rand()<aug:
# random rotation [0 - 360] & translation img_size / 4
rot = np.random.rand() * 360
tra = (np.random.rand(2)-0.5) / 2 * img_size
img = ndimage.rotate(img, rot, reshape=False, mode='reflect')
img = ndimage.shift(img, tra, mode='reflect')
seg = ndimage.rotate(seg, rot, reshape=False, mode='constant')
seg = ndimage.shift(seg, tra, mode='constant')
ali = ndimage.rotate(ali, rot, reshape=False, mode='reflect')
ali = ndimage.shift(ali, tra, mode='reflect')
mnt_r = point_rot(mnt[:, :2], rot/180*np.pi, img.shape, img.shape)
mnt = np.column_stack((mnt_r+tra[[1, 0]], mnt[:, 2]-rot/180*np.pi))
# only keep mnt that stay in pic & not on border
mnt = mnt[(8<=mnt[:,0])*(mnt[:,0]<img_size[1]-8)*(8<=mnt[:, 1])*(mnt[:,1]<img_size[0]-8), :]
return img, seg, ali, mnt
use_multiprocessing = False
def load_data(dataset, tra_ori_model, rand=False, aug=0.0, batch_size=1, sample_rate=None):
if type(dataset[0]) == str:
img_name, folder_name, img_size = get_maximum_img_size_and_names(dataset, sample_rate)
else:
img_name, folder_name, img_size = dataset
if rand:
rand_idx = np.arange(len(img_name))
np.random.shuffle(rand_idx)
img_name = img_name[rand_idx]
folder_name = folder_name[rand_idx]
if batch_size > 1 and use_multiprocessing==True:
p = Pool(batch_size)
p_sub_load_data = partial(sub_load_data, img_size=img_size, aug=aug)
for i in xrange(0,len(img_name), batch_size):
have_alignment = np.ones([batch_size, 1, 1, 1])
image = np.zeros((batch_size, img_size[0], img_size[1], 1))
segment = np.zeros((batch_size, img_size[0], img_size[1], 1))
alignment = np.zeros((batch_size, img_size[0], img_size[1], 1))
minutiae_w = np.zeros((batch_size, img_size[0]/8, img_size[1]/8, 1))-1
minutiae_h = np.zeros((batch_size, img_size[0]/8, img_size[1]/8, 1))-1
minutiae_o = np.zeros((batch_size, img_size[0]/8, img_size[1]/8, 1))-1
batch_name = [img_name[(i+j)%len(img_name)] for j in xrange(batch_size)]
batch_f_name = [folder_name[(i+j)%len(img_name)] for j in xrange(batch_size)]
if batch_size > 1 and use_multiprocessing==True:
results = p.map(p_sub_load_data, zip(batch_name, batch_f_name))
else:
results = map(p_sub_load_data, zip(batch_name, batch_f_name))
for j in xrange(batch_size):
img, seg, ali, mnt = results[j]
if np.sum(ali) == 0:
have_alignment[j, 0, 0, 0] = 0
image[j, :, :, 0] = img / 255.0
segment[j, :, :, 0] = seg / 255.0
alignment[j, :, :, 0] = ali / 255.0
minutiae_w[j, (mnt[:, 1]/8).astype(int), (mnt[:, 0]/8).astype(int), 0] = mnt[:, 0] % 8
minutiae_h[j, (mnt[:, 1]/8).astype(int), (mnt[:, 0]/8).astype(int), 0] = mnt[:, 1] % 8
minutiae_o[j, (mnt[:, 1]/8).astype(int), (mnt[:, 0]/8).astype(int), 0] = mnt[:, 2]
# get seg
label_seg = segment[:, ::8, ::8, :]
label_seg[label_seg>0] = 1
label_seg[label_seg<=0] = 0
minutiae_seg = (minutiae_o!=-1).astype(float)
# get ori & mnt
orientation = tra_ori_model.predict(alignment)
orientation = orientation/np.pi*180+90
orientation[orientation>=180.0] = 0.0 # orientation [0, 180)
minutiae_o = minutiae_o/np.pi*180+90 # [90, 450)
minutiae_o[minutiae_o>360] = minutiae_o[minutiae_o>360]-360 # to current coordinate system [0, 360)
minutiae_ori_o = np.copy(minutiae_o) # copy one
minutiae_ori_o[minutiae_ori_o>=180] = minutiae_ori_o[minutiae_ori_o>=180]-180 # for strong ori label [0,180)
# ori 2 gaussian
gaussian_pdf = signal.gaussian(361, 3)
y = np.reshape(np.arange(1, 180, 2), [1,1,1,-1])
delta = np.array(np.abs(orientation - y), dtype=int)
delta = np.minimum(delta, 180-delta)+180
label_ori = gaussian_pdf[delta]
# ori_o 2 gaussian
delta = np.array(np.abs(minutiae_ori_o - y), dtype=int)
delta = np.minimum(delta, 180-delta)+180
label_ori_o = gaussian_pdf[delta]
# mnt_o 2 gaussian
y = np.reshape(np.arange(1, 360, 2), [1,1,1,-1])
delta = np.array(np.abs(minutiae_o - y), dtype=int)
delta = np.minimum(delta, 360-delta)+180
label_mnt_o = gaussian_pdf[delta]
# w 2 gaussian
gaussian_pdf = signal.gaussian(17, 2)
y = np.reshape(np.arange(0, 8), [1,1,1,-1])
delta = (minutiae_w-y+8).astype(int)
label_mnt_w = gaussian_pdf[delta]
# h 2 gaussian
delta = (minutiae_h-y+8).astype(int)
label_mnt_h = gaussian_pdf[delta]
# mnt cls label -1:neg, 0:no care, 1:pos
label_mnt_s = np.copy(minutiae_seg)
label_mnt_s[label_mnt_s==0] = -1 # neg to -1
label_mnt_s = (label_mnt_s+ndimage.maximum_filter(label_mnt_s, size=(1,3,3,1)))/2 # around 3*3 pos -> 0
# apply segmentation
label_ori = label_ori * label_seg * have_alignment
label_ori_o = label_ori_o * minutiae_seg
label_mnt_o = label_mnt_o * minutiae_seg
label_mnt_w = label_mnt_w * minutiae_seg
label_mnt_h = label_mnt_h * minutiae_seg
yield image, label_ori, label_ori_o, label_seg, label_mnt_w, label_mnt_h, label_mnt_o, label_mnt_s, batch_name
if batch_size > 1 and use_multiprocessing==True:
p.close()
p.join()
return
def merge_mul(x):
return reduce(lambda x,y:x*y, x)
def merge_sum(x):
return reduce(lambda x,y:x+y, x)
def reduce_sum(x):
return K.sum(x,axis=-1,keepdims=True)
# Group with depth
def merge_concat(x):
return K.tf.concat(x,3)
def select_max(x):
x = x / (K.max(x, axis=-1, keepdims=True)+K.epsilon())
x = K.tf.where(K.tf.greater(x, 0.999), x, K.tf.zeros_like(x)) # select the biggest one
x = x / (K.sum(x, axis=-1, keepdims=True)+K.epsilon()) # prevent two or more ori is selected
return x
kernal2angle = np.reshape(np.arange(1, 180, 2, dtype=float), [1,1,1,90])/90.*np.pi #2angle = angle*2
sin2angle, cos2angle = np.sin(kernal2angle), np.cos(kernal2angle)
def ori2angle(ori):
sin2angle_ori = K.sum(ori*sin2angle, -1, keepdims=True)
cos2angle_ori = K.sum(ori*cos2angle, -1, keepdims=True)
modulus_ori = K.sqrt(K.square(sin2angle_ori)+K.square(cos2angle_ori))
return sin2angle_ori, cos2angle_ori, modulus_ori
# find highest peak using gaussian
def ori_highest_peak(y_pred, length=180):
glabel = gausslabel(length=length,stride=2).astype(np.float32)
y_pred = tf.convert_to_tensor(y_pred, np.float32)
ori_gau = K.conv2d(y_pred,glabel,padding='same')
return ori_gau
def ori_acc_delta_k(y_true, y_pred, k=10, max_delta=180):
# get ROI
label_seg = K.sum(y_true, axis=-1)
label_seg = K.tf.cast(K.tf.greater(label_seg, 0), K.tf.float32)
# get pred angle
angle = K.cast(K.argmax(ori_highest_peak(y_pred, max_delta), axis=-1), dtype=K.tf.float32)*2.0+1.0
# get gt angle
angle_t = K.cast(K.argmax(y_true, axis=-1), dtype=K.tf.float32)*2.0+1.0
# get delta
angle_delta = K.abs(angle_t - angle)
acc = K.tf.less_equal(K.minimum(angle_delta, max_delta-angle_delta), k)
acc = K.cast(acc, dtype=K.tf.float32)
# apply ROI
acc = acc*label_seg
acc = K.sum(acc) / (K.sum(label_seg)+K.epsilon())
return acc
def ori_acc_delta_10(y_true, y_pred):
return ori_acc_delta_k(y_true, y_pred, 10)
def ori_acc_delta_20(y_true, y_pred):
return ori_acc_delta_k(y_true, y_pred, 20)
def mnt_acc_delta_10(y_true, y_pred):
return ori_acc_delta_k(y_true, y_pred, 10, 360)
def mnt_acc_delta_20(y_true, y_pred):
return ori_acc_delta_k(y_true, y_pred, 20, 360)
def seg_acc_pos(y_true, y_pred):
y_true = K.tf.where(K.tf.less(y_true,0.0), K.tf.zeros_like(y_true), y_true)
acc = K.cast(K.equal(y_true, K.round(y_pred)), dtype=K.tf.float32)
acc = K.sum(acc * y_true) / (K.sum(y_true)+K.epsilon())
return acc
def seg_acc_neg(y_true, y_pred):
y_true = K.tf.where(K.tf.less(y_true,0.0), K.tf.zeros_like(y_true), y_true)
acc = K.cast(K.equal(y_true, K.round(y_pred)), dtype=K.tf.float32)
acc = K.sum(acc * (1-y_true)) / (K.sum(1-y_true)+K.epsilon())
return acc
def seg_acc_all(y_true, y_pred):
y_true = K.tf.where(K.tf.less(y_true,0.0), K.tf.zeros_like(y_true), y_true)
return K.mean(K.equal(y_true, K.round(y_pred)))
def mnt_mean_delta(y_true, y_pred):
# get ROI
label_seg = K.sum(y_true, axis=-1)
label_seg = K.tf.cast(K.tf.greater(label_seg, 0), K.tf.float32)
# get pred pos
pos = K.cast(K.argmax(y_pred, axis=-1), dtype=K.tf.float32)
# get gt pos
pos_t = K.cast(K.argmax(y_true, axis=-1), dtype=K.tf.float32)
# get delta
pos_delta = K.abs(pos_t - pos)
# apply ROI
pos_delta = pos_delta*label_seg
mean_delta = K.sum(pos_delta) / (K.sum(label_seg)+K.epsilon())
return mean_delta
# currently can only produce one each time
def label2mnt(mnt_s_out, mnt_w_out, mnt_h_out, mnt_o_out, thresh=0.5):
mnt_s_out = np.squeeze(mnt_s_out)
mnt_w_out = np.squeeze(mnt_w_out)
mnt_h_out = np.squeeze(mnt_h_out)
mnt_o_out = np.squeeze(mnt_o_out)
assert len(mnt_s_out.shape)==2 and len(mnt_w_out.shape)==3 and len(mnt_h_out.shape)==3 and len(mnt_o_out.shape)==3
# get cls results
mnt_sparse = sparse.coo_matrix(mnt_s_out>thresh)
mnt_list = np.array(zip(mnt_sparse.row, mnt_sparse.col), dtype=np.int32)
if mnt_list.shape[0] == 0:
return np.zeros((0, 4))
# get regression results
mnt_w_out = np.argmax(mnt_w_out, axis=-1)
mnt_h_out = np.argmax(mnt_h_out, axis=-1)
mnt_o_out = np.argmax(mnt_o_out, axis=-1) # TODO: use ori_highest_peak(np version)
# get final mnt
mnt_final = np.zeros((len(mnt_list), 4))
mnt_final[:, 0] = mnt_sparse.col*8 + mnt_w_out[mnt_list[:,0], mnt_list[:,1]]
mnt_final[:, 1] = mnt_sparse.row*8 + mnt_h_out[mnt_list[:,0], mnt_list[:,1]]
mnt_final[:, 2] = (mnt_o_out[mnt_list[:,0], mnt_list[:,1]]*2-89.)/180*np.pi
mnt_final[mnt_final[:, 2]<0.0, 2] = mnt_final[mnt_final[:, 2]<0.0, 2]+2*np.pi
# New one
mnt_final[:, 2] = (-mnt_final[:, 2]) % (2*np.pi)
mnt_final[:, 3] = mnt_s_out[mnt_list[:,0], mnt_list[:, 1]]
return mnt_final
# image normalization
def img_normalization(img_input, m0=0.0, var0=1.0):
m = K.mean(img_input, axis=[1,2,3], keepdims=True)
var = K.var(img_input, axis=[1,2,3], keepdims=True)
after = K.sqrt(var0*K.tf.square(img_input-m)/var)
image_n = K.tf.where(K.tf.greater(img_input, m), m0+after, m0-after)
return image_n
# atan2 function
def atan2(y_x):
y, x = y_x[0], y_x[1]+K.epsilon()
atan = K.tf.atan(y/x)
angle = K.tf.where(K.tf.greater(x,0.0), atan, K.tf.zeros_like(x))
angle = K.tf.where(K.tf.logical_and(K.tf.less(x,0.0), K.tf.greater_equal(y,0.0)), atan+np.pi, angle)
angle = K.tf.where(K.tf.logical_and(K.tf.less(x,0.0), K.tf.less(y,0.0)), atan-np.pi, angle)
return angle
# traditional orientation estimation
def orientation(image, stride=8, window=17):
with K.tf.name_scope('orientation'):
assert image.get_shape().as_list()[3] == 1, 'Images must be grayscale'
strides = [1, stride, stride, 1]
E = np.ones([window, window, 1, 1])
sobelx = np.reshape(np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=float), [3, 3, 1, 1])
sobely = np.reshape(np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=float), [3, 3, 1, 1])
gaussian = np.reshape(gaussian2d((5, 5), 1), [5, 5, 1, 1])
with K.tf.name_scope('sobel_gradient'):
Ix = K.tf.nn.conv2d(image, sobelx, strides=[1,1,1,1], padding='SAME', name='sobel_x')
Iy = K.tf.nn.conv2d(image, sobely, strides=[1,1,1,1], padding='SAME', name='sobel_y')
with K.tf.name_scope('eltwise_1'):
Ix2 = K.tf.multiply(Ix, Ix, name='IxIx')
Iy2 = K.tf.multiply(Iy, Iy, name='IyIy')
Ixy = K.tf.multiply(Ix, Iy, name='IxIy')
with K.tf.name_scope('range_sum'):
Gxx = K.tf.nn.conv2d(Ix2, E, strides=strides, padding='SAME', name='Gxx_sum')
Gyy = K.tf.nn.conv2d(Iy2, E, strides=strides, padding='SAME', name='Gyy_sum')
Gxy = K.tf.nn.conv2d(Ixy, E, strides=strides, padding='SAME', name='Gxy_sum')
with K.tf.name_scope('eltwise_2'):
Gxx_Gyy = K.tf.subtract(Gxx, Gyy, name='Gxx_Gyy')
theta = atan2([2*Gxy, Gxx_Gyy]) + np.pi
# two-dimensional low-pass filter: Gaussian filter here
with K.tf.name_scope('gaussian_filter'):
phi_x = K.tf.nn.conv2d(K.tf.cos(theta), gaussian, strides=[1,1,1,1], padding='SAME', name='gaussian_x')
phi_y = K.tf.nn.conv2d(K.tf.sin(theta), gaussian, strides=[1,1,1,1], padding='SAME', name='gaussian_y')
theta = atan2([phi_y, phi_x])/2
return theta
def get_tra_ori():
img_input=Input(shape=(None, None, 1))
theta = Lambda(orientation)(img_input)
model = Model(inputs=[img_input,], outputs=[theta,])
return model
tra_ori_model = get_tra_ori()
def get_maximum_img_size_and_names(dataset, sample_rate=None, max_size=None):
if isinstance(dataset, basestring):
dataset = [dataset]
if sample_rate is None:
sample_rate = [1]*len(dataset)
img_name, folder_name, img_size = [], [], []
for folder, rate in zip(dataset, sample_rate):
_, img_name_t = get_files_in_folder(folder, 'img_files/*'+'.bmp')
img_name.extend(img_name_t.tolist()*rate)
folder_name.extend([folder]*img_name_t.shape[0]*rate)
img_size.append(np.array(misc.imread(folder + 'img_files/' + img_name_t[0] + '.bmp', mode='L').shape))
img_name = np.asarray(img_name)
folder_name = np.asarray(folder_name)
img_size = np.max(np.asarray(img_size), axis=0)
# let img_size % 8 == 0
img_size = np.array(np.ceil(img_size / 8) * 8, dtype=np.int32)
return img_name, folder_name, img_size
@@ -0,0 +1,95 @@
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
https://arxiv.org/pdf/1712.09401.pdf
If you use whole or partial function in this code, please cite paper:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
"""
from __future__ import absolute_import
from __future__ import division
from keras.models import Model
from keras.layers import Activation, AveragePooling2D, BatchNormalization, Concatenate, Conv2D, Dense, GlobalAveragePooling2D
from keras.layers import Input, Lambda, MaxPooling2D
from keras.applications.imagenet_utils import _obtain_input_shape
from keras import backend as K
import numpy as np
from CoarseNet_utils import *
def orientation_loss(y_true, y_pred, lamb=1.):
# clip
y_pred = K.tf.clip_by_value(y_pred, K.epsilon(), 1 - K.epsilon())
# get ROI
label_seg = K.sum(y_true, axis=-1, keepdims=True)
label_seg = K.tf.cast(K.tf.greater(label_seg, 0), K.tf.float32)
# weighted cross entropy loss
lamb_pos, lamb_neg = 1., 1.
logloss = lamb_pos*y_true*K.log(y_pred)+lamb_neg*(1-y_true)*K.log(1-y_pred)
logloss = logloss*label_seg # apply ROI
logloss = -K.sum(logloss) / (K.sum(label_seg) + K.epsilon())
# coherence loss, nearby ori should be as near as possible
# Oritentation coherence loss
# 3x3 ones kernel
mean_kernal = np.reshape(np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]], dtype=np.float32)/8, [3, 3, 1, 1])
sin2angle_ori, cos2angle_ori, modulus_ori = ori2angle(y_pred)
sin2angle = K.conv2d(sin2angle_ori, mean_kernal, padding='same')
cos2angle = K.conv2d(cos2angle_ori, mean_kernal, padding='same')
modulus = K.conv2d(modulus_ori, mean_kernal, padding='same')
coherence = K.sqrt(K.square(sin2angle) + K.square(cos2angle)) / (modulus + K.epsilon())
coherenceloss = K.sum(label_seg) / (K.sum(coherence*label_seg) + K.epsilon()) - 1
loss = logloss + lamb*coherenceloss
return loss
def orientation_output_loss(y_true, y_pred):
# clip
y_pred = K.tf.clip_by_value(y_pred, K.epsilon(), 1 - K.epsilon())
# get ROI
label_seg = K.sum(y_true, axis=-1, keepdims=True)
label_seg = K.tf.cast(K.tf.greater(label_seg, 0), K.tf.float32)
# weighted cross entropy loss
lamb_pos, lamb_neg= 1., 1.
logloss = lamb_pos*y_true*K.log(y_pred)+lamb_neg*(1-y_true)*K.log(1-y_pred)
logloss = logloss*label_seg # apply ROI
logloss = -K.sum(logloss) / (K.sum(label_seg) + K.epsilon())
return logloss
def segmentation_loss(y_true, y_pred, lamb=1.):
# clip
y_pred = K.tf.clip_by_value(y_pred, K.epsilon(), 1 - K.epsilon())
# weighted cross entropy loss
total_elements = K.sum(K.tf.ones_like(y_true))
label_pos = K.tf.cast(K.tf.greater(y_true, 0.0), K.tf.float32)
lamb_pos = 0.5 * total_elements / K.sum(label_pos)
lamb_neg = 1 / (2 - 1/lamb_pos)
logloss = lamb_pos*y_true*K.log(y_pred)+lamb_neg*(1-y_true)*K.log(1-y_pred)
logloss = -K.mean(K.sum(logloss, axis=-1))
# smooth loss
smooth_kernal = np.reshape(np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], dtype=np.float32)/8, [3, 3, 1, 1])
smoothloss = K.mean(K.abs(K.conv2d(y_pred, smooth_kernal)))
loss = logloss + lamb*smoothloss
return loss
def minutiae_score_loss(y_true, y_pred):
# clip
y_pred = K.tf.clip_by_value(y_pred, K.epsilon(), 1 - K.epsilon())
# get ROI
label_seg = K.tf.cast(K.tf.not_equal(y_true, 0.0), K.tf.float32)
y_true = K.tf.where(K.tf.less(y_true,0.0), K.tf.zeros_like(y_true), y_true) # set -1 -> 0
# weighted cross entropy loss
total_elements = K.sum(label_seg) + K.epsilon()
lamb_pos, lamb_neg = 10., .5
logloss = lamb_pos*y_true*K.log(y_pred)+lamb_neg*(1-y_true)*K.log(1-y_pred)
# apply ROI
logloss = logloss*label_seg
logloss = -K.sum(logloss) / total_elements
return logloss
@@ -0,0 +1,810 @@
import os
import glob
import shutil
import logging
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage, misc, signal, spatial
from skimage.filters import gaussian
import cv2
import math
def mkdir(path):
if not os.path.exists(path):
os.makedirs(path)
def re_mkdir(path):
if os.path.exists(path):
shutil.rmtree(path)
os.makedirs(path)
def init_log(output_dir):
re_mkdir(output_dir)
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(message)s',
datefmt='%Y%m%d-%H:%M:%S',
filename=os.path.join(output_dir, 'log.log'),
filemode='w')
console = logging.StreamHandler()
console.setLevel(logging.INFO)
logging.getLogger('').addHandler(console)
return logging
def copy_file(path_s, path_t):
shutil.copy(path_s, path_t)
def get_files_in_folder(folder, file_ext=None):
files = glob.glob(os.path.join(folder, "*" + file_ext))
files_name = []
for i in files:
_, name = os.path.split(i)
name, ext = os.path.splitext(name)
files_name.append(name)
return np.asarray(files), np.asarray(files_name)
def point_rot(points, theta, b_size, a_size):
cosA = np.cos(theta)
sinA = np.sin(theta)
b_center = [b_size[1]/2.0, b_size[0]/2.0]
a_center = [a_size[1]/2.0, a_size[0]/2.0]
points = np.dot(points-b_center, np.array([[cosA,-sinA],[sinA,cosA]]))+a_center
return points
def mnt_reader(file_name):
f = open(file_name)
minutiae = []
for i, line in enumerate(f):
if i < 4 or len(line) == 0: continue
w, h, o = [float(x) for x in line.split()]
w, h = int(round(w)), int(round(h))
minutiae.append([w, h, o])
f.close()
return minutiae
def mnt_writer(mnt, image_name, image_size, file_name):
f = open(file_name, 'w')
f.write('%s\n'%(image_name))
f.write('%d %d %d\n'%(mnt.shape[0], image_size[0], image_size[1]))
for i in xrange(mnt.shape[0]):
f.write('%d %d %.6f %.4f\n'%(mnt[i,0], mnt[i,1], mnt[i,2], mnt[i,3]))
f.close()
return
def gabor_fn(ksize, sigma, theta, Lambda, psi, gamma):
sigma_x = sigma
sigma_y = float(sigma) / gamma
# Bounding box
nstds = 3
xmax = ksize[0]/2
ymax = ksize[1]/2
xmin = -xmax
ymin = -ymax
(y, x) = np.meshgrid(np.arange(ymin, ymax + 1), np.arange(xmin, xmax + 1))
# Rotation
x_theta = x * np.cos(theta) + y * np.sin(theta)
y_theta = -x * np.sin(theta) + y * np.cos(theta)
gb_cos = np.exp(-.5 * (x_theta ** 2 / sigma_x ** 2 + y_theta ** 2 / sigma_y ** 2)) * np.cos(2 * np.pi / Lambda * x_theta + psi)
gb_sin = np.exp(-.5 * (x_theta ** 2 / sigma_x ** 2 + y_theta ** 2 / sigma_y ** 2)) * np.sin(2 * np.pi / Lambda * x_theta + psi)
return gb_cos, gb_sin
def gabor_bank(stride=2,Lambda=8):
filters_cos = np.ones([25,25,180/stride], dtype=float)
filters_sin = np.ones([25,25,180/stride], dtype=float)
for n, i in enumerate(xrange(-90,90,stride)):
theta = i*np.pi/180.
kernel_cos, kernel_sin = gabor_fn((24,24),4.5, -theta, Lambda, 0, 0.5)
filters_cos[..., n] = kernel_cos
filters_sin[..., n] = kernel_sin
filters_cos = np.reshape(filters_cos,[25,25,1,-1])
filters_sin = np.reshape(filters_sin,[25,25,1,-1])
return filters_cos, filters_sin
def gaussian2d(shape=(5,5),sigma=0.5):
"""
2D gaussian mask - should give the same result as MATLAB's
fspecial('gaussian',[shape],[sigma])
"""
m,n = [(ss-1.)/2. for ss in shape]
y,x = np.ogrid[-m:m+1,-n:n+1]
h = np.exp( -(x*x + y*y) / (2.*sigma*sigma) )
h[ h < np.finfo(h.dtype).eps*h.max() ] = 0
sumh = h.sum()
if sumh != 0:
h /= sumh
return h
def gausslabel(length=180, stride=2):
gaussian_pdf = signal.gaussian(length+1, 3)
label = np.reshape(np.arange(stride/2, length, stride), [1,1,-1,1])
y = np.reshape(np.arange(stride/2, length, stride), [1,1,1,-1])
delta = np.array(np.abs(label - y), dtype=int)
delta = np.minimum(delta, length-delta)+length/2
return gaussian_pdf[delta]
def angle_delta(A, B, max_D=np.pi*2):
delta = np.abs(A - B)
delta = np.minimum(delta, max_D-delta)
return delta
def fmeasure(P, R):
return 2*P*R/(P+R+1e-10)
def distance(y_true, y_pred, max_D=16, max_O=np.pi/6):
D = spatial.distance.cdist(y_true[:, :2], y_pred[:, :2], 'euclidean')
O = spatial.distance.cdist(np.reshape(y_true[:, 2], [-1, 1]), np.reshape(y_pred[:, 2], [-1, 1]), angle_delta)
return (D<=max_D)*(O<=max_O)
def metric_P_R_F(y_true, y_pred, maxd=16, maxo=np.pi/6):
# Calculate Precision, Recall, F-score
if y_pred.shape[0]==0 or y_true.shape[0]==0:
return 0,0,0,0,0
y_true, y_pred = np.array(y_true), np.array(y_pred)
total_gt, total = float(y_true.shape[0]), float(y_pred.shape[0])
# Using L2 loss
dis = spatial.distance.cdist(y_pred[:, :2], y_true[:, :2], 'euclidean')
mindis,idx = dis.min(axis=1),dis.argmin(axis=1)
#Change to adapt to new annotation: old version. When training, comment it
# y_pred[:,2] = -y_pred[:,2]
angle = abs(np.mod(y_pred[:,2],2*np.pi) - y_true[idx,2])
angle = np.asarray([angle, 2*np.pi-angle]).min(axis=0)
# Satisfy the threshold
tmp=(mindis <= maxd) & (angle<=maxo)
#print('mindis,idx,angle,tmp=%s,%s,%s,%s'%(mindis,idx,angle,tmp))
precision = len(np.unique(idx[(mindis <= maxd) & (angle<=maxo)]))/float(y_pred.shape[0])
recall = len(np.unique(idx[(mindis <= maxd) & (angle<=maxo)]))/float(y_true.shape[0])
#print('pre=%f/ %f'%(len(np.unique(idx[(mindis <= maxd) & (angle<=maxo)])),float(y_pred.shape[0])))
#print('recall=%f/ %f'%(len(np.unique(idx[(mindis <= maxd) & (angle<=maxo)])),float(y_true.shape[0])))
if recall!=0:
loc = np.mean(mindis[(mindis <= maxd) & (angle<=maxo)])
ori = np.mean(angle[(mindis <= maxd) & (angle<=maxo)])
else:
loc = 0
ori = 0
return precision, recall, fmeasure(precision, recall), loc, ori
def nms(mnt):
if mnt.shape[0]==0:
return mnt
# sort score
mnt_sort = mnt.tolist()
mnt_sort.sort(key=lambda x:x[3], reverse=True)
mnt_sort = np.array(mnt_sort)
# cal distance
inrange = distance(mnt_sort, mnt_sort, max_D=16, max_O=np.pi/6).astype(np.float32)
keep_list = np.ones(mnt_sort.shape[0])
for i in xrange(mnt_sort.shape[0]):
if keep_list[i] == 0:
continue
keep_list[i+1:] = keep_list[i+1:]*(1-inrange[i, i+1:])
return mnt_sort[keep_list.astype(np.bool), :]
def fuse_nms(mnt, mnt_set_2):
if mnt.shape[0]==0:
return mnt
# sort score
all_mnt = np.concatenate((mnt, mnt_set_2))
mnt_sort = all_mnt.tolist()
mnt_sort.sort(key=lambda x:x[3], reverse=True)
mnt_sort = np.array(mnt_sort)
# cal distance
inrange = distance(mnt_sort, mnt_sort, max_D=16, max_O=2*np.pi).astype(np.float32)
keep_list = np.ones(mnt_sort.shape[0])
for i in xrange(mnt_sort.shape[0]):
if keep_list[i] == 0:
continue
keep_list[i+1:] = keep_list[i+1:]*(1-inrange[i, i+1:])
return mnt_sort[keep_list.astype(np.bool), :]
def py_cpu_nms(det, thresh):
if det.shape[0]==0:
return det
dets = det.tolist()
dets.sort(key=lambda x:x[3], reverse=True)
dets = np.array(dets)
box_sz = 25
x1 = np.reshape(dets[:,0],[-1,1]) -box_sz
y1 = np.reshape(dets[:,1],[-1,1]) -box_sz
x2 = np.reshape(dets[:,0],[-1,1]) +box_sz
y2 = np.reshape(dets[:,1],[-1,1]) +box_sz
scores = dets[:, 2]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
inter = w * h
ovr = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(ovr <= thresh)[0]
order = order[inds + 1]
return dets[keep, :]
def draw_minutiae(image, minutiae, fname, saveimage= False, r=15, drawScore=False):
image = np.squeeze(image)
fig = plt.figure()
plt.imshow(image,cmap='gray')
plt.hold(True)
# Check if no minutiae
if minutiae.shape[0] > 0:
plt.plot(minutiae[:, 0], minutiae[:, 1], 'rs', fillstyle='none', linewidth=1)
for x, y, o, s in minutiae:
plt.plot([x, x+r*np.cos(o)], [y, y+r*np.sin(o)], 'r-')
if drawScore == True:
plt.text(x - 10, y - 10, '%.2f' % s, color='yellow', fontsize=4)
plt.axis([0,image.shape[1],image.shape[0],0])
plt.axis('off')
if saveimage:
plt.savefig(fname, dpi=500, bbox_inches='tight', pad_inches = 0)
plt.close(fig)
else:
plt.show()
return
def draw_minutiae_overlay(image, minutiae, mnt_gt, fname, saveimage= False, r=15, drawScore=False):
image = np.squeeze(image)
fig = plt.figure()
plt.imshow(image,cmap='gray')
plt.hold(True)
if mnt_gt.shape[1] > 3:
mnt_gt = mnt_gt[:,:3]
if mnt_gt.shape[0] > 0:
if mnt_gt.shape[1] > 3:
mnt_gt = mnt_gt[:, :3]
plt.plot(mnt_gt[:, 0], mnt_gt[:, 1], 'bs', fillstyle='none', linewidth=1)
for x, y, o in mnt_gt:
plt.plot([x, x+r*np.cos(o)], [y, y+r*np.sin(o)], 'b-')
if minutiae.shape[0] > 0:
plt.plot(minutiae[:, 0], minutiae[:, 1], 'rs', fillstyle='none', linewidth=1)
for x, y, o in minutiae:
plt.plot([x, x+r*np.cos(o)], [y, y+r*np.sin(o)], 'r-')
if drawScore == True:
plt.text(x - 10, y - 10, '%.2f' % s, color='yellow', fontsize=4)
plt.axis([0,image.shape[1],image.shape[0],0])
plt.axis('off')
plt.show()
if saveimage:
plt.savefig(fname, dpi=500, bbox_inches='tight')
plt.close(fig)
else:
plt.show()
return
def draw_minutiae_overlay_with_score(image, minutiae, mnt_gt, fname, saveimage=False, r=15):
image = np.squeeze(image)
fig = plt.figure()
plt.imshow(image, cmap='gray')
plt.hold(True)
if mnt_gt.shape[0] > 0:
plt.plot(mnt_gt[:, 0], mnt_gt[:, 1], 'bs', fillstyle='none', linewidth=1)
if mnt_gt.shape[1] > 3:
for x, y, o, s in mnt_gt:
plt.plot([x, x + r * np.cos(o)], [y, y + r * np.sin(o)], 'b-')
plt.text(x - 10, y - 5, '%.2f' % s, color='green', fontsize=4)
else:
for x, y, o in mnt_gt:
plt.plot([x, x + r * np.cos(o)], [y, y + r * np.sin(o)], 'b-')
if minutiae.shape[0] > 0:
plt.plot(minutiae[:, 0], minutiae[:, 1], 'rs', fillstyle='none', linewidth=1)
for x, y, o, s in minutiae:
plt.plot([x, x + r * np.cos(o)], [y, y + r * np.sin(o)], 'r-')
plt.text(x-10,y-10,'%.2f'%s,color='yellow',fontsize=4)
plt.axis([0, image.shape[1], image.shape[0], 0])
plt.axis('off')
if saveimage:
plt.savefig(fname, dpi=500, bbox_inches='tight')
plt.close(fig)
else:
plt.show()
return
def draw_ori_on_img(img, ori, mask, fname, saveimage=False, coh=None, stride=16):
ori = np.squeeze(ori)
#mask = np.squeeze(np.round(mask))
img = np.squeeze(img)
ori = ndimage.zoom(ori, np.array(img.shape)/np.array(ori.shape, dtype=float), order=0)
if mask.shape != img.shape:
mask = ndimage.zoom(mask, np.array(img.shape)/np.array(mask.shape, dtype=float), order=0)
if coh is None:
coh = np.ones_like(img)
fig = plt.figure()
plt.imshow(img,cmap='gray')
plt.hold(True)
for i in xrange(stride,img.shape[0],stride):
for j in xrange(stride,img.shape[1],stride):
if mask[i, j] == 0:
continue
x, y, o, r = j, i, ori[i,j], coh[i,j]*(stride*0.9)
plt.plot([x, x+r*np.cos(o)], [y, y+r*np.sin(o)], 'r-')
plt.axis([0,img.shape[1],img.shape[0],0])
plt.axis('off')
if saveimage:
plt.savefig(fname, bbox_inches='tight')
plt.close(fig)
else:
plt.show()
return
def local_constrast_enhancement(img):
img = img.astype(np.float32)
meanV = cv2.blur(img,(15,15))
normalized = img - meanV
var = abs(normalized)
var = cv2.blur(var,(15,15))
normalized = normalized/(var+10) *0.75
normalized = np.clip(normalized, -1, 1)
normalized = (normalized+1)*127.5
return normalized
def get_quality_map_ori_dict(img, dict, spacing, dir_map = None, block_size = 16):
if img.dtype=='uint8':
img = img.astype(np.float)
img = FastEnhanceTexture(img)
h, w = img.shape
blkH, blkW = dir_map.shape
quality_map = np.zeros((blkH,blkW),dtype=np.float)
fre_map = np.zeros((blkH,blkW),dtype=np.float)
ori_num = len(dict)
#dir_map = math.pi/2 - dir_map
dir_ind = dir_map*ori_num/math.pi
dir_ind = dir_ind.astype(np.int)
dir_ind = dir_ind%ori_num
patch_size = np.sqrt(dict[0].shape[1])
patch_size = patch_size.astype(np.int)
pad_size = (patch_size-block_size)//2
img = np.lib.pad(img, (pad_size, pad_size), 'symmetric')
for i in range(0,blkH):
for j in range(0,blkW):
ind = dir_ind[i,j]
patch = img[i*block_size:i*block_size+patch_size,j*block_size:j*block_size+patch_size]
patch = patch.reshape(patch_size*patch_size,)
patch = patch - np.mean(patch)
patch = patch / (np.linalg.norm(patch)+0.0001)
patch[patch>0.05] = 0.05
patch[patch<-0.05] = -0.05
simi = np.dot(dict[ind], patch)
similar_ind = np.argmax(abs(simi))
quality_map[i,j] = np.max(abs(simi))
fre_map[i,j] = 1./spacing[ind][similar_ind]
quality_map = gaussian(quality_map,sigma=2)
return quality_map, fre_map
def FastEnhanceTexture(img,sigma=2.5,show=False):
img = img.astype(np.float32)
h, w = img.shape
h2 = 2 ** nextpow2(h)
w2 = 2 ** nextpow2(w)
FFTsize = np.max([h2, w2])
x, y = np.meshgrid(range(-FFTsize / 2, FFTsize / 2), range(-FFTsize / 2, FFTsize / 2))
r = np.sqrt(x * x + y * y) + 0.0001
r = r/FFTsize
L = 1. / (1 + (2 * math.pi * r * sigma)** 4)
img_low = LowpassFiltering(img, L)
gradim1= compute_gradient_norm(img)
gradim1 = LowpassFiltering(gradim1,L)
gradim2= compute_gradient_norm(img_low)
gradim2 = LowpassFiltering(gradim2,L)
diff = gradim1-gradim2
ar1 = np.abs(gradim1)
diff[ar1>1] = diff[ar1>1]/ar1[ar1>1]
diff[ar1 <= 1] = 0
cmin = 0.3
cmax = 0.7
weight = (diff-cmin)/(cmax-cmin)
weight[diff<cmin] = 0
weight[diff>cmax] = 1
u = weight * img_low + (1-weight)* img
temp = img - u
lim = 20
temp1 = (temp + lim) * 255 / (2 * lim)
temp1[temp1 < 0] = 0
temp1[temp1 >255] = 255
v = temp1
if show:
plt.imshow(v,cmap='gray')
plt.show()
return v
def compute_gradient_norm(input):
input = input.astype(np.float32)
Gx, Gy = np.gradient(input)
out = np.sqrt(Gx * Gx + Gy * Gy) + 0.000001
return out
def LowpassFiltering(img,L):
h,w = img.shape
h2,w2 = L.shape
img = cv2.copyMakeBorder(img, 0, h2-h, 0, w2-w, cv2.BORDER_CONSTANT, value=0)
img_fft = np.fft.fft2(img)
img_fft = np.fft.fftshift(img_fft)
img_fft = img_fft * L
rec_img = np.fft.ifft2(np.fft.fftshift(img_fft))
rec_img = np.real(rec_img)
rec_img = rec_img[:h,:w]
return rec_img
def nextpow2(x):
return int(math.ceil(math.log(x, 2)))
def construct_dictionary(ori_num = 30):
ori_dict = []
s = []
for i in range(ori_num):
ori_dict.append([])
s.append([])
patch_size2 = 16
patch_size = 32
dict_all = []
spacing_all = []
ori_all = []
Y, X = np.meshgrid(range(-patch_size2,patch_size2), range(-patch_size2,patch_size2))
for spacing in range(6,13):
for valley_spacing in range(3,spacing//2):
ridge_spacing = spacing - valley_spacing
for k in range(ori_num):
theta = np.pi/2-k*np.pi / ori_num
X_r = X * np.cos(theta) - Y * np.sin(theta)
for offset in range(0,spacing-1,2):
X_r_offset = X_r + offset + ridge_spacing / 2
X_r_offset = np.remainder(X_r_offset, spacing)
Y1 = np.zeros((patch_size, patch_size))
Y2 = np.zeros((patch_size, patch_size))
Y1[X_r_offset <= ridge_spacing] = X_r_offset[X_r_offset <= ridge_spacing]
Y2[X_r_offset > ridge_spacing] = X_r_offset[X_r_offset > ridge_spacing] - ridge_spacing
element = -np.sin(2 * math.pi * (Y1 / ridge_spacing / 2)) + np.sin(2 * math.pi * (Y2 / valley_spacing / 2))
element = element.reshape(patch_size*patch_size,)
element = element-np.mean(element)
element = element/ np.linalg.norm(element)
ori_dict[k].append(element)
s[k].append(spacing)
dict_all.append(element)
spacing_all.append(1.0/spacing)
ori_all.append(theta)
for i in range(len(ori_dict)):
ori_dict[i] = np.asarray(ori_dict[i])
s[k] = np.asarray(s[k])
dict_all = np.asarray(dict_all)
dict_all = np.transpose(dict_all)
spacing_all = np.asarray(spacing_all)
ori_all = np.asarray(ori_all)
return ori_dict, s, dict_all, ori_all,spacing_all
def get_maps_STFT(img,patch_size = 64,block_size = 16, preprocess = False):
assert len(img.shape) == 2
nrof_dirs = 16
ovp_size = (patch_size-block_size)//2
if preprocess:
img = FastEnhanceTexture(img, sigma=2.5, show=False)
img = np.lib.pad(img, (ovp_size,ovp_size),'symmetric')
h,w = img.shape
blkH = (h - patch_size)//block_size+1
blkW = (w - patch_size)//block_size+1
local_info = np.empty((blkH,blkW),dtype = object)
x, y = np.meshgrid(range(-patch_size / 2,patch_size / 2), range(-patch_size / 2,patch_size / 2))
x = x.astype(np.float32)
y = y.astype(np.float32)
r = np.sqrt(x*x + y*y) + 0.0001
RMIN = 3 # min allowable ridge spacing
RMAX = 18 # maximum allowable ridge spacing
FLOW = patch_size / RMAX
FHIGH = patch_size / RMIN
dRLow = 1. / (1 + (r / FHIGH) ** 4)
dRHigh = 1. / (1 + (FLOW / r) ** 4)
dBPass = dRLow * dRHigh # bandpass
dir = np.arctan2(y,x)
dir[dir<0] = dir[dir<0] + math.pi
dir_ind = np.floor(dir/(math.pi/nrof_dirs))
dir_ind = dir_ind.astype(np.int,copy=False)
dir_ind[dir_ind==nrof_dirs] = 0
dir_ind_list = []
for i in range(nrof_dirs):
tmp = np.argwhere(dir_ind == i)
dir_ind_list.append(tmp)
sigma = patch_size/3
weight = np.exp(-(x*x + y*y)/(sigma*sigma))
for i in range(0,blkH):
for j in range(0,blkW):
patch =img[i*block_size:i*block_size+patch_size,j*block_size:j*block_size+patch_size].copy()
local_info[i,j] = local_STFT(patch,weight,dBPass)
local_info[i, j].analysis(r,dir_ind_list)
# get the ridge flow from the local information
dir_map,fre_map = get_ridge_flow_top(local_info)
dir_map = smooth_dir_map(dir_map)
return dir_map, fre_map
def smooth_dir_map(dir_map,sigma=2.0,mask = None):
cos2Theta = np.cos(dir_map * 2)
sin2Theta = np.sin(dir_map * 2)
if mask is not None:
assert (dir_map.shape[0] == mask.shape[0])
assert (dir_map.shape[1] == mask.shape[1])
cos2Theta[mask == 0] = 0
sin2Theta[mask == 0] = 0
cos2Theta = gaussian(cos2Theta, sigma, multichannel=False, mode='reflect')
sin2Theta = gaussian(sin2Theta, sigma, multichannel=False, mode='reflect')
dir_map = np.arctan2(sin2Theta,cos2Theta)*0.5
return dir_map
def get_ridge_flow_top(local_info):
blkH,blkW = local_info.shape
dir_map = np.zeros((blkH,blkW)) - 10
fre_map = np.zeros((blkH, blkW)) - 10
for i in range(blkH):
for j in range(blkW):
if local_info[i,j].ori is None:
continue
dir_map[i,j] = local_info[i,j].ori[0] #+ math.pi*0.5
fre_map[i,j] = local_info[i,j].fre[0]
return dir_map,fre_map
class local_STFT:
def __init__(self,patch,weight = None, dBPass = None):
if weight is not None:
patch = patch * weight
patch = patch - np.mean(patch)
norm = np.linalg.norm(patch)
patch = patch / (norm+0.000001)
f = np.fft.fft2(patch)
fshift = np.fft.fftshift(f)
if dBPass is not None:
fshift = dBPass * fshift
self.patch_FFT = fshift
self.patch = patch
self.ori = None
self.fre = None
self.confidence = None
self.patch_size = patch.shape[0]
def analysis(self,r,dir_ind_list=None,N=2):
assert(dir_ind_list is not None)
energy = np.abs(self.patch_FFT)
energy = energy / (np.sum(energy)+0.00001)
nrof_dirs = len(dir_ind_list)
ori_interval = math.pi/nrof_dirs
ori_interval2 = ori_interval/2
pad_size = 1
dir_norm = np.zeros((nrof_dirs + 2,))
for i in range(nrof_dirs):
tmp = energy[dir_ind_list[i][:, 0], dir_ind_list[i][:, 1]]
dir_norm[i + 1] = np.sum(tmp)
dir_norm[0] = dir_norm[nrof_dirs]
dir_norm[nrof_dirs + 1] = dir_norm[1]
# smooth dir_norm
smoothed_dir_norm = dir_norm
for i in range(1, nrof_dirs + 1):
smoothed_dir_norm[i] = (dir_norm[i - 1] + dir_norm[i] * 4 + dir_norm[i + 1]) / 6
smoothed_dir_norm[0] = smoothed_dir_norm[nrof_dirs]
smoothed_dir_norm[nrof_dirs + 1] = smoothed_dir_norm[1]
den = np.sum(smoothed_dir_norm[1:nrof_dirs + 1]) + 0.00001 # verify if den == 1
smoothed_dir_norm = smoothed_dir_norm/den # normalization if den == 1, this line can be removed
ori = []
fre = []
confidence = []
wenergy = energy*r
for i in range(1, nrof_dirs+1):
if smoothed_dir_norm[i] > smoothed_dir_norm[i-1] and smoothed_dir_norm[i] > smoothed_dir_norm[i+1]:
tmp_ori = (i-pad_size)*ori_interval + ori_interval2 + math.pi/2
ori.append(tmp_ori)
confidence.append(smoothed_dir_norm[i])
tmp_fre = np.sum(wenergy[dir_ind_list[i-pad_size][:, 0], dir_ind_list[i-pad_size][:, 1]])/dir_norm[i]
tmp_fre = 1/(tmp_fre+0.00001)
fre.append(tmp_fre)
if len(confidence)>0:
confidence = np.asarray(confidence)
fre = np.asarray(fre)
ori = np.asarray(ori)
ind = confidence.argsort()[::-1]
confidence = confidence[ind]
fre = fre[ind]
ori = ori[ind]
if len(confidence) >= 2 and confidence[0]/confidence[1]>2.0:
self.ori = [ori[0]]
self.fre = [fre[0]]
self.confidence = [confidence[0]]
elif len(confidence)>N:
fre = fre[:N]
ori = ori[:N]
confidence = confidence[:N]
self.ori = ori
self.fre = fre
self.confidence = confidence
else:
self.ori = ori
self.fre = fre
self.confidence = confidence
def get_features_of_topN(self,N=2):
if self.confidence is None:
self.border_wave = None
return
candi_num = len(self.ori)
candi_num = np.min([candi_num,N])
patch_size = self.patch_FFT.shape
for i in range(candi_num):
kernel = gabor_kernel(self.fre[i], theta=self.ori[i], sigma_x=10, sigma_y=10)
kernel_f = np.fft.fft2(kernel.real, patch_size)
kernel_f = np.fft.fftshift(kernel_f)
patch_f = self.patch_FFT * kernel_f
patch_f = np.fft.ifftshift(patch_f) # *np.sqrt(np.abs(fshift)))
rec_patch = np.real(np.fft.ifft2(patch_f))
plt.subplot(121), plt.imshow(self.patch, cmap='gray')
plt.title('Input patch'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(rec_patch, cmap='gray')
plt.title('filtered patch'), plt.xticks([]), plt.yticks([])
plt.show()
def reconstruction(self,weight=None):
f_ifft = np.fft.ifftshift(self.patch_FFT) # *np.sqrt(np.abs(fshift)))
rec_patch = np.real(np.fft.ifft2(f_ifft))
if weight is not None:
rec_patch = rec_patch * weight
return rec_patch
def gabor_filtering(self,theta,fre,weight=None):
patch_size = self.patch_FFT.shape
kernel = gabor_kernel(fre, theta=theta,sigma_x=4,sigma_y=4)
f = kernel.real
f = f - np.mean(f)
f = f / (np.linalg.norm(f)+0.0001)
kernel_f = np.fft.fft2(f,patch_size)
kernel_f = np.fft.fftshift(kernel_f)
patch_f = self.patch_FFT*kernel_f
patch_f = np.fft.ifftshift(patch_f) # *np.sqrt(np.abs(fshift)))
rec_patch = np.real(np.fft.ifft2(patch_f))
if weight is not None:
rec_patch = rec_patch * weight
return rec_patch
def show_orientation_field(img,dir_map,mask=None,fname=None):
h,w = img.shape[:2]
if mask is None:
mask = np.ones((h,w),dtype=np.uint8)
blkH, blkW = dir_map.shape
blk_size = h/blkH
R = blk_size/2*0.8
fig, ax = plt.subplots(1)
ax.imshow(img, cmap='gray')
for i in range(blkH):
y0 = i*blk_size + blk_size/2
y0 = int(y0)
for j in range(blkW):
x0 = j*blk_size + blk_size/2
x0 = int(x0)
ori = dir_map[i,j]
if mask[y0,x0] == 0:
continue
if ori<-9:
continue
x1 = x0 - R * math.cos(ori)
x2 = x0 + R * math.cos(ori)
y1 = y0 - R * math.sin(ori)
y2 = y0 + R * math.sin(ori)
plt.plot([x1, x2], [y1, y2], 'r-', lw=2)
plt.axis('off')
if fname is not None:
fig.savefig(fname,dpi = 500, bbox_inches='tight', pad_inches = 0)
plt.close()
else:
plt.show(block=True)
Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

@@ -0,0 +1,18 @@
001
16 800 768
313 382 3.141593e-01
353 385 3.665191e-01
261 384 3.141593e-01
287 327 4.188790e-01
356 353 4.014257e-01
385 197 6.632251e-01
418 85 6.632251e-01
397 307 7.679449e-01
346 302 7.155850e-01
418 267 7.679449e-01
418 235 7.155850e-01
473 233 8.552113e-01
458 182 3.822271e+00
349 277 3.787364e+00
418 277 3.892084e+00
453 235 3.839724e+00
@@ -0,0 +1,18 @@
002
16 800 768
278 353 0
170 638 6.283185e-01
137 634 6.457718e-01
246 556 6.632251e-01
268 385 7.504916e-01
239 600 6.981317e-01
191 623 7.330383e-01
193 582 8.377580e-01
241 535 9.424778e-01
158 598 8.901179e-01
133 554 1.029744e+00
187 454 1.064651e+00
91 521 1.064651e+00
210 541 1.029744e+00
294 363 3.455752e+00
152 445 4.380776e+00
Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

@@ -0,0 +1,18 @@
001
16 800 768
313 382 3.141593e-01
353 385 3.665191e-01
261 384 3.141593e-01
287 327 4.188790e-01
356 353 4.014257e-01
385 197 6.632251e-01
418 85 6.632251e-01
397 307 7.679449e-01
346 302 7.155850e-01
418 267 7.679449e-01
418 235 7.155850e-01
473 233 8.552113e-01
458 182 3.822271e+00
349 277 3.787364e+00
418 277 3.892084e+00
453 235 3.839724e+00
@@ -0,0 +1,18 @@
002
16 800 768
278 353 0
170 638 6.283185e-01
137 634 6.457718e-01
246 556 6.632251e-01
268 385 7.504916e-01
239 600 6.981317e-01
191 623 7.330383e-01
193 582 8.377580e-01
241 535 9.424778e-01
158 598 8.901179e-01
133 554 1.029744e+00
187 454 1.064651e+00
91 521 1.064651e+00
210 541 1.029744e+00
294 363 3.455752e+00
152 445 4.380776e+00
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

@@ -0,0 +1,294 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Testing CoarseNet\n",
"Code for FineNet in paper \"Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge\" at ICB 2018: https://arxiv.org/pdf/1712.09401.pdf\n",
"\n",
"If you use whole or partial function in this code, please cite paper:\n",
"\n",
" @inproceedings{Nguyen_MinutiaeNet,\n",
"\tauthor = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},\n",
"\ttitle = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},\n",
"\tbooktitle = {The 11th International Conference on Biometrics, 2018},\n",
"\tyear = {2018},\n",
"\t}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To run this script, you need to prepare dataset as follows:\n",
"`path/to/dataset/`:\n",
" - img_files/*.bmp\n",
"\n",
"If using groundtruth mask instead of mask generated by CoarseNet:\n",
" - seg_files/*.bmp\n",
" \n",
"## CoarseNet can run with any image size\n",
"See [CoarseNet_run.py](https://github.com/luannd/MinutiaeNet/blob/master/CoarseNet/CoarseNet_run.py) if running from command line.\n",
"\n",
"CoarseNet can be improved by:\n",
"- Train on new dataset instead of FVC\n",
"- Correct the orientation\n",
"- Tune threshold for different dataset\n",
"\n",
"## CoarseNet can provides:\n",
"- Orientation field estimation\n",
"- Mask for fingerprint area\n",
"- Minutiae location and orientation"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Using TensorFlow backend.\n"
]
}
],
"source": [
"from __future__ import absolute_import\n",
"from __future__ import division\n",
"\n",
"import sys, os\n",
"sys.path.append(os.path.realpath('../CoarseNet'))\n",
"\n",
"os.environ[\"CUDA_VISIBLE_DEVICES\"] = '1'\n",
"os.environ['KERAS_BACKEND'] = 'tensorflow'\n",
"\n",
"\n",
"from keras import backend as K\n",
"\n",
"from MinutiaeNet_utils import *\n",
"from CoarseNet_utils import *\n",
"from CoarseNet_model import *\n",
"import argparse\n",
"\n",
"\n",
"config = K.tf.ConfigProto(gpu_options=K.tf.GPUOptions(allow_growth=True))\n",
"sess = K.tf.Session(config=config)\n",
"K.set_session(sess)\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"\n",
"# Prepare dataset for testing. \n",
"inference_set = ['../Dataset/CoarseNet_test/',]\n",
"\n",
"CoarseNet_path = '../Models/CoarseNet.h5'\n",
"\n",
"output_dir = '../output_CoarseNet/'+datetime.now().strftime('%Y%m%d-%H%M%S')\n",
"\n",
"FineNet_path = '../Models/FineNet.h5'\n",
"\n",
"logging = init_log(output_dir)\n",
"\n",
"# If use FineNet to refine, set into True\n",
"isHavingFineNet = False"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This can test with different folders.\n",
"\n",
"Threshold for each image is automatically chosen"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"for i, deploy_set in enumerate(inference_set):\n",
" set_name = deploy_set.split('/')[-2]\n",
"\n",
" # Read image and GT\n",
" img_name, folder_name, img_size = get_maximum_img_size_and_names(deploy_set)\n",
"\n",
" mkdir(output_dir + '/'+ set_name + '/')\n",
" mkdir(output_dir + '/' + set_name + '/mnt_results/')\n",
" mkdir(output_dir + '/'+ set_name + '/seg_results/')\n",
" mkdir(output_dir + '/' + set_name + '/OF_results/')\n",
"\n",
" logging.info(\"Predicting \\\"%s\\\":\" % (set_name))\n",
"\n",
"\n",
" main_net_model = CoarseNetmodel((None, None, 1), CoarseNet_path, mode='deploy')\n",
"\n",
" # ====== Load FineNet to verify\n",
" if isHavingFineNet == True:\n",
" model_FineNet = FineNetmodel(num_classes=2,\n",
" pretrained_path=FineNet_path,\n",
" input_shape=(224,224,3))\n",
"\n",
" model_FineNet.compile(loss='categorical_crossentropy',\n",
" optimizer=Adam(lr=0),\n",
" metrics=['accuracy'])\n",
"\n",
" for i in xrange(0, len(img_name)):\n",
" \n",
" logging.info(\"\\\"%s\\\" %d / %d: %s\" % (set_name, i + 1, len(img_name), img_name[i]))\n",
"\n",
" image = misc.imread(deploy_set + 'img_files/' + img_name[i] + '.bmp', mode='L')# / 255.0\n",
"\n",
" img_size = image.shape\n",
" img_size = np.array(img_size, dtype=np.int32) // 8 * 8\n",
" image = image[:img_size[0], :img_size[1]]\n",
"\n",
" original_image = image.copy()\n",
"\n",
" # Generate OF\n",
" texture_img = FastEnhanceTexture(image, sigma=2.5, show=False)\n",
" dir_map, fre_map = get_maps_STFT(texture_img, patch_size=64, block_size=16, preprocess=True)\n",
" \n",
" image = np.reshape(image, [1, image.shape[0], image.shape[1], 1])\n",
"\n",
" enh_img, enh_img_imag, enhance_img, ori_out_1, ori_out_2, seg_out, mnt_o_out, mnt_w_out, mnt_h_out, mnt_s_out \\\n",
" = main_net_model.predict(image)\n",
"\n",
" # Use for output mask\n",
" round_seg = np.round(np.squeeze(seg_out))\n",
" seg_out = 1 - round_seg\n",
" kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))\n",
" seg_out = cv2.morphologyEx(seg_out, cv2.MORPH_CLOSE, kernel)\n",
" kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))\n",
" seg_out = cv2.morphologyEx(seg_out, cv2.MORPH_OPEN, kernel)\n",
" kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))\n",
" seg_out = cv2.dilate(seg_out, kernel)\n",
"\n",
" #========== Adaptive threshold ==================\n",
" final_minutiae_score_threashold = 0.45\n",
" early_minutiae_thres = final_minutiae_score_threashold + 0.05\n",
"\n",
"\n",
"\n",
" # In cases of small amount of minutiae given, try adaptive threshold\n",
" while final_minutiae_score_threashold >= 0:\n",
" mnt = label2mnt(np.squeeze(mnt_s_out) * np.round(np.squeeze(seg_out)), mnt_w_out, mnt_h_out, mnt_o_out,\n",
" thresh=early_minutiae_thres)\n",
"\n",
" mnt_nms_1 = py_cpu_nms(mnt, 0.5)\n",
" mnt_nms_2 = nms(mnt)\n",
" # Make sure good result is given\n",
" if mnt_nms_1.shape[0] > 4 and mnt_nms_2.shape[0] > 4:\n",
" break\n",
" else:\n",
" final_minutiae_score_threashold = final_minutiae_score_threashold - 0.05\n",
" early_minutiae_thres = early_minutiae_thres - 0.05\n",
"\n",
"\n",
" mnt_nms = fuse_nms(mnt_nms_1, mnt_nms_2)\n",
"\n",
" mnt_nms = mnt_nms[mnt_nms[:, 3] > early_minutiae_thres, :]\n",
" mnt_refined = []\n",
"\n",
" if isHavingFineNet == True:\n",
" # ======= Verify using FineNet ============\n",
" patch_minu_radio = 22\n",
" if FineNet_path != None:\n",
" for idx_minu in range(mnt_nms.shape[0]):\n",
" try:\n",
" # Extract patch from image\n",
" x_begin = int(mnt_nms[idx_minu, 1]) - patch_minu_radio\n",
" y_begin = int(mnt_nms[idx_minu, 0]) - patch_minu_radio\n",
" patch_minu = original_image[x_begin:x_begin + 2 * patch_minu_radio,\n",
" y_begin:y_begin + 2 * patch_minu_radio]\n",
"\n",
" patch_minu = cv2.resize(patch_minu, dsize=(224, 224), interpolation=cv2.INTER_NEAREST)\n",
"\n",
" ret = np.empty((patch_minu.shape[0], patch_minu.shape[1], 3), dtype=np.uint8)\n",
" ret[:, :, 0] = patch_minu\n",
" ret[:, :, 1] = patch_minu\n",
" ret[:, :, 2] = patch_minu\n",
" patch_minu = ret\n",
" patch_minu = np.expand_dims(patch_minu, axis=0)\n",
"\n",
" # # Can use class as hard decision\n",
" # # 0: minu 1: non-minu\n",
" # [class_Minutiae] = np.argmax(model_FineNet.predict(patch_minu), axis=1)\n",
" #\n",
" # if class_Minutiae == 0:\n",
" # mnt_refined.append(mnt_nms[idx_minu,:])\n",
"\n",
" # Use soft decision: merge FineNet score with CoarseNet score\n",
" [isMinutiaeProb] = model_FineNet.predict(patch_minu)\n",
" isMinutiaeProb = isMinutiaeProb[0]\n",
" # print isMinutiaeProb\n",
" tmp_mnt = mnt_nms[idx_minu, :].copy()\n",
" tmp_mnt[3] = (4*tmp_mnt[3] + isMinutiaeProb) / 5\n",
" mnt_refined.append(tmp_mnt)\n",
"\n",
" except:\n",
" mnt_refined.append(mnt_nms[idx_minu, :])\n",
" else:\n",
" mnt_refined = mnt_nms\n",
"\n",
" mnt_nms_backup = mnt_nms.copy()\n",
" mnt_nms = np.array(mnt_refined)\n",
"\n",
" if mnt_nms.shape[0] > 0:\n",
" mnt_nms = mnt_nms[mnt_nms[:, 3] > final_minutiae_score_threashold, :]\n",
" \n",
" final_mask = ndimage.zoom(np.round(np.squeeze(seg_out)), [8, 8], order=0)\n",
" # Show the orientation\n",
" show_orientation_field(original_image, dir_map + np.pi, mask=final_mask, fname=\"%s/%s/OF_results/%s_OF.jpg\" % (output_dir, set_name, img_name[i]))\n",
"\n",
" fuse_minu_orientation(dir_map, mnt_nms, mode=3)\n",
"\n",
" time_afterpost = time()\n",
" mnt_writer(mnt_nms, img_name[i], img_size, \"%s/%s/mnt_results/%s.mnt\"%(output_dir, set_name, img_name[i]))\n",
" draw_minutiae(original_image, mnt_nms, \"%s/%s/%s_minu.jpg\"%(output_dir, set_name, img_name[i]),saveimage=True)\n",
"\n",
" misc.imsave(\"%s/%s/seg_results/%s_seg.jpg\" % (output_dir, set_name, img_name[i]), final_mask)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.15"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,131 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Training CoarseNet\n",
"Code for FineNet in paper \"Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge\" at ICB 2018: https://arxiv.org/pdf/1712.09401.pdf\n",
"\n",
"If you use whole or partial function in this code, please cite paper:\n",
"\n",
" @inproceedings{Nguyen_MinutiaeNet,\n",
"\tauthor = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},\n",
"\ttitle = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},\n",
"\tbooktitle = {The 11th International Conference on Biometrics, 2018},\n",
"\tyear = {2018},\n",
"\t}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To run this script, you need to prepare dataset as follows:\n",
"`path/to/dataset/`:\n",
"```Shell\n",
" - img_files/*.bmp\n",
" - mnt_files/*.mnt\n",
" - seg_files/*.jpg\n",
"```\n",
"See example at `Dataset/CoarseNet_train/` (these images are example from NIST SD27)\n",
" \n",
"## CoarseNet can run with any image size\n",
"See [CoarseNet_train.py](https://github.com/luannd/MinutiaeNet/blob/master/CoarseNet/CoarseNet_train.py) if running from command line.\n",
"\n",
"Log files, tensorboard, minutiae models can be seen from `output_CoarseNet` folder"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from __future__ import absolute_import\n",
"from __future__ import division\n",
"\n",
"import sys, os\n",
"sys.path.append(os.path.realpath('../CoarseNet'))\n",
"\n",
"os.environ['KERAS_BACKEND'] = 'tensorflow'\n",
"\n",
"from datetime import datetime\n",
"from MinutiaeNet_utils import *\n",
"\n",
"from keras import backend as K\n",
"from keras.optimizers import SGD, Adam\n",
"\n",
"from CoarseNet_utils import *\n",
"from CoarseNet_model import *\n",
"\n",
"lr = 0.005\n",
"\n",
"os.environ[\"CUDA_VISIBLE_DEVICES\"] = '0'\n",
"\n",
"config = K.tf.ConfigProto(gpu_options=K.tf.GPUOptions(allow_growth=True))\n",
"sess = K.tf.Session(config=config)\n",
"K.set_session(sess)\n",
"\n",
"batch_size = 2\n",
"use_multiprocessing = False\n",
"input_size = 400\n",
"\n",
"# Can use multiple folders for training\n",
"train_set = ['../Dataset/CoarseNet_train/',]\n",
"validate_set = ['../path/to/your/data/',]\n",
"\n",
"pretrain_dir = '../Models/CoarseNet.h5'\n",
"output_dir = '../output_CoarseNet/'+datetime.now().strftime('%Y%m%d-%H%M%S')\n",
"FineNet_dir = '../Models/FineNet.h5'\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n",
"output_dir = '../output_CoarseNet/trainResults/' + datetime.now().strftime('%Y%m%d-%H%M%S')\n",
"logging = init_log(output_dir)\n",
"logging.info(\"Learning rate = %s\", lr)\n",
"logging.info(\"Pretrain dir = %s\", pretrain_dir)\n",
"\n",
"train(input_shape=(input_size, input_size), train_set=train_set, output_dir=output_dir,\n",
" pretrain_dir=pretrain_dir, batch_size=batch_size, test_set=validate_set,\n",
" learning_config=Adam(lr=float(lr), beta_1=0.9, beta_2=0.999, epsilon=1e-08, clipnorm=0.9),\n",
" logging=logging)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.15"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,221 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Training FineNet\n",
"Code for FineNet in paper \"Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge\" at ICB 2018: https://arxiv.org/pdf/1712.09401.pdf\n",
"\n",
"If you use whole or partial function in this code, please cite paper:\n",
"\n",
" @inproceedings{Nguyen_MinutiaeNet,\n",
"\tauthor = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},\n",
"\ttitle = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},\n",
"\tbooktitle = {The 11th International Conference on Biometrics, 2018},\n",
"\tyear = {2018},\n",
"\t}\n",
"\n",
"Prepare your data as follows:\n",
"- Prepare minutiae and non-minutiae image patches with any sizes. I suggest to use `44x44` size\n",
"- Put all images in corresponding folers (`minu`, `non_minu`) in \n",
" - `Dataset/train`,\n",
" - `Dataset/test`,\n",
" - `Dataset/validate`.\n",
"- Run following code\n",
"\n",
"Beside running in this notebook, you can run via command line with file [FineNet_train.py](../FineNet/FineNet_train.py)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import sys,os\n",
"sys.path.append(os.path.realpath('../FineNet'))\n",
"\n",
"from keras.optimizers import Adam\n",
"from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard\n",
"from keras.callbacks import ReduceLROnPlateau\n",
"from keras.preprocessing.image import ImageDataGenerator\n",
"from FineNet_model import FineNetmodel, plot_confusion_matrix\n",
"\n",
"import numpy as np\n",
"import os\n",
"from sklearn.metrics import confusion_matrix\n",
"from datetime import datetime\n",
"\n",
"\n",
"os.environ[\"CUDA_VISIBLE_DEVICES\"] = '2'\n",
"os.environ['KERAS_BACKEND'] = 'tensorflow'\n",
"\n",
"\n",
"output_dir = '../output_FineNet/'+datetime.now().strftime('%Y%m%d-%H%M%S')\n",
"\n",
"# Prepare model model saving directory.\n",
"save_dir = os.path.join(os.getcwd(), output_dir)\n",
"log_dir = os.path.join(os.getcwd(), output_dir + '/logs')\n",
"\n",
"# Training parameters\n",
"batch_size = 32\n",
"epochs = 200\n",
"num_classes = 2\n",
"\n",
"# Subtracting pixel mean improves accuracy\n",
"subtract_pixel_mean = True\n",
"\n",
"# Model size, patch\n",
"model_type = 'patch224batch32'\n",
"\n",
"\n",
"# =============== DATA loading ========================\n",
"\n",
"train_path = '../Dataset/train/'\n",
"test_path = '../Dataset/validate/'\n",
"\n",
"input_shape = (224, 224, 3)\n",
"\n",
"# Using data augmentation technique for training\n",
"datagen = ImageDataGenerator(\n",
" # set input mean to 0 over the dataset\n",
" featurewise_center=False,\n",
" # set each sample mean to 0\n",
" samplewise_center=False,\n",
" # divide inputs by std of dataset\n",
" featurewise_std_normalization=False,\n",
" # divide each input by its std\n",
" samplewise_std_normalization=False,\n",
" # apply ZCA whitening\n",
" zca_whitening=False,\n",
" # randomly rotate images in the range (deg 0 to 180)\n",
" rotation_range=180,\n",
" # randomly shift images horizontally\n",
" width_shift_range=0.5,\n",
" # randomly shift images vertically\n",
" height_shift_range=0.5,\n",
" # randomly flip images\n",
" horizontal_flip=True,\n",
" # randomly flip images\n",
" vertical_flip=True)\n",
"\n",
"train_batches = datagen.flow_from_directory(train_path, target_size=(input_shape[0], input_shape[1]), classes=['minu', 'non_minu'], batch_size=batch_size)\n",
"# Feed data from directory into batches\n",
"test_gen = ImageDataGenerator()\n",
"test_batches = test_gen.flow_from_directory(test_path, target_size=(input_shape[0], input_shape[1]), classes=['minu', 'non_minu'], batch_size=batch_size)\n",
"\n",
"\n",
"# =============== end DATA loading ========================\n",
"\n",
"\n",
"\n",
"def lr_schedule(epoch):\n",
" \"\"\"Learning Rate Schedule\n",
" \"\"\"\n",
" lr = 0.5e-2\n",
" if epoch > 180:\n",
" lr *= 0.5e-3\n",
" elif epoch > 150:\n",
" lr *= 1e-3\n",
" elif epoch > 60:\n",
" lr *= 5e-2\n",
" elif epoch > 30:\n",
" lr *= 5e-1\n",
" print('Learning rate: ', lr)\n",
" return lr\n",
"\n",
"\n",
"\n",
"\n",
"#============== Define model ==================\n",
"\n",
"model = FineNetmodel(num_classes = num_classes,\n",
" pretrained_path = '../Models/FineNet.h5',\n",
" input_shape=input_shape)\n",
"\n",
"# Save model architecture\n",
"#plot_model(model, to_file='./modelFineNet.pdf',show_shapes=True)\n",
"\n",
"model.compile(loss='categorical_crossentropy',\n",
" optimizer=Adam(lr=lr_schedule(0)),\n",
" metrics=['accuracy'])\n",
"#model.summary()\n",
"\n",
"#============== End define model ==============\n",
"\n",
"\n",
"#============== Other stuffs for loging and parameters ==================\n",
"model_name = 'FineNet_%s_model.{epoch:03d}.h5' % model_type\n",
"if not os.path.isdir(save_dir):\n",
" os.makedirs(save_dir)\n",
"if not os.path.isdir(log_dir):\n",
" os.makedirs(log_dir)\n",
"\n",
"filepath = os.path.join(save_dir, model_name)\n",
"\n",
"\n",
"# Show in tensorboard\n",
"tensorboard = TensorBoard(log_dir=log_dir, histogram_freq=0, write_graph=True, write_images=False)\n",
"\n",
"# Prepare callbacks for model saving and for learning rate adjustment.\n",
"checkpoint = ModelCheckpoint(filepath=filepath,\n",
" monitor='val_acc',\n",
" verbose=1,\n",
" save_best_only=True)\n",
"\n",
"lr_scheduler = LearningRateScheduler(lr_schedule)\n",
"\n",
"lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),\n",
" cooldown=0,\n",
" patience=5,\n",
" min_lr=0.5e-6)\n",
"\n",
"callbacks = [checkpoint, lr_reducer, lr_scheduler, tensorboard]\n",
"\n",
"#============== End other stuffs ==================\n",
"\n",
"# Begin training\n",
"model.fit_generator(train_batches,\n",
" validation_data=test_batches,\n",
" epochs=epochs, verbose=1,\n",
" callbacks=callbacks)\n",
"\n",
"\n",
"\n",
"# Plot confusion matrix\n",
"score = model.evaluate_generator(test_batches)\n",
"print 'Test accuracy:', score[1]\n",
"predictions = model.predict_generator(test_batches)\n",
"test_labels = test_batches.classes[test_batches.index_array]\n",
"\n",
"cm = confusion_matrix(test_labels, np.argmax(predictions,axis=1))\n",
"cm_plot_labels = ['minu','non_minu']\n",
"plot_confusion_matrix(cm, cm_plot_labels, title='Confusion Matrix')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.14"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -0,0 +1,244 @@
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
https://arxiv.org/pdf/1712.09401.pdf
If you use whole or partial function in this code, please cite paper:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
"""
from __future__ import absolute_import
from __future__ import division
from keras.models import Model
from keras.layers import Activation, AveragePooling2D, BatchNormalization, Concatenate, Conv2D, Dense, GlobalAveragePooling2D
from keras.layers import Input, Lambda, MaxPooling2D
from keras.applications.imagenet_utils import _obtain_input_shape
from keras import backend as K
import matplotlib.pyplot as plt
import numpy as np
import itertools
def preprocess_input(x):
"""Preprocesses a numpy array encoding a batch of images.
"""
return keras.applications.imagenet_utils.preprocess_input(x, mode='tf')
def conv2d_bn(x,
filters,
kernel_size,
strides=1,
padding='same',
activation='relu',
use_bias=False,
name=None):
"""Utility function to apply conv + BN.
"""
x = Conv2D(filters,
kernel_size,
strides=strides,
padding=padding,
use_bias=use_bias,
name=name)(x)
if not use_bias:
bn_axis = 1 if K.image_data_format() == 'channels_first' else 3
bn_name = None if name is None else name + '_bn'
x = BatchNormalization(axis=bn_axis, scale=False, name=bn_name)(x)
if activation is not None:
ac_name = None if name is None else name + '_ac'
x = Activation(activation, name=ac_name)(x)
return x
def inception_resnet_block(x, scale, block_type, block_idx, activation='relu'):
"""Inception-ResNet block.
"""
if block_type == 'block35':
branch_0 = conv2d_bn(x, 32, 1)
branch_1 = conv2d_bn(x, 32, 1)
branch_1 = conv2d_bn(branch_1, 32, 3)
branch_2 = conv2d_bn(x, 32, 1)
branch_2 = conv2d_bn(branch_2, 48, 3)
branch_2 = conv2d_bn(branch_2, 64, 3)
branches = [branch_0, branch_1, branch_2]
elif block_type == 'block17':
branch_0 = conv2d_bn(x, 192, 1)
branch_1 = conv2d_bn(x, 128, 1)
branch_1 = conv2d_bn(branch_1, 160, [1, 7])
branch_1 = conv2d_bn(branch_1, 192, [7, 1])
branches = [branch_0, branch_1]
elif block_type == 'block8':
branch_0 = conv2d_bn(x, 192, 1)
branch_1 = conv2d_bn(x, 192, 1)
branch_1 = conv2d_bn(branch_1, 224, [1, 3])
branch_1 = conv2d_bn(branch_1, 256, [3, 1])
branches = [branch_0, branch_1]
else:
raise ValueError('Unknown Inception-ResNet block type. '
'Expects "block35", "block17" or "block8", '
'but got: ' + str(block_type))
block_name = block_type + '_' + str(block_idx)
channel_axis = 1 if K.image_data_format() == 'channels_first' else 3
mixed = Concatenate(axis=channel_axis, name=block_name + '_mixed')(branches)
up = conv2d_bn(mixed,
K.int_shape(x)[channel_axis],
1,
activation=None,
use_bias=True,
name=block_name + '_conv')
x = Lambda(lambda inputs, scale: inputs[0] + inputs[1] * scale,
output_shape=K.int_shape(x)[1:],
arguments={'scale': scale},
name=block_name)([x, up])
if activation is not None:
x = Activation(activation, name=block_name + '_ac')(x)
return x
def FineNetmodel(num_classes = 2, pretrained_path = None, input_shape = None):
"""Create FineNet architecture.
"""
# Determine proper input shape
input_shape = _obtain_input_shape(
input_shape,
default_size=299,
min_size=139,
data_format=K.image_data_format(),
require_flatten=False,
weights=pretrained_path)
img_input = Input(shape=input_shape)
# Stem block: 35 x 35 x 192
x = conv2d_bn(img_input, 32, 3, strides=2, padding='valid')
x = conv2d_bn(x, 32, 3, padding='valid')
x = conv2d_bn(x, 64, 3)
x = MaxPooling2D(3, strides=2)(x)
x = conv2d_bn(x, 80, 1, padding='valid')
x = conv2d_bn(x, 192, 3, padding='valid')
x = MaxPooling2D(3, strides=2)(x)
# Mixed 5b (Inception-A block): 35 x 35 x 320
branch_0 = conv2d_bn(x, 96, 1)
branch_1 = conv2d_bn(x, 48, 1)
branch_1 = conv2d_bn(branch_1, 64, 5)
branch_2 = conv2d_bn(x, 64, 1)
branch_2 = conv2d_bn(branch_2, 96, 3)
branch_2 = conv2d_bn(branch_2, 96, 3)
branch_pool = AveragePooling2D(3, strides=1, padding='same')(x)
branch_pool = conv2d_bn(branch_pool, 64, 1)
branches = [branch_0, branch_1, branch_2, branch_pool]
channel_axis = 1 if K.image_data_format() == 'channels_first' else 3
x = Concatenate(axis=channel_axis, name='mixed_5b')(branches)
# 10x block35 (Inception-ResNet-A block): 35 x 35 x 320
for block_idx in range(1, 11):
x = inception_resnet_block(x,
scale=0.17,
block_type='block35',
block_idx=block_idx)
# Mixed 6a (Reduction-A block): 17 x 17 x 1088
branch_0 = conv2d_bn(x, 384, 3, strides=2, padding='valid')
branch_1 = conv2d_bn(x, 256, 1)
branch_1 = conv2d_bn(branch_1, 256, 3)
branch_1 = conv2d_bn(branch_1, 384, 3, strides=2, padding='valid')
branch_pool = MaxPooling2D(3, strides=2, padding='valid')(x)
branches = [branch_0, branch_1, branch_pool]
x = Concatenate(axis=channel_axis, name='mixed_6a')(branches)
# 20x block17 (Inception-ResNet-B block): 17 x 17 x 1088
for block_idx in range(1, 21):
x = inception_resnet_block(x,
scale=0.1,
block_type='block17',
block_idx=block_idx)
# Mixed 7a (Reduction-B block): 8 x 8 x 2080
branch_0 = conv2d_bn(x, 256, 1)
branch_0 = conv2d_bn(branch_0, 384, 3, strides=2, padding='valid')
branch_1 = conv2d_bn(x, 256, 1)
branch_1 = conv2d_bn(branch_1, 288, 3, strides=2, padding='valid')
branch_2 = conv2d_bn(x, 256, 1)
branch_2 = conv2d_bn(branch_2, 288, 3)
branch_2 = conv2d_bn(branch_2, 320, 3, strides=2, padding='valid')
branch_pool = MaxPooling2D(3, strides=2, padding='valid')(x)
branches = [branch_0, branch_1, branch_2, branch_pool]
x = Concatenate(axis=channel_axis, name='mixed_7a')(branches)
# 10x block8 (Inception-ResNet-C block): 8 x 8 x 2080
for block_idx in range(1, 10):
x = inception_resnet_block(x,
scale=0.2,
block_type='block8',
block_idx=block_idx)
x = inception_resnet_block(x,
scale=1.,
activation=None,
block_type='block8',
block_idx=10)
# Final convolution block: 8 x 8 x 1536
x = conv2d_bn(x, 1536, 1, name='conv_7b')
# Classification block
x = GlobalAveragePooling2D(name='avg_pool')(x)
x = Dense(num_classes, activation='softmax', name='predictions')(x)
inputs = img_input
# Create model
model = Model(inputs, x, name='FineNet')
# Load weights
if pretrained_path != None:
print 'Loading FineNet weights from %s'%(pretrained_path)
model.load_weights(pretrained_path)
return model
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
This function prints and plots the confusion matrix.
Normalization can be applied by setting `normalize=True`.
"""
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
print("Normalized confusion matrix")
else:
print('Confusion matrix, without normalization')
print(cm)
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, cm[i, j],
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.show()
@@ -0,0 +1,172 @@
"""Code for FineNet in paper "Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge" at ICB 2018
https://arxiv.org/pdf/1712.09401.pdf
If you use whole or partial function in this code, please cite paper:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
"""
import sys,os
sys.path.append(os.path.realpath('../FineNet'))
from keras.optimizers import Adam
from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard
from keras.callbacks import ReduceLROnPlateau
from keras.preprocessing.image import ImageDataGenerator
from FineNet_model import FineNetmodel, plot_confusion_matrix
import numpy as np
import os
from sklearn.metrics import confusion_matrix
from datetime import datetime
os.environ["CUDA_VISIBLE_DEVICES"] = '2'
os.environ['KERAS_BACKEND'] = 'tensorflow'
output_dir = '../output_FineNet/'+datetime.now().strftime('%Y%m%d-%H%M%S')
# Prepare model model saving directory.
save_dir = os.path.join(os.getcwd(), output_dir)
log_dir = os.path.join(os.getcwd(), output_dir + '/logs')
# Training parameters
batch_size = 32
epochs = 200
num_classes = 2
# Subtracting pixel mean improves accuracy
subtract_pixel_mean = True
# Model size, patch
model_type = 'patch224batch32'
# =============== DATA loading ========================
train_path = '../Dataset/train/'
test_path = '../Dataset/validate/'
input_shape = (224, 224, 3)
# Using data augmentation technique for training
datagen = ImageDataGenerator(
# set input mean to 0 over the dataset
featurewise_center=False,
# set each sample mean to 0
samplewise_center=False,
# divide inputs by std of dataset
featurewise_std_normalization=False,
# divide each input by its std
samplewise_std_normalization=False,
# apply ZCA whitening
zca_whitening=False,
# randomly rotate images in the range (deg 0 to 180)
rotation_range=180,
# randomly shift images horizontally
width_shift_range=0.5,
# randomly shift images vertically
height_shift_range=0.5,
# randomly flip images
horizontal_flip=True,
# randomly flip images
vertical_flip=True)
train_batches = datagen.flow_from_directory(train_path, target_size=(input_shape[0], input_shape[1]), classes=['minu', 'non_minu'], batch_size=batch_size)
# Feed data from directory into batches
test_gen = ImageDataGenerator()
test_batches = test_gen.flow_from_directory(test_path, target_size=(input_shape[0], input_shape[1]), classes=['minu', 'non_minu'], batch_size=batch_size)
# =============== end DATA loading ========================
def lr_schedule(epoch):
"""Learning Rate Schedule
"""
lr = 0.5e-2
if epoch > 180:
lr *= 0.5e-3
elif epoch > 150:
lr *= 1e-3
elif epoch > 60:
lr *= 5e-2
elif epoch > 30:
lr *= 5e-1
print('Learning rate: ', lr)
return lr
#============== Define model ==================
model = FineNetmodel(num_classes = num_classes,
pretrained_path = '../Models/FineNet.h5',
input_shape=input_shape)
# Save model architecture
#plot_model(model, to_file='./modelFineNet.pdf',show_shapes=True)
model.compile(loss='categorical_crossentropy',
optimizer=Adam(lr=lr_schedule(0)),
metrics=['accuracy'])
#model.summary()
#============== End define model ==============
#============== Other stuffs for loging and parameters ==================
model_name = 'FineNet_%s_model.{epoch:03d}.h5' % model_type
if not os.path.isdir(save_dir):
os.makedirs(save_dir)
if not os.path.isdir(log_dir):
os.makedirs(log_dir)
filepath = os.path.join(save_dir, model_name)
# Show in tensorboard
tensorboard = TensorBoard(log_dir=log_dir, histogram_freq=0, write_graph=True, write_images=False)
# Prepare callbacks for model saving and for learning rate adjustment.
checkpoint = ModelCheckpoint(filepath=filepath,
monitor='val_acc',
verbose=1,
save_best_only=True)
lr_scheduler = LearningRateScheduler(lr_schedule)
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
cooldown=0,
patience=5,
min_lr=0.5e-6)
callbacks = [checkpoint, lr_reducer, lr_scheduler, tensorboard]
#============== End other stuffs ==================
# Begin training
model.fit_generator(train_batches,
validation_data=test_batches,
epochs=epochs, verbose=1,
callbacks=callbacks)
# Plot confusion matrix
score = model.evaluate_generator(test_batches)
print 'Test accuracy:', score[1]
predictions = model.predict_generator(test_batches)
test_labels = test_batches.classes[test_batches.index_array]
cm = confusion_matrix(test_labels, np.argmax(predictions,axis=1))
cm_plot_labels = ['minu','non_minu']
plot_confusion_matrix(cm, cm_plot_labels, title='Confusion Matrix')
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Dinh-Luan Nguyen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.
Binary file not shown.
+85
View File
@@ -0,0 +1,85 @@
# Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge
By Dinh-Luan Nguyen, Kai Cao and Anil K.Jain
<div align="middle">
<img src="assets/Pic1.gif" width="300" hspace="30"/>
<img src="assets/Pic2.gif" width="300"/>
</div>
For precise fingerprint segmentation, let's refer to this paper: [Automatic Latent Fingerprint Segmentation](https://arxiv.org/pdf/1804.09650.pdf)
### Introduction
We present the framework called **MinutiaeNet** including CoarseNet and FineNet
![MinutiaeNet](assets/MinutiaeNet.jpg)
**CoarseNet** is a residual learning based convolutional neural network that takes a fingerprint image as initial input, and the corresponding enhanced image, segmentation map, and orientation field (computed by the early stages of CoarseNet) as secondary input to generate the minutiae score map. The minutiae orientation is also estimated by comparing with the fingerprint orientation.
![CoarseNet](assets/CoarseNet.jpg)
**FineNet** is a robust inception-resnet based minutiae classifier. It processes each candidate patch, a square region whose center is the candidate minutiae point, to refine the minutiae score map and approximate minutiae orientation by regression. Final minutiae are the classification results.
We refer reader to read [FineNet_architecture.pdf](assets/FineNet_architecture.pdf) for more details of FineNet.
The repository includes:
* Source code of Minutiae Net which includes CoarseNet and FineNet.
* Training code for FineNet and CoarseNet
* Pre-trained weights for FineNet and CoarseNet
* Jupyter notebooks to visualize the minutiae detection pipeline at every step
### License
MinutiaeNet is released under the MIT License.
### Citing
If you find MinutiaeNet useful in your research, please citing:
@inproceedings{Nguyen_MinutiaeNet,
author = {Dinh-Luan Nguyen and Kai Cao and Anil K. Jain},
title = {Robust Minutiae Extractor: Integrating Deep Networks and Fingerprint Domain Knowledge},
booktitle = {The 11th International Conference on Biometrics, 2018},
year = {2018},
}
### Contents
1. [Requirements: software](#requirements-software)
2. [Installation](#installation)
3. [Demo](#demo)
4. [Usage](#usage)
# Requirements: software
`Python 2.7`, `Tensorflow 1.7.0`, `Keras 2.1.6`.
# Installation
To make life easier, I suggest to use Anaconda for easy installation. Version using pip is similar.
```Shell
conda install cv2, numpy, scipy, matplotlib, pydot, graphviz
```
Download models and put into `Models` folder.
- **CoarseNet**: [Googledrive](https://drive.google.com/file/d/1alvw_kAyY4sxdzAkGABQR7waux-rgJKm/view?usp=sharing) || [Dropbox](https://www.dropbox.com/s/gppil4wybdjcihy/CoarseNet.h5?dl=0)
- **FineNet**: [Googledrive](https://drive.google.com/file/d/1wdGZKNNDAyN-fajjVKJoiyDtXAvl-4zq/view?usp=sharing) || [Dropbox](https://www.dropbox.com/s/k7q2vs9255jf2dh/FineNet.h5?dl=0)
# Demo
To help understanding MinutiaeNet, there are 2 notebooks for you to play around:
- Understanding CoarseNet: [demo_CoarseNet.ipynb](Demo_notebooks/demo_CoarseNet.ipynb)
- Understanding FineNet: [demo_FineNet.ipynb](Demo_notebooks/demo_FineNet.ipynb)
- MinutiaeNet - a combination of CoarseNet and FineNet: set `isHavingFineNet = False` in CoarsetNet if you want to fuse results from CoarseNet and FineNet
# Usage
- **FineNet**
* [demo_FineNet.ipynb](Demo_notebooks/demo_FineNet.ipynb) is useful if you want to integrate into existing minutiae dectection framework/SDKs. It shows an example of using a pre-trained model to verify the detection in your own images.
* [train_FineNet.ipynb](Demo_notebooks/train_FineNet.ipynb) shows how to train FineNet on your own dataset.
- **CoarseNet**
* [demo_CoarseNet.ipynb](Demo_notebooks/demo_CoarseNet.ipynb) can be called to generate minutiae as well as masks and orientation.
* [train_CoarseNet.ipynb](Demo_notebooks/train_CoarseNet.ipynb) shows how to train CoarseNet on your own dataset.
Python files which can run directly from command line are also provided.
Note that models as well as architectures here are slightly different from the paper because of the continuing development of this project
Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB