More ready
@@ -36,8 +36,8 @@
|
|||||||
" nazwa pliku: `dark_x.png`, `dim_x.png`, `bright_x.png`\n",
|
" nazwa pliku: `dark_x.png`, `dim_x.png`, `bright_x.png`\n",
|
||||||
"* Oko przymróżone, standardowo otwarte i maksymalnie otwarte (powieki przytrzymane palcami)\n",
|
"* Oko przymróżone, standardowo otwarte i maksymalnie otwarte (powieki przytrzymane palcami)\n",
|
||||||
" nazwa pliku: `squint_x.png`, `open_x.png`, `fully_open_x.png`\n",
|
" nazwa pliku: `squint_x.png`, `open_x.png`, `fully_open_x.png`\n",
|
||||||
"* Oko patrzące na 5 różnych celów za kamerą.\n",
|
"* Oko patrzące w różnych kierunkach. \n",
|
||||||
" nazwa pliku: `-45_x.png`, `-20_x.png`, `0_x.png`, `20_x.png`, `45_x.png`\n",
|
" nazwa pliku: `left_x.png`, `right_x.png`, `top_x.png`, `bottom_x.png`, `far_right_x.png`, `far_left_x.png`\n",
|
||||||
"* Oko w różnych odległościach od kamery: standardowa, pół-metra, metr\n",
|
"* Oko w różnych odległościach od kamery: standardowa, pół-metra, metr\n",
|
||||||
" nazwa pliku: `20cm_x.png`, `50cm_x.png`, `1m_x.png`\n",
|
" nazwa pliku: `20cm_x.png`, `50cm_x.png`, `1m_x.png`\n",
|
||||||
"* Zdjęcie lewego i prawego oka tej samej osoby:\n",
|
"* Zdjęcie lewego i prawego oka tej samej osoby:\n",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN python2 -m ipykernel install --user
|
|||||||
|
|
||||||
# Copy project files
|
# Copy project files
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ./MinutiaeNet /src/MinutiaeNet
|
COPY ./src /src
|
||||||
|
|
||||||
# Launch Jupyter Notebook
|
# Launch Jupyter Notebook
|
||||||
EXPOSE 8888
|
EXPOSE 8888
|
||||||
|
|||||||
@@ -1,386 +0,0 @@
|
|||||||
"""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
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 601 KiB |
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 601 KiB |
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 796 B |
|
Before Width: | Height: | Size: 683 B |
|
Before Width: | Height: | Size: 873 B |
|
Before Width: | Height: | Size: 821 B |
|
Before Width: | Height: | Size: 664 B |
@@ -1,294 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 532 KiB |
|
Before Width: | Height: | Size: 184 KiB |
|
Before Width: | Height: | Size: 478 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,294 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ from scipy import misc, ndimage, signal, sparse, io
|
|||||||
import scipy.ndimage
|
import scipy.ndimage
|
||||||
import cv2
|
import cv2
|
||||||
import sys,os
|
import sys,os
|
||||||
sys.path.append(os.path.realpath('../FineNet'))
|
sys.path.append(os.path.realpath('./FineNet'))
|
||||||
from FineNet_model import FineNetmodel
|
from FineNet_model import FineNetmodel
|
||||||
|
|
||||||
from keras.models import Model
|
from keras.models import Model
|
||||||
@@ -0,0 +1,519 @@
|
|||||||
|
"""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
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import tensorflow as tf
|
||||||
|
from keras import backend as K
|
||||||
|
from keras.layers import Input
|
||||||
|
from keras.layers.core import Lambda
|
||||||
|
from keras.models import Model
|
||||||
|
from MinutiaeNet_utils import *
|
||||||
|
from scipy import misc, ndimage, signal, sparse
|
||||||
|
|
||||||
|
|
||||||
|
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.0 * 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.0) / 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
|
||||||
@@ -558,7 +558,7 @@ def get_maps_STFT(img,patch_size = 64,block_size = 16, preprocess = False):
|
|||||||
|
|
||||||
|
|
||||||
RMIN = 3 # min allowable ridge spacing
|
RMIN = 3 # min allowable ridge spacing
|
||||||
RMAX = 18 # maximum allowable ridge spacing
|
RMAX = 200 # maximum allowable ridge spacing
|
||||||
FLOW = patch_size / RMAX
|
FLOW = patch_size / RMAX
|
||||||
FHIGH = patch_size / RMIN
|
FHIGH = patch_size / RMIN
|
||||||
dRLow = 1. / (1 + (r / FHIGH) ** 4)
|
dRLow = 1. / (1 + (r / FHIGH) ** 4)
|
||||||
@@ -783,7 +783,7 @@ def show_orientation_field(img,dir_map,mask=None,fname=None):
|
|||||||
|
|
||||||
blk_size = h/blkH
|
blk_size = h/blkH
|
||||||
|
|
||||||
R = blk_size/2*0.8
|
R = blk_size/2
|
||||||
fig, ax = plt.subplots(1)
|
fig, ax = plt.subplots(1)
|
||||||
ax.imshow(img, cmap='gray')
|
ax.imshow(img, cmap='gray')
|
||||||
for i in range(blkH):
|
for i in range(blkH):
|
||||||
@@ -801,7 +801,7 @@ def show_orientation_field(img,dir_map,mask=None,fname=None):
|
|||||||
x2 = x0 + R * math.cos(ori)
|
x2 = x0 + R * math.cos(ori)
|
||||||
y1 = y0 - R * math.sin(ori)
|
y1 = y0 - R * math.sin(ori)
|
||||||
y2 = y0 + R * math.sin(ori)
|
y2 = y0 + R * math.sin(ori)
|
||||||
plt.plot([x1, x2], [y1, y2], 'r-', lw=2)
|
plt.plot([x1, x2], [y1, y2], 'r-', lw=1)
|
||||||
plt.axis('off')
|
plt.axis('off')
|
||||||
if fname is not None:
|
if fname is not None:
|
||||||
fig.savefig(fname,dpi = 500, bbox_inches='tight', pad_inches = 0)
|
fig.savefig(fname,dpi = 500, bbox_inches='tight', pad_inches = 0)
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||