Calibrazione da una foto di scacchiera densa: distorsione e mm/px #84

Closed
opened 2026-10-23 21:44:18 +00:00 by Adriano · 4 comments
Owner

COSA: l'acquisitore (scripts/camera_service.py, scripts/calibrazione.py) calibra da UNA foto di scacchiera densa che riempie tutto il campo visivo; l'operatore dà solo il lato dello scacco in mm. Si ricava la mappa di distorsione (visionsuite.calibration.DistortionMap, adatta alle telecentriche) e i mm/px; le immagini date dall'acquisitore escono raddrizzate.
PERCHÉ: Adriano 23/10 sera, fiera A&T Vicenza 27-29/10: banco TC (ottica telecentrica) con camera Itala; demo DXF compare buono/scarto (call Menoncin 23/10).
CRITERIO DI FATTO: foto sintetica di scacchiera distorta che riempie il FOV → griglia trovata senza dare cols/rows, mm/px entro 0,5%, immagine raddrizzata con scarto residuo < 0,3 px; grab di una sorgente calibrata esce raddrizzato; test verdi.
RIF: estende gitea:Adriano/TieMeasureFlow#83.

COSA: l'acquisitore (scripts/camera_service.py, scripts/calibrazione.py) calibra da UNA foto di scacchiera densa che riempie tutto il campo visivo; l'operatore dà solo il lato dello scacco in mm. Si ricava la mappa di distorsione (visionsuite.calibration.DistortionMap, adatta alle telecentriche) e i mm/px; le immagini date dall'acquisitore escono raddrizzate. PERCHÉ: Adriano 23/10 sera, fiera A&T Vicenza 27-29/10: banco TC (ottica telecentrica) con camera Itala; demo DXF compare buono/scarto (call Menoncin 23/10). CRITERIO DI FATTO: foto sintetica di scacchiera distorta che riempie il FOV → griglia trovata senza dare cols/rows, mm/px entro 0,5%, immagine raddrizzata con scarto residuo < 0,3 px; grab di una sorgente calibrata esce raddrizzato; test verdi. RIF: estende gitea:Adriano/TieMeasureFlow#83.
Adriano added the sviluppoassegnata:daitarn labels 2026-10-23 21:44:43 +00:00
Author
Owner

🔵 daitarn — preso in carico 2026-10-23T21:44:41Z
ramo: lavoro/daitarn · capacità: build-windows, gpu

🔵 daitarn — **preso in carico** 2026-10-23T21:44:41Z ramo: `lavoro/daitarn` · capacità: build-windows, gpu
Author
Owner

ANALISI (Opus, claude-opus-5-5) — gitea:Adriano/TieMeasureFlow#84
RAMO: lavoro/daitarn-tmf-calibdensa (da origin/lavoro/daitarn-tmf-dxfcal-worker 351739e), worktree C:/Users/Adriano/lavoro/TieMeasureFlow-calibdensa
COSA: l'acquisitore calibra da UNA foto di scacchiera densa che riempie tutto il FOV. Input operatore: solo square_mm. Uscita: mappa di distorsione + mm/px; i frame dell'acquisitore escono raddrizzati quando la sorgente è calibrata. Ottica telecentrica (banco TC Opto + camera Itala ITA32/ITA50 mono, 2064x1544 / 5 MP).

MODIFICHE

  1. scripts/calibrazione.py
    • nuova calibra_scacchiera_densa(pixels, square_mm):
      a. corner senza conoscere la griglia: cv2.findChessboardCornersSBWithMeta(gray, (min_c, min_r), flags=CALIB_CB_LARGER | CALIB_CB_EXHAUSTIVE | CALIB_CB_ACCURACY | CALIB_CB_NORMALIZE_IMAGE) con pattern minimo piccolo (es. 5x5); meta dà la dimensione reale della griglia trovata. Ordinare i punti in ordine di riga (n_rows x n_cols), righe dall'alto, colonne da sinistra. Se SBWithMeta non è disponibile nella versione di opencv del venv, dirlo nel rapporto e ripiegare su una ricerca di griglia propria (non findChessboardCorners con dimensione fissa). Verificare la versione: uv run --python 3.13 --extra vision-worker --extra dev --with-editable vendor/visionsuite/packages/vs-camera python -c "import cv2; print(cv2.__version__, hasattr(cv2,'findChessboardCornersSBWithMeta'))".
      b. passo medio in px fra corner adiacenti → grid_step = round(passo).
      c. visionsuite.calibration.distortion_map.DistortionMap.from_grid(x, y, n_cols, n_rows, image_size=(w,h), grid_step, crop_domain=False, grid_step_measure=square_mm) → pixel_size = mm/px dell'immagine raddrizzata. Leggere tutto distortion_map.py prima (convenzioni map_col/map_row = cv2.remap, salvataggio/caricamento se esiste; se non esiste salvare map_col/map_row in .npz).
      d. qualità: residuo = distanza dei corner raddrizzati (rimappati con la mappa inversa o riproiettati) dalla griglia ideale; riportare residuo_max_px, residuo_rms_px, n_corners, n_cols, n_rows, copertura_pct (area del quadrilatero dei corner esterni / area immagine).
      e. overlay PNG b64: corner trovati sull'immagine originale.
    • Archivio: oltre al JSON, salva la mappa per sorgente (<stesso nome>.npz), scrittura atomica come il JSON; leggi_mappa(sorgente) con cache in memoria invalidata da mtime; elimina toglie anche la mappa.
    • La vecchia calibra_scacchiera(pixels, cols, rows, square_mm) resta (compatibilità C2), ma la pagina e il default usano la densa.
  2. scripts/camera_service.py
    • C2 POST /camera/calibrate: cols/rows diventano opzionali. Senza → densa (default). Con → vecchio metodo planare. Il frame per calibrare è SEMPRE grezzo (non raddrizzato). Campi di C1 restano tutti presenti (px_per_mm = 1/pixel_size, mm_per_px = pixel_size, rms_px = residuo_rms_px, n_corners, cols=n_cols, rows=n_rows, square_mm, image_width, image_height, scale_spread_pct calcolato come oggi o 0 se la mappa lo rende uniforme, calibrated_at) + nuovi metodo: "densa"|"planare", distorsione: true|false, residuo_max_px, copertura_pct.
    • POST /camera/grab: se la sorgente ha una mappa salvata, il frame esce raddrizzato (cv2.remap, INTER_LINEAR). Nuovo campo json opzionale grezzo: true per avere il frame non corretto. Il raddrizzamento non deve rallentare il grab oltre ~50 ms su 5 MP: mappe float32 precalcolate (cv2.convertMaps a CV_16SC2 se serve) e cache.
    • DELETE C3 toglie JSON e mappa.
  3. scripts/calibrazione_pagina.py (C4): un solo campo «Lato dello scacco (mm)», bottone «Acquisisci e calibra» (grab grezzo dalla sorgente scelta) oppure caricamento di un'immagine; mostra px/mm, mm/px, griglia trovata (n_cols x n_rows), residuo max, copertura, overlay; «Salva». Avvisi chiari se copertura < 70% o residuo_max > 0,5 px. Pagina offline, niente CDN, italiano.
  4. Test in scripts/tests/test_calibrazione.py: scacchiera sintetica densa (es. lato 40 px, griglia che esce dai bordi) con distorsione radiale applicata (barrel, k ~ 1e-7 sui px dal centro) → griglia trovata senza cols/rows; mm/px entro 0,5% del vero; raddrizzando la stessa immagine i corner stanno su griglia regolare con residuo < 0,3 px; grab di sorgente calibrata (sorgente cartella con l'immagine distorta) esce raddrizzata e grezzo:true no; C2 senza cols/rows → metodo densa; vecchio C2 con cols/rows → planare invariato; DELETE toglie anche .npz.
    TEST:
  • uv run --python 3.13 --extra vision-worker --extra dev --with-editable vendor/visionsuite/packages/vs-camera pytest scripts/tests → tutti verdi.
  • uv run --python 3.13 --extra vision --extra vision-worker --extra dev pytest src/vision/tests src/vision_worker/tests → invariati (43).
    NON TOCCARE: src/** (backend, frontend, worker, vision), vendor/visionsuite (submodule: se serve una modifica lì, fermarsi e dirlo), packaging/**. I nomi delle rotte e dei campi esistenti di C1-C4.
ANALISI (Opus, claude-opus-5-5) — gitea:Adriano/TieMeasureFlow#84 RAMO: lavoro/daitarn-tmf-calibdensa (da origin/lavoro/daitarn-tmf-dxfcal-worker 351739e), worktree C:/Users/Adriano/lavoro/TieMeasureFlow-calibdensa COSA: l'acquisitore calibra da UNA foto di scacchiera densa che riempie tutto il FOV. Input operatore: solo square_mm. Uscita: mappa di distorsione + mm/px; i frame dell'acquisitore escono raddrizzati quando la sorgente è calibrata. Ottica telecentrica (banco TC Opto + camera Itala ITA32/ITA50 mono, 2064x1544 / 5 MP). MODIFICHE 1. scripts/calibrazione.py - nuova `calibra_scacchiera_densa(pixels, square_mm)`: a. corner senza conoscere la griglia: `cv2.findChessboardCornersSBWithMeta(gray, (min_c, min_r), flags=CALIB_CB_LARGER | CALIB_CB_EXHAUSTIVE | CALIB_CB_ACCURACY | CALIB_CB_NORMALIZE_IMAGE)` con pattern minimo piccolo (es. 5x5); `meta` dà la dimensione reale della griglia trovata. Ordinare i punti in ordine di riga (n_rows x n_cols), righe dall'alto, colonne da sinistra. Se SBWithMeta non è disponibile nella versione di opencv del venv, dirlo nel rapporto e ripiegare su una ricerca di griglia propria (non `findChessboardCorners` con dimensione fissa). Verificare la versione: `uv run --python 3.13 --extra vision-worker --extra dev --with-editable vendor/visionsuite/packages/vs-camera python -c "import cv2; print(cv2.__version__, hasattr(cv2,'findChessboardCornersSBWithMeta'))"`. b. passo medio in px fra corner adiacenti → `grid_step = round(passo)`. c. `visionsuite.calibration.distortion_map.DistortionMap.from_grid(x, y, n_cols, n_rows, image_size=(w,h), grid_step, crop_domain=False, grid_step_measure=square_mm)` → `pixel_size` = mm/px dell'immagine raddrizzata. Leggere tutto `distortion_map.py` prima (convenzioni map_col/map_row = cv2.remap, salvataggio/caricamento se esiste; se non esiste salvare map_col/map_row in .npz). d. qualità: residuo = distanza dei corner raddrizzati (rimappati con la mappa inversa o riproiettati) dalla griglia ideale; riportare `residuo_max_px`, `residuo_rms_px`, `n_corners`, `n_cols`, `n_rows`, `copertura_pct` (area del quadrilatero dei corner esterni / area immagine). e. overlay PNG b64: corner trovati sull'immagine originale. - `Archivio`: oltre al JSON, salva la mappa per sorgente (`<stesso nome>.npz`), scrittura atomica come il JSON; `leggi_mappa(sorgente)` con cache in memoria invalidata da mtime; `elimina` toglie anche la mappa. - La vecchia `calibra_scacchiera(pixels, cols, rows, square_mm)` resta (compatibilità C2), ma la pagina e il default usano la densa. 2. scripts/camera_service.py - C2 `POST /camera/calibrate`: `cols`/`rows` diventano opzionali. Senza → densa (default). Con → vecchio metodo planare. Il frame per calibrare è SEMPRE grezzo (non raddrizzato). Campi di C1 restano tutti presenti (px_per_mm = 1/pixel_size, mm_per_px = pixel_size, rms_px = residuo_rms_px, n_corners, cols=n_cols, rows=n_rows, square_mm, image_width, image_height, scale_spread_pct calcolato come oggi o 0 se la mappa lo rende uniforme, calibrated_at) + nuovi `metodo: "densa"|"planare"`, `distorsione: true|false`, `residuo_max_px`, `copertura_pct`. - `POST /camera/grab`: se la sorgente ha una mappa salvata, il frame esce raddrizzato (`cv2.remap`, INTER_LINEAR). Nuovo campo json opzionale `grezzo: true` per avere il frame non corretto. Il raddrizzamento non deve rallentare il grab oltre ~50 ms su 5 MP: mappe float32 precalcolate (`cv2.convertMaps` a CV_16SC2 se serve) e cache. - DELETE C3 toglie JSON e mappa. 3. scripts/calibrazione_pagina.py (C4): un solo campo «Lato dello scacco (mm)», bottone «Acquisisci e calibra» (grab grezzo dalla sorgente scelta) oppure caricamento di un'immagine; mostra px/mm, mm/px, griglia trovata (n_cols x n_rows), residuo max, copertura, overlay; «Salva». Avvisi chiari se copertura < 70% o residuo_max > 0,5 px. Pagina offline, niente CDN, italiano. 4. Test in scripts/tests/test_calibrazione.py: scacchiera sintetica densa (es. lato 40 px, griglia che esce dai bordi) con distorsione radiale applicata (barrel, k ~ 1e-7 sui px dal centro) → griglia trovata senza cols/rows; mm/px entro 0,5% del vero; raddrizzando la stessa immagine i corner stanno su griglia regolare con residuo < 0,3 px; grab di sorgente calibrata (sorgente cartella con l'immagine distorta) esce raddrizzata e `grezzo:true` no; C2 senza cols/rows → metodo densa; vecchio C2 con cols/rows → planare invariato; DELETE toglie anche .npz. TEST: - `uv run --python 3.13 --extra vision-worker --extra dev --with-editable vendor/visionsuite/packages/vs-camera pytest scripts/tests` → tutti verdi. - `uv run --python 3.13 --extra vision --extra vision-worker --extra dev pytest src/vision/tests src/vision_worker/tests` → invariati (43). NON TOCCARE: src/** (backend, frontend, worker, vision), vendor/visionsuite (submodule: se serve una modifica lì, fermarsi e dirlo), packaging/**. I nomi delle rotte e dei campi esistenti di C1-C4.
Adriano removed the assegnata:daitarn label 2026-10-24 03:44:58 +00:00
Author
Owner

🤖 goldrake — liberata nessun segno di daitarn da 6.0 h — 2026-10-24T03:44:58Z

🤖 goldrake — **liberata** nessun segno di daitarn da 6.0 h — 2026-10-24T03:44:58Z
Author
Owner

Soluzione

ESITO: fatto (chiusa su richiesta di Adriano del 06/11/2026)
RIF: gitea:Adriano/TieMeasureFlow#84 (estende #83)
COSA: l'acquisitore calibra da una sola foto di scacchiera densa: l'operatore dà solo il lato dello scacco; mappa di distorsione e mm/px; i frame delle sorgenti calibrate escono raddrizzati.
VERIFICATO su origin/V3.0.0 8c361c1: scripts/calibrazione.py:377 calibra_scacchiera_densa(pixels, square_mm) → (esito, DistortionMap); test in scripts/tests/test_calibrazione.py, test_calibrazioni_archivio.py, test_calibrazioni_rotte.py, test_calibrazione_manuale.py.
USO: in fiera dal 24-25/10 (ramo lavoro/daitarn-tmf-dxfcal, unito il 24/10; scheda AI-OS TieMeasureFlow, voce 24/10); le ricette della fiera portano la scala misurata (CalADP 89,63 px/mm sulla FIERA-DXF-COPIA-2).
NOTA: il segno di presa di daitarn era stato liberato da goldrake il 24/10 03:44 per mancanza di battito; il lavoro è stato completato e unito comunque.

**Soluzione** ESITO: fatto (chiusa su richiesta di Adriano del 06/11/2026) RIF: gitea:Adriano/TieMeasureFlow#84 (estende #83) COSA: l'acquisitore calibra da una sola foto di scacchiera densa: l'operatore dà solo il lato dello scacco; mappa di distorsione e mm/px; i frame delle sorgenti calibrate escono raddrizzati. VERIFICATO su origin/V3.0.0 8c361c1: scripts/calibrazione.py:377 calibra_scacchiera_densa(pixels, square_mm) → (esito, DistortionMap); test in scripts/tests/test_calibrazione.py, test_calibrazioni_archivio.py, test_calibrazioni_rotte.py, test_calibrazione_manuale.py. USO: in fiera dal 24-25/10 (ramo lavoro/daitarn-tmf-dxfcal, unito il 24/10; scheda AI-OS TieMeasureFlow, voce 24/10); le ricette della fiera portano la scala misurata (CalADP 89,63 px/mm sulla FIERA-DXF-COPIA-2). NOTA: il segno di presa di daitarn era stato liberato da goldrake il 24/10 03:44 per mancanza di battito; il lavoro è stato completato e unito comunque.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/TieMeasureFlow#84