# face_recognition User Guide

## 1. Quick Start

**face_recognition** is the world's simplest Python library for recognizing and manipulating faces. Built on top of dlib's state-of-the-art deep learning models, it provides both a Python API and command-line tools for face detection, landmark identification, and face recognition with 99.38% accuracy on the Labeled Faces in the Wild benchmark.

**Key features:**

- Detect faces in images using HOG or CNN models
- Identify 68 facial landmarks (eyes, nose, mouth, chin, eyebrows)
- Generate 128-dimensional face encodings for comparison
- Compare faces with configurable tolerance thresholds
- Process images from files, webcams, or video streams
- Two CLI tools for scripted/batch workflows
- GPU acceleration via CUDA when dlib is compiled with CUDA support
- Multi-core parallel processing for batch image operations

**Prerequisites:** Python 3.5+ and a C++ compiler (for dlib)

**Install:**

```bash
pip install face_recognition
```

**Minimal example:**

```python
import face_recognition

# Load images
known_image = face_recognition.load_image_file("known_person.jpg")
unknown_image = face_recognition.load_image_file("unknown_person.jpg")

# Encode faces
known_encoding = face_recognition.face_encodings(known_image)[0]
unknown_encoding = face_recognition.face_encodings(unknown_image)[0]

# Compare
results = face_recognition.compare_faces([known_encoding], unknown_encoding)
print(results)
```

```text
[True]
```

> [!TIP]
> For the fastest path to a working demo, use the `face_recognition` CLI tool — no Python code needed. See the Command-Line Tools section below.

**What to try next:**

- Use the CLI tools for quick face detection and recognition
- Explore facial landmarks for feature extraction
- Build a web service with the Flask example
- Set up real-time webcam recognition

## 2. Installation

### System Requirements

| Requirement | Minimum | Recommended |
|---|---|---|
| Python | 3.5 | 3.8+ |
| OS | Linux, macOS, Windows | Ubuntu/Debian Linux |
| RAM | 2 GB | 4 GB+ |
| C++ Compiler | Required (for dlib) | gcc/g++ on Linux, Xcode on macOS |
| GPU (optional) | NVIDIA with CUDA | For CNN model and batch processing |

### Install Methods

=== "pip (recommended)"
    ```bash
    pip install face_recognition
    ```

=== "Docker (CPU)"
    ```bash
    docker run -it --rm -v /path/to/images:/images ageitgey/face_recognition
    ```

=== "Docker (GPU)"
    ```bash
    docker run -it --rm --runtime=nvidia -v /path/to/images:/images ageitgey/face_recognition:gpu
    ```

=== "From source"
    ```bash
    git clone https://github.com/ageitgey/face_recognition.git
    cd face_recognition
    pip install -r requirements.txt
    python setup.py install
    ```

> [!IMPORTANT]
> The `dlib` dependency requires a C++ compiler. On Ubuntu/Debian, install build tools first: `sudo apt-get install build-essential cmake`. On macOS, install Xcode command-line tools: `xcode-select --install`. On Windows, install Visual Studio Build Tools with C++ workload.

### Dependencies

| Package | Version | Purpose |
|---|---|---|
| `face_recognition_models` | >=0.3.0 | Pre-trained dlib model files (face detection, landmarks, encoding) |
| `dlib` | >=19.7 | Core C++ machine learning library |
| `Click` | >=6.0 | Command-line interface framework |
| `numpy` | any | Array operations for image data and encodings |
| `Pillow` | any | Image loading and format conversion |
| `scipy` | >=0.17.0 | Scientific computing (used in some examples) |

### Post-Install Verification

```bash
face_detection --help
```

```text
Usage: face_detection [OPTIONS] IMAGE_TO_CHECK

Options:
  --cpus INTEGER     number of CPU cores to use in parallel. -1 means "use
                     all in system"
  --model TEXT       Which face detection model to use. Options are "hog" or
                     "cnn".
  --upsample INTEGER How many times to upsample the image looking for faces.
                     Higher numbers find smaller faces.
  --help             Show this message and exit.
```

```bash
python -c "import face_recognition; print(face_recognition.__version__)"
```

```text
1.4.0
```

### Install Troubleshooting

### Error: `Please install face_recognition_models`

**Cause:** The `face_recognition_models` package was not installed or failed to install. This package contains the pre-trained model data files required by dlib.

