Commit: aeaa6e4
Parent: d65ca97

Skip matching pairs below RANSAC's minimum correspondence count

Mårten Åsberg committed on 2026-08-03 at 12:50
An image with only 1-2 detected keypoints produces a degenerate tensor
shape that crashes OpenVINO EP's subgraph shape inference during LightGlue
inference. Since RANSAC needs >= 8 correspondences to fit a fundamental
matrix anyway, such pairs can never pass geometric verification, so skip
them before running the matcher at all rather than crashing on them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
src/exhaustive_matcher.rs +4 -1
diff --git a/src/exhaustive_matcher.rs b/src/exhaustive_matcher.rs
index 0673ff4..bdf426a 100644
@@ -72,7 +72,10 @@ fn run_lightglue(
) -> Result<Vec<(usize, usize)>> {
let n = a.keypoints.len();
let m = b.keypoints.len();
if n == 0 || m == 0 {
// Below this, RANSAC could never fit a fundamental matrix (needs >= 8 correspondences)
// so the pair can never pass verification anyway. Also sidesteps a shape-inference crash
// in OpenVINO EP's subgraph partitioning on degenerate (e.g. single-keypoint) sequences.
if n < ransac::MIN_CORRESPONDENCES || m < ransac::MIN_CORRESPONDENCES {
return Ok(Vec::new());
}
src/feature_extractor.rs +6 -3
diff --git a/src/feature_extractor.rs b/src/feature_extractor.rs
index 5300587..c83388d 100644
@@ -131,15 +131,18 @@ pub fn run(cfg: FeatureExtractorConfig) -> Result<()> {
let (keypoints, descriptors_flat, scores) = run_aliked(&mut session, &pre.tensor)?;
let n = keypoints.len();
if descriptors_flat.len() % n.max(1) != 0 {
let desc_dim = if n == 0 {
128
} else if descriptors_flat.len() % n == 0 {
descriptors_flat.len() / n
} else {
anyhow::bail!(
"{}: descriptor count ({}) doesn't divide evenly by keypoint count ({})",
rel_name,
descriptors_flat.len(),
n
);
}
let desc_dim = if n == 0 { 128 } else { descriptors_flat.len() / n };
};
// The ONNX model already caps output count near its export-time top-k; this only
// trims further if more keypoints than requested were returned.
src/ransac.rs +5 -2
diff --git a/src/ransac.rs b/src/ransac.rs
index b8f6e9b..18b15ae 100644
@@ -3,6 +3,9 @@ use nalgebra::{DMatrix, Matrix3, RowDVector, SVD, Vector3};
pub const DEFAULT_MAX_ITERATIONS: usize = 1000;
pub const DEFAULT_INLIER_THRESHOLD: f64 = 4.0;
pub const DEFAULT_CONFIDENCE: f64 = 0.9999;
/// The normalized 8-point algorithm needs at least this many correspondences to fit
/// a fundamental matrix at all.
pub const MIN_CORRESPONDENCES: usize = 8;
pub struct RansacResult {
/// Row-major 3x3 fundamental matrix.
@@ -37,7 +40,7 @@ fn apply_transform(t: &Matrix3<f64>, p: (f64, f64)) -> (f64, f64) {
/// Normalized 8-point algorithm. Works with >=8 correspondences via least-squares.
fn fit_fundamental(pts0: &[(f64, f64)], pts1: &[(f64, f64)]) -> Option<Matrix3<f64>> {
let n = pts0.len();
if n < 8 || pts1.len() != n {
if n < MIN_CORRESPONDENCES || pts1.len() != n {
return None;
}
@@ -109,7 +112,7 @@ pub fn ransac_fundamental_matrix(
confidence: f64,
) -> Option<RansacResult> {
let n = pts0.len();
if n < 8 || pts1.len() != n {
if n < MIN_CORRESPONDENCES || pts1.len() != n {
return None;
}