Face Recognition using OpenCV

Before Discuss my problem, I described what I want to do. I want to recognize every user based on profile image with single face and determine if the user is fake or not.

The given dataset contains training dataset consist of 10 profile images of each user that already uploaded from the applications and testing images consisting of 6 random user’s profile images.

Here is my Code:

import os
import cv2 as cv
import numpy as np

def get_path_list(root_path):
    '''
        To get a list of path directories from root path

        Parameters
        ----------
        root_path : str
            Location of root directory

        Returns
        -------
        list
            List containing the names of the sub-directories in the
            root directory
    '''
    names = os.listdir(root_path)
    return names

def get_class_id(root_path, train_names):
    '''
        To get a list of train images and a list of image classes id

        Parameters
        ----------
        root_path : str
            Location of images root directory
        train_names : list
            List containing the names of the train sub-directories

        Returns
        -------
        list
            List containing all image in the train directories
        list
            List containing all image classes id
    '''
    image_list = []
    class_ids = []
    for id, person_name in enumerate(train_names):
        person_dir = root_path + "/" + person_name
        for person_img_sample in os.listdir(person_dir):
            img = cv.imread(person_dir + "/" + person_img_sample)
            h, w, _ = img.shape
            ratio = 600 / w
            new_h = h * ratio
            img = cv.resize(img, (600, int(new_h)))
            image_list.append(img)
            class_ids.append(id)
            # print(person_dir + "/" + person_img_sample, id)
    return image_list, class_ids

def detect_faces_and_filter(image_list, image_classes_list=None):
    '''
        To detect a face from given image list and filter it if the face on
        the given image is less than one

        Parameters
        ----------
        image_list : list
            List containing all loaded images
        image_classes_list : list, optional
            List containing all image classes id

        Returns
        -------
        list
            List containing all filtered and cropped face images in grayscale
        list
            List containing all filtered faces location saved in rectangle
        list
            List containing all filtered image classes id
    '''
    cropped_face_images = []
    face_locations = []
    image_class_ids = []
    if image_classes_list is None:
        image_classes_list = [-1] * len(image_list)

    face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')
    for img, id in zip(image_list, image_classes_list):
        img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(img, scaleFactor=1.3,
                                              minNeighbors=7)

        if len(faces) == 1:
            for (x, y, w, h) in faces:
                cropped_face = img[y:y + h, x:x + w]
                cropped_face_images.append(cropped_face)
                face_locations.append((x, y, w, h))
                image_class_ids.append(id)
        else:
            pass

    return cropped_face_images, face_locations, image_class_ids

def train(train_face_grays, image_classes_list):
    '''
        To create and train face recognizer object

        Parameters
        ----------
        train_face_grays : list
            List containing all filtered and cropped face images in grayscale
        image_classes_list : list
            List containing all filtered image classes id

        Returns
        -------
        object
            Recognizer object after being trained with cropped face images
    '''
    recognizer = cv.face.LBPHFaceRecognizer_create()
    recognizer.train(train_face_grays, np.array(image_classes_list))

    return recognizer

def get_test_images_data(test_root_path):
    '''
        To load a list of test images from given path list

        Parameters
        ----------
        test_root_path : str
            Location of images root directory

        Returns
        -------
        list
            List containing all loaded gray test images
    '''
    test_imgs = []

    for img_loc in os.listdir(test_root_path):
        img = cv.imread(test_root_path + "/" + img_loc)
        h, w, _ = img.shape
        ratio = 600 / w
        new_h = h * ratio
        img = cv.resize(img, (600, int(new_h)))
        test_imgs.append(img)

    return test_imgs

def predict(recognizer, test_faces_gray):
    '''
        To predict the test image with the recognizer

        Parameters
        ----------
        recognizer : object
            Recognizer object after being trained with cropped face images
        train_face_grays : list
            List containing all filtered and cropped face images in grayscale

        Returns
        -------
        list
            List containing all prediction results from given test faces
    '''
    results = []
    for face in test_faces_gray:
        class_id, c = recognizer.predict(face)
        results.append(class_id)
        print(class_id, c)

    return results

