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.
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
ApiResponsestandard - 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):
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
# 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
pyrealsense2da 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/usbin 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 colorextrinsics_ir_to_rgb= trasformazione depth → color
{
"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.translationin metri; convertito qui per allinearsi alla convenzione Nuwa) - D4xx pubblica tipicamente coefficienti di distorsione tutti zero (immagini già rettificate dall'ASIC)
Esempi curl
# 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
librealsense2runtime nel container è installato dal repo apt ufficiale Intel (pinned a2.55.1-0~realsense.3337). I bindings Python sono buildati daIntelRealSense/librealsense@v2.55.1.- Il frontend React originale (
/gui) referenziairergbcome 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 servesystemctl enable docker. - Per disabilitare l'auth API setta
API_KEY=(vuoto). Sconsigliato in produzione.