1

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.

Abhilash Awasthi
  • 782
  • 5
  • 22
  • 2
    Don't post images and links to your problems, post the code and the issues **here** in your question. – SierraOscar Jun 11 '16 at 07:58
  • I have posted the code..didn't knew that posting images & links to the problems is not allowed. Kindly don't downvote the question. – Abhilash Awasthi Jun 11 '16 at 08:04
  • @Maddy It's not that it isn't allowed - it's that I people don't log in to Stack Overflow to then go and follow links to other sites to see what your problem is. It takes a few seconds to copy/paste the code into your question so if you haven't put that effort into asking - people won't put any effort into answering. – SierraOscar Jun 11 '16 at 08:28
  • Thanks @Macro Man . I got it. Will keep in mind your advice next time when I post any question. – Abhilash Awasthi Jun 11 '16 at 08:31
  • I also edited your post to correct the formatting - you can learn how to use the markdown syntax [by visiting this page](http://stackoverflow.com/editing-help) – SierraOscar Jun 11 '16 at 08:42
  • Good. Thanks for the link & edits @ Macro Man . Will look into it. Thanks to @Haken Lid also for editing the question. – Abhilash Awasthi Jun 11 '16 at 08:44
  • [No thanks needed](http://stackoverflow.com/help/someone-answers) – Håken Lid Jun 11 '16 at 09:24

3 Answers3

1

From your error understand that you are trying to read the shape. But shape is the attribute of numpy.ndarray. You are trying to read the shape from the result of face detection. But that will only return the position only. Look at the types. Here img is an image and faces is the result of face detection. I hope you got the problem.

Updated with full code. For more clarification

In [1]: import cv2
In [2]: cap = cv2.VideoCapture(0)
In [3]: ret,img = cap.read()
In [4]: cascadePath = "/home/bikz05/Desktop/SNA_work/opencv-2.4.9/data/haarcascades/haarcascade_frontalface_default.xml"
In [5]: faceCascade = cv2.CascadeClassifier(cascadePath) 
In [6]: faces = faceCascade.detectMultiScale(img)
In [7]: type(img)
Out[1]: numpy.ndarray
In [8]: type(faces)
Out[2]: tuple

Look at the diffrence.

In [9]: img.shape
Out[3]: (480, 640, 3)
In [10]: faces.shape
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-40-392225a0e11a> in <module>()
----> 1 faces.shape
AttributeError: 'tuple' object has no attribute 'shape'

If you want the number of faces. It's in the form of list of tuple. You can find the number of faces using len like len(faces)

Rahul K P
  • 15,740
  • 4
  • 35
  • 52
  • 1
    But why the code works fine for most of the images & throws error on some images only. – Abhilash Awasthi Jun 11 '16 at 08:15
  • http://docs.scipy.org/doc/numpy-1.10.1/reference/arrays.ndarray.html Look at the documentation of `numpy.ndarry`. – Rahul K P Jun 11 '16 at 08:18
  • @RahulKP: I see that you are using opencv 2.4.9. I use version 3.0 and it turns out `DetectMultiScale` will return either an empty `tuple` (no matches) or `ndarray` (matches found). Is it the same in version 2.4? I've provided more details in [my answer](http://stackoverflow.com/a/37761914/1977847). – Håken Lid Jun 11 '16 at 09:20
  • @HåkenLid Thank you for the valuable information. I given a vote for you. – Rahul K P Jun 11 '16 at 10:16
1

To get the number of faces it should be:

print "Number of faces found in-> ", img_fname, " are ", len(faces).

I would also recommend that to convert image to gray scale you should write:

gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) instead of gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY) as color images are loaded by openCV in BGR mode.

shiva
  • 2,535
  • 2
  • 18
  • 32
  • You are right about `BGR` vs `RGB`. But in this case it shouldn't make any difference, since the grayscale conversion just averages the three channels. – Håken Lid Jun 11 '16 at 09:51
  • It's not correct to say that `detectMultiScale` returns a list. It returns a ndarray or an empty tuple. – Håken Lid Jun 11 '16 at 09:52
1

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)
Community
  • 1
  • 1
Håken Lid
  • 22,318
  • 9
  • 52
  • 67