9.3 KiB
Saqr PPE Detection — Deployment Guide
Unitree G1 Robot + Intel RealSense D435I
Robot Details
| Item | Value |
|---|---|
| Robot | Unitree G1 Humanoid |
| IP | 192.168.123.164 |
| User | unitree |
| OS | Ubuntu 20.04 (aarch64 / Jetson) |
| Python | 3.10 (conda env: marcus, legacy: teleimager) |
| Camera | Intel RealSense D435I |
| Serial | 243622073459 |
| Port | USB 3.2 @ /dev/video0 |
Repo layout
saqr/ # python package (core/apps/gui/robot/utils)
scripts/ # deploy.sh, start_saqr.sh, run_local.sh, run_robot.sh, saqr-bridge.service
config/ # logging.json
data/ # dataset/, models/ (gitignored)
runtime/ # captures/, logs/, runs/ (gitignored)
docs/ # this file, start.md, use_case_catalogue.pdf
pyproject.toml # installs the `saqr`, `saqr-bridge`, `saqr-gui`, ... scripts
Step 1: Train the Model (Dev Machine)
cd ~/Robotics_workspace/yslootahtech/Project/Saqr
conda activate AI_MSI_yolo
pip install -e .
saqr-train --dataset data/dataset --epochs 100 --batch 16
Verify model exists:
ls -lh data/models/saqr_best.pt
# Expected: ~5.3 MB
Step 2: Deploy to Robot (Dev Machine)
# From the project root:
scripts/deploy.sh # rsync the tree + pip install -e
scripts/deploy.sh --run # ...and start the bridge
scripts/deploy.sh --ip 10.0.0.5 # custom robot IP
The script rsyncs saqr/, scripts/, config/, docs/,
pyproject.toml, requirements.txt, and README.md to
~/Saqr on the robot, then runs pip install -e . inside the
target conda env (default marcus).
Step 3: Install Dependencies (Robot, one-time)
If scripts/deploy.sh ran pip install -e . successfully, you're done.
Otherwise, on the robot:
ssh unitree@192.168.123.164
source ~/miniconda3/etc/profile.d/conda.sh
conda activate marcus
cd ~/Saqr
pip install -e .
Jetson GPU PyTorch (JetPack 5.1 / CUDA 11.4)
pip uninstall torch torchvision -y
pip install --no-cache-dir \
https://developer.download.nvidia.com/compute/redist/jp/v51/pytorch/torch-2.1.0a0+41361538.nv23.06-cp310-cp310-linux_aarch64.whl
pip install --no-cache-dir \
https://developer.download.nvidia.com/compute/redist/jp/v51/pytorch/torchvision-0.16.1a0+5e8e2f1-cp310-cp310-linux_aarch64.whl
System clock (pip/SSL need a correct date)
sudo date -s "2026-04-10 15:00:00"
Verify
python -c "from ultralytics import YOLO; print('ultralytics OK')"
python -c "import torch; print('CUDA:', torch.cuda.is_available())"
python -c "import cv2; print('opencv OK')"
python -c "import saqr; print('saqr', saqr.__version__)"
Step 4: Run Saqr (Robot)
Production: bridge with R2+X / R2+Y
The bridge owns the DDS clients and spawns saqr on demand. On the robot:
cd ~/Saqr
scripts/start_saqr.sh # manual launch
sudo systemctl restart saqr-bridge # systemd-managed (see start.md)
Or without the helper script:
conda activate marcus
python -m saqr.robot.bridge --iface eth0 --source realsense --headless -- --stream 8080
Plain saqr (no bridge)
# With display
scripts/run_robot.sh --stream 8080
# Headless
scripts/run_robot.sh --headless --stream 8080
# V4L2 fallback if RealSense SDK won't enumerate
scripts/run_robot.sh --source /dev/video2 --headless
Equivalent python -m forms:
python -m saqr.apps.saqr_cli --source realsense --model saqr_best.pt --headless
python -m saqr.apps.detect_cli --source /dev/video2 --model saqr_best.pt
python -m saqr.apps.manager_cli --export
Dev machine GUI
pip install -e ".[gui]"
python -m saqr.gui.app --source 0
Bridge behavior (R2+X / R2+Y)
| Press | Action |
|---|---|
| R2 + X | Start saqr subprocess. Robot stays silent (no "Saqr activated." — see note below). |
| R2 + Y | Stop saqr, robot says "Saqr deactivated." Bridge stays ready. |
| Ctrl+C in terminal | Stop saqr (if running) and exit the bridge. |
Per-detection behavior (while saqr is running)
| Transition | TTS (speaker_id=2, English) | Arm action |
|---|---|---|
| → UNSAFE | "Please stop. Wear your proper safety equipment. You are missing {items}." | reject (id=13) + auto release arm |
| → SAFE | "Safe to enter. Have a good day." | — |
| → PARTIAL | — | — |
Bridge startup announces "Saqr is running. Press R2 plus X to start.".
Note on speaker_id=2: TTS speaker_ids are locked to a language on the current G1 firmware.
speaker_id=0is Chinese regardless of input text.speaker_id=2was confirmed English viaProject/Sanad/voice_example.py 6.
Note on SIGINT traceback: when R2+Y stops saqr, Python may print a
KeyboardInterrupttraceback from inside YOLO. The bridge catches the exit, announces "Saqr deactivated.", and stays alive.
Bridge CLI flags
| Flag | Default | Description |
|---|---|---|
--iface |
(default DDS) | DDS network interface, e.g. eth0 |
--timeout |
10.0 |
Arm/Audio/LowState client timeout (seconds) |
--cooldown |
8.0 |
Per-(track_id, status) seconds before re-triggering TTS/arm |
--release-after |
2.0 |
Seconds before auto release arm (0 = never) |
--speaker-id |
2 |
G1 TtsMaker speaker_id |
--dry-run |
off | Parse events but never call the SDK; implies --no-trigger |
--no-trigger |
off | Skip the R2+X/R2+Y loop and start saqr immediately |
--source / --headless / --saqr-conf / --imgsz / --device |
— | Passed through to saqr |
-- <extra> |
— | Everything after -- is forwarded raw to saqr (e.g. -- --stream 8080) |
Step 5: Check Results
cat runtime/captures/result.csv # current state per tracked person
cat runtime/captures/events.csv # audit log
ls runtime/captures/{SAFE,PARTIAL,UNSAFE}/
saqr-manager --export # quick CSV export
# Download to dev machine
scp -r unitree@192.168.123.164:~/Saqr/runtime/captures ./captures_from_robot
Camera sources
| Source | Flag | Notes |
|---|---|---|
| RealSense SDK | --source realsense |
Uses pyrealsense2; preferred |
| RealSense by serial | --source realsense:243622073459 |
|
| V4L2 RGB | --source /dev/video2 |
OpenCV fallback; pure MJPG |
| First OpenCV camera | --source 0 |
|
| Video file | --source path.mp4 |
G1 V4L2 map for the D435I:
/dev/video0 - Stereo module (infrared) — won't open with OpenCV
/dev/video1 - Stereo metadata
/dev/video2 - RGB camera (640x480) ← USE THIS
/dev/video3 - RGB metadata
/dev/video4 - RGB camera (secondary stream)
Tuning parameters
| Parameter | Flag | Default |
|---|---|---|
| Confidence | --conf |
0.35 |
| Max missing frames | --max-missing |
90 |
| Match distance (px) | --match-distance |
250 |
| Confirm frames | --status-confirm-frames |
5 |
Recommended for G1 patrol:
saqr --source realsense --headless \
--conf 0.30 --max-missing 120 --match-distance 300 --status-confirm-frames 7
Compliance rules
| Status | Condition | Colour |
|---|---|---|
| SAFE | Helmet AND vest, no violations | Green |
| PARTIAL | Only helmet OR only vest | Yellow |
| UNSAFE | no-helmet or no-vest, or nothing detected |
Red |
Output files
| File | Location | Description |
|---|---|---|
result.csv |
runtime/captures/ |
Current state of tracked persons |
events.csv |
runtime/captures/ |
Audit log (NEW / STATUS_CHANGE) |
| Person crops | runtime/captures/{SAFE,PARTIAL,UNSAFE}/*.jpg |
Latest crop per track |
| Logs | runtime/logs/Inference/*.log |
Module log output |
Source map
| Path | Purpose |
|---|---|
| saqr/apps/saqr_cli.py | Main PPE tracking entry (saqr) |
| saqr/robot/bridge.py | Saqr → G1 bridge (R2+X/R2+Y) |
| saqr/robot/robot_controller.py | G1 arm + audio + TTS worker |
| saqr/robot/controller.py | G1 wireless-remote DDS reader |
| saqr/core/pipeline.py | Per-frame detect + track + emit |
| saqr/core/tracking.py | PersonTracker, Track |
| saqr/core/events.py | Event-line format (contract with bridge) |
| saqr/apps/detect_cli.py | Simple detection without tracking |
| saqr/apps/train_cli.py | YOLO11n training |
| saqr/apps/manager_cli.py | Capture CRUD + CSV export |
| saqr/gui/app.py | PySide6 desktop GUI |
Troubleshooting
RealSense not detected
lsusb | grep Intel
rs-enumerate-devices | head -10
sudo usbreset /dev/bus/usb/002/002 # if the USB is stuck
ModuleNotFoundError: saqr
# Make sure you're in the right env and the package is installed
which python
pip install -e ~/Saqr
System clock wrong (SSL errors)
sudo date -s "2026-04-10 15:00:00"
Model not found
ls ~/Saqr/data/models/ # should list saqr_best.pt
Too many duplicate track IDs
saqr --source realsense --max-missing 150 --match-distance 300 --headless