Skip to Content
BonicBot A2DevelopmentArtificial IntelligenceLesson 8: Teaching BonicBot to Remember Faces

Lesson 8: Teaching BonicBot to Remember Faces

Learning Objective

Learn how to convert face images into face embeddings, allowing BonicBot to build a database of people it can recognize in the next lesson.


Introduction

In the previous lesson, you collected several face images for different people. However, BonicBot cannot compare or recognize people by looking at the raw pictures directly.

Instead, it first converts every face into a special set of numbers called an embedding.

Think of an embedding as a digital fingerprint for a face.

Instead of storing thousands of image pixels, a deep learning model extracts the most important facial features and represents them as a compact list of numbers. Faces belonging to the same person usually produce very similar embeddings, while different people produce different embeddings.

In this lesson, you’ll use a pre-trained ArcFace model to generate these embeddings for every face in your dataset. The generated embeddings are then stored in a local database.

In the next lesson, BonicBot will compare a newly detected face against this database using Cosine Similarity to determine whose face it is.


Before running the program, organize your face images using the following folder structure.

Each person’s name should be used as the folder name.

face_gallery/ ├── Alice/ │ ├── image1.jpg │ ├── image2.jpg │ └── image3.jpg ├── Bob/ │ ├── image1.jpg │ ├── image2.jpg │ └── image3.jpg ├── Charlie/ │ ├── image1.jpg │ ├── image2.jpg │ └── image3.jpg └── ...

Each folder represents one person, and all the images inside that folder belong to the same individual.

[!TIP] For best results, collect 5–10 clear face images for each person. Try taking pictures from slightly different angles and with different facial expressions.


Setup: Installing Packages

Before running the code, make sure your computer has the required Python packages installed. Open a terminal and run:

pip install opencv-python==4.10.0.84 tf-keras deepface numpy

What each package does

PackagePurpose
opencv-pythonImported as cv2. Used by ensure_detector_backend() to check whether the installed OpenCV build has cv2.CascadeClassifier available — if it does, the script uses "opencv" as its face detector backend; if not, it falls back to probing mediapipe, mtcnn, or retinaface.
tf-kerasA backend dependency required internally by deepface to load and run the ArcFace model. It isn’t imported directly in the script, but DeepFace.represent() will fail without it installed.
deepfaceProvides the DeepFace class used throughout build_face_database(). Specifically, DeepFace.represent(...) is called both to probe fallback detectors in ensure_detector_backend() and to generate the actual ArcFace embedding for each image in the face gallery.
numpyImported as np. Used to convert each raw embedding into a np.array, normalize it with np.linalg.norm(vec) so every stored vector has the same length, and construct the blank test image (np.zeros((100, 100, 3), np.uint8)) used to probe detector backends.

This lesson also uses two built-in Python modules, pickle and pathlib.Path, along with sys, subprocess, and importlib (all part of the standard library, so no separate install is needed for those).

If you already installed opencv-python, tf-keras, and deepface in an earlier lesson, you don’t need to reinstall anything for this lesson — just make sure numpy is present too (it usually is, as a dependency of the others).

If pip install fails, try pip3 install ... instead, or use a virtual environment:

python3 -m venv bonicbot-env source bonicbot-env/bin/activate # On Windows: bonicbot-env\Scripts\activate pip install opencv-python==4.10.0.84 tf-keras deepface numpy

How to run the program

  1. Build your face gallery first. This lesson doesn’t connect to BonicBot at all — it’s an offline batch script — so there’s no robot IP address to configure. Instead, make sure the face_gallery/ folder (as described above) exists in the same directory as your script, with one subfolder per person containing their face images.
  2. Save the code below into a file, e.g. lesson8_face_embeddings.py, in the same directory as your face_gallery/ folder.
  3. Double-check your images. Each face should be reasonably clear and well-lit — the script will skip and report any image it can’t detect a face in, rather than crashing.
  4. Run the script:
python lesson8_face_embeddings.py
  1. Watch the terminal output. You should see a line for every image processed (✅ success or ❌ failure), followed by a summary once all folders have been scanned.
  2. No window will open and there’s no q key to press — the script runs to completion on its own and exits automatically once face_database.pkl has been written.

Don’t have a physical BonicBot?

This lesson doesn’t require a physical BonicBot, a robot connection, or the ROS 2 simulation at all — it’s a standalone Python script that only reads images from your face_gallery/ folder and writes face_database.pkl to disk.

You can complete this lesson entirely on your own computer using photos of yourself, friends, or family members. The embeddings generated here will be used by BonicBot’s camera in the next lesson, where the simulation or a real robot does become relevant.


Code

Click to view the complete program

Prerequisites

Before running the program in this lesson, make sure you have the required Python libraries installed:

pip install opencv-python==4.10.0.84 tf-keras deepface
import sys, subprocess, importlib from pathlib import Path import pickle import numpy as np import cv2 from deepface import DeepFace def ensure_detector_backend(): """Return a working detector backend, installing/probing fallbacks if this OpenCV build lacks cv2.CascadeClassifier.""" if hasattr(cv2, "CascadeClassifier"): return "opencv" print(f"⚠️ cv2 {cv2.__version__} has no CascadeClassifier — probing fallback detectors...") for backend, pip_name, module in [ ("mediapipe", "mediapipe", "mediapipe"), ("mtcnn", "mtcnn", "mtcnn"), ("retinaface", "retina-face", "retinaface"), ]: try: importlib.import_module(module) except ImportError: subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name]) try: DeepFace.represent(np.zeros((100, 100, 3), np.uint8), model_name="ArcFace", detector_backend=backend, enforce_detection=False, align=True) print(f"✅ Using '{backend}' detector backend.") return backend except Exception as e: print(f" ↳ '{backend}' failed: {e}") sys.exit("❌ No working face detector found (tried mediapipe, mtcnn, retinaface).") def build_face_database(dataset_dir="face_gallery", output_pickle="face_database.pkl"): backend = ensure_detector_backend() identities, embeddings = [], [] ok = fail = 0 print(f"🚀 Extracting embeddings (detector: '{backend}')...") for person_folder in Path(dataset_dir).iterdir(): if not person_folder.is_dir(): continue images = [p for ext in ("*.jpg", "*.jpeg", "*.png", "*.JPG", "*.JPEG", "*.PNG") for p in person_folder.glob(ext)] for img_path in images: try: rep = DeepFace.represent(str(img_path), model_name="ArcFace", detector_backend=backend, align=True)[0]["embedding"] vec = np.array(rep) vec = vec / np.linalg.norm(vec) if np.linalg.norm(vec) > 0 else vec identities.append(person_folder.name) embeddings.append(vec) ok += 1 print(f" ✅ {person_folder.name}/{img_path.name}") except Exception as e: fail += 1 print(f" ❌ {person_folder.name}/{img_path.name}: {e}") if not identities: print("\n⚠️ No faces processed. No file saved.") return with open(output_pickle, "wb") as f: pickle.dump({"identities": identities, "embeddings": embeddings}, f) print(f"\n🎉 Saved '{output_pickle}' — {ok} indexed, {fail} skipped.") if __name__ == "__main__": build_face_database()

Replace [IP_ADDRESS] with the IP address of your own BonicBot. Every BonicBot may have a different IP address depending on your network configuration.

📌 Example:

with BonicBot(host="172.20.10.2") as bot:

Replace 172.20.10.2 with the IP address assigned to your BonicBot.


Code Walkthrough

