"""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() 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