Skip to Content
BonicBot A2DevelopmentArtificial IntelligenceLesson 2: Teaching BonicBot to Detect Faces

Lesson 2: Teaching BonicBot to Detect Faces

Learning Objective

Teach BonicBot to detect human faces in its live vision feed, highlight each face with a bounding box, display facial landmarks, count how many people are currently visible, and monitor the robot’s vision performance.


Introduction

In the previous lesson, BonicBot learned to recognize different kinds of objects in its surroundings. This time, we’ll give the robot a much more focused task:

“Can you find every person’s face that you can currently see?”

Instead of looking for many object categories, BonicBot now uses a face detection model that specializes in finding human faces.

Whenever the robot spots a face, it can determine:

  • where the face is located
  • how confident it is about the detection
  • several important facial landmarks, such as the eyes, nose, mouth, and ears

To make the results easy to understand, the program gives every detected face its own color and number. BonicBot also keeps track of how many faces are currently visible and displays a small information panel showing the live face count and processing speed.

Rather than printing information continuously, the robot only reports when the number of detected faces changes. This keeps the terminal clean while still recording important events.


Setup: Installing Packages

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

pip install bonicbot-bridge opencv-python numpy

What each package does

PackagePurpose
bonicbot-bridgeThe official BonicBot SDK. Provides the BonicBot class used to connect to the robot, control its camera, enable face detection, and read back detected faces via bot.get_faces().
opencv-pythonImported as cv2. Used to draw bounding boxes, labels, landmark dots, and the translucent HUD panel, and to display the live video window.
numpyImported as np. Backs the image arrays that both opencv-python and bonicbot-bridge work with internally.

If you already installed these packages in Lesson 1, you don’t need to reinstall them — the same environment works for this lesson.

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 bonicbot-bridge opencv-python numpy

How to run the program

  1. Find your BonicBot’s IP address. Check the robot’s on-device display, its companion app, or your router’s connected-devices list.

  2. Save the code below into a file, e.g. lesson2_faces.py.

  3. Replace [IP_ADDRESS] in the code with your robot’s actual IP address (see the callout box under the code block).

  4. Make sure your BonicBot is powered on and connected to the same network as your computer, and that at least one face can appear in front of its camera.

  5. Run the script:

    python lesson2_faces.py
  6. A window titled “BonicBot Vision” should open, showing colored boxes and landmarks around each detected face, plus a small stats panel in the top-left corner.

  7. Watch the terminal — a new line is only printed when the number of visible faces changes.

  8. Press q with the video window focused to stop the stream and exit cleanly.

Don’t have a physical BonicBot? Try it in simulation (optional)

If you don’t have physical access to a BonicBot, you can still work through this lesson using the ROS 2 simulation environment instead of a real robot:
  1. Launch the BonicBot simulation:

    ros2 launch my_bot robot_system.launch.py use_sim_time:=true world:=obsworld.sdf use_real_camera:=True
    • use_real_camera:=True — the camera feed comes from your laptop’s webcam, while the robot’s body and motion are still simulated. This is the easiest way to test detection, since you can just hold whatever you want detected up to your laptop camera.
    • use_real_camera:=False — the camera feed comes from Gazebo instead (i.e. whatever the simulated camera sees inside the simulated world).
  2. Once the simulation is running, use localhost as the host instead of a physical IP address:

    with BonicBot(host="localhost") as bot:
  3. Everything else in the code — enabling face detection, drawing boxes, reading bot.get_faces() — works the same way, since the simulation exposes the same interface as a real robot. This path is mainly useful for exploring the lesson without hardware on hand; if you have a real BonicBot, connecting to its actual IP address is the recommended way to go through this lesson.


Code

Click to view the complete program

