Files
RealSense-D435i-G1/README.md
T
robot 07d95792e3 Initial commit: RealSense D435 API server
Drop-in replacement for the Nuwa-HP60C camera container, exposing RGB +
aligned depth + intrinsics via REST/WebSocket so the existing
pestello-pose-detector pipeline can consume frames without changes.

Key adaptations for D4xx on USB 2.0 (this robot has no USB 3 port exposed
externally — see STATUS.md):
- pyrealsense2 built from source; the librealsense v2.55.1 install rule
  doesn't ship the main .so, so we copy it from the build dir and add an
  __init__.py that re-exports the submodule.
- Color stream uses YUYV format on USB 2.x (BGR8/RGB8 time out on this
  bus); converted to BGR via cv2.cvtColor in capture().
- rs.align is skipped on USB 2 (incompatible with YUYV); color is
  cv2-resized to depth shape for approximate per-pixel alignment.
- Driver auto-detects usb_type_descriptor and falls back to the
  full-resolution BGR8 + rs.align pipeline when on USB 3.

Tested: /api/{health,status,discover,rgb,depth,depth/color,capture,
intrinsics} all return 200 with valid payloads at 640x480.
2026-05-19 00:58:23 +02:00

169 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Intel RealSense D435 — API Server
Server FastAPI per la depth camera Intel RealSense D4xx (testato su D435)
con accesso remoto via REST/WebSocket. Esecuzione tramite Docker. Mantiene
la stessa shape API del servizio Nuwa HP60C (envelope `ApiResponse`,
endpoint `/api/...`, header `X-Api-Key`, WebSocket `/ws`) così che i client
e il frontend React esistenti funzionano senza modifiche.
## Caratteristiche
- **RGB**: configurabile (default 640x480) BGR8, allineato al depth
- **Depth**: 640x480 uint16 in millimetri (allineato al color via `rs.align`)
- **Intrinsics**: parametri intrinseci color + depth e estrinseci depth→color
- **Discovery**: enumerazione devices via `pyrealsense2`
- **API Server**: REST + WebSocket con envelope `ApiResponse` standard
- **Sicurezza**: API Key opzionale (`X-Api-Key`) su tutti gli endpoint REST
- **NATS**: messaging asincrono opzionale (profilo `nats`)
- **Frontend React**: viewer RGB, depth e 3D point cloud (riusato dal Nuwa)
- **Swagger**: documentazione interattiva API a `/apidocs`
## Hardware
| Parametro | Valore |
|-----------|--------|
| Camera | Intel RealSense D435 |
| Serial number (questo robot) | `933322070696` |
| Firmware | `05.16.00.01` |
| Range depth | 0.1m – 10m |
| Interfaccia | USB 3.0 |
| SDK | librealsense 2.55.1 + pyrealsense2 (built from source) |
| Host | Jetson Xavier NX, Ubuntu 20.04 (JetPack 5.1.x) |
## Configurazione
Crea `.env` (vedi `.env.example`):
```env
API_PORT=8878
API_KEY=cambia-questa-chiave-segreta
CAMERA_WIDTH=640
CAMERA_HEIGHT=480
CAMERA_FPS=30
CAMERA_AUTO_OPEN=1 # apre la camera al boot, niente /api/open manuale
CORS_ORIGINS=* # LAN-only OK
```
## Accesso dalla LAN
Il container gira sul robot e binda la porta `8878` su tutte le interfacce
(`0.0.0.0`). Dal vostro PC sulla stessa LAN raggiungete il servizio via:
```
http://<ip-robot>:8878/api/health # health check
http://<ip-robot>:8878/apidocs # Swagger UI
http://<ip-robot>:8878/gui # frontend React
ws://<ip-robot>:8878/ws # stream WebSocket
```
Il robot ha Avahi attivo, quindi `ubuntu.local:8878` funziona se mDNS
risolve sul vostro PC (`avahi-resolve -n ubuntu.local`). Altrimenti
usate l'IP diretto (`ip a` sul robot per scoprirlo).
> Lo streaming **non richiede internet** — solo la build (~25 min al primo
> avvio) deve raggiungere registry Docker/apt/PyPI/npm via il tinyproxy
> locale del robot.
## Build & avvio
```bash
# Backend (porta 8878)
docker compose up --build -d
# Backend + NATS
docker compose --profile nats up --build -d
```
> Il primo build compila i bindings Python di `pyrealsense2` da source —
> ~20–30 min su Jetson. I build successivi sono cached.
Il container:
- non fa unbind UVC (a differenza del Nuwa) — RealSense usa direttamente uvcvideo
- monta `/dev/bus/usb` in privileged mode per accesso diretto USB
- riparte automaticamente (`restart: unless-stopped`)
## Endpoints
Identici al Nuwa per drop-in compatibility:
| Endpoint | Metodo | Descrizione |
|----------|--------|-------------|
| `/api/health` | GET | Health check (no auth) |
| `/api/status` | GET | Stato SDK/camera e calibrazione |
| `/api/discover` | GET | Rileva camere RealSense su USB |
| `/api/open` | POST | Apre camera per indice (`device_index`) |
| `/api/close` | POST | Chiude la camera |
| `/api/capture` | GET | Frame RGB+depth (JSON, base64) |
| `/api/rgb` | GET | Frame RGB come JPEG |
| `/api/depth` | GET | Frame depth come PNG 16-bit (mm) |
| `/api/depth/color` | GET | Depth colormap JET come JPEG |
| `/api/intrinsics` | GET | Calibrazione completa |
| `/api/pointcloud` | GET | Point cloud (subsampling `step`) |
| `/api/pointcloud/roi` | GET | Point cloud su ROI pixel-space |
| `/api/measure` | GET | Backprojection 3D di un pixel |
| `/api/snapshot` | POST | Salva snapshot RGB |
| `/api/recording/{start,stop,status}` | POST/GET | Registrazione continua |
| `/ws` | WS | Streaming WebSocket (text msgs: `start`, `stop`, `close`) |
#### Payload calibrazione
`/api/intrinsics` ritorna i campi `ir` e `rgb` per compatibilità con il
frontend Nuwa, ma in questo servizio:
- `ir` = intrinseci del **sensore depth** (post-rectification dello stereo IR)
- `rgb` = intrinseci del **sensore color**
- `extrinsics_ir_to_rgb` = trasformazione **depth → color**
```json
{
"ir": { "fx": ..., "fy": ..., "cx": ..., "cy": ...,
"distortion": { "k1":..., "k2":..., "k3":..., "p1":..., "p2":... } },
"rgb": { "fx": ..., "fy": ..., "cx": ..., "cy": ...,
"distortion": { ... } },
"extrinsics_ir_to_rgb": {
"rotation": [r00, r01, r02, r10, r11, r12, r20, r21, r22],
"translation": [tx, ty, tz]
}
}
```
- Rotation row-major 3x3
- Translation in **millimetri** (la D4xx ritorna `extrinsics.translation` in
metri; convertito qui per allinearsi alla convenzione Nuwa)
- D4xx pubblica tipicamente coefficienti di distorsione tutti zero (immagini
già rettificate dall'ASIC)
### Esempi curl
```bash
# Discover
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/discover
# Apri (D435 in genere index 0)
curl -X POST -H "X-Api-Key: $API_KEY" \
"http://localhost:8878/api/open?device_index=0"
# Frame RGB
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/rgb -o rgb.jpg
# Frame depth (PNG 16-bit, valori in mm)
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/depth -o depth.png
# Intrinsics
curl -H "X-Api-Key: $API_KEY" http://localhost:8878/api/intrinsics | jq
# Close
curl -X POST -H "X-Api-Key: $API_KEY" http://localhost:8878/api/close
```
## Note
- `librealsense2` runtime nel container è installato dal repo apt ufficiale
Intel (pinned a `2.55.1-0~realsense.3337`). I bindings Python sono
buildati da `IntelRealSense/librealsense@v2.55.1`.
- Il frontend React originale (`/gui`) referenzia `ir` e `rgb` come label di
display — su questo servizio quei nomi mappano depth/color (vedi sopra).
- L'autostart è gestito da Docker (`restart: unless-stopped`). Per partire
automaticamente al boot del robot serve `systemctl enable docker`.
- Per disabilitare l'auth API setta `API_KEY=` (vuoto). Sconsigliato in produzione.