keypoint_match

Function keypoint_match 

Source
pub fn keypoint_match<I, P>(
    files: I,
    params: KeyPointMatchParameters,
    scale_down_width: Option<f32>,
) -> Result<(i32, Mat), StackerError>
where I: IntoIterator<Item = P>, P: AsRef<Path>,
Expand description

Aligns and combines multiple images by matching keypoints, with optional scaling for performance.

This function processes a sequence of images by:

  1. Detecting ORB features (either at full resolution or scaled-down for faster processing)
  2. Matching features between the first (reference) image and subsequent images
  3. Computing homographies to align images to the reference
  4. Warping images to the reference frame and averaging them

When scaling is enabled:

  • Feature detection/matching occurs on smaller images for performance
  • Homographies are scaled appropriately for full-size warping

§Parameters

  • files: An iterator of paths to image files (any type implementing AsRef<Path>)
  • params: Configuration parameters for keypoint matching:
    • method: Homography computation method (e.g., RANSAC, LMEDS)
    • ransac_reproj_threshold: Maximum reprojection error for inlier classification
  • scale_down_width: Controls performance/accuracy trade-off:
    • Some(width): Process features at specified width (faster, recommended 400-800px)
    • None: Process at full resolution (more accurate but slower)
    • Must be smaller than original image width when specified

§Returns

  • Ok((i32, Mat)).: number of dropped frames + Combined/averaged image in CV_32F format (normalized 0-1 range)
  • Err(StackerError) on failure cases:
    • No input files provided
    • Invalid scale width (when specified)
    • Image loading/processing failures

§Performance Characteristics

  • Parallel processing (Rayon) for multi-image alignment
  • Memory efficient streaming processing
  • Output matches size/coordinates of first (reference) image
  • Scaling typically provides 2-4x speedup with minimal accuracy loss
let keypoint_match_img:opencv::core::Mat = keypoint_match(
    vec!["1.jpg","2.jpg","3.jpg","4.jpg","5.jpg"],
    KeyPointMatchParameters {
        method: opencv::calib3d::RANSAC,
        ransac_reproj_threshold: 5.0,
        match_ratio: 0.9,
         match_keep_ratio: 0.80,
         border_mode: opencv::core::BORDER_CONSTANT,
         border_value: opencv::core::Scalar::default(),
     },
     None)?.1;

§References

Examples found in repository?
examples/main.rs (lines 67-78)
27fn main() -> Result<(), StackerError> {
28    use libstacker::opencv::core::Point;
29
30    let v = vec![Point::new(1, 5), Point::new(2, 5), Point::new(3, 5)];
31    let _ = v
32        .into_iter()
33        .collect::<libstacker::opencv::core::Vector<Point>>();
34
35    let files = collect_image_files(&path::PathBuf::from("image_stacking_py/images"))?;
36    let now = std::time::Instant::now();
37    let mut files = files
38        .into_par_iter()
39        .map(move |f| -> Result<_, StackerError> {
40            let img_gray = imgcodecs::imread(f.to_str().unwrap(), imgcodecs::IMREAD_GRAYSCALE)?;
41            Ok((
42                f,
43                libstacker::sharpness_modified_laplacian(&img_gray)?, // LAPM = 1
44                libstacker::sharpness_variance_of_laplacian(&img_gray)?, // LAPV = 2
45                libstacker::sharpness_tenengrad(&img_gray, 3)?,       // TENG = 3
46                libstacker::sharpness_normalized_gray_level_variance(&img_gray)?, // GLVN = 4
47            ))
48        })
49        .collect::<Result<Vec<_>, StackerError>>()?;
50    println!("Calculated sharpness() in {:?}", now.elapsed());
51
52    // sort images by sharpness using TENG for now
53    files.sort_by(|a, b| a.3.partial_cmp(&b.3).unwrap_or(std::cmp::Ordering::Equal));
54
55    println!("Files ordered by TENG (low quality first)");
56    for f in files.iter() {
57        println!(
58            "{:?} LAPM:{: >8.5} LAPV:{: >9.5} TENG:{: >8.5} GLVN:{: >9.5}",
59            f.0, f.1, f.2, f.3, f.4
60        );
61    }
62    // only keep the filename and skip the first file (the bad one) and reverse the order
63    // keeping the best image first.
64    let files: Vec<_> = files.into_iter().map(|f| f.0).skip(1).rev().collect();
65
66    let now = std::time::Instant::now();
67    let keypoint_match_img = keypoint_match(
68        &files,
69        libstacker::KeyPointMatchParameters {
70            method: opencv::calib3d::RANSAC,
71            ransac_reproj_threshold: 5.0,
72            match_ratio: 0.9,
73            match_keep_ratio: 0.80,
74            border_mode: opencv::core::BORDER_CONSTANT,
75            border_value: opencv::core::Scalar::default(),
76        },
77        None,
78    )?;
79    let keypoint_match_img_duration = now.elapsed();
80    println!(
81        "Calculated keypoint_match() in {:?} dropped frames:{}",
82        keypoint_match_img_duration, keypoint_match_img.0
83    );
84
85    let now = std::time::Instant::now();
86    let keypoint_match_img_400 = keypoint_match(
87        &files,
88        libstacker::KeyPointMatchParameters {
89            method: opencv::calib3d::RANSAC,
90            ransac_reproj_threshold: 5.0,
91            match_ratio: 0.9,
92            match_keep_ratio: 0.80,
93            border_mode: opencv::core::BORDER_CONSTANT,
94            border_value: opencv::core::Scalar::default(),
95        },
96        Some(400.0),
97    )?;
98    let keypoint_match_400_img_duration = now.elapsed();
99    println!(
100        "Calculated keypoint_match(width=400) in {:?} dropped frames:{}",
101        keypoint_match_400_img_duration, keypoint_match_img_400.0
102    );
103
104    let now = std::time::Instant::now();
105    let ecc_match_img = libstacker::ecc_match(
106        &files,
107        libstacker::EccMatchParameters {
108            motion_type: libstacker::MotionType::Homography,
109            max_count: Some(5000),
110            epsilon: Some(1e-5),
111            gauss_filt_size: 5,
112        },
113        None,
114    )?;
115    let ecc_match_img_duration = now.elapsed();
116    println!("Calculated ecc_match() in {:?}", ecc_match_img_duration);
117
118    let now = std::time::Instant::now();
119    let ecc_match_img_400 = libstacker::ecc_match(
120        files,
121        libstacker::EccMatchParameters {
122            motion_type: libstacker::MotionType::Homography,
123            max_count: Some(5000),
124            epsilon: Some(1e-5),
125            gauss_filt_size: 5,
126        },
127        Some(400.0),
128    )?;
129    let ecc_match_400_img_duration = now.elapsed();
130    println!(
131        "Calculated ecc_match(width=400) in {:?}",
132        ecc_match_400_img_duration
133    );
134
135    while highgui::wait_key(33)? != 27 {
136        highgui::imshow(
137            format!(
138                "KeyPoint match (full resolution) [{:?}]",
139                keypoint_match_img_duration
140            )
141            .as_str(),
142            &keypoint_match_img.1,
143        )?;
144        highgui::imshow(
145            format!("ECC match (full resolution) [{:?}]", ecc_match_img_duration).as_str(),
146            &ecc_match_img,
147        )?;
148        highgui::imshow(
149            format!(
150                "KeyPoint match (width 400) [{:?}]",
151                keypoint_match_400_img_duration
152            )
153            .as_str(),
154            &keypoint_match_img_400.1,
155        )?;
156        highgui::imshow(
157            format!("ECC match (width 400) [{:?}]", ecc_match_400_img_duration).as_str(),
158            &ecc_match_img_400,
159        )?;
160    }
161    Ok(())
162}