OMG, we found a face!

I’m wearing the black dress 🖤

I’m pretty new to this whole python developer thing. I completed a BSc IT degree (alongside my BEng Electrical Engineering degree) over 10 years ago and let's just say the only real coding I’ve done since then is a ton of SQL and bits and pieces of HTML while trying to fix my food blog.

I’ve worked in the telecommunications industry since leaving varsity, and after realizing that jobs are not exactly easy to find, I started learning Python with a course on Udemy (Complete Python 3 Masterclass Journey by Jose Portilla — such a cool course where you are a secret agent solving missions using Python). This led me to learn a bit about data science and machine learning, and I decided that looks cool and a bit more exciting than creating PowerPoint presentations all day. I’M GOING TO BE A DATA SCIENTIST/MACHINE LEARNING ENGINEER. I quit my job (hashtag millennial) and decided to dedicate the next few months to learn more things related to my newly chosen career path.

So here we are. My first non-food blog post. My dormant food blog was quite active a few years ago if you want to read reviews of Johannesburg restaurants or a recipe for homemade Maltesers, check it out http://jesska.co.za.

Face Detection in images is pretty magical. I mean you and I can instantly recognize a face in an image, but to a computer, an image is just a bunch of pixels in different colours. Through the wizardry of machine learning, you can now teach a computer to recognize what combination of pixels in an image could possibly be a face.

Thanks to this Face Recognition with Python tutorial on Real Python. I learnt how to use OpenCV to detect faces in different images.

I think the hardest part of the tutorial was getting OpenCV installed on my Mac. Following this https://medium.com/@nuwanprabhath/installing-opencv-in-macos-high-sierra-for-python-3-89c79f0a246a and ignoring all the virtualenv stuff (it installs but then won’t make a virtual environment and I got annoyed, so that’s not happening) it installed, but I also followed another tutorial to install it with anaconda, so right now, I have no idea which one actually worked, but I can import cv2 into python, which means something somehow worked, and I won’t question it just yet. Told you I am new to this. 🤣

OpenCV installed, git repository downloaded, I tried to run the script, and boom, error. FML. Turns out you import cv2, which is actually version 3 and this has different variables and methods. Like just work the first time, code from stranger on the internet.

Anyway, lets step through the code(this is pretty much just for me, so when I look at this in a month I understand wtf is going on). You can download it from github. If it fails to run on your machine, my bad.

I added some eye detection into the original face detection script, it works in a similar way, just using different Classifier file. Some mouths are detected as eyes, but it works pretty well otherwise.

Step 1: Importing dependencies

import cv2
import sys

This will import the OpenCV modules used to detect the faces and eyes, as well as the system module so we can get arguments from the command line when running the script.

Step 2: Defining the image path

# Get the path of the image that the user supplied
imagePath = sys.argv[1]

When you run the script in command line using,

$ python3 detect_face_and_eyes.py image_file.jpg

argv is an array (or a list) used to get the arguments supplied after the word python3. So argv[0] is the name of the script, and in this instance, argv[1] is the name of the image we would like the script to run on. This is basically saying, using python3 run detect_face_and_eyes.py on the image_file.jpg.

Step 3: Defining the paths of the image classifiers

# Define the path to the image classifier files
faceCascPath = “haarcascade_frontalface_default.xml”
eyeCascPath = “haarcascade_eye.xml”

So the image classifiers are xml files written by really smart people that basically tell the computer what to look for in an image to determine whether or not it is a face (or eyes, or cat etc). OpenCV uses cascades, which means it takes a block of pixels, runs a basic test to determine if it’s a face or not, if it is, then it runs another more detailed test, and repeats the process a few times, where it can determine, with a certain probability, that this block of pixels is indeed a face.

You can download the different classifier files to detect different images here https://github.com/opencv/opencv/tree/master/data/haarcascades.

Step 4: Create the cascades using the classifiers

