diff --git a/opensfm/config.py b/opensfm/config.py index 372efcc04..bdf30c0da 100644 --- a/opensfm/config.py +++ b/opensfm/config.py @@ -111,6 +111,9 @@ class OpenSfMConfig: flann_tree: int = 8 # Smaller -> Faster (but might lose good matches) flann_checks: int = 20 + # Seed for the RNG that FLANN's index construction draws from. + # Fixed by default so matching is reproducible; see build_flann_index. + flann_random_seed: int = 42 ################################## # Params for BoW matching diff --git a/opensfm/features.py b/opensfm/features.py index 0fb752231..f87454522 100644 --- a/opensfm/features.py +++ b/opensfm/features.py @@ -670,4 +670,11 @@ def build_flann_index(descriptors: NDArray, config: Dict[str, Any]) -> cv2.flann ) # pyrefly: ignore [not-callable] + # Pin OpenCV's RNG so the index is a pure function of (descriptors, + # seed). FLANN's index construction draws from cv::theRNG(), which is + # thread-local and advances between builds, so without this two workers + # build different indexes from identical descriptors and matching is + # not reproducible. + cv2.setRNGSeed(config["flann_random_seed"]) + return context.flann_Index(descriptors, flann_params)