Python OpenCV: Face detection and counting

Introduction

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.

The code

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 [1], 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[0])

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 [2]. 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.

To write the text, we call the putText function, passing as input the image, the text, the bottom left corner where to put the text, the font, the scale of the text, the color and the thickness [3].

cv2.rectangle(image, ((0,image.shape[0] -25)),(270, image.shape[0]), (255,255,255), -1)
cv2.putText(image, "Number of faces detected: " + str(faces.shape[0]), (0,image.shape[0] -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[0])
 
    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[0] -25)),(270, image.shape[0]), (255,255,255), -1)
    cv2.putText(image, "Number of faces detected: " + str(faces.shape[0]), (0,image.shape[0] -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.

Python OpenCV face detection.png
Figure 1 – Result of face detection with OpenCV.

You can check in figure 2 the corresponding output in the console.

Python OpenCV Face detection printed data.
Figure 2 – Output on the console.

Check on figure 3 an example of the program running for a picture with just a person on it.

Face detection in an image with only one person.
Figure 3 – Face detection in an image with only one person.

Final notes

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.

Related content

References

[1] http://docs.opencv.org/3.0-beta/modules/objdetect/doc/cascade_classification.html?highlight=detectmultiscale#cv2.CascadeClassifier.detectMultiScale

[2] http://docs.opencv.org/3.0-beta/modules/imgproc/doc/drawing_functions.html?highlight=rectangle#cv2.rectangle

[3] http://docs.opencv.org/3.0-beta/modules/imgproc/doc/drawing_functions.html?highlight=rectangle#puttext

Technical details

  • Python version: 2.7.8
  • OpenCV version: 3.2.0

Suggested readings

23 thoughts on “Python OpenCV: Face detection and counting”

      1. import numpy as np
        import cv2
        face_cascade = cv2.CascadeClassifier(‘C:\Users\user\Desktop\opencv\build\etc\haarcascades\haarcascade_frontalface_default.xml’)

        image = cv2.imread(‘C:\Users\user\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[0])

        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[0] -25)),(270, image.shape[0]), (255,255,255), -1)
        cv2.putText(image, “Number of faces detected: ” + str(faces.shape[0]), (0,image.shape[0] -10), cv2.FONT_HERSHEY_TRIPLEX, 0.5, (0,0,0), 1)

        cv2.imshow(‘Image with faces’,image)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

        ——————————————————————————————————

        I get the below error

        Traceback (most recent call last):
        File “C:\Users\user\Desktop\face check.py”, line 1, in
        import numpy as np
        ImportError: No module named numpy
        >>>

        ——————————————————————————————-
        Please help

      1. Hi !
        I get the same error and I can give you a little bit more of context. I’m counting in real time the number of faces on my webcam feed. Everything is working well as long as opencv detects faces. If there’s no faces, the program ends with the same error. I believe a tuplet object can’t be empty (or equal 0) but can’t figure out a solution.
        Your help will be more than appreciated
        Cheers from France !

        1. Hi!

          First of all, sorry for the delay and thanks for the detailed explanation 🙂

          I did a little bit of digging and it seems that the detectMultiScale function actually returns an empty tuple when no match is found and a ndarray when there are matches. See link below for more detail:
          https://stackoverflow.com/questions/37761340/python-opencv-face-detection-code-sometimes-raises-tuple-object-has-no-attrib

          At the time, I only tested with images with positive results and didn’t notice that particularity.

          The fix for this is very simple, just check the length of the faces object and if it is equal to zero, just finish the execution (or do another error handling suitable for that case).

          I will update the tutorial to account for this case 🙂

          Best regards,
          Nuno Santos

      1. Hi !
        I get the same error and I can give you a little bit more of context. I’m counting in real time the number of faces on my webcam feed. Everything is working well as long as opencv detects faces. If there’s no faces, the program ends with the same error. I believe a tuplet object can’t be empty (or equal 0) but can’t figure out a solution.
        Your help will be more than appreciated
        Cheers from France !

        1. Hi!
          First of all, sorry for the delay and thanks for the detailed explanation 🙂
          I did a little bit of digging and it seems that the detectMultiScale function actually returns an empty tuple when no match is found and a ndarray when there are matches. See link below for more detail:
          https://stackoverflow.com/questions/37761340/python-opencv-face-detection-code-sometimes-raises-tuple-object-has-no-attrib
          At the time, I only tested with images with positive results and didn’t notice that particularity.
          The fix for this is very simple, just check the length of the faces object and if it is equal to zero, just finish the execution (or do another error handling suitable for that case).
          I will update the tutorial to account for this case 🙂
          Best regards,
          Nuno Santos

  1. Pingback: Python OpenCV: Saving an image to the file system | techtutorialsx

  2. Pingback: Python OpenCV: Saving an image to the file system | techtutorialsx

  3. Hi I am running python 3.5.3 and opencv 4
    In line 10 I get SyntaxError on line 10 (print type(faces)
    Please help

    1. A bit late, but if anyone else has this issue…
      I was getting a syntax error on every print line under 3.8.
      The solution was to surround everything after “print” in parentheses.
      For your example on line 10, it should be “print(type(faces))”

  4. With the code snippet at the bottom, if one were to copy/paste this into their own .py file, the for loop would include all lines that follow the “for” line, as they are all indented the same amount. The result is a repeating of a rectangle being drawn and the image opening again and again as the loop progresses and the image window is closed.

    To remedy this, only the first two lines following the for loop should be indented.
    The final 4 lines should not be indented at all.

Leave a Reply