# How to Handle Unknown Persons and No Face Detected Cases in face_recognition

> Learn to gracefully handle unknown persons and no face detected scenarios using the ageitgey/face_recognition library. Detect faces and identify unknowns effectively.

- Repository: [Adam Geitgey/face_recognition](https://github.com/ageitgey/face_recognition)
- Tags: how-to-guide
- Published: 2026-03-06

---

**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`](https://github.com/ageitgey/face_recognition/blob/main/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.

```python
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`](https://github.com/ageitgey/face_recognition/blob/main/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.

```python

# 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`](https://github.com/ageitgey/face_recognition/blob/main/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.

```python
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:

```python
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:

```python
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:

```python
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`](https://github.com/ageitgey/face_recognition/blob/main/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`](https://github.com/ageitgey/face_recognition/blob/main/face_recognition/__init__.py) | Public API exports | Re-exports all functions from [`api.py`](https://github.com/ageitgey/face_recognition/blob/main/api.py) |
| [`tests/test_face_recognition.py`](https://github.com/ageitgey/face_recognition/blob/main/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`](https://github.com/ageitgey/face_recognition/blob/main/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 but `compare_faces()` returns all `False` values, 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 `tolerance` parameter in `compare_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`](https://github.com/ageitgey/face_recognition/blob/main/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."