from __future__ import absolute_import, division, print_function
import os, random
import numpy as np
from enum import Enum
import cv2                     # for FaceEncoderModels.LBPH, FaceEncoderModels.OPENFACE
import pickle                  # for FaceEncoderModels.OPENFACE and FaceEncoderModels.DLIBRESNET
from imutils import paths      # for FaceEncoderModels.LBPH
from sklearn.preprocessing import LabelEncoder # for FaceEncoderModels
from classifier import FaceClassifierModels, FaceClassifier
from scipy import misc         # for FaceDetector_FACENET
from image_utils import seriallize_face_encodings
from os.path import join
from scipy import misc
import mxnet as mx

OUTPUT_LBPH_CLASSIFIER       = 'lbph.yml'
OUTPUT_LBPH_LABELER          = 'lbph_le.pickle'

INPUT_OPENFACE_MODEL         = 'openface_nn4.small2.v1.t7'
OUTPUT_OPENFACE_CLASSIFIER   = 'openface_re.pickle'
OUTPUT_OPENFACE_LABELER      = 'openface_le.pickle'

INPUT_DLIBRESNET_MODEL       = 'dlib_face_recognition_resnet_model_v1.dat'
INPUT_DLIBRESNET_MODEL2      = 'shape_predictor_5_face_landmarks.dat'
OUTPUT_DLIBRESNET_CLASSIFIER = 'dlib_re.pickle'
OUTPUT_DLIBRESNET_LABELER    = 'dlib_le.pickle'

INPUT_FACENET_MODEL          = 'facenet_20180402-114759.pb'
OUTPUT_FACENET_CLASSIFIER    = 'facenet_re.pickle'
OUTPUT_FACENET_LABELER       = 'facenet_le.pickle'

INPUT_INSIGHTFACE_MODEL      = 'model-r100-ii/model,0'



class FaceEncoderModels(Enum):

    LBPH                = 0    # [ML] LBPH Local Binary Patterns Histograms
    OPENFACE            = 1    # [DL] OpenCV OpenFace
    DLIBRESNET          = 2    # [DL] DLIB ResNet
    FACENET             = 3    # [DL] FaceNet implementation by <NAME>
    # VGGFACE1_VGG16    = 4    # Refer to models\others\vggface_recognition
    # VGGFACE2_RESNET50 = 5    # Refer to models\others\vggface_recognition
    INSIGHTFACE         = 6
    DEFAULT = LBPH


class FaceEncoder():

    def __init__(self, model=FaceEncoderModels.DEFAULT, path=None, path_training=None, training=False):
        self._base = None
        # self.known_face_embs = None
        if model == FaceEncoderModels.LBPH:
            self._base = FaceEncoder_LBPH(path, path_training, training)
        elif model == FaceEncoderModels.OPENFACE:
            self._base = FaceEncoder_OPENFACE(path, path_training, training)
        elif model == FaceEncoderModels.DLIBRESNET:
            self._base = FaceEncoder_DLIBRESNET(path, path_training, training)
        elif model == FaceEncoderModels.FACENET:
            self._base = FaceEncoder_FACENET(path, path_training, training)
        elif model == FaceEncoderModels.INSIGHTFACE:
            self._base = FaceEncoder_INSIGHTFACE(path, path_training)    

    def identify(self, frame, face_rect, vec = None):
        try:
            face_id, confidence = self._base.identify(frame, face_rect, vec)
            return face_id, confidence
        except:
            return "Unknown", 0

    def train(self, face_detector, path_dataset, verify=False, classifier=FaceClassifierModels.LINEAR_SVM):
        try:
            self._base.train(face_detector, path_dataset, verify, classifier)
            print("Note: Make sure you use the same models for training and testing")
        except Exception as ex:
            print(ex)
            return "Training failed! an exception occurred!"
            

    def encode(self, frame, face_rect = None, num_face = 1):
        return self._base.encode(frame, face_rect, num_face)



class FaceEncoder_Utils():

    def save_training(self, classifier, knownNames, knownEmbeddings, output_clf, output_le):
        #print(len(knownNames))
        #print(len(knownEmbeddings))
        #print("[INFO] Number of classes = {}".format(knownNames))
        le = LabelEncoder()
        labels = le.fit_transform(knownNames)
        #print(le.classes_)
        #print(labels)
        clf = FaceClassifier(classifier)
        clf.fit(knownEmbeddings, labels)
        pred_score = clf.score(knownEmbeddings, labels)
        print("Prediction score: ", pred_score)
        f = open(output_clf, "wb")
        f.write(pickle.dumps(clf))
        f.close()
        f = open(output_le, "wb")
        f.write(pickle.dumps(le))
        f.close() 



class FaceEncoder_LBPH():

    def __init__(self, path=None, path_training=None, training=False):
        self._path_training = path_training
        self._label_encoder = None
        self._clf = cv2.face.LBPHFaceRecognizer_create()
        if training == False:
            self._clf.read(self._path_training + OUTPUT_LBPH_CLASSIFIER)
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_LBPH_LABELER, "rb").read())
            #print(self._label_encoder.classes_)

    def identify(self, frame, face_rect):
        face_id = "Unknown"
        confidence = 99.99
        (x, y, w, h) = face_rect
        frame_gray = frame[y:y+h, x:x+w]
        face = cv2.cvtColor(frame_gray, cv2.COLOR_BGR2GRAY)
        id, confidence = self._clf.predict(face)
        if confidence > 99.99: 
            confidence = 99.99
        face_id = self._label_encoder.classes_[id]
        return face_id, confidence

    def train(self, face_detector, path_dataset, verify, classifier):
        imagePaths = sorted(list(paths.list_images(path_dataset)))
        faceSamples=[]
        ids = []
        knownNames = []

        id = -1
        for (i, imagePath) in enumerate(imagePaths):
            frame = cv2.imread(imagePath, cv2.IMREAD_COLOR)
            name = imagePath.split(os.path.sep)[-2]
            try:
                id = knownNames.index(name)
            except:
                id = id + 1
            #print("name=%s id=%d" % (name, id))

            frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            faces = face_detector.detect(frame)
            for (index, face) in enumerate(faces):
                (x, y, w, h) = face
                faceSamples.append(frame_gray[y:y+h,x:x+w])
                knownNames.append(name)
                ids.append(id)
                #cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 255, 255), 1)
                break

            if verify and len(faces) != 1:
                print("[WARNING] Image {} has {} faces ".format(imagePath, len(faces)))
                cv2.imshow('frame', frame)
                cv2.waitKey(10)

        #print(ids)
        #print(knownNames)

        if verify:
            cv2.destroyAllWindows()

        self._clf.train(faceSamples, np.array(ids))
        self._clf.write(self._path_training + OUTPUT_LBPH_CLASSIFIER)

        le = LabelEncoder()
        labels = le.fit_transform(knownNames)
        #print(le.classes_)
        #print(labels)
        
        f = open(self._path_training + OUTPUT_LBPH_LABELER, "wb")
        f.write(pickle.dumps(le))
        f.close()


class FaceEncoder_OPENFACE():

    def __init__(self, path=None, path_training=None, training=False):
        self._path_training = path_training
        self._clf = None
        self._label_encoder = None
        self._embedder = cv2.dnn.readNetFromTorch(path + INPUT_OPENFACE_MODEL)
        if training == False:
            self._clf = pickle.loads(open(self._path_training + OUTPUT_OPENFACE_CLASSIFIER, "rb").read())
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_OPENFACE_LABELER, "rb").read())
            #print(self._label_encoder.classes_)

    def identify(self, frame, face_rect):
        face_id = "Unknown"
        confidence = 99.99
        vec = self.encode(frame, face_rect)
        predictions_face = self._clf.predict(vec)[0]
        id = np.argmax(predictions_face)
        confidence = predictions_face[id] * 100
        face_id = self._label_encoder.classes_[id]
        return face_id, confidence

    def encode(self, frame, face_rect):
        (x, y, w, h) = face_rect
        face = frame[y:y+h, x:x+w]
        faceBlob = cv2.dnn.blobFromImage(face, 1.0 / 255, (96, 96), (0, 0, 0), swapRB=True, crop=False)
        self._embedder.setInput(faceBlob)
        return self._embedder.forward()

    def train(self, face_detector, path_dataset, verify, classifier):
        knownEmbeddings = []
        knownNames = []
        imagePaths = sorted(list(paths.list_images(path_dataset)))
        for (j, imagePath) in enumerate(imagePaths):
            name = imagePath.split(os.path.sep)[-2]
            frame = cv2.imread(imagePath, cv2.IMREAD_COLOR)
            faces = face_detector.detect(frame)
            for face in faces:
                vec = self.encode(frame, face)
                knownNames.append(name)
                knownEmbeddings.append(vec.flatten())
        FaceEncoder_Utils().save_training(classifier, knownNames, knownEmbeddings, 
            self._path_training + OUTPUT_OPENFACE_CLASSIFIER, 
            self._path_training + OUTPUT_OPENFACE_LABELER)


