327 lines
10 KiB
Markdown
327 lines
10 KiB
Markdown
# 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/
|
|
├── core/ # detection + tracking + events (shared by CLI/GUI/bridge)
|
|
├── apps/ # CLI entry points (saqr, detect, train, manager, view_stream)
|
|
├── gui/ # PySide6 desktop GUI
|
|
├── robot/ # G1 bridge + DDS controller
|
|
├── utils/ # logger
|
|
├── scripts/ # start_saqr.sh (entry), saqr-bridge.service (systemd), deploy.sh
|
|
├── config/ # logging.json
|
|
├── data/ # dataset/, models/ (gitignored)
|
|
├── runtime/ # captures/, runs/ (gitignored)
|
|
├── logs/ # per-module .log files (gitignored)
|
|
├── docs/ # this file, start.md, use_case_catalogue.pdf
|
|
├── pyproject.toml # installs the package (no console scripts — use start_saqr.sh)
|
|
└── README.md
|
|
```
|
|
|
|
`core/`, `apps/`, `gui/`, `robot/`, and `utils/` are the importable Python
|
|
packages. Run them via `python -m apps.saqr_cli`, `python -m robot.bridge`,
|
|
etc. The project root is auto-detected from `core/paths.py`; override with
|
|
`SAQR_ROOT=/custom/path` if the tree lives elsewhere.
|
|
|
|
---
|
|
|
|
## Step 1: Train the Model (Dev Machine)
|
|
|
|
```bash
|
|
cd ~/Robotics_workspace/yslootahtech/Project/Saqr
|
|
conda activate AI_MSI_yolo
|
|
pip install -e .
|
|
python -m apps.train_cli --dataset data/dataset --epochs 100 --batch 16
|
|
```
|
|
|
|
Verify model exists:
|
|
```bash
|
|
ls -lh data/models/saqr_best.pt
|
|
# Expected: ~5.3 MB
|
|
```
|
|
|
|
---
|
|
|
|
## Step 2: Deploy to Robot (Dev Machine)
|
|
|
|
```bash
|
|
# 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 `core/`, `apps/`, `gui/`, `robot/`, `utils/`, `scripts/`,
|
|
`config/`, `docs/`, and `pyproject.toml` 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:
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
sudo date -s "2026-04-10 15:00:00"
|
|
```
|
|
|
|
### Verify
|
|
|
|
```bash
|
|
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 core.paths; print('project root:', core.paths.PROJECT_ROOT)"
|
|
```
|
|
|
|
---
|
|
|
|
## Step 4: Run Saqr (Robot)
|
|
|
|
**The project has one entry point: [scripts/start_saqr.sh](../scripts/start_saqr.sh).**
|
|
Everything — conda activation, DDS iface, camera source, stream port — is
|
|
read from `config/robot_config.json.start_saqr` (env vars override).
|
|
|
|
```bash
|
|
cd ~/Saqr
|
|
scripts/start_saqr.sh # manual (foreground, Ctrl+C to stop)
|
|
sudo systemctl restart saqr-bridge # systemd-owned (see start.md)
|
|
```
|
|
|
|
The launcher execs `python -m robot.bridge` with the production flags.
|
|
The bridge owns the DDS clients and spawns `python -m apps.saqr_cli` as a
|
|
subprocess when R2+X is pressed.
|
|
|
|
### Overrides (no config edit)
|
|
|
|
```bash
|
|
SAQR_SOURCE=/dev/video2 scripts/start_saqr.sh # V4L2 fallback
|
|
CONDA_ENV=teleimager scripts/start_saqr.sh
|
|
DDS_IFACE=wlan0 scripts/start_saqr.sh
|
|
STREAM_PORT=9090 scripts/start_saqr.sh
|
|
```
|
|
|
|
### Companion utilities (off the run path)
|
|
|
|
Operators occasionally need these on the robot. They're not part of the
|
|
normal run flow:
|
|
|
|
```bash
|
|
python -m apps.manager_cli --export # dump a CSV report
|
|
python -m apps.manager_cli # interactive photo CRUD
|
|
```
|
|
|
|
Training (dev machine only):
|
|
```bash
|
|
python -m apps.train_cli --epochs 100
|
|
```
|
|
|
|
---
|
|
|
|
## 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=0` is Chinese regardless of input text.
|
|
> `speaker_id=2` was confirmed English via `Project/Sanad/voice_example.py 6`.
|
|
|
|
> **Note on SIGINT traceback:** when R2+Y stops saqr, Python may print a
|
|
> `KeyboardInterrupt` traceback 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
|
|
|
|
```bash
|
|
cat runtime/captures/result.csv # current state per tracked person
|
|
cat runtime/captures/events.csv # audit log
|
|
ls runtime/captures/{SAFE,PARTIAL,UNSAFE}/
|
|
|
|
python -m apps.manager_cli --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:
|
|
```bash
|
|
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 | `logs/Inference/*.log` | Module log output |
|
|
|
|
---
|
|
|
|
## Source map
|
|
|
|
| Path | Purpose |
|
|
|------|---------|
|
|
| [apps/saqr_cli.py](../apps/saqr_cli.py) | Main PPE tracking entry (`saqr`) |
|
|
| [robot/bridge.py](../robot/bridge.py) | Saqr → G1 bridge (R2+X/R2+Y) |
|
|
| [robot/robot_controller.py](../robot/robot_controller.py) | G1 arm + audio + TTS worker |
|
|
| [robot/controller.py](../robot/controller.py) | G1 wireless-remote DDS reader |
|
|
| [core/pipeline.py](../core/pipeline.py) | Per-frame detect + track + emit |
|
|
| [core/tracking.py](../core/tracking.py) | `PersonTracker`, `Track` |
|
|
| [core/events.py](../core/events.py) | Event-line format (contract with bridge) |
|
|
| [apps/detect_cli.py](../apps/detect_cli.py) | Simple detection without tracking |
|
|
| [apps/train_cli.py](../apps/train_cli.py) | YOLO11n training |
|
|
| [apps/manager_cli.py](../apps/manager_cli.py) | Capture CRUD + CSV export |
|
|
| [gui/app.py](../gui/app.py) | PySide6 desktop GUI |
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### RealSense not detected
|
|
```bash
|
|
lsusb | grep Intel
|
|
rs-enumerate-devices | head -10
|
|
sudo usbreset /dev/bus/usb/002/002 # if the USB is stuck
|
|
```
|
|
|
|
### ModuleNotFoundError: saqr
|
|
```bash
|
|
# Make sure you're in the right env and the package is installed
|
|
which python
|
|
pip install -e ~/Saqr
|
|
```
|
|
|
|
### System clock wrong (SSL errors)
|
|
```bash
|
|
sudo date -s "2026-04-10 15:00:00"
|
|
```
|
|
|
|
### Model not found
|
|
```bash
|
|
ls ~/Saqr/data/models/ # should list saqr_best.pt
|
|
```
|
|
|
|
### Too many duplicate track IDs
|
|
```bash
|
|
saqr --source realsense --max-missing 150 --match-distance 300 --headless
|
|
```
|