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.
169 lines
6.0 KiB
Markdown
169 lines
6.0 KiB
Markdown
# 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.
|