How to Cache Face Encodings for Faster Lookups in Python

Cache face encodings by computing 128-dimensional vectors once, serializing them with pickle, and loading them at startup to avoid expensive neural network recomputation.

When building face recognition systems with the ageitgey/face_recognition library, the biggest performance bottleneck is computing face encodings—the 128-dimensional vectors that represent facial features. This step runs a deep neural network via dlib and becomes prohibitively slow when processing large known-face databases. By caching these encodings to disk, you transform a computationally expensive operation into a fast NumPy array comparison.

Why Cache Face Encodings?

The face_recognition library generates encodings through the face_encodings() function in face_recognition/api.py (lines 203–215). This function invokes a dlib face recognition model that performs forward propagation through a deep neural network to produce a 128-dimensional vector for each detected face.

When you need to identify faces against a database of known individuals, recomputing these vectors for every lookup creates O(n) neural network invocations. By computing each known person's encoding once and persisting it, you reduce subsequent lookups to vectorized NumPy operations using face_distance() (lines 63–75 in api.py), which calculates Euclidean distance via np.linalg.norm.

Step-by-Step: How to Cache Face Encodings

Generate Encodings Once

Process your training images through face_encodings() to extract 128-dimensional vectors. This function accepts an image array and optional face locations, returning a list of encodings.

import face_recognition

image = face_recognition.load_image_file("person.jpg")
face_locations = face_recognition.face_locations(image)
encodings = face_recognition.face_encodings(image, known_face_locations=face_locations)

Serialize with Pickle

Persist the encodings using Python's standard pickle module or numpy.save. The KNN example in examples/face_recognition_knn.py (lines 104–107) demonstrates this pattern by saving a trained classifier to disk.

import pickle

# Save encodings dictionary: {name: [encoding, ...]}

with open("known_faces_cache.pkl", "wb") as f:
    pickle.dump(known_encodings_dict, f)

Load at Startup

At application initialization, deserialize the cache into memory. This makes the encoding database available for instant comparison without neural network overhead. The KNN example shows the loading pattern at lines 130–132.

def load_cache(cache_path="known_faces_cache.pkl"):
    if os.path.exists(cache_path):
        with open(cache_path, "rb") as f:
            return pickle.load(f)
    return {}

Perform Fast Comparisons

With cached encodings loaded, use face_distance() to compute Euclidean distances between the unknown encoding and all known vectors. This function (lines 63–75 in api.py) performs a vectorized NumPy operation that is orders of magnitude faster than neural network inference.

import face_recognition

# known_encodings: list of 128-dim vectors from cache

# unknown_encoding: single 128-dim vector from new image

distances = face_recognition.face_distance(known_encodings, unknown_encoding)
best_match_index = distances.argmin()
if distances[best_match_index] < 0.6:
    name = known_names[best_match_index]

Complete Working Example

This script implements a complete caching workflow, mirroring the patterns found in examples/face_recognition_knn.py and the core API implementation in face_recognition/api.py.

import os
import pickle
import pathlib
import face_recognition

CACHE_PATH = pathlib.Path("known_faces_cache.pkl")

def build_cache(known_dir):
    """
    Scan directory where each subfolder is a person.
    Compute one encoding per image and store dict:
    {person_name: [encoding, ...]}
    """
    cache = {}
    for person in os.listdir(known_dir):
        person_path = os.path.join(known_dir, person)
        if not os.path.isdir(person_path):
            continue
        
        encodings = []
        for img_file in os.listdir(person_path):
            if img_file.startswith("."):
                continue
            img_path = os.path.join(person_path, img_file)
            img = face_recognition.load_image_file(img_path)
            faces = face_recognition.face_locations(img)
            
            if len(faces) == 1:
                enc = face_recognition.face_encodings(img, known_face_locations=faces)[0]
                encodings.append(enc)
        
        if encodings:
            cache[person] = encodings
    
    with open(CACHE_PATH, "wb") as f:
        pickle.dump(cache, f)
    print(f"Cache built with {len(cache)} people")