import time import cv2 import numpy as np from bonicbot_bridge import BonicBot # One distinct BGR color per face "slot" (cycles if more faces than colors) FACE_COLORS = [ (0, 255, 0), # green (255, 255, 0), # cyan (0, 165, 255), # orange (255, 0, 255), # magenta (0, 255, 255), # yellow (255, 0, 0), # blue ] FLASH_DURATION = 12 # frames the border pulse stays visible after a change MIN_CONFIDENCE = 0.8 # only keep detections the bridge is at least 80% sure about def draw_faces(frame, faces): """Draw each face with its own color, an ID label, and its keypoints.""" h_px, w_px = frame.shape[:2] for i, face in enumerate(faces): color = FACE_COLORS[i % len(FACE_COLORS)] bbox = face["bbox"] if isinstance(bbox, dict): xmin = float(bbox["xmin"]) ymin = float(bbox["ymin"]) bw_n = float(bbox["width"]) bh_n = float(bbox["height"]) cx_n = xmin + bw_n / 2 cy_n = ymin + bh_n / 2 else: cx_n, cy_n, bw_n, bh_n = (float(v) for v in bbox) x1 = max(0, int((cx_n - bw_n / 2) * w_px)) y1 = max(0, int((cy_n - bh_n / 2) * h_px)) x2 = min(w_px - 1, int((cx_n + bw_n / 2) * w_px)) y2 = min(h_px - 1, int((cy_n + bh_n / 2) * h_px)) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) label = f"Face #{i + 1} {face['confidence']:.0%}" (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.55, 2) label_y1 = max(0, y1 - th - 10) cv2.rectangle(frame, (x1, label_y1), (x1 + tw + 8, y1), color, -1) cv2.putText(frame, label, (x1 + 4, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 0, 0), 2) kps = face.get("keypoints", []) kp_iter = kps.values() if isinstance(kps, dict) else kps for kp in kp_iter: if isinstance(kp, dict): kx_n, ky_n = float(kp["x"]), float(kp["y"]) else: kx_n, ky_n = float(kp[0]), float(kp[1]) kx, ky = int(kx_n * w_px), int(ky_n * h_px) cv2.circle(frame, (kx, ky), 3, color, -1) return frame def draw_hud(frame, faces, fps, flash_frames_left): """Translucent stats panel (face count + FPS) plus a brief border flash whenever the face count just changed.""" h_px, w_px = frame.shape[:2] panel_w, panel_h = 210, 66 overlay = frame.copy() cv2.rectangle(overlay, (10, 10), (10 + panel_w, 10 + panel_h), (0, 0, 0), -1) frame = cv2.addWeighted(overlay, 0.55, frame, 0.45, 0) cv2.putText(frame, f"Faces: {len(faces)}", (22, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.85, (0, 255, 0), 2) cv2.putText(frame, f"FPS: {fps:.1f}", (22, 64), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (180, 180, 180), 1) if flash_frames_left > 0: border_color = (0, 255, 0) if len(faces) > 0 else (0, 0, 255) cv2.rectangle(frame, (2, 2), (w_px - 3, h_px - 3), border_color, 4) return frame with BonicBot(host='[IP_ADDRESS]') as bot: print("📷 Starting camera and streaming...") bot.system.start_camera() bot.start_camera() bot.camera.wait_for_image(timeout=5.0) print("🧠 Enabling face detection (BonicBot Bridge)...") bot.enable_detection('face') print("✅ Live Stream Active. Press 'q' to quit.\n") prev_time = time.time() fps = 0.0 last_count = -1 flash_frames_left = 0 try: while True: frame = bot.get_image() if frame is not None: faces = [f for f in bot.get_faces() if f.get("confidence", 1.0) >= MIN_CONFIDENCE] now = time.time() dt = now - prev_time prev_time = now if dt > 0: fps = fps * 0.9 + (1.0 / dt) * 0.1 if len(faces) != last_count: ts = time.strftime("%H:%M:%S") if len(faces) == 0: print(f"[{ts}] ⚪ No face in view") else: confs = ", ".join(f"{f['confidence']:.0%}" for f in faces) print(f"[{ts}] 🙂 {len(faces)} face(s) in view ({confs})") last_count = len(faces) flash_frames_left = FLASH_DURATION if flash_frames_left > 0: flash_frames_left -= 1 display = draw_faces(frame.copy(), faces) display = draw_hud(display, faces, fps, flash_frames_left) cv2.imshow("BonicBot Vision", display) if cv2.waitKey(1) & 0xFF == ord("q"): break finally: bot.disable_detection() cv2.destroyAllWindows() bot.stop_camera() bot.system.stop_camera()

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

  • Importstime handles timestamps and FPS calculation; cv2 (OpenCV) draws boxes, labels, landmarks, and the HUD; numpy backs the image arrays; BonicBot is the SDK class used to connect to the robot.
  • FACE_COLORS — A list of six distinct BGR colors. Each detected face is assigned one via i % len(FACE_COLORS), so colors repeat if more than six faces are visible at once.
  • FLASH_DURATION / MIN_CONFIDENCE — Two tunable constants: FLASH_DURATION controls how many frames the border-flash effect lasts after the face count changes, and MIN_CONFIDENCE filters out any detection the model is less than 80% sure about.
  • draw_faces(frame, faces) — For each face, reads its bounding box (handling both a {xmin, ymin, width, height} dict format and a plain [cx, cy, w, h] list format), converts normalized coordinates to pixels, and draws a colored rectangle, a filled label background, and the face number with confidence. It then loops over the face’s keypoints (eyes, nose, mouth, ears) and draws a small dot for each.
  • draw_hud(frame, faces, fps, flash_frames_left) — Draws a semi-transparent black panel in the top-left corner using cv2.addWeighted, then writes the current face count and FPS on top of it. If flash_frames_left is greater than zero, it also draws a colored border around the whole frame — green if faces are present, red if the count just dropped to zero.
  • with BonicBot(host='[IP_ADDRESS]') as bot: — Opens a connection to your robot, automatically closing it when the block ends.
  • bot.system.start_camera() / bot.start_camera() / bot.camera.wait_for_image(...) — Powers on the camera, starts streaming, and waits (up to 5 seconds) for the first frame to arrive.
  • bot.enable_detection('face') — Switches the robot’s vision pipeline from general object detection to the dedicated face-detection model.
  • FPS tracking (prev_time, dt, fps = fps * 0.9 + ... * 0.1) — Measures the time between frames and smooths the frames-per-second estimate using an exponential moving average, so the displayed FPS doesn’t jitter wildly frame to frame.
  • Change-triggered logging (if len(faces) != last_count:) — Compares the current face count to the last printed count. A line is only printed to the terminal when that count actually changes, keeping the output readable instead of flooding it every frame.
  • flash_frames_left — Set to FLASH_DURATION whenever the face count changes, then counted down each loop iteration; this is what drives the temporary border-flash effect in draw_hud.
  • Main loop — Grabs a frame, gets filtered faces, updates FPS and logging, draws faces and the HUD onto a copy of the frame, and shows it in the “BonicBot Vision” window.
  • cv2.waitKey(1) & 0xFF == ord("q") — Checks each iteration whether q was pressed, breaking the loop if so.
  • finally: cleanup block — Runs whether the loop exits normally or due to an error, ensuring face detection is disabled, the display window is closed, and the camera is stopped every time.