class FaceEncoder_DLIBRESNET():

    def __init__(self, path=None, path_training=None, training=False):
        import dlib # lazy loading
        self._path_training = path_training
        self._clf = None
        self._label_encoder = None
        self._embedder = dlib.face_recognition_model_v1(path + INPUT_DLIBRESNET_MODEL)
        self._shaper = dlib.shape_predictor(path + INPUT_DLIBRESNET_MODEL2)
        if training == False:
            self._clf = pickle.loads(open(self._path_training + OUTPUT_DLIBRESNET_CLASSIFIER, "rb").read())
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_DLIBRESNET_LABELER, "rb").read())
            #print(self._label_encoder.classes_)

    def identify(self, frame, face_rect):
        face_id = "Unknown"
        confidence = 99.99
        vec = self.encode(frame, face_rect)
        predictions_face = self._clf.predict(vec)[0]
        #print(predictions_face)
        id = np.argmax(predictions_face)
        confidence = predictions_face[id] * 100
        face_id = self._label_encoder.classes_[id]
        return face_id, confidence

    def encode(self, frame, face_rect):
        import dlib # lazy loading
        (x, y, w, h) = face_rect
        rect = dlib.rectangle(x, y, x+w, y+h)
        frame_rgb = frame[:, :, ::-1]
        shape = self._shaper(frame_rgb, rect)
        vec = self._embedder.compute_face_descriptor(frame_rgb, shape)
        return np.array([vec])

    def train(self, face_detector, path_dataset, verify, classifier):
        knownEmbeddings = []
        knownNames = []
        imagePaths = sorted(list(paths.list_images(path_dataset)))
        for (j, imagePath) in enumerate(imagePaths):
            name = imagePath.split(os.path.sep)[-2]
            frame = cv2.imread(imagePath, cv2.IMREAD_COLOR)
            faces = face_detector.detect(frame)
            for face in faces:
                vec = self.encode(frame, face)
                knownNames.append(name)
                knownEmbeddings.append(vec.flatten())
        FaceEncoder_Utils().save_training(classifier, knownNames, knownEmbeddings, 
            self._path_training + OUTPUT_DLIBRESNET_CLASSIFIER, 
            self._path_training + OUTPUT_DLIBRESNET_LABELER)


class FaceEncoder_FACENET():

    _face_crop_size=160
    _face_crop_margin=0

    def __init__(self, path=None, path_training=None, training=False):
        import tensorflow as tf               # lazy loading
        import facenet.src.facenet as facenet # lazy loading
        self._path_training = path_training
        self._sess = tf.Session()
        with self._sess.as_default():
            facenet.load_model(path + INPUT_FACENET_MODEL)
        if training == False:
            self._clf = pickle.loads(open(self._path_training + OUTPUT_FACENET_CLASSIFIER, "rb").read())
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_FACENET_LABELER, "rb").read())

    def identify(self, frame, face_rect, vec = None): ## 
        face_id = []
        confidence = [] # probabylity
        if vec is None:
            vec = self.encode(frame, face_rect)
        preds = self._clf.predict(vec) #get top n
        preds = preds[0, 0:]
        sorted_inds = [i[0] for i in sorted(enumerate(-preds), key=lambda x:x[1])]
        # id = np.argmax(predictions_face)
        # confidence = predictions_face[id]
        for i in range(5):
            face_id.append(self._label_encoder.classes_[sorted_inds[i]])
            confidence.append(preds[sorted_inds[i]])
        return face_id, confidence


    def set_face_crop(self, crop_size, crop_margin):
        self._face_crop_size = crop_size
        self._face_crop_margin = crop_margin


    def encode(self, frame, bounding_boxes=None, num_face = 1):
        try:
            import tensorflow as tf               # lazy loading
            import facenet.src.facenet as facenet # lazy loading
            img_list = []
            if  bounding_boxes is not None: # have multiple face in photo
                for bounding_boxe in bounding_boxes:
                    if (len(bounding_boxe) == 5 and bounding_boxe[4] < 0.50):
                        return None
                    else:
                        (x, y, w, h) = bounding_boxe
                        if self._face_crop_margin:
                            (x, y, w, h) = (
                                max(x - int(self._face_crop_margin/2), 0), 
                                max(y - int(self._face_crop_margin/2), 0), 
                                min(x+w + int(self._face_crop_margin/2), frame.shape[1]) - x, 
                                min(y+h + int(self._face_crop_margin/2), frame.shape[0]) - y)
                        face = misc.imresize(frame[y:y+h, x:x+w, :], (self._face_crop_size, self._face_crop_size), interp='bilinear')
                        prewhiten_face = facenet.prewhiten(face)
                        img_list.append(prewhiten_face)
            else: # face is aligned
                prewhiten_face = facenet.prewhiten(frame)
                img_list.append(prewhiten_face) 

            images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0")
            embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
            phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0")                
            feed_dict = {images_placeholder: img_list, phase_train_placeholder: False}
            if num_face == 1:
                return self._sess.run(embeddings, feed_dict=feed_dict)[0]
            else:
                return self._sess.run(embeddings, feed_dict=feed_dict)
        except Exception as ex:
            print(ex)
            return None

    def train(self, face_detector, face_embeddings_path, verify, classifier):
        X, Y = seriallize_face_encodings(face_embeddings_path, 1) 
        FaceEncoder_Utils().save_training(classifier, Y, X, 
            self._path_training + OUTPUT_FACENET_CLASSIFIER, 
            self._path_training + OUTPUT_FACENET_LABELER)