def load_cache():
    """Return dict {person: [encodings]} from disk, or empty dict."""
    if CACHE_PATH.is_file():
        with open(CACHE_PATH, "rb") as f:
            return pickle.load(f)
    return {}

def find_match(unknown_img_path, cache, tolerance=0.6):
    """
    Encode unknown image and compare against cached encodings.
    Returns (person_name, distance) or ('unknown', None).
    """
    img = face_recognition.load_image_file(unknown_img_path)
    faces = face_recognition.face_locations(img)
    if not faces:
        return None
    
    unknown_enc = face_recognition.face_encodings(img, known_face_locations=faces)[0]
    best_match = ("unknown", None)
    
    for name, enc_list in cache.items():
        distances = face_recognition.face_distance(enc_list, unknown_enc)
        min_dist = distances.min()
        if min_dist <= tolerance and (best_match[1] is None or min_dist < best_match[1]):
            best_match = (name, float(min_dist))
    
    return best_match

# Example usage

if __name__ == "__main__":
    known_dir = "knn_examples/train"
    if not CACHE_PATH.is_file():
        build_cache(known_dir)
    
    cache = load_cache()
    result = find_match("test_image.jpg", cache)
    print(f"Match: {result}")

Key Source Files and Implementation Details

Understanding the underlying implementation helps optimize your caching strategy:

  • face_recognition/api.py (lines 203–215): Contains face_encodings(), which converts face images into 128-dimensional vectors using the dlib face recognition model. This is the expensive operation to cache.

  • face_recognition/api.py (lines 63–75): Implements face_distance(), which computes Euclidean distance between encodings using np.linalg.norm. This is the fast operation you want to perform on cached data.

  • face_recognition/api.py (lines 217–226): Contains compare_faces(), a convenience wrapper around face_distance() that applies a tolerance threshold.

  • examples/face_recognition_knn.py (lines 104–107 and 130–132): Demonstrates the pickle serialization pattern for persisting trained models, which applies directly to encoding caches.

  • examples/web_service_example.py: Shows real-time usage where pre-computed encodings enable instant comparison against uploaded images.

Summary

  • Face encodings are 128-dimensional vectors generated by expensive neural network inference in dlib via face_recognition.face_encodings().
  • Cache these vectors to disk using pickle or numpy.save after the initial computation to avoid redundant processing.
  • Load encodings at startup to enable fast, vectorized distance calculations using face_recognition.face_distance(), which performs simple NumPy operations rather than neural network inference.
  • Invalidate caches by regenerating the pickle file when adding or removing known individuals from your database.
  • Reference implementation patterns exist in examples/face_recognition_knn.py and the core API in face_recognition/api.py.

Frequently Asked Questions

How much faster is cached face recognition compared to real-time encoding?

Cached face recognition is orders of magnitude faster. Computing a face encoding requires running a deep neural network through dlib, which takes hundreds of milliseconds per face. Once cached, face_recognition.face_distance() performs a vectorized NumPy Euclidean distance calculation across thousands of encodings in microseconds.

What is the best file format for caching face encodings?

Python's standard pickle module is the most common approach, as demonstrated in examples/face_recognition_knn.py (lines 104–107). Alternatively, use numpy.save() or numpy.savez() to store the 128-dimensional arrays if you need cross-language compatibility. Both methods preserve the exact floating-point precision required for accurate face matching.

How do I handle cache invalidation when adding new people?

When your known-face database changes, regenerate the cache by re-running your encoding script against the updated directory structure. Delete the old pickle file and rebuild it using face_recognition.face_encodings() for the new images. This follows the same pattern as the KNN example's training flow, where the model is retrained and resaved when the training directory changes.

Can I cache unknown face encodings during runtime?

Yes, for long-running services processing many images, cache unknown face encodings using functools.lru_cache or a custom dictionary keyed by image hash. This prevents recomputing encodings for identical images submitted multiple times. However, ensure the cache has size limits to prevent memory exhaustion in high-throughput applications.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →