Maddy Maddy - 5 months ago 42
Python Question

OpenCV face detection code sometimes raises `'tuple' object has no attribute 'shape'`

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 -


AttributeError: 'tuple' object has no attribute 'shape'
enter image description here


I get error in the line where I print the number of faces. Any help would be appreciated.

Answer

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)
Comments