class FaceEncoder_INSIGHTFACE():
    def __init__(self, path, ga_model_path=None, gpu=0, image_size='112,112'):  
        #import tensorflow as tf
        # import mxnet as mx
        # from sklearn.decomposition import PCA
        from time import sleep
        from easydict import EasyDict as edict
        # from libinsightface.mtcnn_detector import MtcnnDetector
        # sys.path.append(join(dirname(__file__), '..', 'src', 'common'))
        # from libinsightface.common import face_image
        # from ibinsightface.common import face_preprocess

        # self.args = args
        model = join(path, INPUT_INSIGHTFACE_MODEL)
        ctx = mx.gpu(gpu)
        _vec = image_size.split(',')
        assert len(_vec)==2
        image_size = (int(_vec[0]), int(_vec[1]))
        self.model = None
        self.ga_model = None
        if len(model)>0:
            self.model = self.get_model(ctx, image_size, model, 'fc1')
        # if len(ga_model)>0:
        #     self.ga_model = self.get_model(ctx, image_size, ga_model, 'fc1')

        # self.threshold = threshold
        # self.det_minsize = 50
        # self.det_threshold = [0.6,0.7,0.8]
        # #self.det_factor = 0.9
        # self.image_size = image_size
        # mtcnn_path = os.path.join(os.path.dirname(__file__), 'mtcnn-model')
        # if det==0:
        #     detector = MtcnnDetector(model_folder=mtcnn_path, ctx=ctx, num_worker=1, accurate_landmark = True, threshold=self.det_threshold)
        # else:
        #     detector = MtcnnDetector(model_folder=mtcnn_path, ctx=ctx, num_worker=1, accurate_landmark = True, threshold=[0.0,0.0,0.2])
        # self.detector = detector

   
    def get_model(self, ctx, image_size, model_str, layer):
        # import mxnet as mx
        _vec = model_str.split(',')
        assert len(_vec)==2
        prefix = _vec[0]
        epoch = int(_vec[1])
        print('loading',prefix, epoch)
        sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch)
        all_layers = sym.get_internals()
        sym = all_layers[layer+'_output']
        model = mx.mod.Module(symbol=sym, context=ctx, label_names = None)
        #model.bind(data_shapes=[('data', (args.batch_size, 3, image_size[0], image_size[1]))], label_shapes=[('softmax_label', (args.batch_size,))])
        model.bind(data_shapes=[('data', (1, 3, image_size[0], image_size[1]))])
        model.set_params(arg_params, aux_params)
        return model


    def encode(self, img, *args):
        # import mxnet as mx
        import sklearn
        import imutils

        nimg = imutils.resize(img, 112)
        nimg = cv2.cvtColor(nimg, cv2.COLOR_BGR2RGB)
        aligned = np.transpose(nimg, (2,0,1))

        input_blob = np.expand_dims(aligned, axis=0)
        data = mx.nd.array(input_blob)
        db = mx.io.DataBatch(data=(data,))
        self.model.forward(db, is_train=False)
        embedding = self.model.get_outputs()[0].asnumpy()
        embedding = sklearn.preprocessing.normalize(embedding).flatten()
        return embedding  

    def get_ga(self, aligned):
        # import mxnet as mx
        input_blob = np.expand_dims(aligned, axis=0)
        data = mx.nd.array(input_blob)
        db = mx.io.DataBatch(data=(data,))
        self.ga_model.forward(db, is_train=False)
        ret = self.ga_model.get_outputs()[0].asnumpy()
        g = ret[:,0:2].flatten()
        gender = np.argmax(g)
        a = ret[:,2:202].reshape( (100,2) )
        a = np.argmax(a, axis=1)
        age = int(sum(a))

        return gender, age        



if __name__=='__main__':
    pass

from __future__ import absolute_import, division, print_function
import os, random
import numpy as np
from enum import Enum
import cv2                     # for FaceEncoderModels.LBPH, FaceEncoderModels.OPENFACE
import pickle                  # for FaceEncoderModels.OPENFACE and FaceEncoderModels.DLIBRESNET
from imutils import paths      # for FaceEncoderModels.LBPH
from sklearn.preprocessing import LabelEncoder # for FaceEncoderModels
from classifier import FaceClassifierModels, FaceClassifier
from scipy import misc         # for FaceDetector_FACENET
from image_utils import seriallize_face_encodings
from os.path import join
from scipy import misc
import mxnet as mx

OUTPUT_LBPH_CLASSIFIER       = 'lbph.yml'
OUTPUT_LBPH_LABELER          = 'lbph_le.pickle'

INPUT_OPENFACE_MODEL         = 'openface_nn4.small2.v1.t7'
OUTPUT_OPENFACE_CLASSIFIER   = 'openface_re.pickle'
OUTPUT_OPENFACE_LABELER      = 'openface_le.pickle'

INPUT_DLIBRESNET_MODEL       = 'dlib_face_recognition_resnet_model_v1.dat'
INPUT_DLIBRESNET_MODEL2      = 'shape_predictor_5_face_landmarks.dat'
OUTPUT_DLIBRESNET_CLASSIFIER = 'dlib_re.pickle'
OUTPUT_DLIBRESNET_LABELER    = 'dlib_le.pickle'