**Solution:**
1. Install the models package directly from GitHub:
   ```bash
   pip install git+https://github.com/ageitgey/face_recognition_models
   ```
2. Verify installation:
   ```bash
   python -c "import face_recognition_models; print('OK')"
   ```

**Prevention:** Always install via `pip install face_recognition`, which pulls in models as a dependency automatically.

> [!WARNING]
> Issues #1634 and #608 report this error frequently. If `pip install face_recognition` does not install the models package, install it manually from the GitHub URL above.

### Error: `RuntimeError: Unsupported image type, must be 8bit gray or RGB image`

**Cause:** The image has an unsupported pixel format — typically a 32-bit RGBA PNG, a CMYK JPEG, or a 16-bit image. The dlib library only accepts 8-bit grayscale or 8-bit RGB images.

**Solution:**
1. Convert the image to RGB before processing:
   ```python
   image = face_recognition.load_image_file("photo.png", mode='RGB')
   ```
2. The `load_image_file()` function converts to RGB by default (`mode='RGB'`). If you're loading images manually with PIL or OpenCV, ensure you convert:
   ```python
   from PIL import Image
   import numpy as np
   img = np.array(Image.open("photo.png").convert("RGB"))
   ```

**Prevention:** Always use `face_recognition.load_image_file()` instead of loading images manually. It handles format conversion automatically. Issues #1573, #1575, #21.

### Error: `dlib compilation fails`

**Cause:** Missing C++ compiler or CMake.

**Solution:**

=== "Ubuntu/Debian"
    ```bash
    sudo apt-get install build-essential cmake
    pip install dlib
    ```

=== "macOS"
    ```bash
    xcode-select --install
    brew install cmake
    pip install dlib
    ```

=== "Windows"
    ```bash
    pip install cmake
    pip install dlib
    ```

**Prevention:** Install build tools before attempting to install `face_recognition`.

### Error: `cv2.error: (-215:Assertion failed) !_src.empty() in function 'cv::cvtColor'`

**Cause:** OpenCV received an empty frame from the video capture device. This happens when the webcam is not connected, is in use by another application, or the video file path is incorrect.

**Solution:**
1. Check that the webcam is connected and not in use by another application
2. Verify the video file path exists
3. Add a frame check in your code:
   ```python
   ret, frame = video_capture.read()
   if not ret:
       print("Failed to capture frame")
       break
   ```

**Prevention:** Always check the return value of `video_capture.read()` before processing the frame. Issue #933.

### Error: `pkg_resources` deprecation warning

**Cause:** The `pkg_resources` package (used internally by `face_recognition_models`) is scheduled for removal. Issue #1653.

**Solution:**
```bash
pip install setuptools
```

**Prevention:** Keep `setuptools` installed and up to date.

## 3. Core Usage

### Loading Images

Every workflow starts with loading an image into a numpy array:

```python
import face_recognition

image = face_recognition.load_image_file("photo.jpg")
```

The `load_image_file()` function accepts file paths or file-like objects and returns a numpy array.

| Parameter | Type | Default | Description |
|---|---|---|---|
| `file` | str or file object | — | Image file path or file object |
| `mode` | str | `'RGB'` | Color mode: `'RGB'` (3-channel color) or `'L'` (grayscale) |

```python
# Load as grayscale
gray_image = face_recognition.load_image_file("photo.jpg", mode='L')

# Load from file object
with open("photo.jpg", "rb") as f:
    image = face_recognition.load_image_file(f)
```

> [!NOTE]
> Supported image formats include JPEG, PNG, BMP, GIF, and any other format supported by Pillow. 32-bit images are automatically converted to 8-bit RGB when using the default `mode='RGB'`.

### Detecting Faces

Locate all faces in an image and get their bounding box coordinates:

```python
image = face_recognition.load_image_file("group_photo.jpg")
face_locations = face_recognition.face_locations(image)

for top, right, bottom, left in face_locations:
    print(f"Face found at: Top={top}, Right={right}, Bottom={bottom}, Left={left}")
```

```text
Face found at: Top=142, Right=617, Bottom=409, Left=349
```

| Parameter | Type | Default | Description |
|---|---|---|---|
| `img` | numpy array | — | The image to search |
| `number_of_times_to_upsample` | int | `1` | How many times to upsample the image. Higher values find smaller faces but are slower. |
| `model` | str | `"hog"` | Detection model: `"hog"` (faster, CPU) or `"cnn"` (more accurate, GPU-accelerated) |

