Creating the Face class

The Face class is at the heart of the Smart Doorbell application. With the Face class, we can pass in a picture of a person and have the Face class either return the name of that person or state that the person is unknown based on a repository of photos.   

The following diagram explains the Face class from a high-level perspective:

As you can see, Face is instantiated with the faces repository. An unknown face is passed into the get_name() method and compared to faces stored in the faces repository. Of note are the differences between the picture of Rolo Slope in the faces repository and the one used in the get_name() method (it looks like he got a haircut). Despite these differences, our Face object can recognize Rolo Slope.

Let's write the code to create the Face class:

  1. In Thonny, create a new Python file in the project folder (Smart Doorbell).
  2. Copy the following code and save the file as Face.py:
import face_recognition as fr
import os

class Face:
def __init__(self, faces_dir):
self.faces = {}
for root, dirs, filenames in os.walk(faces_dir):
for filename in filenames:
image = fr.load_image_file(faces_dir + "/" + \
filename)
face_encodings = fr.face_encodings(image)
if len(face_encodings)==1:
self.faces[filename.split(".")[0]] = \
face_encodings

def get_name(self, source, source_type="file"):

if source_type=="file":
image = fr.load_image_file(source)
unknown_face = fr.face_encodings(image)
else:
unknown_face = fr.face_encodings(source)

if len(unknown_face) != 1:
return None

for face_name, face in self.faces.items():
match = fr.compare_faces([face][0], unknown_face[0])
if match[0]:
return face_name
return "Unknown Person"
  1. Run the file by clicking on the green Run current script button.
  2. In the shell, type in the following to import the Face class and hit Enter:
from Face import Face
  1. To instantiate a Face object with the faces repository, type the following into the shell and hit Enter. Please note that it may take a while for this command to finish:
face = Face("faces")
  1. Now that we have a Face object, let's use it to find out the name of unknown. Type the following into the shell and hit Enter:
face.get_name('unknown.jpg')
  1. Verify that you get the name Rolo Slope printed in the shell.

If you are like me, you are amazed that a program to recognize a face can be written with so little code. Let's take a deeper look.

We start with the import of the additional libraries we need for the class:

import face_recognition as fr
import os

After defining the name for the class, we then set up the class initialization method:

class Face:
def __init__(self, faces_dir):
self.faces = {}
for root, dirs, filenames in os.walk(faces_dir):
for filename in filenames:
image = fr.load_image_file(faces_dir + "/" + filename)
face_encodings = fr.face_encodings(image)
if len(face_encodings)==1:
self.faces[filename.split(".")[0]] = face_encodings

We can see that our initialization function takes in the location of our directory (or folder) where our photos are stored. After defining the faces class variable as a dictionary object, we then walk through all of the files in the faces folder. 

Each file is loaded and stored in a variable called image. This variable is then used to create a face_encodings object using the face_recognition library. A face_encodings object represents the faces found in an image or photo. Its length is determined by how many faces it finds. We take advantage of this fact by limiting the faces dictionary object to photos with only one face by using the if statement, if len(face_encodings)==1:. We are not interested in photos with more than one person.

The get_name() method takes in a source and compares it to the repository of known faces via the faces class variable:

def get_name(self, source, source_type="file"):

if source_type=="file":
image = fr.load_image_file(source)
unknown_face = fr.face_encodings(image)
else:
unknown_face = fr.face_encodings(source)

if len(unknown_face) != 1:
return None

for face_name, face in self.faces.items():
match = fr.compare_faces([face][0], unknown_face[0])
if match[0]:
return face_name
return "Unknown Person"

The source_type parameter in the method's signature allows us to use the get_name method for both image files and frames from a webcam. If the source type is a file, then that file is used to create the variable image using the load_image_file() method from the face_recognition library. The unknown_face variable is created from the face_encodings() method with either the image object or a video frame based on source_type. If a face is not found or there is more than one face in the image, then None is returned. We are not interested in images or photos where we cannot distinguish one person.

We then cycle through all of the known faces comparing unknown_face to known faces stored in the faces class variable. If a match is found, then we return the name of the person as a string. If not, then we return the string, Unknown Person.

Now, let's take a look at the Message class.