Perception Layer – Runtime & MQTT¶
This page describes how the perception runtime loop works and how it communicates with other layers over MQTT.
Initialization (main.py)¶
On startup, main.py:
- Logging & paths
- Initializes a shared logger via
LoggersFactory.getLogger(). -
Resolves
base_dir(project root) andUSER_PROFILES_DIR(interaction/db/memory/user_profiles/). -
Configuration
- Loads
perception/config.yamlviaload_perception_config(). -
Extracts
_mqtt_cfg,_yolo_cfg,_sm_cfgfor MQTT, YOLO, and selective memory. -
MQTT client
- Creates a
paho.mqtt.client.Client, assigns the callbacks, then connects to_mqtt_cfg.primary_hostor_mqtt_cfg.fallback_hostand starts the network loop in its own thread. - Subscribes to:
nadine/face_recognition/user_info– user info for face storage.nadine/perception/capture_current_view– request for a one‑shot camera snapshot.nadine/affect/state– Nadine's current affect, published by the interaction layer after each affective appraisal; drives the default memory policy.
-
Handlers only update shared variables; the camera pipeline is never touched from the MQTT thread.
-
RealSense camera
- Starts a RealSense
pipelinewith:- Color stream:
640x480, 30 fps, BGR. - Depth stream:
640x480, 30 fps.
- Color stream:
- Uses
rs.align(rs.stream.color)so depth aligns with color. -
If the camera fails to start (not connected, or claimed by another process), the process logs the error and exits.
-
Models
- YOLO face detector:
model_face = YOLO(_yolo_cfg["model_path"], ...). -
InsightFace:
FaceAnalysis(name="buffalo_s")with the detection and recognition modules,det_size=(640, 640). -
Face database
-
Calls
load_known_faces(face_analyzer):- Iterates over each user in
USER_PROFILES_DIR. - Loads existing face embeddings or derives them from stored face images.
- Iterates over each user in
-
Selective memory
- Reads
selective_memory.policyfrom the config. pad_arousal(default): creates aPADArousalMemoryModulewitharousal_threshold(config value 0.25; code fallback 0.3). Loads only CLIP and Moondream2.vision: creates aSelectiveMemoryModulewith the OpenFace weights fromperception/weights/and the emotion/novelty parameters from_sm_cfg.
Per-Frame Loop¶
The main while True loop does, for each frame, the following five phases:
Capture and detect¶
Grab an aligned color/depth pair and find every face in it.
- Capture frames
- Wait for frames from RealSense with a 5 s timeout. On timeout the loop logs a warning and retries; it does not exit.
- Align depth to color and extract:
color_frame→color_image(BGRnumpyarray).depth_frame→depth_image(depth map).
-
If a snapshot was requested over MQTT, save the current frame to the requested path now.
-
YOLO face detection & tracking
- Run
model_face.track(color_image, ..., conf=0.75). - For each detected face, collect:
- Bounding box
(x1, y1, x2, y2). - Tracker ID.
- Depth at the center of the box from
depth_frame.
- Bounding box
Track and recognize¶
Choose the person to attend to and try to identify them.
- Active user selection
- Among all detected faces, pick the one with minimum positive depth as the “active user”.
- Maintain
tracked_user_idand only update it if:- The closest face changes, or
- More than ~2.5 seconds have passed since the last update.
-
If no face has been seen for 5 s (
NO_FACE_TIMEOUT), clear the tracked user and, if a user had been announced, publishnadine/graph/user_detectedwith{"user_name": null, "confidence": 0.0, "user_id": null}so the interaction layer knows nobody is present. -
Face recognition
- If a
tracked_user_idis set:- Run InsightFace detection on the full frame (
face_analyzer.get(color_image)). - For each InsightFace detection, compute IoU with the YOLO bbox of the tracked user and keep the best match; it must have IoU above 0.2.
- If that face has an embedding, compute cosine similarities to all
known_embeddings. confidenceis reported as(best_similarity + 1) / 2 * 100, i.e. a percentage.- If the best similarity exceeds 0.3, set
nameanduser_id; otherwise the user stays"Unknown".
- Run InsightFace detection on the full frame (
Publish position and identity¶
Tell the control layer where to look and the interaction layer who is there.
- 3D position estimation
- For the tracked face, compute:
- Center pixel
(cx, cy)from the bounding box. - Depth at
(cx, cy)fromdepth_frame. - 3D coordinates
coords_3d = rs2_deproject_pixel_to_point(...).
- Center pixel
-
If coordinates are non‑zero, publish:
- Topic:
nadine/agent/control/look_at - Payload:
{"x": coords_3d[0], "y": coords_3d[1], "z": coords_3d[2]}
- Topic:
-
User identity updates
- Every 5 seconds, or when the recognized name changes, publish:
- Topic:
nadine/graph/user_detected - Payload:
{"user_name": name, "confidence": confidence, "user_id": user_id}
- Topic:
Selective memory¶
Decide whether the current scene is worth keeping.
- Selective memory hook
- Runs only for recognized users (
user_idnotNone), at most once permemorability_check_intervalseconds per user (config 3.0 s; code fallback 2.0 s), on a copy of the frame taken before anything is drawn on it. pad_arousalpolicy (default): read the latestnadine/affect/statevalues; computememorability = base[emotion_label] × intensity; store ifshould_storeis true (memorability ≥arousal_threshold, or the user has no stored scenes yet).visionpolicy: run OpenFace on a padded face crop, compute a CLIP embedding of the frame, and combine emotion salience and novelty into a memorability score; store if it is ≥memorability_threshold.- Both policies then apply a 15 s per-user cooldown (
SCENE_STORE_COOLDOWN): if a scene was stored for this user less than 15 s ago, the store is skipped. - On a store, a Moondream2 description is generated, the scene is written (see the Selective Memory page), and
nadine/memory/scene_storedis published.
Display and storage¶
Draw the debug view and persist new face data when asked.
- Visualization
- For the tracked face only, draw the bounding box, the label
name (confidence%), and the 3D coordinates. -
Show the window
"Face & 3D Tracking". -
Face storage
- When
nadine/face_recognition/user_infohas been received and the tracked face has an InsightFace match:- At most one face per user every 10 s (
FACE_STORE_COOLDOWN), and only while the user has fewer than 3 stored face images. store_face_for_user(...)writes the face crop and its embedding and publishesnadine/graph/face_stored.- Set a flag to reload
known_embeddingson the next loop.
- At most one face per user every 10 s (
On exit (key q or exception), the pipeline is stopped, windows are closed, and the MQTT loop is stopped.
MQTT Topics (Runtime Summary)¶
Subscribed
nadine/face_recognition/user_info- Payload:
{"user_name": "...", "user_id": "..."} -
Purpose: instruct perception to store a face image + embedding for this user.
-
nadine/perception/capture_current_view - Payload: file path string
-
Purpose: capture a single RGB frame and save it to the given path.
-
nadine/affect/state - Payload:
{"label": str, "arousal": float, "intensity": float} - Published by the interaction layer's affective appraisal node after every turn. The
pad_arousalmemory policy useslabelandintensityto decide whether the current scene is memorable.
Published
nadine/agent/control/look_at- Payload:
{"x": float, "y": float, "z": float} -
Used by the control layer to orient Nadine’s head/eyes toward the user.
-
nadine/graph/user_detected - Payload:
{"user_name": str, "confidence": float, "user_id": Optional[str]} -
Used by the interaction layer to know who is in front of Nadine. All three fields are
null/0.0when the tracked user has been absent for 5 s. -
nadine/graph/face_stored - Payload:
{"user_name": str, "user_id": str, "status": "face_stored"} -
Emitted after new face data has been persisted.
-
nadine/memory/scene_stored - Payload:
{"user_id": str, "user_name": str, "memorability": float, "emotion_label": str, "intensity": float, "description": str} - Under
pad_arousal,emotion_labelandintensityare the affect values that triggered the store. Undervision,emotion_labelis the dominant facial emotion andintensityis the emotion salience. - No component in the interaction layer subscribes to this topic at present; it is available for monitoring and future use.
Utilities¶
Perception reuses utilities from perception/utils.py:
LoggersFactory– central logger for all perception logs.user_info_init– initializes defaultuser_info.jsoncontent for new users.