INPUT_FACENET_MODEL          = 'facenet_20180402-114759.pb'
OUTPUT_FACENET_CLASSIFIER    = 'facenet_re.pickle'
OUTPUT_FACENET_LABELER       = 'facenet_le.pickle'

INPUT_INSIGHTFACE_MODEL      = 'model-r100-ii/model,0'



class FaceEncoderModels(Enum):

    LBPH                = 0    # [ML] LBPH Local Binary Patterns Histograms
    OPENFACE            = 1    # [DL] OpenCV OpenFace
    DLIBRESNET          = 2    # [DL] DLIB ResNet
    FACENET             = 3    # [DL] FaceNet implementation by <NAME>
    # VGGFACE1_VGG16    = 4    # Refer to models\others\vggface_recognition
    # VGGFACE2_RESNET50 = 5    # Refer to models\others\vggface_recognition
    INSIGHTFACE         = 6
    DEFAULT = LBPH


class FaceEncoder():

    def __init__(self, model=FaceEncoderModels.DEFAULT, path=None, path_training=None, training=False):
        self._base = None
        # self.known_face_embs = None
        if model == FaceEncoderModels.LBPH:
            self._base = FaceEncoder_LBPH(path, path_training, training)
        elif model == FaceEncoderModels.OPENFACE:
            self._base = FaceEncoder_OPENFACE(path, path_training, training)
        elif model == FaceEncoderModels.DLIBRESNET:
            self._base = FaceEncoder_DLIBRESNET(path, path_training, training)
        elif model == FaceEncoderModels.FACENET:
            self._base = FaceEncoder_FACENET(path, path_training, training)
        elif model == FaceEncoderModels.INSIGHTFACE:
            self._base = FaceEncoder_INSIGHTFACE(path, path_training)    

    def identify(self, frame, face_rect, vec = None):
        try:
            face_id, confidence = self._base.identify(frame, face_rect, vec)
            return face_id, confidence
        except:
            return "Unknown", 0

    def train(self, face_detector, path_dataset, verify=False, classifier=FaceClassifierModels.LINEAR_SVM):
        try:
            self._base.train(face_detector, path_dataset, verify, classifier)
            print("Note: Make sure you use the same models for training and testing")
        except Exception as ex:
            print(ex)
            return "Training failed! an exception occurred!"
            

    def encode(self, frame, face_rect = None, num_face = 1):
        return self._base.encode(frame, face_rect, num_face)



class FaceEncoder_Utils():

    def save_training(self, classifier, knownNames, knownEmbeddings, output_clf, output_le):
        #print(len(knownNames))
        #print(len(knownEmbeddings))
        #print("[INFO] Number of classes = {}".format(knownNames))
        le = LabelEncoder()
        labels = le.fit_transform(knownNames)
        #print(le.classes_)
        #print(labels)
        clf = FaceClassifier(classifier)
        clf.fit(knownEmbeddings, labels)
        pred_score = clf.score(knownEmbeddings, labels)
        print("Prediction score: ", pred_score)
        f = open(output_clf, "wb")
        f.write(pickle.dumps(clf))
        f.close()
        f = open(output_le, "wb")
        f.write(pickle.dumps(le))
        f.close() 



class FaceEncoder_LBPH():

    def __init__(self, path=None, path_training=None, training=False):
        self._path_training = path_training
        self._label_encoder = None
        self._clf = cv2.face.LBPHFaceRecognizer_create()
        if training == False:
            self._clf.read(self._path_training + OUTPUT_LBPH_CLASSIFIER)
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_LBPH_LABELER, "rb").read())
            #print(self._label_encoder.classes_)

    def identify(self, frame, face_rect):
        face_id = "Unknown"
        confidence = 99.99
        (x, y, w, h) = face_rect
        frame_gray = frame[y:y+h, x:x+w]
        face = cv2.cvtColor(frame_gray, cv2.COLOR_BGR2GRAY)
        id, confidence = self._clf.predict(face)
        if confidence > 99.99: 
            confidence = 99.99
        face_id = self._label_encoder.classes_[id]
        return face_id, confidence

    def train(self, face_detector, path_dataset, verify, classifier):
        imagePaths = sorted(list(paths.list_images(path_dataset)))
        faceSamples=[]
        ids = []
        knownNames = []

        id = -1
        for (i, imagePath) in enumerate(imagePaths):
            frame = cv2.imread(imagePath, cv2.IMREAD_COLOR)
            name = imagePath.split(os.path.sep)[-2]
            try:
                id = knownNames.index(name)
            except:
                id = id + 1
            #print("name=%s id=%d" % (name, id))

            frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            faces = face_detector.detect(frame)
            for (index, face) in enumerate(faces):
                (x, y, w, h) = face
                faceSamples.append(frame_gray[y:y+h,x:x+w])
                knownNames.append(name)
                ids.append(id)
                #cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 255, 255), 1)
                break

            if verify and len(faces) != 1:
                print("[WARNING] Image {} has {} faces ".format(imagePath, len(faces)))
                cv2.imshow('frame', frame)
                cv2.waitKey(10)

        #print(ids)
        #print(knownNames)

        if verify:
            cv2.destroyAllWindows()

        self._clf.train(faceSamples, np.array(ids))
        self._clf.write(self._path_training + OUTPUT_LBPH_CLASSIFIER)

        le = LabelEncoder()
        labels = le.fit_transform(knownNames)
        #print(le.classes_)
        #print(labels)
        
        f = open(self._path_training + OUTPUT_LBPH_LABELER, "wb")
        f.write(pickle.dumps(le))
        f.close()


