main/
main.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2021,2025 lacklustr@protonmail.com https://github.com/eadf
3
4use libstacker::opencv::{highgui, imgcodecs};
5use libstacker::prelude::*;
6use rayon::prelude::*;
7use std::path;
8
9/// Returns paths to all jpg,jpeg,tif and png files in a single directory (non-recursive)
10pub fn collect_image_files(
11    path: &std::path::Path,
12) -> Result<Vec<std::path::PathBuf>, StackerError> {
13    Ok(std::fs::read_dir(path)?
14        .flatten()
15        .filter_map(|f| f.path().is_file().then(|| f.path()))
16        .filter(|p| p.extension().is_some() && p.extension().unwrap().to_str().is_some())
17        .filter(|p| {
18            let extension = p.extension().unwrap().to_str().unwrap().to_uppercase();
19            extension == "JPG" || extension == "JPEG" || extension == "TIF" || extension == "PNG"
20        })
21        .collect())
22}
23
24/// Run an example aligning images found under "image_stacking_py/images"
25/// The example displays two images, one is aligned with the `keypoint_match()` method,
26/// the other with `ecc_match()`
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}