lardon3d/docs/architecture/calibration_solver_preflight_v1.md

6.1 KiB

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 :

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.