class FaceEncoder_OPENFACE():

    def __init__(self, path=None, path_training=None, training=False):
        self._path_training = path_training
        self._clf = None
        self._label_encoder = None
        self._embedder = cv2.dnn.readNetFromTorch(path + INPUT_OPENFACE_MODEL)
        if training == False:
            self._clf = pickle.loads(open(self._path_training + OUTPUT_OPENFACE_CLASSIFIER, "rb").read())
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_OPENFACE_LABELER, "rb").read())
            #print(self._label_encoder.classes_)

    def identify(self, frame, face_rect):
        face_id = "Unknown"
        confidence = 99.99
        vec = self.encode(frame, face_rect)
        predictions_face = self._clf.predict(vec)[0]
        id = np.argmax(predictions_face)
        confidence = predictions_face[id] * 100
        face_id = self._label_encoder.classes_[id]
        return face_id, confidence

    def encode(self, frame, face_rect):
        (x, y, w, h) = face_rect
        face = frame[y:y+h, x:x+w]
        faceBlob = cv2.dnn.blobFromImage(face, 1.0 / 255, (96, 96), (0, 0, 0), swapRB=True, crop=False)
        self._embedder.setInput(faceBlob)
        return self._embedder.forward()

    def train(self, face_detector, path_dataset, verify, classifier):
        knownEmbeddings = []
        knownNames = []
        imagePaths = sorted(list(paths.list_images(path_dataset)))
        for (j, imagePath) in enumerate(imagePaths):
            name = imagePath.split(os.path.sep)[-2]
            frame = cv2.imread(imagePath, cv2.IMREAD_COLOR)
            faces = face_detector.detect(frame)
            for face in faces:
                vec = self.encode(frame, face)
                knownNames.append(name)
                knownEmbeddings.append(vec.flatten())
        FaceEncoder_Utils().save_training(classifier, knownNames, knownEmbeddings, 
            self._path_training + OUTPUT_OPENFACE_CLASSIFIER, 
            self._path_training + OUTPUT_OPENFACE_LABELER)


class FaceEncoder_DLIBRESNET():

    def __init__(self, path=None, path_training=None, training=False):
        import dlib # lazy loading
        self._path_training = path_training
        self._clf = None
        self._label_encoder = None
        self._embedder = dlib.face_recognition_model_v1(path + INPUT_DLIBRESNET_MODEL)
        self._shaper = dlib.shape_predictor(path + INPUT_DLIBRESNET_MODEL2)
        if training == False:
            self._clf = pickle.loads(open(self._path_training + OUTPUT_DLIBRESNET_CLASSIFIER, "rb").read())
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_DLIBRESNET_LABELER, "rb").read())
            #print(self._label_encoder.classes_)

    def identify(self, frame, face_rect):
        face_id = "Unknown"
        confidence = 99.99
        vec = self.encode(frame, face_rect)
        predictions_face = self._clf.predict(vec)[0]
        #print(predictions_face)
        id = np.argmax(predictions_face)
        confidence = predictions_face[id] * 100
        face_id = self._label_encoder.classes_[id]
        return face_id, confidence

    def encode(self, frame, face_rect):
        import dlib # lazy loading
        (x, y, w, h) = face_rect
        rect = dlib.rectangle(x, y, x+w, y+h)
        frame_rgb = frame[:, :, ::-1]
        shape = self._shaper(frame_rgb, rect)
        vec = self._embedder.compute_face_descriptor(frame_rgb, shape)
        return np.array([vec])

    def train(self, face_detector, path_dataset, verify, classifier):
        knownEmbeddings = []
        knownNames = []
        imagePaths = sorted(list(paths.list_images(path_dataset)))
        for (j, imagePath) in enumerate(imagePaths):
            name = imagePath.split(os.path.sep)[-2]
            frame = cv2.imread(imagePath, cv2.IMREAD_COLOR)
            faces = face_detector.detect(frame)
            for face in faces:
                vec = self.encode(frame, face)
                knownNames.append(name)
                knownEmbeddings.append(vec.flatten())
        FaceEncoder_Utils().save_training(classifier, knownNames, knownEmbeddings, 
            self._path_training + OUTPUT_DLIBRESNET_CLASSIFIER, 
            self._path_training + OUTPUT_DLIBRESNET_LABELER)


class FaceEncoder_FACENET():

    _face_crop_size=160
    _face_crop_margin=0

    def __init__(self, path=None, path_training=None, training=False):
        import tensorflow as tf               # lazy loading
        import facenet.src.facenet as facenet # lazy loading
        self._path_training = path_training
        self._sess = tf.Session()
        with self._sess.as_default():
            facenet.load_model(path + INPUT_FACENET_MODEL)
        if training == False:
            self._clf = pickle.loads(open(self._path_training + OUTPUT_FACENET_CLASSIFIER, "rb").read())
            self._label_encoder = pickle.loads(open(self._path_training + OUTPUT_FACENET_LABELER, "rb").read())

    def identify(self, frame, face_rect, vec = None): ## 
        face_id = []
        confidence = [] # probabylity
        if vec is None:
            vec = self.encode(frame, face_rect)
        preds = self._clf.predict(vec) #get top n
        preds = preds[0, 0:]
        sorted_inds = [i[0] for i in sorted(enumerate(-preds), key=lambda x:x[1])]
        # id = np.argmax(predictions_face)
        # confidence = predictions_face[id]
        for i in range(5):
            face_id.append(self._label_encoder.classes_[sorted_inds[i]])
            confidence.append(preds[sorted_inds[i]])
        return face_id, confidence


    def set_face_crop(self, crop_size, crop_margin):
        self._face_crop_size = crop_size
        self._face_crop_margin = crop_margin


    def encode(self, frame, bounding_boxes=None, num_face = 1):
        try:
            import tensorflow as tf               # lazy loading
            import facenet.src.facenet as facenet # lazy loading
            img_list = []
            if  bounding_boxes is not None: # have multiple face in photo
                for bounding_boxe in bounding_boxes:
                    if (len(bounding_boxe) == 5 and bounding_boxe[4] < 0.50):
                        return None
                    else:
                        (x, y, w, h) = bounding_boxe
                        if self._face_crop_margin:
                            (x, y, w, h) = (
                                max(x - int(self._face_crop_margin/2), 0), 
                                max(y - int(self._face_crop_margin/2), 0), 
                                min(x+w + int(self._face_crop_margin/2), frame.shape[1]) - x, 
                                min(y+h + int(self._face_crop_margin/2), frame.shape[0]) - y)
                        face = misc.imresize(frame[y:y+h, x:x+w, :], (self._face_crop_size, self._face_crop_size), interp='bilinear')
                        prewhiten_face = facenet.prewhiten(face)
                        img_list.append(prewhiten_face)
            else: # face is aligned
                prewhiten_face = facenet.prewhiten(frame)
                img_list.append(prewhiten_face) 

            images_placeholder = tf.get_default_graph().get_tensor_by_name("input:0")
            embeddings = tf.get_default_graph().get_tensor_by_name("embeddings:0")
            phase_train_placeholder = tf.get_default_graph().get_tensor_by_name("phase_train:0")                
            feed_dict = {images_placeholder: img_list, phase_train_placeholder: False}
            if num_face == 1:
                return self._sess.run(embeddings, feed_dict=feed_dict)[0]
            else:
                return self._sess.run(embeddings, feed_dict=feed_dict)
        except Exception as ex:
            print(ex)
            return None

    def train(self, face_detector, face_embeddings_path, verify, classifier):
        X, Y = seriallize_face_encodings(face_embeddings_path, 1) 
        FaceEncoder_Utils().save_training(classifier, Y, X, 
            self._path_training + OUTPUT_FACENET_CLASSIFIER, 
            self._path_training + OUTPUT_FACENET_LABELER)

