I am trying to build a face detection application in python using opencv.
Please see below for my code snippets:
# Loading the Haar Cascade Classifier
cascadePath = "/home/work/haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascadePath)
# Dictionary to store image name & number of face detected in it
num_faces_dict = {}
# Iterate over image directory.
# Read the image, convert it in grayscale, detect faces using HaarCascade Classifier
# Draw a rectangle on the image
for img_fname in os.listdir('/home/work/images/caltech_face_dataset/'):
img_path = '/home/work/images/caltech_face_dataset/' + img_fname
im = imread(img_path)
gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)
faces = faceCascade.detectMultiScale(im)
print "Number of faces found in-> ", img_fname, " are ", faces.shape[0]
num_faces_dict[img_fname] = faces.shape[0]
for (x,y,w,h) in faces:
cv2.rectangle(im, (x,y), (x+w,y+h), (255,255,255), 3)
rect_img_path = '/home/work/face_detected/rect_' + img_fname
cv2.imwrite(rect_img_path,im)
This code works fine for most of the images but for some of them it throws an error -
I get error in the line where I print the number of faces. Any help would be appreciated.
The cause of the problem is that detectMultiScale
returns an empty tuple ()
when there's no matches, but a numpy.ndarray
when there are matches.
>>> faces = classifier.detectMultiScale(cv2.imread('face.jpg'))
>>> print(type(faces), faces)
<class 'numpy.ndarray'> [[ 30 150 40 40]]
>>> faces = classifier.detectMultiScale(cv2.imread('wall.jpg'))
>>> print(type(faces), faces)
<class 'tuple'> ()
You might expect that a negative result would be a ndarray of shape (0,4), but that's not the case.
This behaviour and the reasoning behind it is not explained in the documentation, which instead indicates that the return value should be "objects".
OpenCV has a lot of warts like this, and the cryptic error messages doesn't help. One way deal with it is to add logging statements or asserts into your code to check that everything is the type you expected.
It's also very useful to explore how a library works in a repl such as ipython. This is used in Rahul K P's answer.
In this case, you can solve your problem by not using shape
. Python has many data types that are sequences or collections, for example tuple
, list
and dict
. All of these implement the len()
built-in function and you can also loop over them using for x in y
. In contrast shape
is only a property of numpy.ndarray
, and not found in any of the built-in python data types.
Your code should work if you rewrite it to use len(faces)
instead of faces.shape[0]
, since the former works with both tuple and ndarray.
for img_fname in os.listdir('/home/work/images/caltech_face_dataset/'):
img_path = '/home/work/images/caltech_face_dataset/' + img_fname
im = imread(img_path)
gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)
faces = faceCascade.detectMultiScale(gray) # use the grayscale image
print "Number of faces found in-> {} are {}".format(
img_fname, len(faces)) # len() works with both tuple and ndarray
num_faces_dict[img_fname] = len(faces)
# when faces is (), the following loop will never run, so it's safe.
for (x,y,w,h) in faces:
cv2.rectangle(im, (x,y), (x+w,y+h), (255,255,255), 3)
rect_img_path = '/home/work/face_detected/rect_' + img_fname
cv2.imwrite(rect_img_path,im)