refactor(pose_3d): rimuovi agent/chat, aggiungi cloud+diagnostics+camera polso
Rimosso il modulo agent (LLM chat) e la relativa config OpenRouter. La tab Pose 3D ora: espone i giunti reali via G1 /diagnostics per specchiare l'URDF senza IK; acquisisce point cloud Nuwa+D435i (/api/pose_3d/clouds); mostra il modello 3D della camera sul polso; degrada gracefully a 200 (wrist=null) quando il robot è offline. Il vecchio renderer canvas-2D (app.js view3d + diagnostic3d.js) è stato consolidato nel viewport Three.js di pose_3d.js. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9d30073d57
commit
6c2452fe20
20 files changed
+152757
-2714
No files matched your search
@@ -0,0 +1,144 @@
|
||||
# Bug report — D435i camera service: intrinsics mismatch nel back-projection 3D
|
||||
|
||||
**Servizio:** `front_camera` (RealSense D435i) — `http://192.168.123.99:8878`
|
||||
**Severità:** alta — punti 3D ritornati con XY sovrastimati di ~54%
|
||||
**Endpoint affetti:** almeno `/api/measure`; molto probabilmente anche `/api/pointcloud` e `/api/pointcloud/roi`
|
||||
**Endpoint NON affetti:** `/api/intrinsics` (espone i parametri corretti)
|
||||
**Data test:** 2026-05-27
|
||||
**Reporter:** sviluppo presa-mano
|
||||
|
||||
---
|
||||
|
||||
## 1. Sintomo
|
||||
|
||||
Le coordinate 3D `(X, Y, Z)` ritornate dal back-projection lato server sono **stirate** lateralmente di un fattore **~1.54** sugli assi X e Y, mentre Z (depth) è plausibile.
|
||||
|
||||
Conseguenza: oggetti di dimensioni note appaiono ~50% più grandi del reale in qualsiasi misurazione 3D, e i point cloud non si sovrappongono ad altre camere correttamente calibrate.
|
||||
|
||||
---
|
||||
|
||||
## 2. Test di verifica
|
||||
|
||||
Marker ArUco fisico di **lato 103.0 mm** (`id=20`, dictionary DICT_4X4_50) posizionato davanti al D435i a ~735 mm.
|
||||
|
||||
### 2.1 Intrinsics riportate dal servizio (`GET /api/intrinsics`)
|
||||
|
||||
```json
|
||||
{
|
||||
"ir": { "fx": 392.30963134765625, "fy": 392.30963134765625,
|
||||
"cx": 318.48004150390625, "cy": 242.43441772460938 },
|
||||
"rgb": { "fx": 603.88037109375, "fy": 603.6130981445312,
|
||||
"cx": 320.6182556152344, "cy": 250.35804748535156 },
|
||||
"extrinsics_ir_to_rgb": { "rotation": [...], "translation": [14.68, -0.07, 0.29] }
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Detect del marker (su RGB image del D435i)
|
||||
|
||||
Marker-finder ritorna le 4 corner del tag nel sistema **pixel del frame RGB**:
|
||||
|
||||
```
|
||||
corners (RGB px): [(377, 68), (372, 152), (289, 147), (294, 63)]
|
||||
```
|
||||
|
||||
### 2.3 `GET /api/measure?u=&v=&radius=3` per ciascuna corner
|
||||
|
||||
```
|
||||
pixel (377, 68) → x=+0.1092 y=-0.3255 z=0.732 m
|
||||
pixel (372, 152) → x=+0.0999 y=-0.1687 z=0.732 m
|
||||
pixel (289, 147) → x=-0.0554 y=-0.1793 z=0.737 m
|
||||
pixel (294, 63) → x=-0.0461 y=-0.3375 z=0.738 m
|
||||
```
|
||||
|
||||
### 2.4 Misurazioni geometriche dai punti 3D ritornati
|
||||
|
||||
| Misura | Atteso | Ottenuto | Errore |
|
||||
|-----------------|---------------|---------------|----------|
|
||||
| Lato C0–C1 | 103.00 mm | 157.13 mm | **+52.6%** |
|
||||
| Lato C1–C2 | 103.00 mm | 155.71 mm | **+51.2%** |
|
||||
| Lato C2–C3 | 103.00 mm | 158.48 mm | **+53.9%** |
|
||||
| Lato C3–C0 | 103.00 mm | 155.92 mm | **+51.4%** |
|
||||
| Diagonale 0–2 | 145.66 mm | 220.19 mm | **+51.2%** |
|
||||
| Diagonale 1–3 | 145.66 mm | 223.32 mm | **+53.3%** |
|
||||
| Z medio (depth) | — | 735 mm | plausibile |
|
||||
|
||||
L'errore è **omogeneo ~52%** su tutte le coppie. Z (depth) è coerente — l'errore è confinato a X, Y.
|
||||
|
||||
---
|
||||
|
||||
## 3. Diagnosi (root cause)
|
||||
|
||||
Il rapporto fra le focali è esattamente:
|
||||
|
||||
```
|
||||
fx_rgb / fx_ir = 603.88037 / 392.30963 = 1.5393
|
||||
```
|
||||
|
||||
L'errore osservato sui lati è **1.524 ± 0.014**.
|
||||
|
||||
Verifica numerica della corner `(u=377, v=68)`, depth `z=0.732 m`:
|
||||
|
||||
```
|
||||
Con RGB intrinsics (corretto): X = (377 − 320.62) × 0.732 / 603.88 = 0.0683 m
|
||||
Con IR intrinsics (errato): X = (377 − 318.48) × 0.732 / 392.31 = 0.1091 m
|
||||
Server ritorna: = 0.1092 m ← coincide con IR
|
||||
```
|
||||
|
||||
**Conclusione:** il servizio sta usando le **intrinsics IR** (`fx=392.31, cx=318.48`) per back-projettare pixel che sono in **coordinate RGB** (allineamento depth-to-color attivo). Il risultato è X e Y inflati di `fx_rgb/fx_ir ≈ 1.54×`.
|
||||
|
||||
Il Z è corretto perché dipende solo dal valore di depth letto al pixel, non dalle intrinsics.
|
||||
|
||||
---
|
||||
|
||||
## 4. Fix consigliato
|
||||
|
||||
Nella routine di back-projection (probabilmente in `/api/measure`, `/api/pointcloud`, `/api/pointcloud/roi`):
|
||||
|
||||
```python
|
||||
# ERRATO (sospetto attuale):
|
||||
X = (u - cx_ir) * z / fx_ir
|
||||
Y = (v - cy_ir) * z / fy_ir
|
||||
|
||||
# CORRETTO (depth allineata al frame color → usa RGB intrinsics):
|
||||
X = (u - cx_rgb) * z / fx_rgb
|
||||
Y = (v - cy_rgb) * z / fy_rgb
|
||||
```
|
||||
|
||||
Se la pipeline NON sta facendo `align_to(color)` sul depth stream, allora il fix opposto: i pixel passati devono essere quelli del frame IR e si usano le intrinsics IR. Ma la semantica attuale (`/api/rgb` espone il frame color, `/api/measure` accetta `(u,v)` con range coerente col color frame ~640×480) suggerisce che l'API è progettata per RGB-aligned depth — quindi è il back-projection lato server che ha l'intrinsics sbagliata.
|
||||
|
||||
---
|
||||
|
||||
## 5. Test di non-regressione consigliati
|
||||
|
||||
Dopo il fix, verificare che:
|
||||
|
||||
1. **Tag di dimensione nota** (es. 103mm): le 4 corner back-proiettate formano un quadrato con lato `103.00 ± 5 mm` a 0.7m di distanza.
|
||||
2. **Coerenza con `/api/intrinsics`**: per ogni pixel `(u, v)` con depth `z`, vale:
|
||||
```
|
||||
x_returned ≈ (u - cx_rgb) * z / fx_rgb (entro tolleranza distorsione)
|
||||
y_returned ≈ (v - cy_rgb) * z / fy_rgb
|
||||
```
|
||||
3. **Cross-camera consistency**: il point cloud del D435i di un oggetto sovrapposto al cloud Nuwa (eye-in-hand polso DX) dello stesso oggetto deve avere errore residuo `< 30 mm` dopo hand-eye calibration.
|
||||
|
||||
---
|
||||
|
||||
## 6. Workaround temporaneo (lato client)
|
||||
|
||||
Nel viewport 3D di presa-mano è stato aggiunto un campo `D435i scale XY` con default **0.65** (`= fx_ir / fx_rgb`) che riscala il cloud per compensare il bug. Da rimuovere/portare a 1.0 quando il server è fixato.
|
||||
|
||||
---
|
||||
|
||||
## 7. Comandi per riprodurre
|
||||
|
||||
```bash
|
||||
# 1) Verifica intrinsics
|
||||
curl -sS http://192.168.123.99:8878/api/intrinsics
|
||||
|
||||
# 2) Detect marker (presuppone marker-finder a :8082 e tag 20 visibile)
|
||||
curl -sS -X POST http://localhost:8082/detect \
|
||||
-F "image_url=http://192.168.123.99:8878/api/rgb" \
|
||||
-F 'markers=[{"id":20,"size_mm":103}]'
|
||||
|
||||
# 3) Per ogni corner (u,v) ritornata, misura 3D
|
||||
curl -sS "http://192.168.123.99:8878/api/measure?u=377&v=68&radius=3"
|
||||
```
|
||||
Reference in new issue
Block a user