class FaceEncoder_INSIGHTFACE():
    def __init__(self, path, ga_model_path=None, gpu=0, image_size='112,112'):  
        #import tensorflow as tf
        # import mxnet as mx
        # from sklearn.decomposition import PCA
        from time import sleep
        from easydict import EasyDict as edict
        # from libinsightface.mtcnn_detector import MtcnnDetector
        # sys.path.append(join(dirname(__file__), '..', 'src', 'common'))
        # from libinsightface.common import face_image
        # from ibinsightface.common import face_preprocess

        # self.args = args
        model = join(path, INPUT_INSIGHTFACE_MODEL)
        ctx = mx.gpu(gpu)
        _vec = image_size.split(',')
        assert len(_vec)==2
        image_size = (int(_vec[0]), int(_vec[1]))
        self.model = None
        self.ga_model = None
        if len(model)>0:
            self.model = self.get_model(ctx, image_size, model, 'fc1')
        # if len(ga_model)>0:
        #     self.ga_model = self.get_model(ctx, image_size, ga_model, 'fc1')

        # self.threshold = threshold
        # self.det_minsize = 50
        # self.det_threshold = [0.6,0.7,0.8]
        # #self.det_factor = 0.9
        # self.image_size = image_size
        # mtcnn_path = os.path.join(os.path.dirname(__file__), 'mtcnn-model')
        # if det==0:
        #     detector = MtcnnDetector(model_folder=mtcnn_path, ctx=ctx, num_worker=1, accurate_landmark = True, threshold=self.det_threshold)
        # else:
        #     detector = MtcnnDetector(model_folder=mtcnn_path, ctx=ctx, num_worker=1, accurate_landmark = True, threshold=[0.0,0.0,0.2])
        # self.detector = detector

   
    def get_model(self, ctx, image_size, model_str, layer):
        # import mxnet as mx
        _vec = model_str.split(',')
        assert len(_vec)==2
        prefix = _vec[0]
        epoch = int(_vec[1])
        print('loading',prefix, epoch)
        sym, arg_params, aux_params = mx.model.load_checkpoint(prefix, epoch)
        all_layers = sym.get_internals()
        sym = all_layers[layer+'_output']
        model = mx.mod.Module(symbol=sym, context=ctx, label_names = None)
        #model.bind(data_shapes=[('data', (args.batch_size, 3, image_size[0], image_size[1]))], label_shapes=[('softmax_label', (args.batch_size,))])
        model.bind(data_shapes=[('data', (1, 3, image_size[0], image_size[1]))])
        model.set_params(arg_params, aux_params)
        return model


    def encode(self, img, *args):
        # import mxnet as mx
        import sklearn
        import imutils

        nimg = imutils.resize(img, 112)
        nimg = cv2.cvtColor(nimg, cv2.COLOR_BGR2RGB)
        aligned = np.transpose(nimg, (2,0,1))

        input_blob = np.expand_dims(aligned, axis=0)
        data = mx.nd.array(input_blob)
        db = mx.io.DataBatch(data=(data,))
        self.model.forward(db, is_train=False)
        embedding = self.model.get_outputs()[0].asnumpy()
        embedding = sklearn.preprocessing.normalize(embedding).flatten()
        return embedding  

    def get_ga(self, aligned):
        # import mxnet as mx
        input_blob = np.expand_dims(aligned, axis=0)
        data = mx.nd.array(input_blob)
        db = mx.io.DataBatch(data=(data,))
        self.ga_model.forward(db, is_train=False)
        ret = self.ga_model.get_outputs()[0].asnumpy()
        g = ret[:,0:2].flatten()
        gender = np.argmax(g)
        a = ret[:,2:202].reshape( (100,2) )
        a = np.argmax(a, axis=1)
        age = int(sum(a))

        return gender, age        



if __name__=='__main__':
    pass

# for FaceEncoderModels.LBPH, FaceEncoderModels.OPENFACE # for FaceEncoderModels.OPENFACE and FaceEncoderModels.DLIBRESNET # for FaceEncoderModels.LBPH # for FaceEncoderModels # for FaceDetector_FACENET # [ML] LBPH Local Binary Patterns Histograms # [DL] OpenCV OpenFace # [DL] DLIB ResNet # [DL] FaceNet implementation by <NAME> # VGGFACE1_VGG16    = 4    # Refer to models\others\vggface_recognition # VGGFACE2_RESNET50 = 5    # Refer to models\others\vggface_recognition # self.known_face_embs = None #print(len(knownNames)) #print(len(knownEmbeddings)) #print("[INFO] Number of classes = {}".format(knownNames)) #print(le.classes_) #print(labels) #print(self._label_encoder.classes_) #print("name=%s id=%d" % (name, id)) #cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 255, 255), 1) #print(ids) #print(knownNames) #print(le.classes_) #print(labels) #print(self._label_encoder.classes_) # lazy loading #print(self._label_encoder.classes_) #print(predictions_face) # lazy loading # lazy loading # lazy loading ## # probabylity #get top n # id = np.argmax(predictions_face) # confidence = predictions_face[id] # lazy loading # lazy loading # have multiple face in photo # face is aligned #import tensorflow as tf # import mxnet as mx # from sklearn.decomposition import PCA # from libinsightface.mtcnn_detector import MtcnnDetector # sys.path.append(join(dirname(__file__), '..', 'src', 'common')) # from libinsightface.common import face_image # from ibinsightface.common import face_preprocess # self.args = args # if len(ga_model)>0: #     self.ga_model = self.get_model(ctx, image_size, ga_model, 'fc1') # self.threshold = threshold # self.det_minsize = 50 # self.det_threshold = [0.6,0.7,0.8] # #self.det_factor = 0.9 # self.image_size = image_size # mtcnn_path = os.path.join(os.path.dirname(__file__), 'mtcnn-model') # if det==0: #     detector = MtcnnDetector(model_folder=mtcnn_path, ctx=ctx, num_worker=1, accurate_landmark = True, threshold=self.det_threshold) # else: #     detector = MtcnnDetector(model_folder=mtcnn_path, ctx=ctx, num_worker=1, accurate_landmark = True, threshold=[0.0,0.0,0.2]) # self.detector = detector # import mxnet as mx #model.bind(data_shapes=[('data', (args.batch_size, 3, image_size[0], image_size[1]))], label_shapes=[('softmax_label', (args.batch_size,))]) # import mxnet as mx # import mxnet as mx