feat: complete MVS-M1 dense reconstruction
This commit is contained in:
parent
4f8d1af002
commit
e5e09ab6c6
8 changed files with 3134 additions and 6 deletions
28
README.md
28
README.md
|
|
@ -34,6 +34,18 @@ persistante, enrichissable et versionnable.
|
|||
Adjustment final par composante, tous PASS / FROZEN
|
||||
- **Sparse SfM Gate F** : orchestration durable, runtime gouverné et publication
|
||||
atomique, PASS / FROZEN
|
||||
- **MVS-M1** : frontière externe OpenMVS v2.4.0
|
||||
`InterfaceCOLMAP`/`DensifyPointCloud`, export COLMAP déterministe (OpenCV
|
||||
undistortion, observations transformées et tracks réels), texte exporté en
|
||||
flux et tracks indexés sans rescanner quadratiquement les observations ; espace
|
||||
de travail privé neuf par invocation sous le staging appelant, sans réemploi ;
|
||||
identité dense liée à la reconstruction de base, au jeu source, au
|
||||
`calibration_scope_identity` historique, au binding numérique MVS, au backend
|
||||
et aux paramètres ; `L3DMDID2` v2 (220 octets) et binding `L3DMCAL1` v1 ; PLY
|
||||
OpenMVS binaire little-endian validé (en-tête <= 1 MiB en octets bruts,
|
||||
LF/CRLF acceptés, CR seul malformé rejeté, ligne <= 64 KiB), fusionné en
|
||||
mode 0 — IMPLEMENTED /
|
||||
VALIDATION PENDING
|
||||
- **Geometric Verification Model v1** : identité, masque d'inliers et modèle 3×3 persistants
|
||||
- **Geometric Verifier v1** : Fundamental USAC/MAGSAC, reprise et lots resource-aware
|
||||
- **Task Kind Registry** : identité métier durable et reconstruction runtime explicite
|
||||
|
|
@ -171,9 +183,19 @@ fallback CPU. La feasibility Vulkan SIFT/RootSIFT a été rejetée ; ces deux ma
|
|||
restent sur OpenCV L2. Track Model/Builder, les primitives géométriques Gate C,
|
||||
le noyau Sparse SfM incrémental Gate D et le Bundle Adjustment final Gate E sont
|
||||
implémentés et validés. L'orchestration Sparse SfM Gate F est PASS / FROZEN ;
|
||||
l'intégration Governor Gate G est **PASS / FROZEN** ; le DAG,
|
||||
le viewer et les étapes denses restent des tickets séparés planifiés. Le Resource Governor ne
|
||||
constitue pas un Resource System générique : voir la décision d’architecture.
|
||||
l'intégration Governor Gate G est **PASS / FROZEN**. MVS-M1 est **IMPLEMENTED /
|
||||
VALIDATION PENDING** : une frontière OpenMVS v2.4.0 externe et bornée, sans
|
||||
publication dense durable ni MVS complet. Les sources sont liées par SHA-256
|
||||
complet, borné à 1 GiB par fichier régulier (sans budget agrégé de dataset) ; les
|
||||
octets source restent un binding distinct de l'identité dense. Celle-ci lie la
|
||||
reconstruction de base, le jeu d'images source, le `calibration_scope_identity`
|
||||
historique, le binding numérique de calibration MVS `L3DMCAL1` v1, le backend et
|
||||
les paramètres dans `L3DMDID2` v2 (220 octets). Chaque appel utilise un espace de
|
||||
travail privé neuf sous le staging appelant, sans réemploi d'une scène,
|
||||
profondeur, cache ou sortie antérieure. Le DAG, le viewer et les autres étapes
|
||||
denses restent des tickets séparés planifiés.
|
||||
Le Resource Governor ne constitue pas un Resource System générique : voir la décision
|
||||
d’architecture.
|
||||
|
||||
## Licence
|
||||
|
||||
|
|
|
|||
|
|
@ -253,6 +253,103 @@ durables, scratch/SSD, GPU SfM/BA et nouvelles composantes déconnectées.
|
|||
|
||||
---
|
||||
|
||||
### MVS-M1. Dense externe borné
|
||||
|
||||
MVS-M1 est **IMPLEMENTED / VALIDATION PENDING**. Son identité scientifique lie
|
||||
l'identité de reconstruction de base, l'identité du jeu d'images source, le
|
||||
`calibration_scope_identity` historique, le binding numérique de calibration MVS,
|
||||
le backend et les paramètres. Les octets des sources restent liés séparément par
|
||||
leur SHA-256 ; ils ne sont pas remplacés par le binding de calibration. Le record
|
||||
d'identité dense est `L3DMDID2`, version 2, de taille fixe 220 octets. Sa frontière
|
||||
externe est OpenMVS v2.4.0 : Sparse/H est exporté vers COLMAP, puis traité par
|
||||
`InterfaceCOLMAP` et `DensifyPointCloud`.
|
||||
|
||||
Le binding numérique de calibration est le record `L3DMCAL1`, version 1. Il est
|
||||
formé après tri strict des `image_id` et rejet des identifiants dupliqués ; tous
|
||||
les entiers et binary64 y sont encodés explicitement en little-endian de largeur
|
||||
fixe. Les NaN et infinis sont rejetés et toute valeur `-0` est canonisée en `+0`
|
||||
avant l'encodage. Il lie, pour chaque image, son identifiant, ses dimensions et
|
||||
les paramètres numériques de calibration utilisés par l'undistortion.
|
||||
|
||||
L'export COLMAP est déterministe et écrit en flux : locale classique, notation
|
||||
scientifique et précision binary64 fidèle au round-trip (`max_digits10 - 1`
|
||||
chiffres après le point). Les images et les observations source sont undistordues
|
||||
de façon déterministe avec OpenCV ; les observations exportées sont les
|
||||
coordonnées transformées. `points3D.txt` contient les tracks COLMAP réels,
|
||||
produits dans l'ordre déterministe à partir d'un index de landmarks et de leurs
|
||||
observations : l'export ne rescane pas quadratiquement les observations pour
|
||||
chaque landmark.
|
||||
|
||||
Chaque invocation crée sous le staging fourni par l'appelant un espace de travail
|
||||
privé neuf. Aucune scène, profondeur, cache ou sortie d'une invocation antérieure
|
||||
n'est recherchée ni réemployée. Les images undistordues et tous les intermédiaires
|
||||
COLMAP/OpenMVS restent dans cet espace de travail.
|
||||
|
||||
Le résultat accepté est le PLY fusionné ; `fusion_mode` doit être 0. Le lecteur
|
||||
valide le format de sortie par défaut d'OpenMVS v2.4.0 : PLY binaire
|
||||
little-endian. Le plafond d'en-tête est de 1 MiB et compte les octets bruts :
|
||||
une fin de ligne CRLF consomme donc deux octets, contrairement à LF. Les règles
|
||||
de lignes acceptent LF et CRLF ; un CR seul malformé est rejeté. Chaque ligne
|
||||
est bornée à 64 KiB. Le lecteur borne les listes de propriétés `view_indices` et
|
||||
`view_weights` avant
|
||||
allocation ou lecture de leur contenu, et valide les valeurs numériques sans
|
||||
dépendre de la locale du processus.
|
||||
|
||||
La capacité CPU-only est conditionnelle : le probe borné interroge chaque
|
||||
exécutable backend, vérifie qu'il s'identifie comme OpenMVS v2.4.0 et relève les
|
||||
options effectivement exposées. Si `DensifyPointCloud` expose `--cuda-device`,
|
||||
l'adaptateur demande explicitement le périphérique CPU ; sinon il n'ajoute aucune
|
||||
option CUDA. Les options requises, dont `--max-threads`, doivent être exposées ;
|
||||
celle-ci reçoit le nombre de threads d'exécution. Ce nombre est opérationnel et
|
||||
exclu de l'identité scientifique.
|
||||
|
||||
Les contrats B1–B6 du jalon sont les suivants :
|
||||
|
||||
- **B1 — result failure atomicity.** Un échec ne retourne aucun
|
||||
résultat MVS-M1 partiel.
|
||||
- **B2 — source-image status classification.** Les erreurs source,
|
||||
snapshot, I/O, backend, output et OOM restent distinctes.
|
||||
- **B3 — source byte identity binding.** Chaque source acceptée est
|
||||
un fichier régulier dont la taille du descripteur est contrôlée avant le hash ;
|
||||
son SHA-256 complet est calculé avec une mémoire de streaming fixe, jusqu'à
|
||||
1 GiB au maximum par fichier source, puis la stabilité du fichier est vérifiée
|
||||
après lecture. Il n'existe volontairement aucun budget agrégé de 1 GiB pour le
|
||||
jeu de sources : la vérification totale croît avec les octets d'entrée acceptés
|
||||
et son admission/comptage global relève d'un futur Task/Governor.
|
||||
- **B4 — deterministic COLMAP numeric export.** Les images, observations et
|
||||
tracks sont ceux décrits ci-dessus ; aucune observation distordue n'est exportée
|
||||
à la place de sa coordonnée transformée, et l'export des tracks ne réalise pas
|
||||
de rescan quadratique landmark-observation. Une coordonnée source undistordue
|
||||
non finie est `INVALID_SOURCE_IMAGE`, tandis qu'une pose du snapshot invalide
|
||||
reste `INVALID_SNAPSHOT`.
|
||||
- **B5 — write/close FD cleanup.** Les descripteurs possédés sont nettoyés
|
||||
explicitement lors de l'écriture et de la fermeture,
|
||||
y compris sur erreur.
|
||||
- **B6 — C ABI / `std::bad_alloc` containment.** L'API C ne laisse pas
|
||||
traverser `std::bad_alloc`.
|
||||
|
||||
Indépendamment de la borne de ligne PLY, l'inspection de l'en-tête d'image
|
||||
encodée avant décodage est bornée à 64 KiB. Les formats acceptés implémentés sont
|
||||
PNG, JPEG et BMP ; les dimensions, le nombre de pixels et l'estimation de working
|
||||
set sont validés avant décodage (16 384 pixels par dimension, 40 Mpx et 240 MB).
|
||||
Les intermédiaires d'undistortion déterministe
|
||||
et les intermédiaires COLMAP/OpenMVS sont privés à l'espace de travail neuf créé
|
||||
par invocation sous le staging appelant ; aucune invocation ne réemploie des
|
||||
artefacts antérieurs.
|
||||
|
||||
Les deux exécutables backend sont hashés sous un budget borné partagé, distinct
|
||||
du plafond par fichier des sources. Les processus possédés et leurs groupes sont
|
||||
nettoyés ; stdout et stderr sont intégralement drainés pour chaque backend, avec
|
||||
au plus 1 MiB retenu par log. L'excédent est consommé puis écarté, sans empêcher
|
||||
le backend de poursuivre et de réussir.
|
||||
|
||||
Restent différés : publication dense dans la Project DB, Task Runtime,
|
||||
Queue/Governor, `ResourceEstimate`, annulation, publication dense durable,
|
||||
mesh, texturing, viewer, scratch/SSD et infrastructure backend généralisée.
|
||||
Ce jalon ne marque ni MVS complet ni PASS / FROZEN.
|
||||
|
||||
---
|
||||
|
||||
### I. Reconstruction Layers
|
||||
|
||||
| Aspect | Description |
|
||||
|
|
@ -360,7 +457,26 @@ and Sparse SfM Gate C geometry are **IMPLEMENTED**. The synchronous in-memory
|
|||
incremental Sparse SfM Gate D core is **IMPLEMENTED / PASS**, and final
|
||||
per-component Gate E BA is **PASS / FROZEN**. Gate F orchestration and Gate G
|
||||
Governor integration are **PASS / FROZEN**. Phase H v1 is **PASS / FROZEN**.
|
||||
MVS, mesh, texturing and viewer remain **PLANNED**.
|
||||
MVS-M1 is **IMPLEMENTED / VALIDATION PENDING**: `L3DMDID2` v2 (220-byte) dense
|
||||
identity binds the base reconstruction, source-image-set,
|
||||
historical `calibration_scope_identity`, exact `L3DMCAL1` v1 numeric calibration
|
||||
binding, backend and parameters, while source bytes remain separately bound;
|
||||
deterministic OpenCV-undistorted COLMAP images, transformed observations and real
|
||||
tracks exported from an index without a quadratic landmark-observation rescan;
|
||||
and the external OpenMVS v2.4.0
|
||||
`InterfaceCOLMAP`/`DensifyPointCloud` boundary. Every invocation has a fresh
|
||||
private workspace beneath caller staging and reuses no prior scene, depth, cache
|
||||
or output. Its numeric calibration binding sorts image IDs, uses explicit
|
||||
fixed-width little-endian fields, rejects NaN/Inf and canonicalizes `-0`; PLY
|
||||
headers are capped at 1 MiB of raw bytes (so CRLF costs two), accept LF/CRLF,
|
||||
reject malformed bare CR, and cap each line at 64 KiB.
|
||||
Non-finite undistorted source coordinates are `INVALID_SOURCE_IMAGE`; invalid
|
||||
snapshot poses remain `INVALID_SNAPSHOT`. It validates OpenMVS's default binary
|
||||
little-endian PLY, including bounded `view_indices`/`view_weights` lists with
|
||||
locale-independent numeric validation, requires fusion mode 0 for the fused PLY,
|
||||
and has conditional CPU-only capability plus supported `--max-threads`. It does
|
||||
not include durable dense publication, runtime/governor integration, mesh,
|
||||
texturing or viewer; those remain **PLANNED**.
|
||||
|
||||
Ce document décrit la vision architecturale cible du pipeline de
|
||||
reconstruction. Les modules listés ici ne sont pas tous implémentés.
|
||||
|
|
|
|||
|
|
@ -58,7 +58,31 @@ Lardon3D suit une feuille de route ordonnée qui privilégie la stabilité et la
|
|||
|
||||
### Phase 5 : Reconstruction
|
||||
- ✅ Orchestration de reconstruction incrémentale H v1 — **PASS / FROZEN**
|
||||
- 📋 MVS / dense
|
||||
- 🔄 MVS-M1 — **IMPLEMENTED / VALIDATION PENDING** : identité dense `L3DMDID2`
|
||||
v2 (220 octets) liant reconstruction de base, jeu d'images source,
|
||||
`calibration_scope_identity` historique, binding numérique exact `L3DMCAL1` v1,
|
||||
backend et paramètres ; les octets source restent liés séparément ; frontière
|
||||
OpenMVS v2.4.0 externe
|
||||
`InterfaceCOLMAP`/`DensifyPointCloud` ; export COLMAP déterministe avec
|
||||
undistortion OpenCV, observations transformées et tracks réels ; texte COLMAP
|
||||
exporté en flux et tracks déterministes indexés sans rescan quadratique des
|
||||
observations. Le binding de calibration trie les `image_id`, encode des champs
|
||||
little-endian explicites à largeur fixe, rejette NaN/Inf et canonise `-0`. Chaque
|
||||
invocation crée sous le staging appelant un espace de travail privé neuf, sans
|
||||
réemployer scène, profondeur, cache ou sortie antérieure. Le PLY par défaut
|
||||
OpenMVS v2.4.0, binaire little-endian, est validé avec listes
|
||||
`view_indices`/`view_weights` bornées, nombres indépendants de la locale,
|
||||
en-tête <= 1 MiB en octets bruts (CRLF = deux octets), lignes <= 64 KiB,
|
||||
LF/CRLF acceptés et CR seul malformé rejeté ; le PLY fusionné est requis en mode
|
||||
de fusion 0. Une coordonnée source undistordue
|
||||
non finie est `INVALID_SOURCE_IMAGE`, une pose snapshot invalide reste
|
||||
`INVALID_SNAPSHOT`. Capacité CPU-only conditionnelle et `--max-threads`
|
||||
supporté ; bornes locales. Le SHA-256 source est complet et borné à 1 GiB par
|
||||
fichier régulier, sans budget agrégé du jeu de sources ; le hashage des deux
|
||||
binaires backend relève d'un budget partagé distinct. Sans nouveau sous-système.
|
||||
Restent différés : publication Project DB/durable, Task Runtime,
|
||||
Queue/Governor, `ResourceEstimate`, annulation, mesh, texturing, viewer,
|
||||
scratch/SSD et infrastructure backend généralisée.
|
||||
- 📋 Mesh
|
||||
- 📋 Contraintes externes
|
||||
- 📋 Consolidation
|
||||
|
|
|
|||
131
include/lardon3d/dense_mvs.h
Normal file
131
include/lardon3d/dense_mvs.h
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#ifndef LARDON3D_DENSE_MVS_H
|
||||
#define LARDON3D_DENSE_MVS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lardon3d/sparse_sfm_incremental.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
LARDON3D_DENSE_MVS_SHA256_SIZE = 32,
|
||||
LARDON3D_DENSE_MVS_PARAMETER_RECORD_SIZE = 40,
|
||||
LARDON3D_DENSE_MVS_IDENTITY_RECORD_SIZE = 220,
|
||||
LARDON3D_DENSE_MVS_BACKEND_MANIFEST_RECORD_SIZE = 148,
|
||||
LARDON3D_DENSE_MVS_VERSION_IDENTITY_SIZE = 32,
|
||||
LARDON3D_DENSE_MVS_PATH_CAPACITY = 4096,
|
||||
LARDON3D_DENSE_MVS_KIND_OPENMVS = 1,
|
||||
LARDON3D_DENSE_MVS_VERSION = 1,
|
||||
LARDON3D_DENSE_MVS_OPENMVS_VERSION = 0x00020400,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_DENSE_MVS_OK = 0,
|
||||
LARDON3D_DENSE_MVS_INVALID_ARGUMENT,
|
||||
LARDON3D_DENSE_MVS_INVALID_SNAPSHOT,
|
||||
LARDON3D_DENSE_MVS_INVALID_SOURCE_IMAGE,
|
||||
LARDON3D_DENSE_MVS_IO_ERROR,
|
||||
LARDON3D_DENSE_MVS_BACKEND_ERROR,
|
||||
LARDON3D_DENSE_MVS_INVALID_OUTPUT,
|
||||
LARDON3D_DENSE_MVS_OUT_OF_MEMORY,
|
||||
} Lardon3DDenseMvsStatus;
|
||||
|
||||
typedef struct {
|
||||
uint32_t resolution_level;
|
||||
uint32_t minimum_resolution;
|
||||
uint32_t number_views;
|
||||
uint32_t fusion_mode;
|
||||
} Lardon3DDenseMvsParameters;
|
||||
|
||||
/* The version fields identify the pinned version established by bounded --help probes. */
|
||||
typedef struct {
|
||||
unsigned char interface_colmap_version_identity[32];
|
||||
unsigned char interface_colmap_binary_sha256[32];
|
||||
unsigned char densify_point_cloud_version_identity[32];
|
||||
unsigned char densify_point_cloud_binary_sha256[32];
|
||||
} Lardon3DDenseMvsBackendManifest;
|
||||
|
||||
typedef struct {
|
||||
unsigned char base_reconstruction_identity[32];
|
||||
unsigned char source_image_set_identity[32];
|
||||
unsigned char calibration_scope_identity[32];
|
||||
unsigned char calibration_binding_identity[32];
|
||||
uint32_t dense_kind;
|
||||
uint32_t dense_version;
|
||||
uint32_t backend_kind;
|
||||
uint32_t backend_version;
|
||||
/* SHA-256 of the fixed-order OpenMVS v1 backend manifest. */
|
||||
unsigned char backend_binary_sha256[32];
|
||||
unsigned char parameter_fingerprint[32];
|
||||
} Lardon3DDenseMvsIdentity;
|
||||
|
||||
typedef struct {
|
||||
uint64_t image_id;
|
||||
unsigned char immutable_sha256[32];
|
||||
const char *source_path;
|
||||
Lardon3DSparseGeometryCalibration calibration;
|
||||
} Lardon3DDenseMvsSourceImage;
|
||||
|
||||
typedef struct {
|
||||
const Lardon3DSparseIncrementalResult *snapshot;
|
||||
/* Source-pixel coordinates keyed by the snapshot observation identity. */
|
||||
const Lardon3DSparseIncrementalObservation *source_observations;
|
||||
size_t source_observation_count;
|
||||
const Lardon3DDenseMvsSourceImage *source_images;
|
||||
size_t source_image_count;
|
||||
unsigned char base_reconstruction_identity[32];
|
||||
unsigned char calibration_scope_identity[32];
|
||||
Lardon3DDenseMvsParameters parameters;
|
||||
uint32_t execution_thread_count;
|
||||
const char *staging_directory;
|
||||
const char *interface_colmap_executable;
|
||||
const char *densify_point_cloud_executable;
|
||||
} Lardon3DDenseMvsInput;
|
||||
|
||||
typedef struct {
|
||||
Lardon3DDenseMvsStatus status;
|
||||
unsigned char dense_identity[32];
|
||||
unsigned char parameter_fingerprint[32];
|
||||
unsigned char base_reconstruction_identity[32];
|
||||
unsigned char source_image_set_identity[32];
|
||||
unsigned char backend_implementation_sha256[32];
|
||||
char point_cloud_path[LARDON3D_DENSE_MVS_PATH_CAPACITY];
|
||||
uint64_t point_count;
|
||||
} Lardon3DDenseMvsResult;
|
||||
|
||||
bool lardon3d_dense_mvs_parameter_fingerprint_record(
|
||||
const Lardon3DDenseMvsParameters *parameters,
|
||||
unsigned char record[LARDON3D_DENSE_MVS_PARAMETER_RECORD_SIZE]);
|
||||
bool lardon3d_dense_mvs_parameter_fingerprint(
|
||||
const Lardon3DDenseMvsParameters *parameters, unsigned char digest[32]);
|
||||
bool lardon3d_dense_mvs_backend_manifest_record(
|
||||
const Lardon3DDenseMvsBackendManifest *manifest,
|
||||
unsigned char record[LARDON3D_DENSE_MVS_BACKEND_MANIFEST_RECORD_SIZE]);
|
||||
bool lardon3d_dense_mvs_backend_manifest_digest(
|
||||
const Lardon3DDenseMvsBackendManifest *manifest, unsigned char digest[32]);
|
||||
bool lardon3d_dense_mvs_identity_record(
|
||||
const Lardon3DDenseMvsIdentity *identity,
|
||||
unsigned char record[LARDON3D_DENSE_MVS_IDENTITY_RECORD_SIZE]);
|
||||
bool lardon3d_dense_mvs_identity_digest(
|
||||
const Lardon3DDenseMvsIdentity *identity, unsigned char digest[32]);
|
||||
bool lardon3d_dense_mvs_source_image_set_identity(
|
||||
const Lardon3DDenseMvsSourceImage *images, size_t count,
|
||||
unsigned char digest[32]);
|
||||
bool lardon3d_dense_mvs_calibration_binding_identity(
|
||||
const Lardon3DDenseMvsSourceImage *images, size_t count,
|
||||
unsigned char digest[32]);
|
||||
|
||||
/* run() replaces result on success and resets it to failure-atomic state on error. */
|
||||
Lardon3DDenseMvsStatus lardon3d_dense_mvs_run(
|
||||
const Lardon3DDenseMvsInput *input, Lardon3DDenseMvsResult *result);
|
||||
void lardon3d_dense_mvs_result_destroy(Lardon3DDenseMvsResult *result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
28
meson.build
28
meson.build
|
|
@ -43,6 +43,13 @@ opencv_benchmark = dependency(
|
|||
modules: ['opencv_core', 'opencv_imgcodecs', 'opencv_features2d', 'opencv_imgproc'],
|
||||
)
|
||||
|
||||
opencv_dense_mvs = dependency(
|
||||
'opencv5',
|
||||
required: true,
|
||||
include_type: 'system',
|
||||
modules: ['opencv_core', 'opencv_imgcodecs', 'opencv_imgproc', 'opencv_calib3d'],
|
||||
)
|
||||
|
||||
opencv_geometry = dependency(
|
||||
'opencv5',
|
||||
version: '>=5.0.0',
|
||||
|
|
@ -158,6 +165,8 @@ executable(
|
|||
'src/incremental_reconstruction_identity.cpp',
|
||||
'src/incremental_reconstruction.cpp',
|
||||
'src/incremental_reconstruction_task.cpp',
|
||||
'src/dense_mvs_identity.cpp',
|
||||
'src/dense_mvs.cpp',
|
||||
'src/task.c',
|
||||
'src/task_checkpoint.c',
|
||||
'src/task_kind_registry.c',
|
||||
|
|
@ -179,7 +188,16 @@ executable(
|
|||
'-DLARDON3D_SPARSE_SFM_TASK_AVAILABLE',
|
||||
'-DLARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_AVAILABLE',
|
||||
],
|
||||
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry, ceres]
|
||||
dependencies: [
|
||||
ncursesw,
|
||||
threads,
|
||||
sqlite3,
|
||||
openssl,
|
||||
opencv,
|
||||
opencv_geometry,
|
||||
opencv_dense_mvs,
|
||||
ceres,
|
||||
]
|
||||
+ matcher_backend_dependencies,
|
||||
)
|
||||
|
||||
|
|
@ -980,6 +998,14 @@ incremental_reconstruction_test = executable(
|
|||
)
|
||||
test('incremental-reconstruction', incremental_reconstruction_test, timeout: 120)
|
||||
|
||||
dense_mvs_test = executable(
|
||||
'test-dense-mvs',
|
||||
sources: ['tests/test_dense_mvs.cpp', 'src/dense_mvs_identity.cpp', 'src/dense_mvs.cpp'],
|
||||
include_directories: include_directories('include'),
|
||||
dependencies: [openssl, opencv_dense_mvs],
|
||||
)
|
||||
test('dense-mvs', dense_mvs_test, timeout: 30)
|
||||
|
||||
sparse_sfm_payload_test = executable(
|
||||
'test-sparse-sfm-payload',
|
||||
sources: [
|
||||
|
|
|
|||
1288
src/dense_mvs.cpp
Normal file
1288
src/dense_mvs.cpp
Normal file
File diff suppressed because it is too large
Load diff
214
src/dense_mvs_identity.cpp
Normal file
214
src/dense_mvs_identity.cpp
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
#include <lardon3d/dense_mvs.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <openssl/evp.h>
|
||||
#include <vector>
|
||||
|
||||
namespace lardon3d::dense_mvs_detail {
|
||||
bool source_image_set_identity(const Lardon3DDenseMvsSourceImage *images,
|
||||
size_t count, unsigned char digest[32]);
|
||||
bool calibration_binding_identity(const Lardon3DDenseMvsSourceImage *images,
|
||||
size_t count, unsigned char digest[32]);
|
||||
} // namespace lardon3d::dense_mvs_detail
|
||||
|
||||
namespace {
|
||||
bool put_u32(unsigned char *out, size_t cap, size_t *at, uint32_t value) {
|
||||
if (!out || !at || *at > cap - 4) return false;
|
||||
for (unsigned int i = 0; i < 4; ++i) out[(*at)++] = (unsigned char)(value >> (8U * i));
|
||||
return true;
|
||||
}
|
||||
bool put_u64(unsigned char *out, size_t cap, size_t *at, uint64_t value) {
|
||||
if (!out || !at || *at > cap - 8) return false;
|
||||
for (unsigned int i = 0; i < 8; ++i) out[(*at)++] = (unsigned char)(value >> (8U * i));
|
||||
return true;
|
||||
}
|
||||
bool put_f64(unsigned char *out, size_t cap, size_t *at, double value) {
|
||||
if (!std::isfinite(value)) return false;
|
||||
if (value == 0.0) value = 0.0;
|
||||
uint64_t bits = 0;
|
||||
static_assert(sizeof bits == sizeof value);
|
||||
std::memcpy(&bits, &value, sizeof bits);
|
||||
return put_u64(out, cap, at, bits);
|
||||
}
|
||||
bool sha(const unsigned char *data, size_t size, unsigned char out[32]) {
|
||||
unsigned int length = 0;
|
||||
return data && out && EVP_Digest(data, size, out, &length, EVP_sha256(), nullptr) == 1 && length == 32;
|
||||
}
|
||||
bool nonzero(const unsigned char value[32]) {
|
||||
for (size_t i = 0; i < 32; ++i) if (value[i] != 0) return true;
|
||||
return false;
|
||||
}
|
||||
bool manifest_valid(const Lardon3DDenseMvsBackendManifest *manifest) {
|
||||
return manifest && nonzero(manifest->interface_colmap_version_identity) &&
|
||||
nonzero(manifest->interface_colmap_binary_sha256) &&
|
||||
nonzero(manifest->densify_point_cloud_version_identity) &&
|
||||
nonzero(manifest->densify_point_cloud_binary_sha256);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" bool lardon3d_dense_mvs_parameter_fingerprint_record(
|
||||
const Lardon3DDenseMvsParameters *parameters, unsigned char record[40]) {
|
||||
if (!parameters || !record || parameters->number_views == 0) return false;
|
||||
std::memset(record, 0, 40); std::memcpy(record, "L3DMPRM1", 8);
|
||||
size_t at = 8;
|
||||
return put_u32(record, 40, &at, 1) && put_u32(record, 40, &at, 1) &&
|
||||
put_u32(record, 40, &at, parameters->resolution_level) &&
|
||||
put_u32(record, 40, &at, parameters->minimum_resolution) &&
|
||||
put_u32(record, 40, &at, parameters->number_views) &&
|
||||
put_u32(record, 40, &at, parameters->fusion_mode) &&
|
||||
put_u32(record, 40, &at, 1) && /* FIXED_K_OPENCV_UNDISTORT_V1 */
|
||||
put_u32(record, 40, &at, 1) && /* OPENMVS_2_4_CPU_ONLY_V1 */
|
||||
at == 40;
|
||||
}
|
||||
extern "C" bool lardon3d_dense_mvs_parameter_fingerprint(
|
||||
const Lardon3DDenseMvsParameters *parameters, unsigned char digest[32]) {
|
||||
unsigned char record[40];
|
||||
return digest &&
|
||||
lardon3d_dense_mvs_parameter_fingerprint_record(parameters, record) &&
|
||||
sha(record, sizeof(record), digest);
|
||||
}
|
||||
extern "C" bool lardon3d_dense_mvs_backend_manifest_record(
|
||||
const Lardon3DDenseMvsBackendManifest *manifest, unsigned char record[148]) {
|
||||
if (!manifest_valid(manifest) || !record) return false;
|
||||
std::memset(record, 0, 148);
|
||||
std::memcpy(record, "L3DMBKD1", 8);
|
||||
size_t at = 8;
|
||||
if (!put_u32(record, 148, &at, 1) || !put_u32(record, 148, &at, 1)) return false;
|
||||
std::memcpy(record + at, manifest->interface_colmap_version_identity, 32); at += 32;
|
||||
std::memcpy(record + at, manifest->interface_colmap_binary_sha256, 32); at += 32;
|
||||
if (!put_u32(record, 148, &at, 2)) return false;
|
||||
std::memcpy(record + at, manifest->densify_point_cloud_version_identity, 32); at += 32;
|
||||
std::memcpy(record + at, manifest->densify_point_cloud_binary_sha256, 32); at += 32;
|
||||
return at == 148;
|
||||
}
|
||||
extern "C" bool lardon3d_dense_mvs_backend_manifest_digest(
|
||||
const Lardon3DDenseMvsBackendManifest *manifest, unsigned char digest[32]) {
|
||||
unsigned char record[148];
|
||||
return digest && lardon3d_dense_mvs_backend_manifest_record(manifest, record) &&
|
||||
sha(record, sizeof(record), digest);
|
||||
}
|
||||
extern "C" bool lardon3d_dense_mvs_identity_record(
|
||||
const Lardon3DDenseMvsIdentity *id, unsigned char record[220]) {
|
||||
if (!id || !record || id->dense_kind != 1 || id->dense_version != 1 ||
|
||||
id->backend_kind != 1 || id->backend_version == 0 || !nonzero(id->base_reconstruction_identity) ||
|
||||
!nonzero(id->source_image_set_identity) || !nonzero(id->calibration_scope_identity) ||
|
||||
!nonzero(id->calibration_binding_identity) ||
|
||||
!nonzero(id->backend_binary_sha256) || !nonzero(id->parameter_fingerprint)) return false;
|
||||
std::memset(record, 0, 220); std::memcpy(record, "L3DMDID2", 8); size_t at = 8;
|
||||
if (!put_u32(record, 220, &at, 2) || !put_u32(record, 220, &at, id->dense_kind) ||
|
||||
!put_u32(record, 220, &at, id->dense_version) || !put_u32(record, 220, &at, id->backend_kind) ||
|
||||
!put_u32(record, 220, &at, id->backend_version)) return false;
|
||||
for (const auto *field : {id->base_reconstruction_identity, id->source_image_set_identity,
|
||||
id->calibration_scope_identity, id->calibration_binding_identity,
|
||||
id->backend_binary_sha256,
|
||||
id->parameter_fingerprint}) { std::memcpy(record + at, field, 32); at += 32; }
|
||||
return at == 220;
|
||||
}
|
||||
extern "C" bool lardon3d_dense_mvs_identity_digest(
|
||||
const Lardon3DDenseMvsIdentity *id, unsigned char digest[32]) {
|
||||
unsigned char record[220];
|
||||
return digest && lardon3d_dense_mvs_identity_record(id, record) &&
|
||||
sha(record, sizeof(record), digest);
|
||||
}
|
||||
bool lardon3d::dense_mvs_detail::calibration_binding_identity(
|
||||
const Lardon3DDenseMvsSourceImage *images, size_t count,
|
||||
unsigned char digest[32]) {
|
||||
constexpr size_t kRecordSize = 80;
|
||||
if (!images || !digest || count == 0 ||
|
||||
count > (SIZE_MAX - 20) / kRecordSize)
|
||||
return false;
|
||||
std::vector<const Lardon3DDenseMvsSourceImage *> ordered;
|
||||
ordered.reserve(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (images[i].image_id == 0) return false;
|
||||
ordered.push_back(&images[i]);
|
||||
}
|
||||
std::sort(ordered.begin(), ordered.end(), [](auto a, auto b) {
|
||||
return a->image_id < b->image_id;
|
||||
});
|
||||
for (size_t i = 1; i < count; ++i)
|
||||
if (ordered[i - 1]->image_id == ordered[i]->image_id) return false;
|
||||
std::vector<unsigned char> record(20 + count * kRecordSize, 0);
|
||||
std::memcpy(record.data(), "L3DMCAL1", 8);
|
||||
size_t at = 8;
|
||||
if (!put_u32(record.data(), record.size(), &at, 1) ||
|
||||
!put_u64(record.data(), record.size(), &at, count))
|
||||
return false;
|
||||
for (const auto *image : ordered) {
|
||||
const auto &cal = image->calibration;
|
||||
if (!put_u64(record.data(), record.size(), &at, image->image_id) ||
|
||||
!put_u32(record.data(), record.size(), &at, cal.width) ||
|
||||
!put_u32(record.data(), record.size(), &at, cal.height) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.fx) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.fy) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.cx) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.cy) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.k1) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.k2) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.p1) ||
|
||||
!put_f64(record.data(), record.size(), &at, cal.p2))
|
||||
return false;
|
||||
}
|
||||
return at == record.size() && sha(record.data(), record.size(), digest);
|
||||
}
|
||||
|
||||
extern "C" bool lardon3d_dense_mvs_calibration_binding_identity(
|
||||
const Lardon3DDenseMvsSourceImage *images, size_t count,
|
||||
unsigned char digest[32]) {
|
||||
try {
|
||||
return lardon3d::dense_mvs_detail::calibration_binding_identity(
|
||||
images, count, digest);
|
||||
} catch (const std::bad_alloc &) {
|
||||
return false;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool lardon3d::dense_mvs_detail::source_image_set_identity(
|
||||
const Lardon3DDenseMvsSourceImage *images, size_t count,
|
||||
unsigned char digest[32]) {
|
||||
if (!images || !digest || count == 0 || count > (SIZE_MAX - 20) / 40)
|
||||
return false;
|
||||
std::vector<const Lardon3DDenseMvsSourceImage *> ordered;
|
||||
ordered.reserve(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (images[i].image_id == 0 || !images[i].source_path ||
|
||||
!nonzero(images[i].immutable_sha256))
|
||||
return false;
|
||||
ordered.push_back(&images[i]);
|
||||
}
|
||||
std::sort(ordered.begin(), ordered.end(), [](auto a, auto b) {
|
||||
return a->image_id < b->image_id;
|
||||
});
|
||||
for (size_t i = 1; i < count; ++i)
|
||||
if (ordered[i - 1]->image_id == ordered[i]->image_id) return false;
|
||||
std::vector<unsigned char> record(20 + count * 40, 0);
|
||||
std::memcpy(record.data(), "L3DMSRC1", 8);
|
||||
size_t at = 8;
|
||||
if (!put_u32(record.data(), record.size(), &at, 1) ||
|
||||
!put_u64(record.data(), record.size(), &at, count))
|
||||
return false;
|
||||
for (const auto *image : ordered) {
|
||||
if (!put_u64(record.data(), record.size(), &at, image->image_id))
|
||||
return false;
|
||||
std::memcpy(record.data() + at, image->immutable_sha256, 32);
|
||||
at += 32;
|
||||
}
|
||||
return at == record.size() && sha(record.data(), record.size(), digest);
|
||||
}
|
||||
|
||||
extern "C" bool lardon3d_dense_mvs_source_image_set_identity(
|
||||
const Lardon3DDenseMvsSourceImage *images, size_t count,
|
||||
unsigned char digest[32]) {
|
||||
try {
|
||||
return lardon3d::dense_mvs_detail::source_image_set_identity(
|
||||
images, count, digest);
|
||||
} catch (const std::bad_alloc &) {
|
||||
return false;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
1307
tests/test_dense_mvs.cpp
Normal file
1307
tests/test_dense_mvs.cpp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue