From 10373185a959d3a5bf5f26d591b0c063e3743167 Mon Sep 17 00:00:00 2001 From: Abhay Date: Tue, 18 Feb 2020 14:03:11 +0530 Subject: [PATCH] First Commit --- FaceSimilarity.py | 64 ++++++ models.py | 516 ++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 100 +++++++++ utils.py | 94 +++++++++ vggface.py | 112 ++++++++++ 5 files changed, 886 insertions(+) create mode 100644 FaceSimilarity.py create mode 100644 models.py create mode 100644 requirements.txt create mode 100644 utils.py create mode 100644 vggface.py diff --git a/FaceSimilarity.py b/FaceSimilarity.py new file mode 100644 index 0000000..eb89539 --- /dev/null +++ b/FaceSimilarity.py @@ -0,0 +1,64 @@ +# face verification with the VGGFace2 model +import matplotlib.pyplot as plt +from PIL import Image +import numpy as np +from scipy.spatial.distance import cosine +from mtcnn.mtcnn import MTCNN +from utils import preprocess_input +from vggface import VGGFace +import sys + + +# extract a single face from a given photograph +def extract_face(filename, required_size=(224, 224)): + img = plt.imread(filename) + detector = MTCNN() + results = detector.detect_faces(img) + x1, y1, width, height = results[0]['box'] + x2, y2 = x1 + width, y1 + height + # extract the face + face = img[y1:y2, x1:x2] + # resize pixels to the model size + image = Image.fromarray(face) + image = image.resize(required_size) + face_array = np.asarray(image) + return face_array + + +# extract faces and calculate face embeddings for a list of photo files +def get_embeddings(filenames): + # extract faces + faces = [extract_face(f) for f in filenames] + # convert into an array of samples + samples = np.asarray(faces, 'float32') + # prepare the face for the model, e.g. center pixels + samples = preprocess_input(samples, version=2) + + model = VGGFace(model='resnet50', include_top=False, input_shape=(224, 224, 3), pooling='avg') + pred = model.predict(samples) + return pred + + +# determine if a candidate face is a match for a known face +def is_match(known_embedding, candidate_embedding, thresh=0.5): + # calculate distance between embeddings + score = cosine(known_embedding, candidate_embedding) + + print('*****************************************************************') + print('Threshold for the face similarity score is 0.5') + + if score <= thresh: + print('Face is a Match with score of %.3f' % score) + else: + print('Face is not a Match with score of %.3f' % score) + + print('********************************************************************') + + +def main(): + embeddings = get_embeddings([sys.argv[1], sys.argv[2]]) + is_match(embeddings[0], embeddings[1]) + + +if __name__ == '__main__': + main() diff --git a/models.py b/models.py new file mode 100644 index 0000000..01b4c0d --- /dev/null +++ b/models.py @@ -0,0 +1,516 @@ +'''VGGFace models for Keras. + +# Notes: +- Resnet50 and VGG16 are modified architectures from Keras Application folder. [Keras](https://keras.io) + +- Squeeze and excitation block is taken from [Squeeze and Excitation Networks in + Keras](https://github.com/titu1994/keras-squeeze-excite-network) and modified. + +''' + + +from keras.layers import Flatten, Dense, Input, GlobalAveragePooling2D, \ + GlobalMaxPooling2D, Activation, Conv2D, MaxPooling2D, BatchNormalization, \ + AveragePooling2D, Reshape, Permute, multiply +from keras_applications.imagenet_utils import _obtain_input_shape +from keras.utils import layer_utils +from keras.utils.data_utils import get_file +from keras import backend as K +import utils +from keras.engine.topology import get_source_inputs +import warnings +from keras.models import Model +from keras import layers + + +def VGG16(include_top=True, weights='vggface', + input_tensor=None, input_shape=None, + pooling=None, + classes=2622): + input_shape = _obtain_input_shape(input_shape, + default_size=224, + min_size=48, + data_format=K.image_data_format(), + require_flatten=include_top) + + if input_tensor is None: + img_input = Input(shape=input_shape) + else: + if not K.is_keras_tensor(input_tensor): + img_input = Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + # Block 1 + x = Conv2D(64, (3, 3), activation='relu', padding='same', name='conv1_1')( + img_input) + x = Conv2D(64, (3, 3), activation='relu', padding='same', name='conv1_2')(x) + x = MaxPooling2D((2, 2), strides=(2, 2), name='pool1')(x) + + # Block 2 + x = Conv2D(128, (3, 3), activation='relu', padding='same', name='conv2_1')( + x) + x = Conv2D(128, (3, 3), activation='relu', padding='same', name='conv2_2')( + x) + x = MaxPooling2D((2, 2), strides=(2, 2), name='pool2')(x) + + # Block 3 + x = Conv2D(256, (3, 3), activation='relu', padding='same', name='conv3_1')( + x) + x = Conv2D(256, (3, 3), activation='relu', padding='same', name='conv3_2')( + x) + x = Conv2D(256, (3, 3), activation='relu', padding='same', name='conv3_3')( + x) + x = MaxPooling2D((2, 2), strides=(2, 2), name='pool3')(x) + + # Block 4 + x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv4_1')( + x) + x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv4_2')( + x) + x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv4_3')( + x) + x = MaxPooling2D((2, 2), strides=(2, 2), name='pool4')(x) + + # Block 5 + x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv5_1')( + x) + x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv5_2')( + x) + x = Conv2D(512, (3, 3), activation='relu', padding='same', name='conv5_3')( + x) + x = MaxPooling2D((2, 2), strides=(2, 2), name='pool5')(x) + + if include_top: + # Classification block + x = Flatten(name='flatten')(x) + x = Dense(4096, name='fc6')(x) + x = Activation('relu', name='fc6/relu')(x) + x = Dense(4096, name='fc7')(x) + x = Activation('relu', name='fc7/relu')(x) + x = Dense(classes, name='fc8')(x) + x = Activation('softmax', name='fc8/softmax')(x) + else: + if pooling == 'avg': + x = GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = Model(inputs, x, name='vggface_vgg16') # load weights + if weights == 'vggface': + if include_top: + weights_path = get_file('rcmalli_vggface_tf_vgg16.h5', + utils. + VGG16_WEIGHTS_PATH, + cache_subdir=utils.VGGFACE_DIR) + else: + weights_path = get_file('rcmalli_vggface_tf_notop_vgg16.h5', + utils.VGG16_WEIGHTS_PATH_NO_TOP, + cache_subdir=utils.VGGFACE_DIR) + model.load_weights(weights_path, by_name=True) + if K.backend() == 'theano': + layer_utils.convert_all_kernels_in_model(model) + + if K.image_data_format() == 'channels_first': + if include_top: + maxpool = model.get_layer(name='pool5') + shape = maxpool.output_shape[1:] + dense = model.get_layer(name='fc6') + layer_utils.convert_dense_weights_data_format(dense, shape, + 'channels_first') + + if K.backend() == 'tensorflow': + warnings.warn('You are using the TensorFlow backend, yet you ' + 'are using the Theano ' + 'image data format convention ' + '(`image_data_format="channels_first"`). ' + 'For best performance, set ' + '`image_data_format="channels_last"` in ' + 'your Keras config ' + 'at ~/.keras/keras.json.') + return model + + +def resnet_identity_block(input_tensor, kernel_size, filters, stage, block, + bias=False): + filters1, filters2, filters3 = filters + if K.image_data_format() == 'channels_last': + bn_axis = 3 + else: + bn_axis = 1 + conv1_reduce_name = 'conv' + str(stage) + "_" + str(block) + "_1x1_reduce" + conv1_increase_name = 'conv' + str(stage) + "_" + str( + block) + "_1x1_increase" + conv3_name = 'conv' + str(stage) + "_" + str(block) + "_3x3" + + x = Conv2D(filters1, (1, 1), use_bias=bias, name=conv1_reduce_name)( + input_tensor) + x = BatchNormalization(axis=bn_axis, name=conv1_reduce_name + "/bn")(x) + x = Activation('relu')(x) + + x = Conv2D(filters2, kernel_size, use_bias=bias, + padding='same', name=conv3_name)(x) + x = BatchNormalization(axis=bn_axis, name=conv3_name + "/bn")(x) + x = Activation('relu')(x) + + x = Conv2D(filters3, (1, 1), use_bias=bias, name=conv1_increase_name)(x) + x = BatchNormalization(axis=bn_axis, name=conv1_increase_name + "/bn")(x) + + x = layers.add([x, input_tensor]) + x = Activation('relu')(x) + return x + + +def resnet_conv_block(input_tensor, kernel_size, filters, stage, block, + strides=(2, 2), bias=False): + filters1, filters2, filters3 = filters + if K.image_data_format() == 'channels_last': + bn_axis = 3 + else: + bn_axis = 1 + conv1_reduce_name = 'conv' + str(stage) + "_" + str(block) + "_1x1_reduce" + conv1_increase_name = 'conv' + str(stage) + "_" + str( + block) + "_1x1_increase" + conv1_proj_name = 'conv' + str(stage) + "_" + str(block) + "_1x1_proj" + conv3_name = 'conv' + str(stage) + "_" + str(block) + "_3x3" + + x = Conv2D(filters1, (1, 1), strides=strides, use_bias=bias, + name=conv1_reduce_name)(input_tensor) + x = BatchNormalization(axis=bn_axis, name=conv1_reduce_name + "/bn")(x) + x = Activation('relu')(x) + + x = Conv2D(filters2, kernel_size, padding='same', use_bias=bias, + name=conv3_name)(x) + x = BatchNormalization(axis=bn_axis, name=conv3_name + "/bn")(x) + x = Activation('relu')(x) + + x = Conv2D(filters3, (1, 1), name=conv1_increase_name, use_bias=bias)(x) + x = BatchNormalization(axis=bn_axis, name=conv1_increase_name + "/bn")(x) + + shortcut = Conv2D(filters3, (1, 1), strides=strides, use_bias=bias, + name=conv1_proj_name)(input_tensor) + shortcut = BatchNormalization(axis=bn_axis, name=conv1_proj_name + "/bn")( + shortcut) + + x = layers.add([x, shortcut]) + x = Activation('relu')(x) + return x + + +def RESNET50(include_top=True, weights='vggface', + input_tensor=None, input_shape=None, + pooling=None, + classes=8631): + input_shape = _obtain_input_shape(input_shape, + default_size=224, + min_size=32, + data_format=K.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = Input(shape=input_shape) + else: + if not K.is_keras_tensor(input_tensor): + img_input = Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + if K.image_data_format() == 'channels_last': + bn_axis = 3 + else: + bn_axis = 1 + + x = Conv2D( + 64, (7, 7), use_bias=False, strides=(2, 2), padding='same', + name='conv1/7x7_s2')(img_input) + x = BatchNormalization(axis=bn_axis, name='conv1/7x7_s2/bn')(x) + x = Activation('relu')(x) + x = MaxPooling2D((3, 3), strides=(2, 2))(x) + + x = resnet_conv_block(x, 3, [64, 64, 256], stage=2, block=1, strides=(1, 1)) + x = resnet_identity_block(x, 3, [64, 64, 256], stage=2, block=2) + x = resnet_identity_block(x, 3, [64, 64, 256], stage=2, block=3) + + x = resnet_conv_block(x, 3, [128, 128, 512], stage=3, block=1) + x = resnet_identity_block(x, 3, [128, 128, 512], stage=3, block=2) + x = resnet_identity_block(x, 3, [128, 128, 512], stage=3, block=3) + x = resnet_identity_block(x, 3, [128, 128, 512], stage=3, block=4) + + x = resnet_conv_block(x, 3, [256, 256, 1024], stage=4, block=1) + x = resnet_identity_block(x, 3, [256, 256, 1024], stage=4, block=2) + x = resnet_identity_block(x, 3, [256, 256, 1024], stage=4, block=3) + x = resnet_identity_block(x, 3, [256, 256, 1024], stage=4, block=4) + x = resnet_identity_block(x, 3, [256, 256, 1024], stage=4, block=5) + x = resnet_identity_block(x, 3, [256, 256, 1024], stage=4, block=6) + + x = resnet_conv_block(x, 3, [512, 512, 2048], stage=5, block=1) + x = resnet_identity_block(x, 3, [512, 512, 2048], stage=5, block=2) + x = resnet_identity_block(x, 3, [512, 512, 2048], stage=5, block=3) + + x = AveragePooling2D((7, 7), name='avg_pool')(x) + + if include_top: + x = Flatten()(x) + x = Dense(classes, activation='softmax', name='classifier')(x) + else: + if pooling == 'avg': + x = GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = Model(inputs, x, name='vggface_resnet50') + + # load weights + if weights == 'vggface': + if include_top: + weights_path = get_file('rcmalli_vggface_tf_resnet50.h5', + utils.RESNET50_WEIGHTS_PATH, + cache_subdir=utils.VGGFACE_DIR) + else: + weights_path = get_file('rcmalli_vggface_tf_notop_resnet50.h5', + utils.RESNET50_WEIGHTS_PATH_NO_TOP, + cache_subdir=utils.VGGFACE_DIR) + model.load_weights(weights_path) + if K.backend() == 'theano': + layer_utils.convert_all_kernels_in_model(model) + if include_top: + maxpool = model.get_layer(name='avg_pool') + shape = maxpool.output_shape[1:] + dense = model.get_layer(name='classifier') + layer_utils.convert_dense_weights_data_format(dense, shape, + 'channels_first') + + if K.image_data_format() == 'channels_first' and K.backend() == 'tensorflow': + warnings.warn('You are using the TensorFlow backend, yet you ' + 'are using the Theano ' + 'image data format convention ' + '(`image_data_format="channels_first"`). ' + 'For best performance, set ' + '`image_data_format="channels_last"` in ' + 'your Keras config ' + 'at ~/.keras/keras.json.') + elif weights is not None: + model.load_weights(weights) + + return model + + +def senet_se_block(input_tensor, stage, block, compress_rate=16, bias=False): + conv1_down_name = 'conv' + str(stage) + "_" + str( + block) + "_1x1_down" + conv1_up_name = 'conv' + str(stage) + "_" + str( + block) + "_1x1_up" + + num_channels = int(input_tensor.shape[-1]) + bottle_neck = int(num_channels // compress_rate) + + se = GlobalAveragePooling2D()(input_tensor) + se = Reshape((1, 1, num_channels))(se) + se = Conv2D(bottle_neck, (1, 1), use_bias=bias, + name=conv1_down_name)(se) + se = Activation('relu')(se) + se = Conv2D(num_channels, (1, 1), use_bias=bias, + name=conv1_up_name)(se) + se = Activation('sigmoid')(se) + + x = input_tensor + x = multiply([x, se]) + return x + + +def senet_conv_block(input_tensor, kernel_size, filters, + stage, block, bias=False, strides=(2, 2)): + filters1, filters2, filters3 = filters + if K.image_data_format() == 'channels_last': + bn_axis = 3 + else: + bn_axis = 1 + + bn_eps = 0.0001 + + conv1_reduce_name = 'conv' + str(stage) + "_" + str(block) + "_1x1_reduce" + conv1_increase_name = 'conv' + str(stage) + "_" + str( + block) + "_1x1_increase" + conv1_proj_name = 'conv' + str(stage) + "_" + str(block) + "_1x1_proj" + conv3_name = 'conv' + str(stage) + "_" + str(block) + "_3x3" + + x = Conv2D(filters1, (1, 1), use_bias=bias, strides=strides, + name=conv1_reduce_name)(input_tensor) + x = BatchNormalization(axis=bn_axis, name=conv1_reduce_name + "/bn",epsilon=bn_eps)(x) + x = Activation('relu')(x) + + x = Conv2D(filters2, kernel_size, padding='same', use_bias=bias, + name=conv3_name)(x) + x = BatchNormalization(axis=bn_axis, name=conv3_name + "/bn",epsilon=bn_eps)(x) + x = Activation('relu')(x) + + x = Conv2D(filters3, (1, 1), name=conv1_increase_name, use_bias=bias)(x) + x = BatchNormalization(axis=bn_axis, name=conv1_increase_name + "/bn" ,epsilon=bn_eps)(x) + + se = senet_se_block(x, stage=stage, block=block, bias=True) + + shortcut = Conv2D(filters3, (1, 1), use_bias=bias, strides=strides, + name=conv1_proj_name)(input_tensor) + shortcut = BatchNormalization(axis=bn_axis, + name=conv1_proj_name + "/bn",epsilon=bn_eps)(shortcut) + + m = layers.add([se, shortcut]) + m = Activation('relu')(m) + return m + + +def senet_identity_block(input_tensor, kernel_size, + filters, stage, block, bias=False): + filters1, filters2, filters3 = filters + if K.image_data_format() == 'channels_last': + bn_axis = 3 + else: + bn_axis = 1 + + bn_eps = 0.0001 + + conv1_reduce_name = 'conv' + str(stage) + "_" + str(block) + "_1x1_reduce" + conv1_increase_name = 'conv' + str(stage) + "_" + str( + block) + "_1x1_increase" + conv3_name = 'conv' + str(stage) + "_" + str(block) + "_3x3" + + x = Conv2D(filters1, (1, 1), use_bias=bias, + name=conv1_reduce_name)(input_tensor) + x = BatchNormalization(axis=bn_axis, name=conv1_reduce_name + "/bn",epsilon=bn_eps)(x) + x = Activation('relu')(x) + + x = Conv2D(filters2, kernel_size, padding='same', use_bias=bias, + name=conv3_name)(x) + x = BatchNormalization(axis=bn_axis, name=conv3_name + "/bn",epsilon=bn_eps)(x) + x = Activation('relu')(x) + + x = Conv2D(filters3, (1, 1), name=conv1_increase_name, use_bias=bias)(x) + x = BatchNormalization(axis=bn_axis, name=conv1_increase_name + "/bn",epsilon=bn_eps)(x) + + se = senet_se_block(x, stage=stage, block=block, bias=True) + + m = layers.add([se, input_tensor]) + m = Activation('relu')(m) + + return m + + +def SENET50(include_top=True, weights='vggface', + input_tensor=None, input_shape=None, + pooling=None, + classes=8631): + input_shape = _obtain_input_shape(input_shape, + default_size=224, + min_size=197, + data_format=K.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = Input(shape=input_shape) + else: + if not K.is_keras_tensor(input_tensor): + img_input = Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + if K.image_data_format() == 'channels_last': + bn_axis = 3 + else: + bn_axis = 1 + + bn_eps = 0.0001 + + x = Conv2D( + 64, (7, 7), use_bias=False, strides=(2, 2), padding='same', + name='conv1/7x7_s2')(img_input) + x = BatchNormalization(axis=bn_axis, name='conv1/7x7_s2/bn',epsilon=bn_eps)(x) + x = Activation('relu')(x) + x = MaxPooling2D((3, 3), strides=(2, 2))(x) + + x = senet_conv_block(x, 3, [64, 64, 256], stage=2, block=1, strides=(1, 1)) + x = senet_identity_block(x, 3, [64, 64, 256], stage=2, block=2) + x = senet_identity_block(x, 3, [64, 64, 256], stage=2, block=3) + + x = senet_conv_block(x, 3, [128, 128, 512], stage=3, block=1) + x = senet_identity_block(x, 3, [128, 128, 512], stage=3, block=2) + x = senet_identity_block(x, 3, [128, 128, 512], stage=3, block=3) + x = senet_identity_block(x, 3, [128, 128, 512], stage=3, block=4) + + x = senet_conv_block(x, 3, [256, 256, 1024], stage=4, block=1) + x = senet_identity_block(x, 3, [256, 256, 1024], stage=4, block=2) + x = senet_identity_block(x, 3, [256, 256, 1024], stage=4, block=3) + x = senet_identity_block(x, 3, [256, 256, 1024], stage=4, block=4) + x = senet_identity_block(x, 3, [256, 256, 1024], stage=4, block=5) + x = senet_identity_block(x, 3, [256, 256, 1024], stage=4, block=6) + + x = senet_conv_block(x, 3, [512, 512, 2048], stage=5, block=1) + x = senet_identity_block(x, 3, [512, 512, 2048], stage=5, block=2) + x = senet_identity_block(x, 3, [512, 512, 2048], stage=5, block=3) + + x = AveragePooling2D((7, 7), name='avg_pool')(x) + + if include_top: + x = Flatten()(x) + x = Dense(classes, activation='softmax', name='classifier')(x) + else: + if pooling == 'avg': + x = GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = Model(inputs, x, name='vggface_senet50') + + # load weights + if weights == 'vggface': + if include_top: + weights_path = get_file('rcmalli_vggface_tf_senet50.h5', + utils.SENET50_WEIGHTS_PATH, + cache_subdir=utils.VGGFACE_DIR) + else: + weights_path = get_file('rcmalli_vggface_tf_notop_senet50.h5', + utils.SENET50_WEIGHTS_PATH_NO_TOP, + cache_subdir=utils.VGGFACE_DIR) + model.load_weights(weights_path) + if K.backend() == 'theano': + layer_utils.convert_all_kernels_in_model(model) + if include_top: + maxpool = model.get_layer(name='avg_pool') + shape = maxpool.output_shape[1:] + dense = model.get_layer(name='classifier') + layer_utils.convert_dense_weights_data_format(dense, shape, + 'channels_first') + + if K.image_data_format() == 'channels_first' and K.backend() == 'tensorflow': + warnings.warn('You are using the TensorFlow backend, yet you ' + 'are using the Theano ' + 'image data format convention ' + '(`image_data_format="channels_first"`). ' + 'For best performance, set ' + '`image_data_format="channels_last"` in ' + 'your Keras config ' + 'at ~/.keras/keras.json.') + elif weights is not None: + model.load_weights(weights) + + return model diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3c281ad --- /dev/null +++ b/requirements.txt @@ -0,0 +1,100 @@ +absl-py==0.9.0 +apturl==0.5.2 +astor==0.8.1 +beautifulsoup4==4.4.1 +blinker==1.3 +Brlapi==0.6.4 +chardet==2.3.0 +checkbox-support==0.22 +Click==7.0 +cmake==3.13.3 +command-not-found==0.3 +cryptography==1.2.3 +dateparser==0.7.2 +defer==1.0.6 +dlib==19.9.0 +face-recognition==1.2.3 +face-recognition-models==0.3.0 +feedparser==5.1.3 +Flask==1.1.1 +gast==0.3.3 +grpcio==1.27.1 +guacamole==0.9.2 +h5py==2.10.0 +html5lib==0.999 +httplib2==0.9.1 +idna==2.0 +impyute==0.0.8 +itsdangerous==1.1.0 +Jinja2==2.11.1 +joblib==0.14.1 +Keras==2.3.1 +Keras-Applications==1.0.8 +Keras-Preprocessing==1.1.0 +keras-vggface==0.6 +language-selector==0.1 +louis==2.6.4 +lxml==3.5.0 +Mako==1.0.3 +Markdown==3.2.1 +MarkupSafe==1.1.1 +mtcnn==0.1.0 +numpy==1.18.1 +oauthlib==1.0.3 +onboard==1.2.0 +opencv-python==4.2.0.32 +padme==1.1.1 +pexpect==4.0.1 +Pillow==7.0.0 +plainbox==0.25 +protobuf==3.11.3 +ptyprocess==0.5 +pyasn1==0.1.9 +pycups==1.9.73 +pycurl==7.43.0 +pydot==1.4.1 +pygobject==3.20.0 +PyJWT==1.3.0 +pyparsing==2.4.6 +python-apt==1.1.0b1+ubuntu0.16.4.8 +python-dateutil==2.8.1 +python-debian==0.1.27 +python-systemd==231 +pytz==2019.3 +pyxdg==0.25 +PyYAML==5.3 +regex==2020.1.8 +reportlab==3.3.0 +requests==2.9.1 +scikit-learn==0.22.1 +scipy==1.4.1 +sessioninstaller==0.0.0 +six==1.14.0 +system-service==0.3 +tensorboard==1.10.0 +tensorflow==1.10.0 +termcolor==1.1.0 +tzlocal==2.0.0 +ubuntu-drivers-common==0.0.0 +ufw==0.35 +unattended-upgrades==0.1 +unity-scope-calculator==0.1 +unity-scope-chromiumbookmarks==0.1 +unity-scope-colourlovers==0.1 +unity-scope-devhelp==0.1 +unity-scope-firefoxbookmarks==0.1 +unity-scope-gdrive==0.7 +unity-scope-manpages==0.1 +unity-scope-openclipart==0.1 +unity-scope-texdoc==0.1 +unity-scope-tomboy==0.1 +unity-scope-virtualbox==0.1 +unity-scope-yelp==0.1 +unity-scope-zotero==0.1 +urllib3==1.13.1 +usb-creator==0.3.0 +Werkzeug==1.0.0 +xdiagnose==3.8.4.1 +xgboost==0.90 +xkit==0.0.0 +XlsxWriter==0.7.3 diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..528d0e8 --- /dev/null +++ b/utils.py @@ -0,0 +1,94 @@ +'''VGGFace models for Keras. + +# Notes: +- Utility functions are modified versions of Keras functions [Keras](https://keras.io) + +''' +import numpy as np +from keras import backend as K +from keras.utils.data_utils import get_file + +V1_LABELS_PATH = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_labels_v1.npy' +V2_LABELS_PATH = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_labels_v2.npy' + +VGG16_WEIGHTS_PATH = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_tf_vgg16.h5' +VGG16_WEIGHTS_PATH_NO_TOP = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_tf_notop_vgg16.h5' + + +RESNET50_WEIGHTS_PATH = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_tf_resnet50.h5' +RESNET50_WEIGHTS_PATH_NO_TOP = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_tf_notop_resnet50.h5' + +SENET50_WEIGHTS_PATH = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_tf_senet50.h5' +SENET50_WEIGHTS_PATH_NO_TOP = 'https://github.com/rcmalli/keras-vggface/releases/download/v2.0/rcmalli_vggface_tf_notop_senet50.h5' + + +VGGFACE_DIR = 'models/vggface' + + +def preprocess_input(x, data_format=None, version=1): + x_temp = np.copy(x) + if data_format is None: + data_format = K.image_data_format() + assert data_format in {'channels_last', 'channels_first'} + + if version == 1: + if data_format == 'channels_first': + x_temp = x_temp[:, ::-1, ...] + x_temp[:, 0, :, :] -= 93.5940 + x_temp[:, 1, :, :] -= 104.7624 + x_temp[:, 2, :, :] -= 129.1863 + else: + x_temp = x_temp[..., ::-1] + x_temp[..., 0] -= 93.5940 + x_temp[..., 1] -= 104.7624 + x_temp[..., 2] -= 129.1863 + + elif version == 2: + if data_format == 'channels_first': + x_temp = x_temp[:, ::-1, ...] + x_temp[:, 0, :, :] -= 91.4953 + x_temp[:, 1, :, :] -= 103.8827 + x_temp[:, 2, :, :] -= 131.0912 + else: + x_temp = x_temp[..., ::-1] + x_temp[..., 0] -= 91.4953 + x_temp[..., 1] -= 103.8827 + x_temp[..., 2] -= 131.0912 + else: + raise NotImplementedError + + return x_temp + + +def decode_predictions(preds, top=5): + LABELS = None + if len(preds.shape) == 2: + if preds.shape[1] == 2622: + fpath = get_file('rcmalli_vggface_labels_v1.npy', + V1_LABELS_PATH, + cache_subdir=VGGFACE_DIR) + LABELS = np.load(fpath) + elif preds.shape[1] == 8631: + fpath = get_file('rcmalli_vggface_labels_v2.npy', + V2_LABELS_PATH, + cache_subdir=VGGFACE_DIR) + LABELS = np.load(fpath) + else: + raise ValueError('`decode_predictions` expects ' + 'a batch of predictions ' + '(i.e. a 2D array of shape (samples, 2622)) for V1 or ' + '(samples, 8631) for V2.' + 'Found array with shape: ' + str(preds.shape)) + else: + raise ValueError('`decode_predictions` expects ' + 'a batch of predictions ' + '(i.e. a 2D array of shape (samples, 2622)) for V1 or ' + '(samples, 8631) for V2.' + 'Found array with shape: ' + str(preds.shape)) + results = [] + for pred in preds: + top_indices = pred.argsort()[-top:][::-1] + result = [[str(LABELS[i].encode('utf8')), pred[i]] for i in top_indices] + result.sort(key=lambda x: x[1], reverse=True) + results.append(result) + return results diff --git a/vggface.py b/vggface.py new file mode 100644 index 0000000..775eede --- /dev/null +++ b/vggface.py @@ -0,0 +1,112 @@ +'''VGGFace models for Keras. + +# Reference: +- [Deep Face Recognition](http://www.robots.ox.ac.uk/~vgg/publications/2015/Parkhi15/parkhi15.pdf) +- [VGGFace2: A dataset for recognising faces across pose and age](http://www.robots.ox.ac.uk/~vgg/data/vgg_face2/vggface2.pdf) + +''' +from __future__ import print_function +from models import RESNET50, VGG16, SENET50 + + +def VGGFace(include_top=True, model='vgg16', weights='vggface', + input_tensor=None, input_shape=None, + pooling=None, + classes=None): + """Instantiates the VGGFace architectures. + Optionally loads weights pre-trained + on VGGFace datasets. Note that when using TensorFlow, + for best performance you should set + `image_data_format="channels_last"` in your Keras config + at ~/.keras/keras.json. + The model and the weights are compatible with both + TensorFlow and Theano. The data format + convention used by the model is the one + specified in your Keras config file. + # Arguments + include_top: whether to include the 3 fully-connected + layers at the top of the network. + weights: one of `None` (random initialization) + or "vggface" (pre-training on VGGFACE datasets). + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) + to use as image input for the model. + model: selects the one of the available architectures + vgg16, resnet50 or senet50 default is vgg16. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` (with `channels_last` data format) + or `(3, 224, 244)` (with `channels_first` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 48. + E.g. `(200, 200, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional layer. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional layer, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + # Returns + A Keras model instance. + # Raises + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + + if weights not in {'vggface', None}: + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization) or `vggface`' + '(pre-training on VGGFace Datasets).') + + if model == 'vgg16': + + if classes is None: + classes = 2622 + + if weights == 'vggface' and include_top and classes != 2622: + raise ValueError( + 'If using `weights` as vggface original with `include_top`' + ' as true, `classes` should be 2622') + + return VGG16(include_top=include_top, input_tensor=input_tensor, + input_shape=input_shape, pooling=pooling, + weights=weights, + classes=classes) + + + if model == 'resnet50': + + if classes is None: + classes = 8631 + + if weights == 'vggface' and include_top and classes != 8631: + raise ValueError( + 'If using `weights` as vggface original with `include_top`' + ' as true, `classes` should be 8631') + + return RESNET50(include_top=include_top, input_tensor=input_tensor, + input_shape=input_shape, pooling=pooling, + weights=weights, + classes=classes) + + if model == 'senet50': + + if classes is None: + classes = 8631 + + if weights == 'vggface' and include_top and classes != 8631: + raise ValueError( + 'If using `weights` as vggface original with `include_top`' + ' as true, `classes` should be 8631') + + return SENET50(include_top=include_top, input_tensor=input_tensor, + input_shape=input_shape, pooling=pooling, + weights=weights, + classes=classes)