pub fn ecc_match<I, P>(
files: I,
params: EccMatchParameters,
scale_down_width: Option<f32>,
) -> Result<Mat, StackerError>
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
- Converts images to grayscale for alignment computation
- Optionally scales images down for faster ECC computation
- Estimates transformation (translation/affine/homography) using ECC maximization
- Applies transformation to original full-resolution images
- 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 (anyAsRef<Path>
type)params
: ECC configuration parameters:motion_type
: Type of transformation to estimate:Translation
,Euclidean
,Affine
, orHomography
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}