def draw_prediction_results(predict_results, test_image_list, test_faces_rects, train_names):
    '''
        To draw prediction results on the given test images and acceptance status

        Parameters
        ----------
        predict_results : list
            List containing all prediction results from given test faces
        test_image_list : list
            List containing all loaded test images
        test_faces_rects : list
            List containing all filtered faces location saved in rectangle
        train_names : list
            List containing the names of the train sub-directories

        Returns
        -------
        list
            List containing all test images after being drawn with
            final result
    '''
    verification_statuses = []
    unverified_agent = ['Riza', 'High T']

    for i, pred in enumerate(predict_results):
        name = train_names[pred]
        if name in unverified_agent:
            verification_statuses.append([name, 0])
        else:
            verification_statuses.append([name, 1])
    
    drawn_imgs = []
    font = cv.FONT_HERSHEY_SIMPLEX
    for ver, img, (x, y, w, h) in zip(verification_statuses, test_image_list, test_faces_rects):

        if ver[1] == 1:
            img = cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 8)
            img = cv.putText(img, ver[0], (x, y + h + 55), font, 2, (0, 255, 0), 5, cv.FONT_HERSHEY_SIMPLEX)
        else:
            img = cv.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 8)
            img = cv.putText(img, ver[0] + " (Fake)", (x, y + h + 55), font, 2, (0, 0, 255), 5, cv.FONT_HERSHEY_SIMPLEX)
        img = cv.resize(img, (250, 250))
        drawn_imgs.append([img, ver[1]])

    return drawn_imgs

def combine_and_show_result(image_list):
    '''
        To show the final image that already combine into one image

        Parameters
        ----------
        image_list : nparray
            Array containing image data
    '''
    verified_imgs = []
    unverified_imgs = []
    white_img = np.zeros([250, 250, 3], dtype=np.uint8)
    white_img.fill(255)

    first_row_odd = []
    second_row_even = []
    indexs = 1
    for img, ver in image_list:
        if (indexs % 2) == 1:
            first_row_odd.append(img)
        else:
            second_row_even.append(img)
        indexs = indexs + 1

    row2 = cv.hconcat(second_row_even)
    row1 = cv.hconcat(first_row_odd)
    
    result = cv.vconcat([row1, row2])
    
    cv.imshow("MIB: International", result)
    cv.waitKey(0)
    cv.destroyAllWindows()

if __name__ == '__main__':
    
    '''
        Please modify train_root_path value according to the location of
        your data train root directory

        -------------------
        Modifiable
        -------------------
    '''
    train_root_path = 'dataset/train'
    '''
        -------------------
        End of modifiable
        -------------------
    '''

    train_names = get_path_list(train_root_path)
    train_image_list, image_classes_list = get_class_id(train_root_path, train_names)
    train_face_grays, _, filtered_classes_list = detect_faces_and_filter(train_image_list, image_classes_list)
    # print(filtered_classes_list)
    recognizer = train(train_face_grays, filtered_classes_list)

    '''
        Please modify train_root_path value according to the location of
        your data train root directory

        -------------------
        Modifiable
        -------------------
    '''
    test_root_path = 'dataset/test'
    '''
        -------------------
        End of modifiable
        -------------------
    '''
    
    test_image_list = get_test_images_data(test_root_path)
    test_faces_gray, test_faces_rects, _ = detect_faces_and_filter(test_image_list)
    predict_results = predict(recognizer, test_faces_gray)
    predicted_test_image_list = draw_prediction_results(predict_results, test_image_list, test_faces_rects, train_names)

    combine_and_show_result(predicted_test_image_list)

Here is my Result:

But in the first row of column 2, it’s recognize as Riza (Fake), the actual result must be recognize as Agent M, what can I do to hyperparameter tuning? any recommendation and what value should I used? thank you

why are you ignoring the ‘distance’ value (c) here ?
if it’s too high, it could not predict correctly
(it will always return an id, no matter what !)

this looks like a broken approach to me,
as every ‘unverified’ user must be already in the train database, right ?
what do you do with new, ‘unknown’ users ?

Thank You for your advice, I will improve the approach and consider the distance value.

Best Regards,

Nfsaja_5536

some more tips to improve it:

  • dont use very small face crops, as they will have an improper distribution of lbp features. reject anything smaller than 160x160 (like your 2nd image)
  • try to align faces. use landmarks to find eye / nose positions, then warp it, so they’re all in the same place
  • use a different, dnn based classifier, e.g. FaceRecognizerSF