Face Recognition DB

In this tutorial we will learn to write a python application to create a simple face db system for enrollment and identification using deepsight

Dependencies

You will need to install opencv, scipy and numpy python modules. Run pip install opencv-python scipy numpy in a terminal (both windows and linux).

If you have issues importing cv2 module, please check this link to troubleshoot.

Python code

Create a file face_db.py with the following code in it.

import cv2
import requests
import numpy as np
import json
import argparse
import signal
import logging
import datetime, time
import glob
from scipy import spatial
import os

face_api = "http://localhost:5000/inferImage?returnFaceId=true&returnFaceLandmarks=true"
compare_api = "http://localhost:5000/compareFaces"


# init logger
logger = logging.getLogger('FaceDB')
logger.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(ch)


# parse arguments
parser = argparse.ArgumentParser(description='Face DB System')
parser.add_argument('--enroll', action='store_true', help='Scan db folder for faces and update db')
parser.add_argument('--src', action='store', default=0, nargs='?', help='Set source image')
parser.add_argument('--w', action='store', default=320, nargs='?', help='Set width')
parser.add_argument('--h', action='store', default=240, nargs='?', help='Set height')
args = parser.parse_args()


# catch exit signal
def signal_handler(signal, frame):
    if args.enroll:
        logger.info("Saving Face DB")
        with open('att_db','w') as att:
            att.write(json.dumps(db,2))
   
    exit(0)
signal.signal(signal.SIGINT, signal_handler)

    
# enroll a new face into db
def enroll(embedding):
    name = input("New face detected, enter name\n")
    if name != "":
        db[name] = embedding
        print("Enrolled %s into db!"%name)


def build_montages(image_list, image_shape, montage_shape):
    """
    ---------------------------------------------------------------------------------------------
    author: Kyle Hounslow
    ---------------------------------------------------------------------------------------------
    Converts a list of single images into a list of 'montage' images of specified rows and columns.
    A new montage image is started once rows and columns of montage image is filled.
    Empty space of incomplete montage images are filled with black pixels
    ---------------------------------------------------------------------------------------------
    :param image_list: python list of input images
    :param image_shape: tuple, size each image will be resized to for display (width, height)
    :param montage_shape: tuple, shape of image montage (width, height)
    :return: list of montage images in numpy array format
    ---------------------------------------------------------------------------------------------
    example usage:
    # load single image
    img = cv2.imread('lena.jpg')
    # duplicate image 25 times
    num_imgs = 25
    img_list = []
    for i in xrange(num_imgs):
        img_list.append(img)
    # convert image list into a montage of 256x256 images tiled in a 5x5 montage
    montages = make_montages_of_images(img_list, (256, 256), (5, 5))
    # iterate through montages and display
    for montage in montages:
        cv2.imshow('montage image', montage)
        cv2.waitKey(0)
    ----------------------------------------------------------------------------------------------
    """
    if len(image_shape) != 2:
        raise Exception('image shape must be list or tuple of length 2 (rows, cols)')
    if len(montage_shape) != 2:
        raise Exception('montage shape must be list or tuple of length 2 (rows, cols)')
    image_montages = []
    # start with black canvas to draw images onto
    montage_image = np.zeros(shape=(image_shape[1] * (montage_shape[1]), image_shape[0] * montage_shape[0], 3),
                          dtype=np.uint8)
    cursor_pos = [0, 0]
    start_new_img = False
    for img in image_list:
        if type(img).__module__ != np.__name__:
            raise Exception('input of type {} is not a valid numpy array'.format(type(img)))
        start_new_img = False
        img = cv2.resize(img, image_shape)
        # draw image to black canvas
        montage_image[cursor_pos[1]:cursor_pos[1] + image_shape[1], cursor_pos[0]:cursor_pos[0] + image_shape[0]] = img
        cursor_pos[0] += image_shape[0]  # increment cursor x position
        if cursor_pos[0] >= montage_shape[0] * image_shape[0]:
            cursor_pos[1] += image_shape[1]  # increment cursor y position
            cursor_pos[0] = 0
            if cursor_pos[1] >= montage_shape[1] * image_shape[1]:
                cursor_pos = [0, 0]
                image_montages.append(montage_image)
                # reset black canvas
                montage_image = np.zeros(shape=(image_shape[1] * (montage_shape[1]), image_shape[0] * montage_shape[0], 3),
                                      dtype=np.uint8)
                start_new_img = True
    if start_new_img is False:
        image_montages.append(montage_image)  # add unfinished montage
    return image_montages

def scan_faces():
    db = {"names":[],"embeddings":[]}
    path = "db/*/"
    dirs = glob.glob(path)
    for person_name in dirs:        
        clean_person_name = person_name[3:-1]
        logger.info("Enrolling %s"%clean_person_name)

        images = glob.glob(person_name+"/*.*")
        count = 0
        person_face_list = []
        for image in images:
            img = cv2.imread(image)
            r,imgbuf = cv2.imencode(".bmp", img)
            image = {'pic':bytearray(imgbuf)}

            r = requests.post(face_api, files=image)
            result = r.json()

            if len(result) > 1:
                faces = result[:-1]
                for face in faces:
                    rect, embedding = [face[i] for i in ['faceRectangle','faceEmbeddings']]
                    x,y,w,h = [rect[i] for i in ['left', 'top', 'width', 'height']]

                    cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0),1,8)        
                    person_face_list.append(img[y:y+h,x:x+w])
                    db["names"].append(clean_person_name)
                    db["embeddings"].append(embedding)
        
        montages = build_montages(person_face_list, (256, 256), (4, 2))
        for montage in montages:
            cv2.imshow(clean_person_name, montage)
            cv2.waitKey(1000)
            cv2.destroyAllWindows()
    
    with open("db.txt","w") as dbfile:
        dbfile.write(json.dumps(db))

def load_db():    
    if os.path.exists("db.txt"):
        with open("db.txt","r") as dbfile:
            db = json.loads(dbfile.read())
        return db
    else:
        return {}


# start processing
if __name__ == "__main__":
    db = load_db()
    if args.enroll or (len(db.keys())==0):
        db = scan_faces()
        print("Exit")
        exit()
    
    if args.src != 0:
        
        dbtree = spatial.KDTree(db["embeddings"])        

        img = cv2.imread(args.src)
        r,imgbuf = cv2.imencode(".bmp", img)
        image = {'pic':bytearray(imgbuf)}

        r = requests.post(face_api, files=image)
        result = r.json()

        if len(result) > 1:
            faces = result[:-1]
            for face in faces:
                rect, embedding = [face[i] for i in ['faceRectangle','faceEmbeddings']]
                x,y,w,h = [rect[i] for i in ['left', 'top', 'width', 'height']]

                dist, idx = dbtree.query(embedding)                               
                name = db["names"][idx]
                logger.info("closest match distance %f with %s"%(dist, name ))
                if dist > 0.5:
                    name = "unknown"
                
                cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,255),5,8)
                cv2.rectangle(img, (x,y+h-20), (x+w,y+h), (255,0,255), -1, 8)
                cv2.putText(img, "%s"%(name), (x,y+h), cv2.FONT_HERSHEY_DUPLEX, 1,  (255,255,255),2,8)

        cv2.imshow("result", img)
        cv2.waitKey(1000)
        cv2.destroyAllWindows()


print("Exit")

Usage

# For help with usage
usage: face_db.py [-h] [--enroll] [--src [SRC]] [--w [W]] [--h [H]]

Face DB System

optional arguments:
  -h, --help   show this help message and exit
  --enroll     Scan db folder for faces and update db
  --src [SRC]  Set source image
  --w [W]      Set width
  --h [H]      Set height

Running the program

  1. Create a folder called db
  2. Place images with people’s faces in their respective folders inside db. Ex. db/person1/face1.jpg
  3. Run face_db.py --enroll and let it enroll faces
  4. After enrollment you can start comparing with new faces from face_db.py --src newimage.jpg