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
- Create a folder called
db
- Place images with people’s faces in their respective folders inside
db
. Ex.db/person1/face1.jpg
- Run
face_db.py --enroll
and let it enroll faces - After enrollment you can start comparing with new faces from
face_db.py --src newimage.jpg