From 83c12a4a5d6ba72552078a19181a669ac866c0db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Mon, 30 Sep 2019 14:54:55 +0200 Subject: [PATCH 01/12] Convert images to RGB Flask API. Minimum Python version 3.5. Former-commit-id: 2598f869fc3d74bbcae0eed58fbdfe06e602c078 --- README.md | 2 +- api/app.py | 2 ++ face_recognition/face_features_extractor.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e422ac..23592d1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ This repository provides a framework for creating and using Face Recognition system. # Installation -Make sure you have [Python 3](https://realpython.com/installing-python/) and +Make sure you have [Python 3.5+](https://realpython.com/installing-python/) and [`pip`](https://www.makeuseof.com/tag/install-pip-for-python/) installed. Install dependencies diff --git a/api/app.py b/api/app.py index 9d629d6..7fc59a9 100644 --- a/api/app.py +++ b/api/app.py @@ -57,6 +57,8 @@ def post(self): abort(400, "Image field '{}' doesn't exist in request!".format(IMAGE_KEY)) img = Image.open(io.BytesIO(args[IMAGE_KEY].read())) + # convert image to RGB (stripping alpha channel if exists) + img = img.convert('RGB') faces = face_recogniser(img) return \ { diff --git a/face_recognition/face_features_extractor.py b/face_recognition/face_features_extractor.py index 360dd92..a8bbff7 100644 --- a/face_recognition/face_features_extractor.py +++ b/face_recognition/face_features_extractor.py @@ -7,7 +7,6 @@ class FaceFeaturesExtractor: def __init__(self): - # TODO adjust threshold for detecting face self.aligner = MTCNN(prewhiten=False, keep_all=True, thresholds=[0.6, 0.7, 0.9]) self.facenet_preprocess = transforms.Compose([preprocessing.Whitening()]) self.facenet = InceptionResnetV1(pretrained='vggface2').eval() From 3e74bc9003cf7b0e70d96ecd9c155076e32e9ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Mon, 30 Sep 2019 15:42:24 +0200 Subject: [PATCH 02/12] Not using PyTorch CPU image because of macOS compatibility. Former-commit-id: 5d7638a21ad9187d905c079614d2963a7278a7a0 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 01da111..bac62c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ joblib==0.13.2 matplotlib==3.1.1 seaborn==0.9.0 scikit_learn==0.21.3 -torch==1.2.0+cpu -torchvision==0.4.0+cpu +torch==1.2.0 +torchvision==0.4.0 Werkzeug==0.15.2 opencv-python==4.1.0.25 From 906fd42d4884b6be8d8eb341ccf3abf398baaac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Mon, 30 Sep 2019 15:47:01 +0200 Subject: [PATCH 03/12] Downgrade matplotlib to support Python 3.5 Former-commit-id: dfa4dbeb7e7ba1934b11b8309f0517d398b60a9f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bac62c6..716a07d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ facenet_pytorch==0.1.0 Flask==1.1.1 flask_restplus==0.13.0 joblib==0.13.2 -matplotlib==3.1.1 +matplotlib==3.0.0 seaborn==0.9.0 scikit_learn==0.21.3 torch==1.2.0 From a2f4b2ad8c187d778b01e478e0cc0d019b7b8980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Wed, 6 Nov 2019 15:50:17 +0100 Subject: [PATCH 04/12] Minor changes. Former-commit-id: 440c9e1f0e813e16512c73e8208da7fe194629f5 --- util/{align-mtcnn.py => align_mtcnn.py} | 0 util/collect_face_images.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename util/{align-mtcnn.py => align_mtcnn.py} (100%) diff --git a/util/align-mtcnn.py b/util/align_mtcnn.py similarity index 100% rename from util/align-mtcnn.py rename to util/align_mtcnn.py diff --git a/util/collect_face_images.py b/util/collect_face_images.py index 8615d1e..b4b336e 100644 --- a/util/collect_face_images.py +++ b/util/collect_face_images.py @@ -15,7 +15,7 @@ def main(directory, name, test): # Display the resulting frame cv2.imshow(name, frame) if not test and i != 0 and i % 10 == 0: - cv2.imwrite("{}/{}{}.png".format(directory, name, i / 10), frame) + cv2.imwrite("{}/{}{}.png".format(directory, name, int(i / 10)), frame) i += 1 if cv2.waitKey(1) & 0xFF == ord('q'): break From a212ce2e7fc187d530938c7178836957d46703b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Fri, 8 Nov 2019 11:21:03 +0100 Subject: [PATCH 05/12] Fixed bug in exif orientation normalizer. Former-commit-id: 84fb83012cf3847567fb9e18a7a156bbd1b3832f --- face_recognition/preprocessing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/face_recognition/preprocessing.py b/face_recognition/preprocessing.py index da87085..50b29d9 100644 --- a/face_recognition/preprocessing.py +++ b/face_recognition/preprocessing.py @@ -21,7 +21,7 @@ class ExifOrientationNormalize(object): """ def __call__(self, img): - if 'parsed_exif' in img.info: + if 'parsed_exif' in img.info and exif_orientation_tag in img.info['parsed_exif']: orientation = img.info['parsed_exif'][exif_orientation_tag] transposes = exif_transpose_sequences[orientation] for trans in transposes: From c885f8d307b210b1b9daefe88e44cec22b8d75fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Fri, 8 Nov 2019 11:21:40 +0100 Subject: [PATCH 06/12] Exif orientation normalization before processing images. Former-commit-id: 7bc5c95bda2318b17c660db018cb95cba791d9e3 --- api/app.py | 3 +++ inference/classifier.py | 3 +++ inference/video_classifier.py | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/api/app.py b/api/app.py index 7fc59a9..a9b9057 100644 --- a/api/app.py +++ b/api/app.py @@ -4,8 +4,10 @@ from flask import Flask from flask_restplus import Api, Resource, fields, abort, inputs from werkzeug.datastructures import FileStorage +from face_recognition import preprocessing face_recogniser = joblib.load('model/face_recogniser.pkl') +preprocess = preprocessing.ExifOrientationNormalize() IMAGE_KEY = 'image' INCLUDE_PREDICTIONS_KEY = 'include_predictions' @@ -57,6 +59,7 @@ def post(self): abort(400, "Image field '{}' doesn't exist in request!".format(IMAGE_KEY)) img = Image.open(io.BytesIO(args[IMAGE_KEY].read())) + img = preprocess(img) # convert image to RGB (stripping alpha channel if exists) img = img.convert('RGB') faces = face_recogniser(img) diff --git a/inference/classifier.py b/inference/classifier.py index 179edd7..8a33a26 100755 --- a/inference/classifier.py +++ b/inference/classifier.py @@ -6,6 +6,7 @@ from PIL import Image from .util import draw_bb_on_img from .constants import MODEL_PATH +from face_recognition import preprocessing def parse_args(): @@ -26,8 +27,10 @@ def recognise_faces(img): def main(): args = parse_args() + preprocess = preprocessing.ExifOrientationNormalize() img = Image.open(args.image_path) filename = img.filename + img = preprocess(img) img = img.convert('RGB') faces, img = recognise_faces(img) diff --git a/inference/video_classifier.py b/inference/video_classifier.py index abc4624..947ab21 100755 --- a/inference/video_classifier.py +++ b/inference/video_classifier.py @@ -4,6 +4,7 @@ import cv2 import numpy as np from PIL import Image +from face_recognition import preprocessing from .util import draw_bb_on_img from .constants import MODEL_PATH @@ -11,6 +12,7 @@ def main(): cap = cv2.VideoCapture(0) face_recogniser = joblib.load(MODEL_PATH) + preprocess = preprocessing.ExifOrientationNormalize() while True: # Capture frame-by-frame @@ -18,7 +20,7 @@ def main(): frame = cv2.flip(frame, 1) img = Image.fromarray(frame) - faces = face_recogniser(img) + faces = face_recogniser(preprocess(img)) if faces is not None: draw_bb_on_img(faces, img) From dd1fa61017526eb6bdc99d411ab9c583f07af97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Mon, 11 Nov 2019 14:21:10 +0100 Subject: [PATCH 07/12] Updated train and generate_embeddings script which are now compatible. Former-commit-id: 491da197a47ec9c9f3837c9ceb4d672034e938bf --- training/train.py | 59 ++++++++++++++++++++++++++++++------- util/generate_embeddings.py | 17 ++++++++++- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/training/train.py b/training/train.py index 66bb88a..0e76d03 100644 --- a/training/train.py +++ b/training/train.py @@ -5,23 +5,34 @@ from PIL import Image from torchvision import transforms, datasets from sklearn.linear_model import LogisticRegression +from sklearn.model_selection import GridSearchCV +from sklearn import metrics from face_recognition import preprocessing, FaceFeaturesExtractor, FaceRecogniser - MODEL_DIR_PATH = 'model' def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument('-d', '--dataset-path', required=True, help='Path to folder with images.') + parser = argparse.ArgumentParser( + description='Script for training Face Recognition model. You can either give path to dataset or provide path ' + 'to pre-generated embeddings, labels and class_to_idx. You can pre-generate this with ' + 'util/generate_embeddings.py script.') + parser.add_argument('-d', '--dataset-path', help='Path to folder with images.') + parser.add_argument('-e', '--embeddings-path', help='Path to file with embeddings.') + parser.add_argument('-l', '--labels-path', help='Path to file with labels.') + parser.add_argument('-c', '--class-to-idx-path', help='Path to pickled class_to_idx dict.') + parser.add_argument('--grid-search', action='store_true', + help='If this option is enabled, grid search will be performed to estimate C parameter of ' + 'Logistic Regression classifier. In order to use this option you have to have at least ' + '3 examples of every class in your dataset. It is recommended to enable this option.') return parser.parse_args() def dataset_to_embeddings(dataset, features_extractor): transform = transforms.Compose([ - preprocessing.ExifOrientationNormalize(), - transforms.Resize(1024) - ]) + preprocessing.ExifOrientationNormalize(), + transforms.Resize(1024) + ]) embeddings = [] labels = [] @@ -40,17 +51,43 @@ def dataset_to_embeddings(dataset, features_extractor): return np.stack(embeddings), labels +def load_data(args, features_extractor): + if args.embeddings_path: + return np.loadtxt(args.embeddings_path), \ + np.loadtxt(args.labels_path, dtype='str').tolist(), \ + joblib.load(args.class_to_idx_path) + + dataset = datasets.ImageFolder(args.dataset_path) + embeddings, labels = dataset_to_embeddings(dataset, features_extractor) + return embeddings, labels, dataset.class_to_idx + + +def train(args, embeddings, labels): + softmax = LogisticRegression(solver='lbfgs', multi_class='multinomial', C=10, max_iter=10000) + if args.grid_search: + clf = GridSearchCV( + estimator=softmax, + param_grid={'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000]}, + cv=3 + ) + else: + clf = softmax + clf.fit(embeddings, labels) + + return clf.best_estimator_ if args.grid_search else clf + + def main(): args = parse_args() features_extractor = FaceFeaturesExtractor() - dataset = datasets.ImageFolder(args.dataset_path) - embeddings, labels = dataset_to_embeddings(dataset, features_extractor) + embeddings, labels, class_to_idx = load_data(args, features_extractor) + clf = train(args, embeddings, labels) - clf = LogisticRegression(C=10, solver='lbfgs', multi_class='multinomial') - clf.fit(embeddings, labels) + idx_to_class = {v: k for k, v in class_to_idx.items()} - idx_to_class = {v: k for k, v in dataset.class_to_idx.items()} + target_names = map(lambda i: i[1], sorted(idx_to_class.items(), key=lambda i: i[0])) + print(metrics.classification_report(labels, clf.predict(embeddings), target_names=list(target_names))) if not os.path.isdir(MODEL_DIR_PATH): os.mkdir(MODEL_DIR_PATH) diff --git a/util/generate_embeddings.py b/util/generate_embeddings.py index a9e5ee5..269e6e5 100644 --- a/util/generate_embeddings.py +++ b/util/generate_embeddings.py @@ -1,5 +1,6 @@ import argparse import os +import joblib import numpy as np import torch from torchvision import datasets @@ -10,7 +11,8 @@ def parse_args(): parser = argparse.ArgumentParser( "Script for generating face embeddings. Output of this script is 'embeddings.txt' which contains embeddings " - "for all input images and 'labels.txt' which contains label for every embedding.") + "for all input images, 'labels.txt' which contains label for every embedding and 'class_to_idx.pkl' which " + "is serializes dictionary which maps classes to its index.") parser.add_argument('--input-folder', required=True, help='Root folder where images are. This folder contains sub-folders for each class.') parser.add_argument('--output-folder', required=True, @@ -18,6 +20,17 @@ def parse_args(): return parser.parse_args() +def normalise_string(string): + return string.lower().replace(' ', '_') + + +def normalise_dict_keys(dictionary): + new_dict = dict() + for key in dictionary.keys(): + new_dict[normalise_string(key)] = dictionary[key] + return new_dict + + def main(): torch.set_grad_enabled(False) args = parse_args() @@ -26,11 +39,13 @@ def main(): dataset = datasets.ImageFolder(args.input_folder) embeddings, labels = dataset_to_embeddings(dataset, features_extractor) + dataset.class_to_idx = normalise_dict_keys(dataset.class_to_idx) idx_to_class = {v: k for k, v in dataset.class_to_idx.items()} labels = list(map(lambda idx: idx_to_class[idx], labels)) np.savetxt(args.output_folder + os.path.sep + 'embeddings.txt', embeddings) np.savetxt(args.output_folder + os.path.sep + 'labels.txt', np.array(labels, dtype=np.str).reshape(-1, 1), fmt="%s") + joblib.dump(dataset.class_to_idx, args.output_folder + os.path.sep + 'class_to_idx.pkl') if __name__ == '__main__': From fdadf9386a69bb9b2b6960571a92c3942e1a24df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Mon, 11 Nov 2019 14:40:05 +0100 Subject: [PATCH 08/12] Dockerfile and README update. Former-commit-id: 9a9b1083e5145195138f4dc6ce627cad3ef06074 --- README.md | 35 +++++++++++++++++++++++++++++++++-- api/Dockerfile | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 23592d1..c0fe570 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -This repository provides a framework for creating and using Face Recognition system. +# Framework for creating and using Face Recognition system. +This repository provides a simple framework for creating and using Face Recognition system. There is also a +[blog post](https://arsfutura.co/magazine/face-recognition-with-facenet-and-mtcnn/) associated with this repository. # Installation Make sure you have [Python 3.5+](https://realpython.com/installing-python/) and @@ -6,7 +8,7 @@ Make sure you have [Python 3.5+](https://realpython.com/installing-python/) and Install dependencies ``` -pip install -r requirements.txt -f https://download.pytorch.org/whl/torch_stable.html +pip install -r requirements.txt ``` # Train Face Recognition system @@ -34,6 +36,35 @@ After preparing images run following command to train Face Recognition system: ``` Previous command will generate `model/face_recogniser.pkl` which represents trained Face Recognition system. +`train.py` has other options for training too. Slow part of training is generating embeddings from images. You could +pre-generate embeddings with `util/generate_embeddings.py` and then just forward path to embeddings to train script, +that would speed up experimenting with training a lot. + +``` +usage: train.py [-h] [-d DATASET_PATH] [-e EMBEDDINGS_PATH] [-l LABELS_PATH] + [-c CLASS_TO_IDX_PATH] [--grid-search] + +Script for training Face Recognition model. You can either give path to +dataset or provide path to pre-generated embeddings, labels and class_to_idx. +You can pre-generate this with util/generate_embeddings.py script. + +optional arguments: + -h, --help show this help message and exit + -d DATASET_PATH, --dataset-path DATASET_PATH + Path to folder with images. + -e EMBEDDINGS_PATH, --embeddings-path EMBEDDINGS_PATH + Path to file with embeddings. + -l LABELS_PATH, --labels-path LABELS_PATH + Path to file with labels. + -c CLASS_TO_IDX_PATH, --class-to-idx-path CLASS_TO_IDX_PATH + Path to pickled class_to_idx dict. + --grid-search If this option is enabled, grid search will be + performed to estimate C parameter of Logistic + Regression classifier. In order to use this option you + have to have at least 3 examples of every class in + your dataset. It is recommended to enable this option. +``` + # Using Face Recognition After training Face Recognition system you can use it in several ways. You can use one of inference scripts or REST API. diff --git a/api/Dockerfile b/api/Dockerfile index f63bc1d..0203202 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -11,7 +11,7 @@ WORKDIR /app # We copy just the requirements.txt first to leverage Docker cache COPY ./requirements.txt /app/requirements.txt -RUN pip3 install -r requirements.txt -f https://download.pytorch.org/whl/torch_stable.html +RUN pip3 install -r requirements.txt COPY face_recognition /app/face_recognition COPY model /app/model From 2da6318fb949151d1f641ab8d1fdd40fcb576b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Tue, 12 Nov 2019 13:38:04 +0100 Subject: [PATCH 09/12] Added uWSGI as production server. Former-commit-id: 7ab83a1143c7d72fe67577c2575cedc828a87aba --- README.md | 9 +++++++-- api/Dockerfile | 6 ++++-- requirements.txt | 1 + tasks/{run_server.sh => run_dev_server.sh} | 0 tasks/run_prod_server.sh | 3 +++ 5 files changed, 15 insertions(+), 4 deletions(-) rename tasks/{run_server.sh => run_dev_server.sh} (100%) create mode 100755 tasks/run_prod_server.sh diff --git a/README.md b/README.md index c0fe570..b6b3656 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,14 @@ Video stream example: You can use trained Face Recognition system as REST API, `api` folder contains simple [Flask](https://palletsprojects.com/p/flask/) API which provides frontend for Face Recognition system. -Run server using following command: +Run development server using following command: ``` -tasks/run_server.sh +tasks/run_dev_server.sh +``` + +Run production server using following command: +``` +tasks/run_prod_server.sh ``` Server is running on port `5000`. diff --git a/api/Dockerfile b/api/Dockerfile index 0203202..e2c7c50 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -4,7 +4,7 @@ MAINTAINER Luka Dulčić "culuma@arsfutura.co" RUN mkdir -p /app && \ apt-get update -y && \ - apt-get install -y libsm6 libxext6 libxrender-dev libglib2.0-0 + apt-get install -y build-essential python3-dev libsm6 libxext6 libxrender-dev libglib2.0-0 WORKDIR /app @@ -16,5 +16,7 @@ RUN pip3 install -r requirements.txt COPY face_recognition /app/face_recognition COPY model /app/model COPY api /app/api +COPY tasks/run_prod_server.sh /app/run_prod_server.sh +RUN chmod +x run_prod_server.sh -CMD [ "python3", "-m", "api.app" ] +CMD [ "./run_prod_server.sh" ] diff --git a/requirements.txt b/requirements.txt index 716a07d..2b7446a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ torch==1.2.0 torchvision==0.4.0 Werkzeug==0.15.2 opencv-python==4.1.0.25 +uWSGI==2.0.18 diff --git a/tasks/run_server.sh b/tasks/run_dev_server.sh similarity index 100% rename from tasks/run_server.sh rename to tasks/run_dev_server.sh diff --git a/tasks/run_prod_server.sh b/tasks/run_prod_server.sh new file mode 100755 index 0000000..cafe70f --- /dev/null +++ b/tasks/run_prod_server.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +uwsgi --http 0.0.0.0:5000 --wsgi-file api/app.py --callable app --processes 5 --threads 2 \ No newline at end of file From e7ce19d40bdac0922e20b01730e6ee9f0b7cdbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Tue, 12 Nov 2019 14:17:03 +0100 Subject: [PATCH 10/12] README update. Former-commit-id: c69b1ebf172d2ded1e538ace3ce894292aa7bcaa --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b6b3656..efdf11c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Framework for creating and using Face Recognition system. This repository provides a simple framework for creating and using Face Recognition system. There is also a -[blog post](https://arsfutura.co/magazine/face-recognition-with-facenet-and-mtcnn/) associated with this repository. +[blog post](https://arsfutura.co/magazine/face-recognition-with-facenet-and-mtcnn/) associated with this repository +which gives more details about the framework. # Installation Make sure you have [Python 3.5+](https://realpython.com/installing-python/) and @@ -168,7 +169,8 @@ Run server docker run --name face-recognition-api -d -p 5000:5000 face-recognition-api ``` -> WARNING If you are processing high-resolution images in a containers limited amount of memory you could encounter OOM. +> WARNING If you are processing high-resolution images in a containers with limited amount of memory you could +> encounter OOM. ## References * F. Schroff, D. Kalenichenko, and J. Philbin. Facenet: A unified embedding for face recognition and clustering. arXiv preprint arXiv:1503.03832, 2015. [PDF](https://arxiv.org/pdf/1503.03832.pdf) From b8a8cc2912ed7980a6c3f697457f7ca70bae45e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Tue, 12 Nov 2019 15:46:56 +0100 Subject: [PATCH 11/12] Added LICENSE Former-commit-id: 6609e51d22cf89c73f3fdbacb092b14946d27b05 --- LICENSE | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f3e40e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019, Ars Futura +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 891b2650f35bb6036ce4ecf25c2b378f7db6334d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Dul=C4=8Di=C4=87?= Date: Wed, 13 Nov 2019 09:58:02 +0100 Subject: [PATCH 12/12] Resolved conflicts. Former-commit-id: fdb6e3e50214bc3c00c9f75e33613532699442a5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 309775f..9bd16df 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository provides a simple framework for creating and using Face Recognit [blog post](https://arsfutura.co/magazine/face-recognition-with-facenet-and-mtcnn/) associated with this repository which gives more details about the framework. -![Face Recognition illustration](readme-illustration.png) +![Face Recognition illustration](images/readme-illustration.png) # Installation Make sure you have [Python 3.5+](https://realpython.com/installing-python/) and