"""
# Face Recognization
This notebook is created after I was routed to the dataset from the https://machinelearningmastery.com/ blog. 

The steps in the notebook are referred from below blog pages https://machinelearningmastery.com/how-to-develop-a-face-recognition-system-using-facenet-in-keras-and-an-svm-classifier/

https://machinelearningmastery.com/how-to-perform-face-detection-with-classical-and-deep-learning-methods-in-python-with-keras/
"""
"""
## Face Detection using MTCNN
### MTCNN
The MTCNN (Multi-Task Cascaded Convolutional Neural Network) is popular because it achieved then state-of-the-art results on a range of benchmark datasets, and because it is capable of also recognizing other facial features such as eyes and mouth, called landmark detection.

The network uses a cascade structure with three networks; first the image is rescaled to a range of different sizes (called an image pyramid), then the first model (Proposal Network or P-Net) proposes candidate facial regions, the second model (Refine Network or R-Net) filters the bounding boxes, and the third model (Output Network or O-Net) proposes facial landmarks.

The three models are not connected directly; instead, outputs of the previous stage are fed as input to the next stage. This allows additional processing to be performed between stages
"""
!pip install mtcnn
from mtcnn import MTCNN
from cv2 import imread
from PIL import Image
import numpy as np
import os
import matplotlib.pyplot as plt
import pickle

import warnings
warnings.filterwarnings('ignore')
# load image from file using PIL
filename = '../input/5-celebrity-faces-dataset/train/jerry_seinfeld/httpmediapopsugarassetscomfilesusersxlargejpg.jpg'
image = Image.open(filename)
# convert to RGB, if needed
image = image.convert('RGB')
# convert to array
pixels = np.asarray(image)
plt.imshow(pixels)
print(pixels.shape)
# create the face detector, using default weights
detector = MTCNN()
# detect faces in the image
results = detector.detect_faces(pixels)
## Assuming that only 1 face is present
# extract the bounding box from the first face
x1, y1, width, height = results[0]['box']

# bug fix - Sometimes the library will return a negative pixel index
x1, y1 = abs(x1), abs(y1)
x2, y2 = x1 + width, y1 + height
# extract the face. this is like cropping the face from image
face = pixels[y1:y2, x1:x2]
# resize pixels to the model size
image = Image.fromarray(face)
image = image.resize((160, 160))
face_array = np.asarray(image)
# Lets show the extracted face
plt.imshow(face_array)
# Function to extract a single face from a given photograph
def extract_face(filename=None, image_pixels=None, required_size=(160, 160)):
    if filename is not None:
        image = Image.open(filename)
        image = image.convert('RGB')
        pixels = np.asarray(image)
    elif image_pixels is not None:
        pixels = image_pixels
    detector = MTCNN()
    results = detector.detect_faces(pixels)
    x1, y1, width, height = results[0]['box']
    x1, y1 = abs(x1), abs(y1)
    x2, y2 = x1 + width, y1 + height
    face = pixels[y1:y2, x1:x2]
    box_dimensions = (x1, y1, width, height)
    image = Image.fromarray(face)
    image = image.resize(required_size)
    face_array = np.asarray(image)
    return face_array, box_dimensions
## Lets extract faces of elton john for example
image_path = '../input/5-celebrity-faces-dataset/train/elton_john'
files = os.listdir(image_path)
i=1
for filename in files:
    full_path = os.path.join(image_path, filename)
    face, _ = extract_face(filename=full_path)
    plt.subplot(2, 7, i)
    plt.axis('off')
    plt.imshow(face)
    i+=1
    if i>14:
        break
# load images and extract faces for all images in a directory
def load_faces(directory):
    faces = []
    # enumerate files
    for filename in os.listdir(directory):
        # path
        path = os.path.join(directory,filename)
        # get face
        face, _ = extract_face(filename=path)
        # store
        faces.append(face)
    return faces
# load a dataset that contains one subdir for each class that in turn contains images
def load_dataset(directory):
    X, y = [], []
    for subdir in os.listdir(directory):
        path = os.path.join(directory, subdir)
        if not os.path.isdir(path):
            continue
        faces = load_faces(path)
        labels = [subdir for _ in range(len(faces))]
        print(f'Loaded {len(faces)} examples for class: {subdir}')
        X.extend(faces)
        y.extend(labels)
    return np.asarray(X), np.asarray(y)
# load train dataset
#../input/5-celebrity-faces-dataset/train/ben_afflek/httpcsvkmeuaeccjpg.jpg
X_train, y_train = load_dataset(r'../input/5-celebrity-faces-dataset/train')
print(X_train.shape, y_train.shape)

# load validation dataset
X_val, y_val = load_dataset(r'../input/5-celebrity-faces-dataset/val')
print(X_val.shape, y_val.shape)
"""
## Face Embeddings
### FaceNet
FaceNet is a face recognition system developed in 2015 by researchers at Google that achieved then state-of-the-art results on a range of face recognition benchmark datasets. The FaceNet system can be used to extract high-quality features from faces, called face embeddings, that can then be used to train a face identification system.

The model is a deep convolutional neural network trained via a triplet loss function that encourages vectors for the same identity to become more similar (smaller distance), whereas vectors for different identities are expected to become less similar (larger distance). The focus on training a model to create embeddings directly (rather than extracting them from an intermediate layer of a model) was an important innovation in this work.
"""
# Download the Pre-Trained Keras FaceNet Model
!wget --no-check-certificate 'https://drive.google.com/uc?export=download&id=1PZ_6Zsy1Vb0s0JmjEmVd8FS99zoMCiN1' -O 'facenet_keras.h5'
# example of loading the keras facenet model
from tensorflow.keras.models import load_model
# load the model
model = load_model('facenet_keras.h5')
# Function to get the face embedding for one face
def get_embedding(model, face_pixels):
    # scale pixel values
    face_pixels = face_pixels.astype('float32')
    # standardize pixel values across channels (global)
    mean, std = face_pixels.mean(), face_pixels.std()
    face_pixels = (face_pixels - mean) / std
    # transform face into one sample
    samples = np.expand_dims(face_pixels, axis=0)
    # make prediction to get embedding
    yhat = model.predict(samples)
    return yhat[0]