# Create the cascade
faceCascade = cv2.CascadeClassifier(faceCascPath)
eyeCascade = cv2.CascadeClassifier(eyeCascPath)

This creates new Cascade variables using the classifier files defined previously. This will tell OpenCV which cascades to use on the image when we want to do object detection. There are two variables here because we want to look for both faces and eyes in the image.

Step 5: Reading the image with OpenCV

# Read the image
image = cv2.imread(imagePath)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

This uses the imread function of OpenCV to read in the image. The next line then converts the colour of that image to a grayscale image, which makes it easier to do the object detection.

Step 6: Detecting the faces

# Detect faces in the image
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(20, 20)
)
print(“Found {0} faces!”.format(len(faces)))

The detectMultiScale function takes in the following arguments:

  • The image that you would like to detect objects on (gray in this instance)
  • The scaleFactor compensates for objects that may be different sizes. If you run the code and there are things in the image being detected as faces that aren’t faces, make this number a higher value. If there are faces that aren’t being detected, make this number a lower value. The value of this variable, always has to be greater than 1.
  • minNeighbors helps to minimize false positives. By setting it to 0, you will find everything that remotely looks like a face, by increasing it, you reduce the risk of finding false positives. (great explanation over here https://stackoverflow.com/questions/22249579/opencv-detectmultiscale-minneighbors-parameter)
  • minSize determines the size of the sliding window that moves over the image to detect the objects within it.

This function returns an array of lists of integers defining the rectangular area on the image where each of the objects was detected. The number of faces detected is just the length of the array.

array([[168, 70, 66, 66],
[ 56, 55, 75, 75],
[267, 69, 87, 87],
[354, 78, 74, 74]], dtype=int32)

Step 7: Finding the eyes

# Draw a rectangle around the faces
for (x, y, w, h) in faces:
cv2.rectangle(image, (x, y), (x+w, y+h), (255, 0, 0), 2)
    #For each face, locate the eyes and draw a rectangle around them
faceGray = gray[y:y+h, x:x+w]
faceColor = image[y:y+h, x:x+w]
eyes = eyeCascade.detectMultiScale(
faceGray,
scaleFactor=1.009,
minNeighbors=8 )
    for (ex,ey,ew,eh) in eyes:
cv2.rectangle(faceColor,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

We first draw a rectangle around each face by looping through the lists in the face array. Each list has 4 numbers, the x and y coordinate and the width and height of the rectangle. The rectangle function takes in the image you would like to draw the rectangles on, the start and end points of the rectangle, the color (in BGR) and the thickness of the line.

Then we make smaller images of each face in both gray and colour, and run the algorithm to detect eyes in each of the smaller face images. This works exactly the same way as the face detection done in the previous step, it just uses a different classifier to do the detection of eyes in the image. Again, rectangles are drawn over each eye that was detected in the image.

Step 8: Displaying the results

cv2.imshow(“All around us are familiar faces”, image)
cv2.waitKey(0)
cv2.destroyAllWindows()

The imshow function shows the image with all the faces and eyes that have had rectangles drawn over them, the first argument is just the name you would like to display at the top of the window that the image opens in.

The waitKey(0) function waits for the user to push any key while viewing the image, and then closes it. If you are using a Mac, you have to actually click on the image, then press a key in order for the window to close.

The destoryAllWindows function is just a bit of housekeeping.

There you have it. A couple of lines of code, and you can perform face and eye detection on many images.

So what are the results?

$ python3 detect_face_and_eyes.py friends.jpg
Yay, everything detected correctly.
$ python3 detect_face_and_eyes.py spice_girls.png
Ok, I guess their mouths are a little eye like…
$ python3 detect_face_and_eyes.py himym.jpg
Not bad, mouth-eye still a problem, but hey, I’m impressed.

If you try it out and get some weird rectangles, play around with the scaleFactor and minNeighbours values in the detection functions until you find something that works.