Face locations are returned as tuples in **(top, right, bottom, left)** order.

```python
# Use CNN model for higher accuracy (requires GPU for reasonable speed)
face_locations = face_recognition.face_locations(image, model="cnn")

# Find smaller faces by upsampling
face_locations = face_recognition.face_locations(image, number_of_times_to_upsample=2)
```

> [!TIP]
> The HOG model is the right choice for most use cases — it runs well on CPU and is fast enough for real-time webcam processing. Use CNN only if you need higher accuracy and have a CUDA-capable GPU.

### Batch Face Detection (GPU)

Process multiple images in a single GPU batch for significantly faster throughput:

```python
images = [face_recognition.load_image_file(f) for f in ["img1.jpg", "img2.jpg", "img3.jpg"]]
batch_locations = face_recognition.batch_face_locations(images, number_of_times_to_upsample=0)

for i, face_locations in enumerate(batch_locations):
    print(f"Image {i}: found {len(face_locations)} face(s)")
```

| Parameter | Type | Default | Description |
|---|---|---|---|
| `images` | list of numpy arrays | — | List of images to process |
| `number_of_times_to_upsample` | int | `1` | Upsample count for face detection |
| `batch_size` | int | `128` | Number of images per GPU batch |

> [!IMPORTANT]
> `batch_face_locations()` always uses the CNN model internally. It only provides a speed benefit when using a CUDA-capable GPU. On CPU-only systems, use `face_locations()` in a loop instead.

### Finding Facial Landmarks

Identify facial features (eyes, nose, mouth, chin, eyebrows) for each detected face:

```python
image = face_recognition.load_image_file("photo.jpg")
landmarks_list = face_recognition.face_landmarks(image)

for face_landmarks in landmarks_list:
    for feature_name, points in face_landmarks.items():
        print(f"{feature_name}: {len(points)} points")
```

```text
chin: 17 points
left_eyebrow: 5 points
right_eyebrow: 5 points
nose_bridge: 4 points
nose_tip: 5 points
left_eye: 6 points
right_eye: 6 points
top_lip: 12 points
bottom_lip: 12 points
```

| Parameter | Type | Default | Description |
|---|---|---|---|
| `face_image` | numpy array | — | The image to search |
| `face_locations` | list or None | `None` | Pre-computed face locations (skips detection if provided) |
| `model` | str | `"large"` | Landmark model: `"large"` (68 points, 9 features) or `"small"` (5 points, 3 features) |

**Large model features (68 landmarks):**

| Feature | Points |
|---|---|
| `chin` | 17 |
| `left_eyebrow` | 5 |
| `right_eyebrow` | 5 |
| `nose_bridge` | 4 |
| `nose_tip` | 5 |
| `left_eye` | 6 |
| `right_eye` | 6 |
| `top_lip` | 12 |
| `bottom_lip` | 12 |

**Small model features (5 landmarks):**

| Feature | Points |
|---|---|
| `nose_tip` | 1 |
| `left_eye` | 2 |
| `right_eye` | 2 |

> [!WARNING]
> Passing an invalid model name (anything other than `"large"` or `"small"`) raises `ValueError: Invalid landmarks model type. Supported models are ['small', 'large'].`

### Encoding Faces

Generate a 128-dimensional encoding vector for each face, used for comparison:

```python
image = face_recognition.load_image_file("person.jpg")
encodings = face_recognition.face_encodings(image)

print(f"Found {len(encodings)} face(s)")
print(f"Encoding dimensions: {len(encodings[0])}")
```

```text
Found 1 face(s)
Encoding dimensions: 128
```

| Parameter | Type | Default | Description |
|---|---|---|---|
| `face_image` | numpy array | — | Image containing faces |
| `known_face_locations` | list or None | `None` | Pre-computed face locations (skips detection if provided) |
| `num_jitters` | int | `1` | Times to re-sample each face for encoding. Higher is more accurate but slower (100 = 100x slower). |
| `model` | str | `"small"` | Landmark model used internally: `"large"` or `"small"` |

```python
# Higher accuracy encoding (slower)
encodings = face_recognition.face_encodings(image, num_jitters=10)

# Skip face detection by providing known locations
locations = face_recognition.face_locations(image)
encodings = face_recognition.face_encodings(image, known_face_locations=locations)
```

### Comparing Faces

Check if faces match by comparing their encodings:

```python
known_image = face_recognition.load_image_file("known_person.jpg")
unknown_image = face_recognition.load_image_file("unknown_person.jpg")

known_encoding = face_recognition.face_encodings(known_image)[0]
unknown_encoding = face_recognition.face_encodings(unknown_image)[0]

results = face_recognition.compare_faces([known_encoding], unknown_encoding)
print(f"Match: {results[0]}")
```

```text
Match: True
```

| Parameter | Type | Default | Description |
|---|---|---|---|
| `known_face_encodings` | list | — | List of known face encodings to compare against |
| `face_encoding_to_check` | numpy array | — | The face encoding to check |
| `tolerance` | float | `0.6` | Distance threshold for a match. Lower is stricter. |

### Measuring Face Distance

Get the euclidean distance between face encodings for fine-grained similarity:

```python
distances = face_recognition.face_distance(known_encodings, unknown_encoding)

for i, distance in enumerate(distances):
    print(f"Distance to person {i}: {distance:.4f}")
    print(f"  Match at 0.6 threshold: {distance <= 0.6}")
    print(f"  Match at 0.5 threshold: {distance <= 0.5}")
```

```text
Distance to person 0: 0.3142
  Match at 0.6 threshold: True
  Match at 0.5 threshold: True
Distance to person 1: 0.7856
  Match at 0.6 threshold: False
  Match at 0.5 threshold: False
```

| Parameter | Type | Default | Description |
|---|---|---|---|
| `face_encodings` | list | — | List of face encodings to compare |
| `face_to_compare` | numpy array | — | The face encoding to measure against |

> [!TIP]
> The default tolerance of `0.6` works well for most cases. Lower it to `0.5` to reduce false positives at the cost of more false negatives. Use `face_distance()` to find the optimal threshold for your dataset.

### Command-Line Tools

Two CLI commands are installed with the package:

#### `face_recognition` — Identify Faces

Compares faces in images against a folder of known people:

```bash
face_recognition ./known_people/ ./unknown_pictures/
```

```text
unknown_pictures/photo1.jpg,Barack Obama
unknown_pictures/photo2.jpg,unknown_person
unknown_pictures/photo3.jpg,Joe Biden
```

The known people folder should contain one image per person, with the filename as the person's name (e.g., `Barack_Obama.jpg`).

| Argument/Option | Default | Description |
|---|---|---|
| `known_people_folder` | — | Directory of known face images (filename = person name) |
| `image_to_check` | — | Image file or directory to check |
| `--cpus` | `1` | CPU cores for parallel processing. `-1` = all cores. |
| `--tolerance` | `0.6` | Match tolerance. Lower = stricter. |
| `--show-distance` | `False` | Include face distance in output. |

```bash
# Process a folder with all CPU cores
face_recognition --cpus -1 ./known_people/ ./photo_folder/

# Stricter matching with distance output
face_recognition --tolerance 0.5 --show-distance 1 ./known_people/ ./test.jpg
```

```text
test.jpg,Barack Obama,0.3142
```

> [!NOTE]
> Images larger than 1600px on either dimension are automatically scaled down to 1600x1600 for faster processing. Recognition still works on the scaled image.

#### `face_detection` — Locate Faces

Finds faces in images and outputs bounding box coordinates:

```bash
face_detection ./photo.jpg
```

```text
photo.jpg,142,617,409,349
```

Output format: `filename,top,right,bottom,left`

| Argument/Option | Default | Description |
|---|---|---|
| `image_to_check` | — | Image file or directory to scan |
| `--cpus` | `1` | CPU cores for parallel processing. `-1` = all cores. |
| `--model` | `"hog"` | Detection model: `"hog"` or `"cnn"` |
| `--upsample` | `0` | Times to upsample for smaller face detection |

```bash
# Use CNN model for higher accuracy
face_detection --model cnn ./photos/

# Find small faces with upsampling
face_detection --upsample 2 ./group_photo.jpg
```

## 4. Common Tasks

### Recognize Faces in a Webcam Stream