Line-by-line explanation

  • Importssys, subprocess, and importlib support the automatic fallback-detector installation logic; Path from pathlib handles folder/file traversal over face_gallery/; pickle serializes the final embedding database to disk; numpy (as np) handles vector math; cv2 is checked for CascadeClassifier support; DeepFace from deepface generates the actual face embeddings.
  • ensure_detector_backend() — Checks whether the installed cv2 build has hasattr(cv2, "CascadeClassifier"); if so, it immediately returns "opencv" as the detector backend. If not, it loops through a list of fallback backends (mediapipe, mtcnn, retinaface), attempting to import each one first and installing it via pip (using subprocess.check_call) if the import fails. For each backend, it runs a quick sanity check by calling DeepFace.represent() on a blank black image (np.zeros((100, 100, 3), np.uint8)) with enforce_detection=False — if that call succeeds without raising, the backend is considered usable and is returned. If none of the backends work, the function calls sys.exit(...) with an error message.
  • build_face_database(dataset_dir="face_gallery", output_pickle="face_database.pkl") — The main function that does the actual work:
    • Calls ensure_detector_backend() once at the start to decide which detector to use for every image.
    • Initializes empty identities and embeddings lists, plus ok/fail counters for the summary.
    • Iterates over every subfolder inside dataset_dir using Path(dataset_dir).iterdir(), skipping anything that isn’t a directory (if not person_folder.is_dir(): continue) — this is what lets each person’s folder name double as their identity label.
    • For each person folder, builds a list of image paths by globbing multiple extensions (*.jpg, *.jpeg, *.png, and their uppercase variants) so it works across differently-cased file extensions.
    • For each image, calls DeepFace.represent(str(img_path), model_name="ArcFace", detector_backend=backend, align=True)[0]["embedding"] to get the raw embedding vector, wraps it in np.array(vec), and normalizes it by dividing by its own np.linalg.norm(vec) (guarding against division by zero) — this ensures every stored embedding has unit length, which matters for the cosine similarity comparison used in the next lesson.
    • On success, appends the person’s folder name to identities and the normalized vector to embeddings, increments ok, and prints a ✅ line. On failure (e.g. no face detected in that image), increments fail and prints a ❌ line with the exception message instead of stopping the whole run.
    • After all folders are processed, if identities is empty (no faces were successfully embedded at all), it prints a warning and returns early without writing a file.
    • Otherwise, it opens output_pickle in binary write mode and uses pickle.dump({"identities": identities, "embeddings": embeddings}, f) to save both lists together as a single dictionary, then prints a final summary line showing how many images were indexed versus skipped.
  • if __name__ == "__main__": build_face_database() — Standard Python entry point guard; running the file directly (rather than importing it as a module) triggers the whole embedding-extraction process using the default face_gallery input folder and face_database.pkl output filename.

Expected Output

Click to see expected output

🚀 Starting batch embedding extraction... ✅ Processed: Alice (image1.jpg) ✅ Processed: Alice (image2.jpg) ✅ Processed: Bob (image1.jpg) ✅ Processed: Charlie (image1.jpg) 🎉 Parsing Complete! 💾 Database saved to 'face_database.pkl' 📊 Summary: 12 faces successfully indexed, 0 failed/skipped.

After the program finishes, a new file named

face_database.pkl

will be created.

This file contains the face embeddings for every person in your dataset and will be used in the next lesson for face recognition.


🔧 Under the Hood

What is a face embedding?

A computer cannot understand a face the way humans do.

Instead, a deep learning model first converts the face into a list of numbers called an embedding.

Alice [0.14, -0.27, 0.81, ...]

This list of numbers captures the important features of the face while ignoring unnecessary details such as the background.

In this lesson, the ArcFace model generates one embedding for every face image.

The program then:

  1. Reads every person’s folder.
  2. Finds all the face images.
  3. Uses ArcFace to generate an embedding for each image.
  4. Normalizes the embedding so every vector has the same length.
  5. Stores all embeddings together in a file called face_database.pkl.

In the next lesson, BonicBot will generate an embedding for a newly detected face and compare it with the embeddings stored in this database.

The closest match will tell BonicBot who it is looking at.


Student Challenge

Add another person to your face gallery.

Collect several images for the new person, place them in a new folder, and run the program again.

How many embeddings are added to the database?

Hint

Create another folder inside face_gallery.

For example:

face_gallery/ └── David/ ├── image1.jpg ├── image2.jpg ├── image3.jpg

Run the program again to rebuild the database.


Reflection Question

Why do you think BonicBot stores a compact embedding instead of saving every face image and comparing pictures pixel by pixel?

Last updated on