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.
Preparing the Face Gallery
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 numpyWhat each package does
| Package | Purpose |
|---|---|
opencv-python | Imported 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-keras | A 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. |
deepface | Provides 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. |
numpy | Imported 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 numpyHow to run the program
- 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. - Save the code below into a file, e.g.
lesson8_face_embeddings.py, in the same directory as yourface_gallery/folder. - 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.
- Run the script:
python lesson8_face_embeddings.py- 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.
- No window will open and there’s no
qkey to press — the script runs to completion on its own and exits automatically onceface_database.pklhas 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 deepfaceimport 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
- Imports —
sys,subprocess, andimportlibsupport the automatic fallback-detector installation logic;Pathfrompathlibhandles folder/file traversal overface_gallery/;pickleserializes the final embedding database to disk;numpy(asnp) handles vector math;cv2is checked forCascadeClassifiersupport;DeepFacefromdeepfacegenerates the actual face embeddings. ensure_detector_backend()— Checks whether the installedcv2build hashasattr(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 toimporteach one first and installing it viapip(usingsubprocess.check_call) if the import fails. For each backend, it runs a quick sanity check by callingDeepFace.represent()on a blank black image (np.zeros((100, 100, 3), np.uint8)) withenforce_detection=False— if that call succeeds without raising, the backend is considered usable and is returned. If none of the backends work, the function callssys.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
identitiesandembeddingslists, plusok/failcounters for the summary. - Iterates over every subfolder inside
dataset_dirusingPath(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 innp.array(vec), and normalizes it by dividing by its ownnp.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
identitiesand the normalized vector toembeddings, incrementsok, and prints a ✅ line. On failure (e.g. no face detected in that image), incrementsfailand prints a ❌ line with the exception message instead of stopping the whole run. - After all folders are processed, if
identitiesis empty (no faces were successfully embedded at all), it prints a warning and returns early without writing a file. - Otherwise, it opens
output_picklein binary write mode and usespickle.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.
- Calls
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 defaultface_galleryinput folder andface_database.pkloutput 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.pklwill 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:
- Reads every person’s folder.
- Finds all the face images.
- Uses ArcFace to generate an embedding for each image.
- Normalizes the embedding so every vector has the same length.
- 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.jpgRun 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?