```python
import face_recognition
import cv2
import numpy as np

# Load known faces
known_image = face_recognition.load_image_file("known_person.jpg")
known_encoding = face_recognition.face_encodings(known_image)[0]
known_names = ["Known Person"]
known_encodings = [known_encoding]

video_capture = cv2.VideoCapture(0)

while True:
    ret, frame = video_capture.read()
    if not ret:
        break

    rgb_frame = frame[:, :, ::-1]

    face_locations = face_recognition.face_locations(rgb_frame)
    face_encodings = face_recognition.face_encodings(rgb_frame, face_locations)

    for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
        matches = face_recognition.compare_faces(known_encodings, face_encoding)
        name = "Unknown"

        face_distances = face_recognition.face_distance(known_encodings, face_encoding)
        best_match_index = np.argmin(face_distances)
        if matches[best_match_index]:
            name = known_names[best_match_index]

        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
        cv2.rectangle(frame, (left, bottom - 35), (right, bottom), (0, 0, 255), cv2.FILLED)
        cv2.putText(frame, name, (left + 6, bottom - 6), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), 1)

    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

video_capture.release()
cv2.destroyAllWindows()
```

> [!TIP]
> For better performance, process every other frame and resize to 1/4 size: `small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)`. Scale face coordinates back up by 4x for display.

### Faster Webcam Recognition

Process at 1/4 resolution and skip every other frame:

```python
import face_recognition
import cv2
import numpy as np

video_capture = cv2.VideoCapture(0)

# Load known faces
known_image = face_recognition.load_image_file("known_person.jpg")
known_encoding = face_recognition.face_encodings(known_image)[0]
known_encodings = [known_encoding]
known_names = ["Known Person"]

face_locations = []
face_names = []
process_this_frame = True

while True:
    ret, frame = video_capture.read()
    if not ret:
        break

    if process_this_frame:
        small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
        rgb_small_frame = small_frame[:, :, ::-1]

        face_locations = face_recognition.face_locations(rgb_small_frame)
        face_encodings_list = face_recognition.face_encodings(rgb_small_frame, face_locations)

        face_names = []
        for encoding in face_encodings_list:
            matches = face_recognition.compare_faces(known_encodings, encoding)
            name = "Unknown"
            distances = face_recognition.face_distance(known_encodings, encoding)
            best_idx = np.argmin(distances)
            if matches[best_idx]:
                name = known_names[best_idx]
            face_names.append(name)

    process_this_frame = not process_this_frame

    for (top, right, bottom, left), name in zip(face_locations, face_names):
        top *= 4; right *= 4; bottom *= 4; left *= 4
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
        cv2.rectangle(frame, (left, bottom - 35), (right, bottom), (0, 0, 255), cv2.FILLED)
        cv2.putText(frame, name, (left + 6, bottom - 6), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), 1)

    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

video_capture.release()
cv2.destroyAllWindows()
```

### Recognize Faces in a Video File

```python
import face_recognition
import cv2

input_movie = cv2.VideoCapture("input_video.mp4")
length = int(input_movie.get(cv2.CAP_PROP_FRAME_COUNT))

fourcc = cv2.VideoWriter_fourcc(*'XVID')
output_movie = cv2.VideoWriter('output.avi', fourcc, 29.97, (640, 360))

# Load known faces
known_image = face_recognition.load_image_file("known_person.png")
known_encoding = face_recognition.face_encodings(known_image)[0]

frame_number = 0
while True:
    ret, frame = input_movie.read()
    if not ret:
        break

    frame_number += 1
    rgb_frame = frame[:, :, ::-1]

    face_locations = face_recognition.face_locations(rgb_frame)
    face_encodings_list = face_recognition.face_encodings(rgb_frame, face_locations)

    for (top, right, bottom, left), encoding in zip(face_locations, face_encodings_list):
        match = face_recognition.compare_faces([known_encoding], encoding, tolerance=0.50)
        if match[0]:
            cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
            cv2.rectangle(frame, (left, bottom - 25), (right, bottom), (0, 0, 255), cv2.FILLED)
            cv2.putText(frame, "Known Person", (left + 6, bottom - 6),
                        cv2.FONT_HERSHEY_DUPLEX, 0.5, (255, 255, 255), 1)

    print(f"Writing frame {frame_number} / {length}")
    output_movie.write(frame)

input_movie.release()
cv2.destroyAllWindows()
```

### Apply Digital Makeup

Use facial landmarks to draw on facial features:

```python
from PIL import Image, ImageDraw
import face_recognition

image = face_recognition.load_image_file("photo.jpg")
face_landmarks_list = face_recognition.face_landmarks(image)

pil_image = Image.fromarray(image)
for face_landmarks in face_landmarks_list:
    d = ImageDraw.Draw(pil_image, 'RGBA')

    # Eyebrows
    d.polygon(face_landmarks['left_eyebrow'], fill=(68, 54, 39, 128))
    d.polygon(face_landmarks['right_eyebrow'], fill=(68, 54, 39, 128))

    # Lips
    d.polygon(face_landmarks['top_lip'], fill=(150, 0, 0, 128))
    d.polygon(face_landmarks['bottom_lip'], fill=(150, 0, 0, 128))

    # Eyes
    d.polygon(face_landmarks['left_eye'], fill=(255, 255, 255, 30))
    d.polygon(face_landmarks['right_eye'], fill=(255, 255, 255, 30))

    # Eyeliner
    d.line(face_landmarks['left_eye'] + [face_landmarks['left_eye'][0]],
           fill=(0, 0, 0, 110), width=6)
    d.line(face_landmarks['right_eye'] + [face_landmarks['right_eye'][0]],
           fill=(0, 0, 0, 110), width=6)

pil_image.show()
```

### Blur Faces in a Webcam Stream

```python
import face_recognition
import cv2

video_capture = cv2.VideoCapture(0)

while True:
    ret, frame = video_capture.read()
    if not ret:
        break

    small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
    face_locations = face_recognition.face_locations(small_frame, model="cnn")

    for top, right, bottom, left in face_locations:
        top *= 4; right *= 4; bottom *= 4; left *= 4
        face_image = frame[top:bottom, left:right]
        face_image = cv2.GaussianBlur(face_image, (99, 99), 30)
        frame[top:bottom, left:right] = face_image

    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

video_capture.release()
cv2.destroyAllWindows()
```

### Build a KNN Face Classifier

For recognizing a large set of known people, train a k-nearest neighbors model:

```python
import face_recognition
from face_recognition.face_recognition_cli import image_files_in_folder
from sklearn import neighbors
import os
import math
import pickle

def train(train_dir, model_save_path=None, n_neighbors=None, knn_algo='ball_tree'):
    X = []
    y = []

    for class_dir in os.listdir(train_dir):
        if not os.path.isdir(os.path.join(train_dir, class_dir)):
            continue
        for img_path in image_files_in_folder(os.path.join(train_dir, class_dir)):
            image = face_recognition.load_image_file(img_path)
            face_bounding_boxes = face_recognition.face_locations(image)
            if len(face_bounding_boxes) == 1:
                X.append(face_recognition.face_encodings(
                    image, known_face_locations=face_bounding_boxes)[0])
                y.append(class_dir)

    if n_neighbors is None:
        n_neighbors = int(round(math.sqrt(len(X))))

    knn_clf = neighbors.KNeighborsClassifier(
        n_neighbors=n_neighbors, algorithm=knn_algo, weights='distance')
    knn_clf.fit(X, y)

    if model_save_path is not None:
        with open(model_save_path, 'wb') as f:
            pickle.dump(knn_clf, f)

    return knn_clf
```

> [!IMPORTANT]
> The KNN example requires scikit-learn: `pip install scikit-learn`. Organize training images as `train_dir/person_name/image.jpg` — one subdirectory per person, each containing one or more face images of that person. Each training image must contain exactly one face.

**Training directory structure:**

```text
train_dir/
├── person_1/
│   ├── img1.jpg
│   ├── img2.jpg
├── person_2/
│   ├── img1.jpg
│   └── img2.jpg
└── ...
```

### Build a Face Recognition Web Service

```python
import face_recognition
from flask import Flask, jsonify, request, redirect

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
app = Flask(__name__)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_image():
    if request.method == 'POST':
        if 'file' not in request.files:
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            return redirect(request.url)
        if file and allowed_file(file.filename):
            return detect_faces_in_image(file)
    return '''
    <!doctype html>
    <title>Face Recognition</title>
    <h1>Upload a picture to check for faces</h1>
    <form method="POST" enctype="multipart/form-data">
      <input type="file" name="file">
      <input type="submit" value="Upload">
    </form>
    '''

def detect_faces_in_image(file_stream):
    img = face_recognition.load_image_file(file_stream)
    encodings = face_recognition.face_encodings(img)
    result = {"face_found_in_image": len(encodings) > 0, "face_count": len(encodings)}
    return jsonify(result)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5001, debug=True)
```

> [!NOTE]
> This requires Flask: `pip install flask`. Test with: `curl -XPOST -F "file=@photo.jpg" http://127.0.0.1:5001`

### Detect Eye Blinks

Use the eye aspect ratio (EAR) algorithm with facial landmarks:

```python
import face_recognition
import cv2
from scipy.spatial import distance as dist

def get_ear(eye):
    A = dist.euclidean(eye[1], eye[5])
    B = dist.euclidean(eye[2], eye[4])
    C = dist.euclidean(eye[0], eye[3])
    return (A + B) / (2.0 * C)

video_capture = cv2.VideoCapture(0)

while True:
    ret, frame = video_capture.read()
    if not ret:
        break

    small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
    rgb_small_frame = small_frame[:, :, ::-1]

    face_landmarks_list = face_recognition.face_landmarks(rgb_small_frame)

    for face_landmarks in face_landmarks_list:
        left_ear = get_ear(face_landmarks['left_eye'])
        right_ear = get_ear(face_landmarks['right_eye'])

        if left_ear < 0.2 and right_ear < 0.2:
            print("EYES CLOSED")
        else:
            print("EYES OPEN")

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

video_capture.release()
cv2.destroyAllWindows()
```

### Run on a Raspberry Pi

```python
import face_recognition
import picamera
import numpy as np

camera = picamera.PiCamera()
camera.resolution = (320, 240)
output = np.empty((240, 320, 3), dtype=np.uint8)

# Load a known face
known_image = face_recognition.load_image_file("known_person.jpg")
known_encoding = face_recognition.face_encodings(known_image)[0]

while True:
    camera.capture(output, format="rgb")

    face_locations = face_recognition.face_locations(output)
    face_encodings_list = face_recognition.face_encodings(output, face_locations)

    for face_encoding in face_encodings_list:
        match = face_recognition.compare_faces([known_encoding], face_encoding)
        if match[0]:
            print("Known person detected!")
        else:
            print("Unknown person")
```

> [!WARNING]
> Raspberry Pi requires `picamera[array]` module and camera enabled via `raspi-config`. Use a low resolution (320x240) for acceptable performance on RPi 2+.

## 5. Troubleshooting

### Diagnostic Decision Tree

```text
Problem?
├── Import error → Check dlib and face_recognition_models are installed
├── No faces found → Try upsampling, check image format, try CNN model
├── Wrong matches → Lower tolerance (try 0.5 or 0.4)
├── Too slow → Use HOG model, reduce image size, process fewer frames
├── Webcam not working → Check cv2.VideoCapture(0) returns frames
└── CUDA errors → Verify dlib was compiled with CUDA support
```

### Error: `ValueError: Invalid landmarks model type`

**Cause:** An invalid model name was passed to `face_landmarks()`. Only `"large"` and `"small"` are supported.

**Solution:**
```python
# Correct usage
landmarks = face_recognition.face_landmarks(image, model="large")  # 68 points
landmarks = face_recognition.face_landmarks(image, model="small")  # 5 points
```

**Prevention:** Only use `"large"` or `"small"` as the model parameter.

### Error: `RuntimeError: Error while calling cudaOccupancyMaxPotentialBlockSize`

**Cause:** CUDA driver or runtime version mismatch, or insufficient GPU memory. Issue #1641.

**Solution:**
1. Verify CUDA installation: `nvidia-smi`
2. Ensure dlib was compiled with CUDA: `python -c "import dlib; print(dlib.DLIB_USE_CUDA)"`
3. If CUDA is not available, fall back to HOG model:
   ```python
   face_locations = face_recognition.face_locations(image, model="hog")
   ```

**Prevention:** Match CUDA toolkit version with your GPU driver version. Use HOG model if GPU is unreliable.

### Error: `compute_face_descriptor(): incompatible function arguments`

**Cause:** Image data is in the wrong format — typically a PIL Image object was passed instead of a numpy array, or the array has the wrong dtype. Issue #1582.

**Solution:**
```python
# Always use load_image_file() or convert properly
image = face_recognition.load_image_file("photo.jpg")
# OR
import numpy as np
from PIL import Image
image = np.array(Image.open("photo.jpg").convert("RGB"))
```

**Prevention:** Use `face_recognition.load_image_file()` for all image loading.

### Multiprocessing output is interleaved

**Cause:** When using `--cpus` greater than 1, worker processes write to stdout concurrently, which can produce jumbled output. Issue #1665.

**Solution:** Post-process the output by sorting lines by filename:
```bash
face_recognition --cpus -1 ./known/ ./test/ | sort
```

