Spaces:
Runtime error
Runtime error
enniorampello
commited on
Commit
·
3322c73
1
Parent(s):
3ab67fa
app added
Browse files- .gitignore +10 -0
- README.md +32 -1
- app.py +69 -0
- create_bucket.py +16 -0
- feature_pipeline.py +81 -0
- inference-pipeline.py +71 -0
- requirements.txt +11 -0
- training_pipline.py +46 -0
.gitignore
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
venv/
|
2 |
+
credentials.json
|
3 |
+
cmake*
|
4 |
+
__pycache__/
|
5 |
+
.inference*
|
6 |
+
encodings.pkl
|
7 |
+
labels.pkl
|
8 |
+
model.pkl
|
9 |
+
dlib
|
10 |
+
flagged
|
README.md
CHANGED
@@ -10,4 +10,35 @@ pinned: false
|
|
10 |
license: mit
|
11 |
---
|
12 |
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
license: mit
|
11 |
---
|
12 |
|
13 |
+
# Face Recognition ML pipeline
|
14 |
+
This project is composed by three different components: feature pipeline, model retraining and inference pipeline. The pipeline diagram can be seen below.
|
15 |
+

|
16 |
+
|
17 |
+
## Feature pipeline
|
18 |
+
|
19 |
+
This pipeline is hosted on a GCP (Google Cloud Platform) instance and performs the following tasks:
|
20 |
+
1. Receive an image from the user (from a Gradio UI).
|
21 |
+
2. Create multiple different versions of the images by applying filters, rotating it, brighten it, etc...
|
22 |
+
3. For each of the resulting images, generate an embedding using the [Histogram of Gradients (HOG)](https://en.wikipedia.org/wiki/Histogram_of_oriented_gradients) method
|
23 |
+
4. Upload the original image and the embeddings to a GCP bucket.
|
24 |
+
5. Trigger model retraining.
|
25 |
+
|
26 |
+
## Model retraining
|
27 |
+
|
28 |
+
Once the training pipeline is called, the embeddings are taken from the bucket, and the training of the KNN classifier is performed. The model is then stored on the local device as our inference pipeline uses the same GCP instance to run the inference.
|
29 |
+
|
30 |
+
## Inference pipeline
|
31 |
+
|
32 |
+
Our inference pipeline is in real-time, hence the Gradio UI shows the live webcam with all the predictions on each face. Note: This is not a very well trained model, and hence there would be several false positives.
|
33 |
+
|
34 |
+
|
35 |
+
## Gradio public links:
|
36 |
+
- The app to upload an image is provided [here](https://1d5d09cf-5ef3-47f6.gradio.live/)
|
37 |
+
- The app to run the real-time inference is [here](https://c3c30239-c028-48a3.gradio.live/)
|
38 |
+
## Project structure
|
39 |
+
|
40 |
+
- The dataset is stored in `./dataset/<first name of the person>`.
|
41 |
+
- The feature pipeline creates a 128d vector for each image in the dataset folder along with some augmentations. Once this is done, the encodings are stored in the file `./encodings.pkl`.
|
42 |
+
- The training pipeline then makes use of the encoding file and trains a knn classifier and stores the model in the file `./model.pkl`. The training file also stores the labels (before using a label encoder) in the file 'labels.pkl'.
|
43 |
+
- The inference pipeline makes use of all these files and runs the inference in the gradio UI.
|
44 |
+
|
app.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import random
|
3 |
+
import gradio as gr
|
4 |
+
|
5 |
+
from google.cloud import storage
|
6 |
+
from google.oauth2 import service_account
|
7 |
+
|
8 |
+
from feature_pipeline import preprocess
|
9 |
+
from training_pipline import train
|
10 |
+
|
11 |
+
use_bucket = True
|
12 |
+
|
13 |
+
creds = service_account.Credentials.from_service_account_info({
|
14 |
+
"type": "service_account",
|
15 |
+
"project_id": "scalable-ml-lab-2",
|
16 |
+
"private_key_id": os.environ.get("GCP_PRIVATE_KEY_ID"),
|
17 |
+
"private_key": os.environ.get("GCP_PRIVATE_KEY"),
|
18 |
+
"client_email": os.environ.get("GCP_CLIENT_EMAIL"),
|
19 |
+
"client_id": os.environ.get("GCP_CLIENT_ID"),
|
20 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
21 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
22 |
+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
23 |
+
"client_x509_cert_url": f"https://www.googleapis.com/robot/v1/metadata/x509/613127572368-compute%40developer.gserviceaccount.com"
|
24 |
+
})
|
25 |
+
client = storage.Client(credentials=creds)
|
26 |
+
|
27 |
+
def upload_to_bucket(client, bucket_name, file_name, object_name):
|
28 |
+
try:
|
29 |
+
bucket = client.bucket(bucket_name)
|
30 |
+
blob = bucket.blob(object_name)
|
31 |
+
blob.upload_from_filename(file_name)
|
32 |
+
|
33 |
+
return True
|
34 |
+
except Exception as e:
|
35 |
+
print(e)
|
36 |
+
return False
|
37 |
+
|
38 |
+
|
39 |
+
def image_upload(_, img, name):
|
40 |
+
file_id = random.randint(0, 1000000)
|
41 |
+
|
42 |
+
if os.path.exists(f"./dataset/{name}") == False:
|
43 |
+
os.mkdir(f"./dataset/{name}")
|
44 |
+
|
45 |
+
img.save(f"dataset/{name}/{name}_{file_id}.jpg")
|
46 |
+
|
47 |
+
embeddings_filename = preprocess()
|
48 |
+
|
49 |
+
if use_bucket:
|
50 |
+
upload_to_bucket(client, "bucket-faces", f"./dataset/{name}/{name}_{file_id}.jpg", f"{name}/{name}_{file_id}.jpg")
|
51 |
+
upload_to_bucket(client, "bucket-embeddings", embeddings_filename, embeddings_filename)
|
52 |
+
|
53 |
+
train(client, "bucket-embeddings", embeddings_filename)
|
54 |
+
|
55 |
+
return "Model retrained!"
|
56 |
+
|
57 |
+
|
58 |
+
gr.Interface(
|
59 |
+
image_upload,
|
60 |
+
[
|
61 |
+
gr.Markdown("""
|
62 |
+
# Hello!! Enter your first name and upload one picture of your face.
|
63 |
+
## The face recognition model will be retrained with the knowledge you gave it of your face.
|
64 |
+
"""),
|
65 |
+
gr.Webcam(source="webcam", type="pil", label="Upload a beautiful picture of yourself."),
|
66 |
+
gr.Textbox(placeholder="write your name here...", label="Your name.")
|
67 |
+
],
|
68 |
+
outputs="text"
|
69 |
+
).launch(server_name="0.0.0.0", share=True)
|
create_bucket.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from google.cloud import storage
|
2 |
+
from google.oauth2 import service_account
|
3 |
+
|
4 |
+
creds = service_account.Credentials.from_service_account_file("credentials.json")
|
5 |
+
client = storage.Client(credentials=creds)
|
6 |
+
|
7 |
+
def create_bucket(bucket_name):
|
8 |
+
try:
|
9 |
+
bucket = client.create_bucket(bucket_name)
|
10 |
+
print(f"Bucket {bucket.name} created")
|
11 |
+
return True
|
12 |
+
except Exception as e:
|
13 |
+
print(e)
|
14 |
+
return False
|
15 |
+
|
16 |
+
create_bucket("bucket-embeddings")
|
feature_pipeline.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
import imutils
|
3 |
+
from imutils import paths
|
4 |
+
import face_recognition
|
5 |
+
import pickle
|
6 |
+
import cv2
|
7 |
+
import os
|
8 |
+
import argparse
|
9 |
+
|
10 |
+
def augment_image(file_path):
|
11 |
+
"""
|
12 |
+
Augment the image by rotating it by 10, -10, 30, -30 degrees, flipping it horizontally and increasing/decreasing the brightness
|
13 |
+
|
14 |
+
params:
|
15 |
+
file_path: path to the image file
|
16 |
+
|
17 |
+
returns:
|
18 |
+
list of 8 augmented images
|
19 |
+
"""
|
20 |
+
image = cv2.imread(file_path)
|
21 |
+
images = []
|
22 |
+
|
23 |
+
# convert the input image from BGR to RGB
|
24 |
+
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
25 |
+
|
26 |
+
# add the original image to the list of images
|
27 |
+
images.append(rgb_image)
|
28 |
+
# rotate the image by 10 degrees and add it to the list of images
|
29 |
+
images.append(imutils.rotate_bound(rgb_image, 10))
|
30 |
+
# rotate the image by -10 degrees and add it to the list of images
|
31 |
+
images.append(imutils.rotate_bound(rgb_image, -10))
|
32 |
+
# rotate the image by 30 degrees and add it to the list of images
|
33 |
+
images.append(imutils.rotate_bound(rgb_image, 30))
|
34 |
+
# rotate the image by -30 degrees and add it to the list of images
|
35 |
+
images.append(imutils.rotate_bound(rgb_image, -30))
|
36 |
+
# flip the image horizontally and add it to the list of images
|
37 |
+
images.append(cv2.flip(rgb_image, 1))
|
38 |
+
#increase the brightness of the image and add it to the list of images
|
39 |
+
images.append(cv2.convertScaleAbs(rgb_image, alpha=1.5, beta=0))
|
40 |
+
#decrease the brightness of the image and add it to the list of images
|
41 |
+
images.append(cv2.convertScaleAbs(rgb_image, alpha=0.5, beta=0))
|
42 |
+
|
43 |
+
return images
|
44 |
+
|
45 |
+
|
46 |
+
def preprocess():
|
47 |
+
dataset = './dataset' # path to input dataset directory
|
48 |
+
encodings_file = './encodings.pkl' #path to output encodings file
|
49 |
+
|
50 |
+
imagePaths = list(paths.list_images(dataset))
|
51 |
+
known_encodings = []
|
52 |
+
known_names = []
|
53 |
+
s = time.time()
|
54 |
+
print(imagePaths)
|
55 |
+
for i, imagePath in enumerate(imagePaths):
|
56 |
+
images = augment_image(imagePath)
|
57 |
+
name = imagePath.split(os.path.sep)[-2]
|
58 |
+
print(f"processing image [{name}] {i+1}/{len(imagePaths)}")
|
59 |
+
|
60 |
+
for l in images:
|
61 |
+
boxes = face_recognition.face_locations(l, model='hog')
|
62 |
+
encodings = face_recognition.face_encodings(l, boxes)
|
63 |
+
|
64 |
+
# creating the training set
|
65 |
+
for encoding in encodings:
|
66 |
+
known_encodings.append(encoding)
|
67 |
+
known_names.append(name)
|
68 |
+
|
69 |
+
e = time.time()
|
70 |
+
print(f"Encoding dataset took: {(e-s)} seconds")
|
71 |
+
data = {"encodings": known_encodings, "names": known_names}
|
72 |
+
f = open(encodings_file, "wb")
|
73 |
+
f.write(pickle.dumps(data))
|
74 |
+
f.close()
|
75 |
+
|
76 |
+
return encodings_file
|
77 |
+
|
78 |
+
# replace the above few lines with the script to upload this to hopsworks
|
79 |
+
|
80 |
+
if __name__ == '__main__':
|
81 |
+
preprocess()
|
inference-pipeline.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# import the necessary packages
|
2 |
+
import face_recognition
|
3 |
+
import pickle
|
4 |
+
import argparse
|
5 |
+
import cv2
|
6 |
+
from sklearn.neighbors import KNeighborsClassifier
|
7 |
+
import gradio as gr
|
8 |
+
import numpy as np
|
9 |
+
|
10 |
+
|
11 |
+
def inf(_, image):
|
12 |
+
# input_image = 'multiple.jpg'
|
13 |
+
# encodings_file = 'encodings.pkl'
|
14 |
+
detection_method = 'hog'
|
15 |
+
classifier_model_file = 'model.pkl'
|
16 |
+
labels_file = 'labels.pkl'
|
17 |
+
# data = pickle.loads(open(encodings_file, "rb").read())
|
18 |
+
|
19 |
+
# image = cv2.imread(input_image)
|
20 |
+
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
21 |
+
|
22 |
+
boxes = face_recognition.face_locations(rgb_image, model=detection_method)
|
23 |
+
encodings = face_recognition.face_encodings(rgb_image, boxes)
|
24 |
+
print(f'Found {len(boxes)} faces')
|
25 |
+
|
26 |
+
if len(boxes) == 0:
|
27 |
+
return image
|
28 |
+
# initialize the list of names for each face detected
|
29 |
+
names = []
|
30 |
+
|
31 |
+
# # load the model from disk
|
32 |
+
model = pickle.loads(open(classifier_model_file, "rb").read())
|
33 |
+
# predict on the new encodings and display the probabilities
|
34 |
+
y_pred = model.predict_proba(encodings)
|
35 |
+
# print(y_pred)
|
36 |
+
max_y = y_pred.max(axis=1)
|
37 |
+
print(max_y)
|
38 |
+
labels = pickle.loads(open(labels_file, "rb").read())
|
39 |
+
for id, m in enumerate(max_y):
|
40 |
+
|
41 |
+
if m >= 0.8:
|
42 |
+
names.append(labels[y_pred.argmax(axis=1)[id]])
|
43 |
+
else:
|
44 |
+
names.append('Unknown')
|
45 |
+
|
46 |
+
|
47 |
+
# loop over the recognized faces
|
48 |
+
for ((top, right, bottom, left), name) in zip(boxes, names):
|
49 |
+
# draw the predicted face name on the image
|
50 |
+
cv2.rectangle(image, (left, top), (right, bottom), (0, 255, 0), 2)
|
51 |
+
y = top - 15 if top - 15 > 15 else top + 15
|
52 |
+
cv2.putText(image, name, (left, y), cv2.FONT_HERSHEY_SIMPLEX,
|
53 |
+
0.75, (0, 255, 0), 2)
|
54 |
+
|
55 |
+
# save the resulting image
|
56 |
+
# cv2.imwrite('output.jpg', image)
|
57 |
+
return image
|
58 |
+
|
59 |
+
|
60 |
+
demo = gr.Interface(
|
61 |
+
inf,
|
62 |
+
[
|
63 |
+
gr.Markdown("""
|
64 |
+
## Welcome to the face recognition software !!!
|
65 |
+
"""),
|
66 |
+
gr.Image(source="webcam", streaming=True)],
|
67 |
+
"image",
|
68 |
+
live=True
|
69 |
+
)
|
70 |
+
demo.launch(server_name="0.0.0.0", share=True)
|
71 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Click
|
2 |
+
dlib
|
3 |
+
face-recognition
|
4 |
+
face-recognition-models
|
5 |
+
imutils
|
6 |
+
numpy
|
7 |
+
Pillow
|
8 |
+
opencv-python
|
9 |
+
scikit-learn
|
10 |
+
pydrive
|
11 |
+
gdown
|
training_pipline.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pickle
|
2 |
+
import argparse
|
3 |
+
import cv2
|
4 |
+
import numpy as np
|
5 |
+
from sklearn.svm import SVC
|
6 |
+
from sklearn.preprocessing import LabelEncoder
|
7 |
+
from sklearn.neighbors import KNeighborsClassifier
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
def train(storage_client, bucket_name, embeddings_file):
|
12 |
+
|
13 |
+
bucket = storage_client.get_bucket(bucket_name)
|
14 |
+
blob = bucket.blob(embeddings_file)
|
15 |
+
blob.download_to_filename(embeddings_file)
|
16 |
+
|
17 |
+
data = pickle.loads(open(embeddings_file, "rb").read())
|
18 |
+
|
19 |
+
# train a classification model on these embeddings
|
20 |
+
# use the model to make predictions on the test data
|
21 |
+
|
22 |
+
X = data['encodings']
|
23 |
+
y_raw = data['names']
|
24 |
+
|
25 |
+
le = LabelEncoder()
|
26 |
+
y = le.fit_transform(y_raw)
|
27 |
+
print(le.classes_)
|
28 |
+
# save the labels in a file
|
29 |
+
f = open('labels.pkl', "wb")
|
30 |
+
f.write(pickle.dumps(le.classes_))
|
31 |
+
f.close()
|
32 |
+
|
33 |
+
model = KNeighborsClassifier(n_neighbors=3)
|
34 |
+
model.fit(X, y)
|
35 |
+
|
36 |
+
accuracy = model.score(X, y)
|
37 |
+
print(f'Accuracy: {accuracy}')
|
38 |
+
|
39 |
+
#save the model to disk
|
40 |
+
f = open('model.pkl', "wb")
|
41 |
+
f.write(pickle.dumps(model))
|
42 |
+
f.close()
|
43 |
+
|
44 |
+
|
45 |
+
if __name__ == '__main__':
|
46 |
+
train()
|