Expected Output

Click to see expected output

Visual Output:

Terminal Output:

📷 Starting vision system... 🧠 Loading face detection model... ✅ Vision system ready. [14:08:23] 🙂 2 face(s) in view (99%, 96%)

The BonicBot vision window will display:

  • A colored box around every detected face
  • Face numbers (Face #1, Face #2, …)
  • Detection confidence
  • Six facial landmarks for each face
  • A live face counter
  • Current processing speed (FPS)
  • A brief border flash whenever the number of visible faces changes

🔧 Under the Hood

How does BonicBot detect faces?

Every image received by BonicBot’s vision system is analyzed using MediaPipe Face Detection, a lightweight deep learning model designed for real-time robotics applications.

For every face the model finds, it predicts:

  • a bounding box
  • a confidence score
  • six facial landmark points

The program converts these predictions into positions on the robot’s display, where each face receives:

  • its own colored bounding box
  • a unique face number
  • confidence information
  • landmark markers

The robot also maintains a small dashboard showing:

  • the current number of detected faces
  • the processing speed (Frames Per Second)

To avoid flooding the terminal with repeated messages, BonicBot only prints an event when the number of visible faces changes. This event-based logging is commonly used in robotics because it highlights important changes instead of repeating the same information hundreds of times every second.


Student Challenge

Modify the program so BonicBot also displays the center position of each detected face underneath its label.

For example:

Face #1 (315, 184)

This helps visualize exactly where BonicBot believes each face is located.

Hint

The bounding box already contains the normalized center coordinates (cx_n, cy_n). Convert them into pixel coordinates using the display width and height before drawing them with cv2.putText().


Reflection Question

Imagine BonicBot is welcoming students into a classroom.

Why is teaching BonicBot to detect a face much simpler and safer than teaching it to recognize your specific face?

Last updated on