The objective of this post is to demonstrate how to detect and count faces in an image, using OpenCV and Python. In this simple example, we will use a Haar feature-based cascade classifier for the face detection.
As usual, we will start by including the modules needed for the image processing. These are the OpenCV and the Numpy modules.
import numpy as np import cv2
Then we will create an object of class CascadeClassifier. The constructor of this class receives as input the path to the classifier file. In this case, we are going to use the haarcascade_frontalface_default.xml classifier file. This file can be found at the opencv\sources\data\haarcascades directory of the OpenCV uncompressed files we get after downloading. You can also get the file here.
In order to guarantee that the file is correctly accessed, I’m going to use the whole file path. As shown bellow, I have the file in my desktop directory, but you can put it in any directory you like, as long as you pass the correct path.
face_cascade = cv2.CascadeClassifier('C:/Users/N/Desktop/haarcascade_frontalface_default.xml')
Then we will load the image with the imread function of the cv2 module. This function will receive as input the path to the image. We will pass as input the image in which we want to to the face detection.
We will also convert the image to grayscale, in order to apply the classification. To do so, we use the cvtColor function. This will receive as input the original image and as second input the code for the color space conversion. In this case, to convert from RGB to gray, we use the COLOR_BGR2GRAY code.
image = cv2.imread('C:/Users/N/Desktop/Test.jpg') grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
Now, to do the actual face detection, we will call the detectMultiScale method on our face_cascade object. As input we will pass our converted gray image. Additionally, this function can receive some tuning parameters, such as the scaleFactor or the object to detect minSize. You can read more about these parameters here but, for the sake of simplicity, we will just pass as input the image.
This method will return the detected objects (in this case, the faces) as rectangles , so we can easily mark them in the output image.
Just to confirm the type of the output, we will use Python’s type function to check the type of the returned object. It will be a Numpy ndarray.
Update: As explained here in more detail, when no match is found by the detectMultiScale method call, then it will return an empty Python tuple. This was not being taken into account in the original tutorial code, so it would give an error when using images without faces. The fix for this corresponds to checking the length of the returned object and if it is equal to zero, then we end the execution. This condition is added in the final code, at the end of this section.
faces = face_cascade.detectMultiScale(grayImage) print type(faces) print faces
We can also know more about our output by calling the shape method on our array, which will return its dimensions. As we will see, we will get a matrix of N rows and 4 columns, being N the number of faces detected and 4 the dimensions of the rectangle of each face.
So, if we get the N rows, we know how many faces were detected, as indicated bellow.
print faces.shape print "Number of faces detected: " + str(faces.shape)
Now, we will iterate the results and draw the rectangles around the faces in our original image. To do so, we call the rectangle function on the cv2 module. This function receives as input the image, the initial and final vertexes of the rectangle, the RGB color for the rectangle and the thickness of the rectangle . We will do this in a for in loop, to get and draw all the rectangles.
for (x,y,w,h) in faces: cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),1)
We will also put the number of faces detected in the bottom left of our image. First, we will draw another rectangle to put the text on top, because we don’t know the color of the images. So, this guarantees that the text will always be easily readable.
The starting vertex will be at coordinates (x = 0, y = image height – 25) and the end vertex will be at (x= 270, image height). Note that to avoid more complex code, this is hardcoded for the size of the text we will specify.
Note that, to get the height of the image, we can do the same trick we did before with the ndarray. Since the image is a matrix, we call the shape method and get the number of rows.
We will pass the color white and a thickness of -1 to the rectangle function, meaning the rectangle will be filled.
cv2.rectangle(image, ((0,image.shape -25)),(270, image.shape), (255,255,255), -1) cv2.putText(image, "Number of faces detected: " + str(faces.shape), (0,image.shape -10), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0,0,0), 1)
Note that this is just an add on, but if you prefer, for simplicity, you can remove it from the code, since the number of faces will be printed on the console.
Finally, we show the edited image with the rectangles and the text, and wait for a key to be pressed to destroy the windows.
cv2.imshow('Image with faces',image) cv2.waitKey(0) cv2.destroyAllWindows()
You can check the full working code bellow. Note that we add a check for the length of the faces object to avoid getting an error when no face is found since, as mentioned, the detectMultiScale method returns a tuple in that case, which doesn’t have the shape method.
import numpy as np import cv2 face_cascade = cv2.CascadeClassifier('C:/Users/N/Desktop/haarcascade_frontalface_default.xml') image = cv2.imread('C:/Users/N/Desktop/test.jpg') grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(grayImage) print type(faces) if len(faces) == 0: print "No faces found" else: print faces print faces.shape print "Number of faces detected: " + str(faces.shape) for (x,y,w,h) in faces: cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),1) cv2.rectangle(image, ((0,image.shape -25)),(270, image.shape), (255,255,255), -1) cv2.putText(image, "Number of faces detected: " + str(faces.shape), (0,image.shape -10), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0,0,0), 1) cv2.imshow('Image with faces',image) cv2.waitKey(0) cv2.destroyAllWindows()
Testing the code
To test the code, just change the paths for your classifier and picture and run it on IDLE. In my case, I ran it against a picture of Portugal team (original picture obtained from here).
As can be seen in figure 1, the result was pretty accurate, with the faces of the 11 players being recognized.
You can check in figure 2 the corresponding output in the console.
Check on figure 3 an example of the program running for a picture with just a person on it.
As can be seen by the examples provided, the classifier works pretty well. Nevertheless, don’t expect an 100% accuracy. There was no tuning on the parameters and the images were relatively simple. Naturally, there will be use cases where false positives and false negatives will appear, which is normal.
- Python version: 2.7.8
- OpenCV version: 3.2.0
- Python dlib: face detection
- Python dlib: face landmarks estimation
- MediaPipe: hand landmarks estimation
- ESP32 Camera: face detection