How to Handle Unknown Persons and No Face Detected Cases in face_recognition
Use face_locations() to detect if any face exists, then check if compare_faces() returns all False values to identify unknown persons.
The face_recognition library provides a straightforward pipeline for detecting and identifying faces, but handling edge cases—specifically when no face is recognized or when an unknown person appears—requires understanding how the API handles empty results and non-matching encodings. This guide explains how to distinguish between "no face detected" and "face detected but unknown" using the actual implementation in ageitgey/face_recognition.
Understanding the Three-Stage Recognition Pipeline
The library processes images through three distinct stages, each with specific return behaviors for empty or unknown cases.
Stage 1: Face Detection with face_locations()
The face_locations() function scans an image and returns a list of bounding-box tuples (top, right, bottom, left). When no faces are present, it returns an empty list [].
According to the implementation in face_recognition/api.py (lines 108-122), the function uses the HOG or CNN model to generate candidate boxes, then filters them based on the number_of_times_to_upsample parameter. If the underlying detector finds no candidates, the list comprehension returns empty.
import face_recognition
image = face_recognition.load_image_file("crowd.jpg")
face_locations = face_recognition.face_locations(image)
if not face_locations:
print("No faces detected in image")
# Handle "no person" case here
Stage 2: Feature Extraction with face_encodings()
For each bounding box returned by face_locations(), face_encodings() computes a 128-dimensional face embedding. If passed an empty list of locations, it returns an empty list of encodings.
The implementation in face_recognition/api.py (lines 203-215) iterates over the provided locations, extracts the face image chip, and runs it through the pre-trained dlib face recognition model. No locations means no iterations, resulting in an empty return.
# If face_locations is empty, this returns []
face_encodings = face_recognition.face_encodings(image, face_locations)
Stage 3: Identity Matching with compare_faces()
The compare_faces() function measures Euclidean distance between a candidate encoding and a list of known encodings. It returns a list of boolean values—True for matches within the tolerance threshold, False for non-matches.
According to face_recognition/api.py (lines 217-226), if the known_face_encodings list is empty, the function returns an empty list []. If encodings exist but none match, it returns a list of False values.
known_encodings = [...] # Your database of known faces
unknown_encoding = face_encodings[0]
matches = face_recognition.compare_faces(known_encodings, unknown_encoding)
if not any(matches):
print("Unknown person detected")
Distinguishing Between "No Face" and "Unknown Person"
Understanding the return values at each stage allows you to implement distinct logic for three distinct scenarios:
| Scenario | face_locations |
face_encodings |
compare_faces |
Handling Strategy |
|---|---|---|---|---|
| No face in image | [] |
[] |
N/A | Log "no detection" and skip processing. |
| Face detected, unknown identity | Non-empty | Non-empty | All False |
Log "unknown person" or trigger enrollment workflow. |
| Face detected, known identity | Non-empty | Non-empty | At least one True |
Return identified name and confidence. |
Practical Implementation: Handling Unknown Persons in Code
Basic Detection Pattern
This pattern checks for empty results at each stage to differentiate between "no face" and "unknown person" cases:
import face_recognition
def process_image(image_path, known_encodings, known_names):
image = face_recognition.load_image_file(image_path)
# Check for no faces
locations = face_recognition.face_locations(image)
if not locations:
return "no_face_detected"
# Get encodings for detected faces
encodings = face_recognition.face_encodings(image, locations)
# Check each face against known database
for encoding in encodings:
matches = face_recognition.compare_faces(known_encodings, encoding)
if any(matches):
match_index = matches.index(True)
return f"known:{known_names[match_index]}"
else:
return "unknown_person"
return "no_face_detected"
Reusable Wrapper Function
For production applications, wrap the logic into a helper that returns None for both "no face" and "unknown person" cases, or distinguishes them via separate return values:
import os
import face_recognition
def identify_person(image_path, known_dir, tolerance=0.6):
"""
Returns the name of the recognized person, 'unknown', or None if no face detected.
"""
img = face_recognition.load_image_file(image_path)
# Stage 1: Detect faces
locations = face_recognition.face_locations(img)
if not locations:
return None # No face found
# Stage 2: Encode the first detected face
unknown_enc = face_recognition.face_encodings(img, locations)[0]
# Build known faces database
known_encodings, known_names = [], []
for fname in os.listdir(known_dir):
if not fname.lower().endswith((".jpg", ".png", ".jpeg")):
continue
name = os.path.splitext(fname)[0]
known_img = face_recognition.load_image_file(os.path.join(known_dir, fname))
known_encodings.append(face_recognition.face_encodings(known_img)[0])
known_names.append(name)
# Stage 3: Compare
results = face_recognition.compare_faces(known_encodings, unknown_enc, tolerance=tolerance)
if any(results):
return known_names[results.index(True)]
else:
return "unknown"
Integration Example
Use the wrapper in a batch processing script to handle mixed scenarios:
known_folder = "known_people"
image_to_check = "security_camera_frame.jpg"
result = identify_person(image_to_check, known_folder)
if result is None:
print("⚠️ No face detected in frame")
elif result == "unknown":
print("❓ Unknown person detected – potential security alert")
else:
print(f"✅ Recognized: {result}")
Key Source Files and Implementation Details
Understanding the underlying implementation helps debug edge cases:
| File | Purpose | Key Functions |
|---|---|---|
face_recognition/api.py |
Core API implementation | face_locations() (lines 108-122), face_encodings() (lines 203-215), compare_faces() (lines 217-226) |
face_recognition/__init__.py |
Public API exports | Re-exports all functions from api.py |
tests/test_face_recognition.py |
Unit tests | test_compare_faces_empty_lists verifies behavior when known faces list is empty |
The compare_faces() function specifically handles the empty known faces case by returning an empty list rather than raising an exception, as verified in the test suite at tests/test_face_recognition.py (lines 30-47).
Summary
- Check
face_locations()first: An empty list indicates no face is present in the image, allowing early exit before encoding computation. - Distinguish unknown persons: When
face_locations()finds faces butcompare_faces()returns allFalsevalues, the subject is an unknown person rather than a detection failure. - Handle empty databases: If the known faces list is empty,
compare_faces()returns an empty list[], which evaluates as falsy but requires explicit checking to distinguish from "no matches found". - Use tolerance thresholds: Adjust the
toleranceparameter incompare_faces()(default 0.6) to control strictness for unknown person classification.
Frequently Asked Questions
What happens when face_locations returns an empty list?
When face_locations() returns an empty list [], the library detected no human faces in the provided image. This occurs when the image contains no faces, faces are too small or obscured, or the detection model (HOG or CNN) fails to find valid facial features. You should treat this as a "no person present" scenario and skip the encoding and comparison steps.
How do I set a tolerance threshold for unknown face detection?
The compare_faces() function accepts a tolerance parameter (default 0.6) that controls the strictness of the Euclidean distance threshold between face encodings. Lower values (e.g., 0.4) make the matching stricter, reducing false positives but potentially classifying known persons as unknown. Higher values (e.g., 0.8) are more lenient but may incorrectly classify strangers as known individuals. Adjust this based on your security requirements and testing with your specific dataset.
Can face_recognition distinguish between multiple unknown people in one image?
The library itself does not assign persistent identities to unknown persons—it only reports that a given face does not match the known database. However, you can distinguish between different unknown individuals within the same image by comparing the face encodings against each other using face_distance(). If two unknown faces have significantly different encodings (high Euclidean distance), they belong to different individuals. To track the same unknown person across multiple images or video frames, you would need to implement your own clustering or tracking logic using the 128-dimensional encodings as feature vectors.
Why does compare_faces return an empty list instead of False values?
When you pass an empty list as the known_face_encodings argument to compare_faces(), the function returns an empty list [] rather than a list of False values. This behavior, verified in tests/test_face_recognition.py (lines 30-47), occurs because there are no known faces to compare against, making the concept of "match" or "no match" undefined. You should explicitly check if your known faces database is empty before calling compare_faces(), or check if the result list is empty, to avoid misinterpreting an empty database as "no matches found."
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →