Sonr — YOLO11 Talking Objects + MediaPipe Hands + Depth Anything v2 + OCR
Real-time accessibility vision pipeline. Runs Ultralytics YOLO11 with ByteTrack for object detection + persistent IDs, overlays MediaPipe hand landmarks, estimates monocular metric depth via Depth Anything v2 on a background thread, and speaks short scene descriptions via the macOS say command. On demand (key r), reads arbitrary text in the frame via EasyOCR.
python src/sonr.py [--model MODEL] [--source SOURCE] [--conf CONF] [--iou IOU] [--device DEVICE]
[--mirror | --no-mirror]
[--depth | --no-depth] [--metric-deadband M]
[--ocr | --no-ocr]
[--speak-interval SECONDS] [--per-item-cooldown SECONDS]
[--min-speak-hnorm HNORM]
[--min-stable N] [--hold-seconds SECONDS]
[--zone-deadband X] [--dist-deadband X]
[--no-show] [--no-tty-quit] [--watchdog SECONDS]
[--debug] [--log-every SECONDS] [--quiet-cooldowns]
Input / detection
--model(str, defaultyolo11n.pt): YOLO11 model path or name.--source(str, default0):0for the default webcam, an integer index for another, or a video file path.--conf(float, default0.75): YOLO confidence threshold.--iou(float, default0.5): NMS IoU threshold.--device(str | None): device hint (cpu,cuda,mps). If unset, ultralytics chooses; depth estimator auto-selects MPS on Apple Silicon.--mirror/--no-mirror(default on): horizontally flip the frame. On for selfie-view webcams (so hand handedness and left/right narration match the user). Off for video files or forward-facing cameras.
Distance (Depth Anything v2)
--depth/--no-depth(default on): use Depth Anything v2 metric-indoor for distance estimates. With--no-depth, falls back to a bbox-height heuristic.--metric-deadband(float, default0.3): hysteresis around metric distance bucket boundaries (meters). Prevents flapping between e.g. "close to you" and "nearby" when an object sits near a boundary.
Distance buckets in meters:
≤ 0.7 m→ "very close to you"≤ 1.5 m→ "close to you"≤ 3.0 m→ "nearby"≤ 6.0 m→ "in the distance"> 6.0 m→ "far away"
OCR
--ocr/--no-ocr(default on): enable on-demand text reading. Pressrduring the run to OCR the current frame and have the text spoken. Submitted on the unflipped raw frame so mirrored text (when--mirroris on) reads correctly.
Narration / TTS
--speak-interval(float, default2.0): minimum seconds between spoken utterances (anti-spam floor).--per-item-cooldown(float, default8.0): seconds before the sametrack_id(or(label, zone)for untracked detections) can be re-announced. Different items can speak in between, so the system stays responsive without nagging about the same chair every 2 seconds.--min-speak-hnorm(float, default0.05): ignore detections smaller than this normalized bbox height.
Stability / anti-flicker
--min-stable(int, default3): frames an object must persist before becoming "stable".--hold-seconds(float, default0.75): keep recently-seen objects alive briefly across frame drops.--zone-deadband(float, default0.06): hysteresis around left/center/right zone boundaries.--dist-deadband(float, default0.04): hysteresis around bbox-height distance boundaries (only used when--no-depth).
Display / runtime
--no-show(flag): disable the OpenCV preview window.--debug(flag): verbose logs (FPS, detections, TTS events, OCR events).--log-every(float, default1.0): heartbeat log period (seconds).--quiet-cooldowns(flag): suppress cooldown-related debug spam.--no-tty-quit(flag): ignore theqkey (ESC still quits).--watchdog(float, default0.0): exit if no frames arrive for this many seconds.0disables.
v— toggle voice on/offb— toggle bounding-box drawingh— toggle hand-landmark drawing/processingr— read text in the current frame (OCR). Speaks "No text found" if nothing detected within 5 s.qorESC— quit
A new phrase drains any pending queued phrases and is enqueued. The currently-speaking utterance is allowed to finish naturally (interrupting mid-word felt jarring during testing, and with phrases capped to ~2 seconds the worst-case lag is small enough to live with).
Phrase length is capped (top 3 items by importance, max 2 per zone) to keep individual utterances short. Pluralization is applied (person → people, cup → cups, etc.).
- Detection runs synchronously per frame in the main loop (~50 ms YOLO).
- Hand landmarks run synchronously per frame (~10–20 ms MediaPipe).
- Depth estimation runs on a background thread (
DepthWorker). Main loop submits the latest frame and reads whatever depth map is currently ready (~125 ms behind on M-series MPS). Until the first inference completes, the bbox-height heuristic is used as a transient fallback. - Text OCR runs on a background thread (
OcrWorker). User-triggered viar. Receives the unflipped raw frame so mirrored text reads correctly. - TTS runs on a background thread (
TTSWorker).
Net effect: main capture/detection loop runs at full YOLO speed; all heavier ML inference is decoupled.
- Dependency missing → import error and exit with install hint.
- Cannot open video source (bad path, webcam in use, permissions blocked) → early exit with an error.
- No frames received (camera freeze / bad stream) → may retry; may exit if
--watchdogis set. - HuggingFace download fails → depth or OCR may not initialize; the rest of the pipeline still runs.
Default webcam demo:
python src/sonr.py --source 0
Lower confidence to catch held objects:
python src/sonr.py --source 0 --conf 0.5
Video file, disable depth (faster), disable mirror (text/plates in scene aren't reversed):
python src/sonr.py --source clip.mp4 --no-mirror --no-depth
- macOS only: TTS uses the built-in
saycommand. - Performance on M-series Mac: ~15 fps preview with depth + plates enabled.
- The metric-indoor depth model saturates outdoors (~6-10 m); use
--no-depthif you'll be testing outside. - MediaPipe hand landmarks are pinned to
mediapipe==0.10.14because newer versions removed the legacysolutionsAPI. Seerequirements.txt.
All Python modules live in src/. The CLI script src/sonr.py orchestrates the following:
src/detection.py— YOLO11 wrapper with ByteTrack (Detector,Detectionwithtrack_id).src/geometry.py— left/center/right zone helpers with hysteresis.src/distance.py— distance bucketing (bbox-height fallback + metric depth).src/depth.py— Depth Anything v2 wrapper (DepthEstimator) and threaded worker (DepthWorker).src/tracking.py—StableTrackerfor frame-to-frame deduplication.src/narration.py— phrase composition with pluralization (SpeakItem,compose_phrase).src/speech.py— background-thread macOSsayworker (TTSWorker).src/hands.py— MediaPipe hand-landmark overlay (HandTracker).src/ocr.py— EasyOCR-based text reader (OcrWorker).