Bryan Lumbantobing
Bryan Lumbantobing

Reputation: 693

python unbound local error in for loop function

I try to make face detection and recognition using OpenCV that capture image/video from pc cam. But when I run it this error happen

$ python face_recog.py
Traceback (most recent call last):
  File "face_recog.py", line 28, in <module>
    if face_extractor(frame) is not None:
  File "face_recog.py", line 19, in face_extractor
    return cropped_face | ''
UnboundLocalError: local variable 'cropped_face' referenced before assignment

Here the full code for my app. Basically, it imports the library and reads haar cascade object lib in the XML file. and then use face_extractor function to read image/video from cam

import cv2
import numpy as np


face_path = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")

def face_extractor(img):
    #img_umat = cv2.UMat(img)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    face = face_path.detectMultiScale(gray, 1.3, 5)
    
    if face in ():
        return None

        
    for(x, y, w, h) in face:
        cropped_face = img[y: y+h, x: x+w]
        
    return cropped_face 
        
capture = cv2.VideoCapture(0, cv2.CAP_DSHOW) #captureDevice = camera
count = 0

while True:
    
    ret, frame = capture.read()
    
    if face_extractor(frame) is not None:
        
        count = count + 1
        face = cv2.resize(face_extractor(frame), (200, 200))
        #face_umat = cv2.UMat(face)
        face = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
            
        output_path = "C:/Users/bryan/Desktop/test/Cascade/Output_img/user"+str(count)+".jpg"
        cv2.imwrite(output_path, face)
                
        cv2.putText(face, str(count), (50, 50), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 2)
        cv2.imshow("face cropper", face)
            
    else:
        print("FACE NOT FOUND !")
        pass
        
    if cv2.waitKey(1) == 13 or count == 100:
        break
        
capture.release()
cv2.destroyAllWindows()
print("ALL SAMPLES COLLECTED !!")
        
        

Upvotes: 0

Views: 583

Answers (1)

rayryeng
rayryeng

Reputation: 104503

detectMultiScale returns a list of tuples where each element in this list gives you the top left corner of where a face was detected as well as the width and height of the extent of the face. This returns an empty list if you detect no faces, so if face in () makes absolutely no sense. What you are essentially doing is checking to see if face (which is a list) is contained inside an empty tuple which is always going to be False as there are no elements in this tuple. Once that proceeds, you will potentially have an image with no faces, so the second for loop doesn't execute because face would be an empty list. cropped_face is never assigned to anything and thus your error is produced.

As suggested by the comments, check to see if the list is empty by doing if not face instead.

Also supposing you have faces detected, the next for loop will only return the last face in the face list as the cropped_face variable keeps overwriting itself. You may want to return a list of crops if you have more than one face detected instead:

def face_extractor(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    face = face_path.detectMultiScale(gray, 1.3, 5)
    
    if not face:  # Change
        return None

    return [img[y:y+h, x:x+w] for (x, y, w, h) in face] # Change

Minor Note

I would argue that you can get rid of the first if statement because if the face list is empty, the second for loop written above will also return an empty list. Unless you are specifically ensuring that None is returned if there are no faces, you could really just do:

def face_extractor(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    face = face_path.detectMultiScale(gray, 1.3, 5)
    
    return [img[y:y+h, x:x+w] for (x, y, w, h) in face] # Change

The empty list implicitly means no faces were detected which would be more uniform in terms of what is being returned - a list of faces which can be empty vs. a list of faces and None for the edge case if we detect no faces.


Judging from your use case, it appears that only one face is ever going to be detected at one time, so a cheap way to do this is to simply sample into the list and grab the first face. This is not the way I would do this long-term, but to be compatible with the rest of your code let's do that.

def face_extractor(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    face = face_path.detectMultiScale(gray, 1.3, 5)

    if not face:  # Change
        return None

    (x, y, w, h) = face[0]  # Change
    cropped_face = img[y:y+h, x:x+w]
    return cropped_face

I am maintaining that you return None if no faces can be found because now we're just returning a single crop instead of a list.

Upvotes: 1

Related Questions