Files
Presa-Mano/docs/bug_d435i_intrinsics_mismatch.md
T
Adriano Dal PastroandClaude Opus 4.7 6c2452fe20 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>
2026-05-27 17:34:42 +02:00

145 lines
5.6 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.
# 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"
```