# convert each face in the test set to an embedding
newTrainX = []
for face_pixels in X_train:
    embedding = get_embedding(model, face_pixels)
    newTrainX.append(embedding)
newTrainX = np.asarray(newTrainX)
print(newTrainX.shape)
# convert each face in the test set to an embedding
newTestX = []
for face_pixels in X_val:
    embedding = get_embedding(model, face_pixels)
    newTestX.append(embedding)
newTestX = np.asarray(newTestX)
print(newTestX.shape)
"""
## Face Classification Steps
1. Vector Normalization
2. Label Encode the Targets
3. Use SVM model to train and predict
"""
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import Normalizer
from sklearn.svm import SVC
# Normalize input vectors - We need to normalize the face embeddings
in_encoder = Normalizer(norm='l2')
X_train = in_encoder.transform(newTrainX)
X_val = in_encoder.transform(newTestX)
# Label encode targets
out_encoder = LabelEncoder()
out_encoder.fit(y_train)
y_train = out_encoder.transform(y_train)
y_val = out_encoder.transform(y_val)
#pickle.dump(model, open('LabelEncoder.sav', 'wb'))
# fit model
svc_model = SVC(kernel='linear', probability=True)
svc_model.fit(X_train, y_train)
#pickle.dump(model, open('SVC.sav', 'wb'))
# predict
yhat_train = svc_model.predict(X_train)
yhat_test = svc_model.predict(X_val)
X_train.shape
score_train = accuracy_score(y_train, yhat_train)
score_test = accuracy_score(y_val, yhat_test)
print('Accuracy: train=%.3f, test=%.3f' % (score_train*100, score_test*100))
"""
## Model Deployment
"""
## Load the trained LabelEncoder and SVM model
loaded_encoder = out_encoder
loaded_predictor = svc_model
loaded_facenet = load_model('facenet_keras.h5')
loaded_encoder.classes_
# Single Function capable to process input image and return the bounding box dimensions and the prediction
def face_recognize(image):
    ## Extract the face and bounding box dimensions from the image by using pretrained MTCNN model
    faces, box_dimensions = extract_face(image_pixels=image)
    X = np.asarray(faces)
    ## Get the Face Embeddings for the extracted face pixels and store as numpy array
    embedding = get_embedding(loaded_facenet, X)
    X = []
    X.append(embedding)
    X = np.asarray(X)
    ## Predict label for the face by using the pretrained models
    prediction = loaded_predictor.predict(X)
    predicted_label = loaded_encoder.inverse_transform([prediction])
    return predicted_label[0], box_dimensions
## Testing the model with the random image
filename = '../input/5-celebrity-faces-dataset/val/madonna/httpcdncdnjustjaredcomwpcontentuploadsheadlinesmadonnatalksparisattackstearsjpg.jpg'
image = Image.open(filename)
image = image.convert('RGB')
pixels = np.asarray(image)
face_recognize(pixels)
"""
## Face Recognization in Video
Let us try to check how the model performs with videos. We will be using a sample gif with ben affleck and a basketball.
"""
# Downloading the gif for model processing
!wget --no-check-certificate 'https://c.tenor.com/zHK0-3w4MgYAAAAC/ben-affleck-basketball.gif' -O 'ben_afflek.gif'
"""
![](https://c.tenor.com/zHK0-3w4MgYAAAAC/ben-affleck-basketball.gif)
"""
## Utilizing opencv to read the gif and imageio to write the updated gif with prediction and bounding box over the detected face

import cv2
import imageio
capture = cv2.VideoCapture('ben_afflek.gif')
image_lst = []

while True:
    IsTrue, frames = capture.read()
    if frames is None:
        break
    frames = cv2.cvtColor(frames, cv2.COLOR_BGR2RGB)
    person_name, box_dimensions = face_recognize(frames)
    x,y,w,h = box_dimensions
    cv2.rectangle(frames,(x,y),(x+w,y+h),(0,255,0),2)
    cv2.putText(frames,person_name,(x+w+10,y+h),0,0.3,(0,255,0))
    image_lst.append(frames)

capture.release()

# Convert to gif using the imageio.mimsave method
imageio.mimsave('ben_afflek_processed.gif', image_lst, fps=10)
"""
![](./ben_afflek_processed.gif)
"""
"""
#### References :-
https://machinelearningmastery.com/how-to-develop-a-face-recognition-system-using-facenet-in-keras-and-an-svm-classifier/

https://machinelearningmastery.com/how-to-perform-face-detection-with-classical-and-deep-learning-methods-in-python-with-keras/

https://theailearner.com/2021/05/29/creating-gif-from-video-using-opencv-and-imageio/
"""