**Prevention:** Use `--cpus 1` if output ordering matters, or redirect to a file and sort afterward.

### No faces detected in an image

**Cause:** Faces may be too small, the image may be low quality, or faces may be at extreme angles.

**Solution:**
1. Increase upsampling:
   ```python
   face_locations = face_recognition.face_locations(image, number_of_times_to_upsample=2)
   ```
2. Try the CNN model for better accuracy:
   ```python
   face_locations = face_recognition.face_locations(image, model="cnn")
   ```
3. Ensure the image is in RGB format (not BGR from OpenCV):
   ```python
   rgb_image = cv2_image[:, :, ::-1]
   ```

**Prevention:** Use the CNN model and higher upsampling for challenging images.

> [!CAUTION]
> Face detection accuracy degrades significantly with very small faces (below ~80px), extreme head rotation (profile views), heavy occlusion, or very low-resolution images. There is no workaround for these physical limitations of the model.

### Inaccurate photo recognition

**Cause:** The default tolerance of 0.6 may be too permissive for your dataset, leading to false positives. Issue #1647.

**Solution:**
1. Lower the tolerance:
   ```python
   results = face_recognition.compare_faces(known_encodings, unknown_encoding, tolerance=0.5)
   ```
2. Use `face_distance()` to understand the distribution of distances in your dataset and select an appropriate threshold
3. Use multiple training images per person for better encoding stability

**Prevention:** Test with `--show-distance` to calibrate the tolerance for your specific use case.

## 6. FAQ

**Q: What is the accuracy of the face recognition?**

The underlying model achieves 99.38% accuracy on the Labeled Faces in the Wild benchmark. Real-world accuracy depends on image quality, lighting, and face angles. The default tolerance of 0.6 balances false positives against false negatives.

**Q: What is the difference between HOG and CNN face detection models?**

HOG (Histogram of Oriented Gradients) is faster and works well on CPU. CNN (Convolutional Neural Network) is more accurate but significantly slower without GPU acceleration. HOG is the default and recommended for most use cases. Use CNN when you need higher accuracy and have a CUDA-capable GPU.

**Q: How do I improve recognition accuracy?**

Lower the tolerance (e.g., `0.5` instead of the default `0.6`) to reduce false positives. Use `num_jitters` greater than 1 when computing encodings for more stable results. Provide multiple training images per person when building a classifier. Ensure images are well-lit and faces are reasonably frontal.

**Q: Does face_recognition work with video files?**

Yes. Use OpenCV (`cv2`) to read video frames, convert each frame from BGR to RGB (`frame[:, :, ::-1]`), and pass it to any face_recognition function. See the "Recognize Faces in a Video File" recipe above.

**Q: Can I use a GPU for face detection?**

Yes. Install dlib with CUDA support (requires NVIDIA GPU, CUDA toolkit, and cuDNN). Then use `model="cnn"` for `face_locations()` or use `batch_face_locations()` for GPU-batched processing.

**Q: What image formats are supported?**

Any format supported by Pillow, including JPEG, PNG, BMP, GIF, TIFF, and WebP. The `load_image_file()` function handles format conversion automatically.

**Q: How do I handle images with no faces?**

`face_locations()` returns an empty list `[]` if no faces are found. `face_encodings()` also returns an empty list. Always check the length before indexing:

```python
encodings = face_recognition.face_encodings(image)
if len(encodings) > 0:
    encoding = encodings[0]
else:
    print("No faces found")
```

**Q: What is the face encoding format?**

Each face encoding is a 128-dimensional numpy array of float values. Encodings can be serialized with `pickle` or `numpy.save()` for storage. The euclidean distance between two encodings indicates how similar the faces are — distances below 0.6 typically indicate the same person.

**Q: Can I use face_recognition on a Raspberry Pi?**

Yes, on Raspberry Pi 2 or later. Install with `pip install face_recognition` (compilation will take a long time). Use low resolutions (320x240) and the `picamera` module for camera input. Performance will be slower than desktop systems.

**Q: Does it work with OpenCV images?**

Yes, but OpenCV uses BGR color order while face_recognition expects RGB. Convert with: `rgb_image = cv2_image[:, :, ::-1]`

For full architecture details, deployment options, and configuration reference, see the Admin Guide.

---

_Generated May 2026 from ageitgey/face_recognition at 2e2dcce. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
