diff --git a/docs/architecture/calibration_solver_preflight_v1.md b/docs/architecture/calibration_solver_preflight_v1.md new file mode 100644 index 0000000..aac82ba --- /dev/null +++ b/docs/architecture/calibration_solver_preflight_v1.md @@ -0,0 +1,113 @@ +# Calibration Solver Preflight v1 + +**PASS — sélection d'un futur générateur externe d'évidence ; aucune +implémentation n'est introduite ici.** Ce document n'autorise aucune +modification de `CALIBRATION_SCIENCE_V1`, de `CALIBRATION_TOOLING_V1`, de +`L3DCALB1`, du Project DB ou de Sparse SfM. S21 historique reste exclu. + +## Décision + +La plus petite voie conforme est un exécutable C++ externe, versionné et +haché, construit contre **OpenCV 5.0.x** avec `core`, `imgproc`, `imgcodecs`, +`objdetect` et `calib`. Il n'est pas lié par Lardon3D, n'écrit pas le Project +DB et produit seulement un bundle d'évidence borné. Un adaptateur ultérieur +traduit ce bundle vers `calibration_tooling.h`; Lardon3D reste seul producteur +`L3DCALB1` et seul appelant de l'importeur. + +Les alternatives évaluées sont : OpenCV ChArUco externe, retenu ; outils tiers +généralistes, non retenus faute de contrat public stable pour résidus point par +point et flags exacts ; solveur custom, hors scope car OpenCV fournit déjà la +détection, la calibration pinhole et les résidus nécessaires. + +## API OpenCV et modèle + +La cible est créée par `cv::aruco::CharucoBoard(cv::Size(9, 7), 0.030f, +0.021f, cv::aruco::getPredefinedDictionary(cv::aruco::DICT_5X5_100))`, puis +rendue par `generateImage`. La version OpenCV et `legacyPattern=false` sont +archivés. La détection emploie `cv::aruco::CharucoDetector::detectBoard` sans +matrice caméra au premier passage et archive coins, IDs, marqueurs et paramètres +de détection. `CharucoBoard::matchImagePoints` associe ID, pixel et point objet +en mètres. Une vue est explicitement rejetée, avec motif, si elle ne satisfait +pas Science v1. + +Les coordonnées détectées `Point2f` ne sont pas le format de calcul publié : +l'exécutable les convertit une fois en `Point2d`; il reconstruit les points +objets `Point3d` depuis les IDs et la grille mesurée, plutôt que de propager les +`float` de commodité de la planche. `calibrateCamera` reçoit donc des matrices +`CV_64F`, et ses paramètres, poses et résidus sont conservés en `binary64`. + +Le solveur appelle la surcharge étendue `cv::calibrateCamera` : + +```text +flags = cv::CALIB_FIX_K3 +criteria = COUNT | EPS, 500, DBL_EPSILON +cameraMatrix = Matx33d::eye() +distCoeffs = Mat::zeros(5, 1, CV_64F) +``` + +L'ordre OpenCV est `[k1,k2,p1,p2,k3]`; `CALIB_FIX_K3` fixe le dernier à zéro, +donc les quatre autres se mappent directement à Sparse SfM v1. `fx`, `fy`, +`cx`, `cy`, `k1`, `k2`, `p1` et `p2` restent libres. Sont interdits : +`CALIB_FIX_ASPECT_RATIO`, `CALIB_FIX_PRINCIPAL_POINT`, +`CALIB_FIX_FOCAL_LENGTH`, `CALIB_FIX_K1`, `CALIB_FIX_K2`, +`CALIB_ZERO_TANGENT_DIST`, `CALIB_FIX_TANGENT_DIST`, +`CALIB_RATIONAL_MODEL`, `CALIB_THIN_PRISM_MODEL`, `CALIB_TILTED_MODEL`, les +flags de fixage associés, fisheye, QR/LU, EXIF comme intrinsics et calibration +par image. + +La surcharge étendue retourne RMS global et par vue. L'outil doit recalculer +avec `cv::projectPoints` chaque résidu par coin, maximum, fraction `>1 px` et +RMS avec les mêmes poses ; le succès du solveur seul n'est jamais suffisant. +Ces API emploient `fx,fy,cx,cy` en pixels et la même distorsion directe que le +modèle gelé : aucune conversion de modèle n'est permise. + +## Entrée, sortie et déterminisme + +L'entrée externe est un répertoire immuable d'originaux hachés, manifeste +d'état optique, cible/mesures et chaîne de décodage-orientation. Les vues sont +triées par SHA-256 source avant détection. Détection et solve sont deux étapes +matérialisées (`detection.json` et `solve.json`) d'un seul exécutable : IDs, +pixels subpixel, décisions et rejets sont ainsi vérifiables avant les trois +solves. Les fichiers ont ordre canonique, nombres `binary64` hexadécimaux, +tableaux ordonnés et limites déclarées. + +La sortie archive nom/version/SHA de l'exécutable, OS/architecture, build +OpenCV et bibliothèques, configuration hachée, identité de session, vues et +motifs, points objet/image, résidus, poses diagnostic, huit paramètres, +supports, hold-out, trois répétitions et calculs des flags `0x01..0x08`. Les +images de campagne, postérieures à la calibration, sont ajoutées seulement par +l'adaptateur comme entrées `image_id`/SHA/dimensions dans l'ordre +`selected_execution` : elles ne sont jamais des entrées de solveur. + +La preuve initiale est CPU1 : `cv::setNumThreads(1)`, `cv::setRNGSeed` fixé, +aucun travail parallèle, `OMP_NUM_THREADS=1`, `OPENBLAS_NUM_THREADS=1` et +`MKL_NUM_THREADS=1` lorsqu'applicables. `cv::getBuildInformation`, backend de +threads et environnement effectif sont archivés. Les trois exécutions sur même +hôte/architecture/octets doivent donner les mêmes huit `binary64`, décisions +et rapports canoniques : tout écart bloque Science v1. + +## Équivalence de coordonnées et versionnement + +La géométrie est : origine haut-gauche, `x` droite, `y` bas, pixels continus à +centres demi-entiers. Chaque image archive SHA, décodeur/version, EXIF, +dimensions avant/après et les équations `0°:(x,y)`, `90°:(H-y,x)`, +`180°:(W-x,H-y)`, `270°:(y,W-x)`. Vingt coins par vue repassent dans la +chaîne Feature Store ; chaque erreur est `<=0.01 px` et les dimensions orientées +doivent être identiques. + +L'hôte de préflight fournit `opencv 5.0.0-9`. La production future épingle un +exécutable contre OpenCV 5.0.x avec SHA obligatoire ; le package Arch est bon +pour développement, pas l'unique identité de production. Sont archivés en +plus : compilateur, flags, build info, bibliothèques/SHA, OS, architecture et +politique CPU. Les quatre SHA `L3DCALB1` restent autoritaires. + +## Prochaine tranche et première preuve physique + +La prochaine tranche implémente uniquement l'exécutable externe et ses tests : +caméra pinhole/distorsion connues, projections ChArUco, bruit/outliers, +récupération, résidus, répétitions, hold-out, entrées invalides et bundle +byte-identique. Puis seulement : fabriquer/mesurer la cible, figer l'état +optique, acquérir au moins 40 vues, hacher, résoudre trois fois, vérifier +Science v1, passer le bundle à Calibration Tooling dans un projet dédié et +vérifier `CALIBRATION → READY`. La preuve s'arrête avant Sparse SfM ; S21 ne +participe jamais. diff --git a/docs/roadmap/roadmap.md b/docs/roadmap/roadmap.md index c99661a..40eadc3 100644 --- a/docs/roadmap/roadmap.md +++ b/docs/roadmap/roadmap.md @@ -153,6 +153,12 @@ v1. Il valide les HARD REJECTS, produit l'artefact déterministe et s'arrête à la transition `CALIBRATION → READY`; il ne résout aucune calibration, ne lance pas Sparse SfM et ne rétro-calibre pas S21. +`CALIBRATION_SOLVER_PREFLIGHT_V1=PASS` retient un exécutable externe épinglé +sur OpenCV 5.0.x, dont le seul rôle futur est de produire le bundle d'évidence +consommé par Calibration Tooling. Il ne devient pas une dépendance runtime et +ne lance aucun stage de reconstruction. Voir [Calibration Solver Preflight +v1](../architecture/calibration_solver_preflight_v1.md). + ## PHOTO QUALITY TRIAGE / ACQUISITION SELECTION — PASS / FROZEN L'étape qualité canonique implémentée se place après la découverte bornée, les