Function ecc_match

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

Aligns and stacks images using OpenCV’s Enhanced Correlation Coefficient (ECC) algorithm.

Performs intensity-based image alignment using pyramidal ECC maximization, with optional scaling for performance. The first image serves as the reference frame for alignment.

§Algorithm Overview

  1. Converts images to grayscale for alignment computation
  2. Optionally scales images down for faster ECC computation
  3. Estimates transformation (translation/affine/homography) using ECC maximization
  4. Applies transformation to original full-resolution images
  5. Averages all aligned images

§When to Use

  • When images need alignment but lack distinctive features for keypoint matching
  • For alignment under illumination changes (ECC is illumination-invariant)
  • When precise subpixel alignment is required

§Parameters

  • files: Iterator of image paths (any AsRef<Path> type)
  • params: ECC configuration parameters:
    • motion_type: Type of transformation to estimate:
      • Translation, Euclidean, Affine, or Homography
    • max_count: Maximum iterations (None for OpenCV default)
    • epsilon: Convergence threshold (None for OpenCV default)
    • gauss_filt_size: Gaussian filter size for pyramid levels (odd number ≥1)
  • scale_down_width: Performance/accuracy trade-off:
    • Some(width): Process ECC at specified width (faster)
    • None: Process at full resolution (more precise)
    • Must be < original width when specified

§Returns

  • Ok(Mat): Stacked image in CV_32F format (normalized 0-1 range)
  • Err(StackerError) on:
    • No input files
    • Invalid scale width
    • Image loading/processing failures

§Performance Notes

  • Parallel processing for multi-image alignment
  • Scaling provides 3-5x speedup with minor accuracy impact
  • Computation complexity depends on:
    • Motion type (Translation < Affine < Homography)
    • Image resolution
    • Convergence criteria

§Example

// Fast homography alignment with scaling
let aligned = ecc_match(
    ["1.jpg", "2.jpg", "3.jpg"],
    EccMatchParameters {
        motion_type: MotionType::Homography,
        max_count: Some(5000),
        epsilon: Some(1e-6),
        gauss_filt_size: 3,
    },
    Some(800.0)  // Scale to 800px width
)?;

// Precise affine alignment (full resolution)
let precise = ecc_match(
    vec!["1.tif", "2.tif"],
    EccMatchParameters {
        motion_type: MotionType::Affine,
        max_count: Some(5000),
        epsilon: Some(1e-6),
        gauss_filt_size: 3,
    },
    None  // No scaling
)?;

§References

Examples found in repository?
examples/main.rs (lines 105-114)
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_key(|f| ordered_float::OrderedFloat(f.3));
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}