Skip to main content

akaze/
lib.rs

1mod contrast_factor;
2mod derivatives;
3mod descriptors;
4mod detector_response;
5mod evolution;
6mod fed_tau;
7mod image;
8mod nonlinear_diffusion;
9mod scale_space_extrema;
10
11use crate::image::{gaussian_blur, GrayFloatImage};
12use ::image::{DynamicImage, GenericImageView, ImageResult};
13use bitarray::BitArray;
14use cv_core::nalgebra::Point2;
15use cv_core::ImagePoint;
16use evolution::*;
17use log::*;
18use nonlinear_diffusion::pm_g2;
19use std::path::Path;
20
21/// A point of interest in an image.
22/// This pretty much follows from OpenCV conventions.
23#[derive(Debug, Clone, Copy)]
24pub struct KeyPoint {
25    /// The horizontal coordinate in a coordinate system is
26    /// defined s.t. +x faces right and starts from the top
27    /// of the image.
28    /// the vertical coordinate in a coordinate system is defined
29    /// s.t. +y faces toward the bottom of an image and starts
30    /// from the left side of the image.
31    pub point: (f32, f32),
32    /// The magnitude of response from the detector.
33    pub response: f32,
34
35    /// The radius defining the extent of the keypoint, in pixel units
36    pub size: f32,
37
38    /// The level of scale space in which the keypoint was detected.
39    pub octave: usize,
40
41    /// A classification ID
42    pub class_id: usize,
43
44    /// The orientation angle
45    pub angle: f32,
46}
47
48impl ImagePoint for KeyPoint {
49    fn image_point(&self) -> Point2<f64> {
50        Point2::new(self.point.0 as f64, self.point.1 as f64)
51    }
52}
53
54/// Contains the configuration parameters of AKAZE.
55///
56/// The most important parameter to pay attention to is `detector_threshold`.
57/// [`Config::new`] can be used to set this threshold and let all other parameters
58/// remain default. You can also use the helpers [`Config::sparse`] and
59/// [`Config::dense`]. The default value of `detector_threshold` is `0.001`.
60///
61#[derive(Debug, Copy, Clone)]
62pub struct Akaze {
63    /// Default number of sublevels per scale level
64    pub num_sublevels: u32,
65
66    /// Maximum octave evolution of the image 2^sigma (coarsest scale sigma units)
67    pub max_octave_evolution: u32,
68
69    /// Base scale offset (sigma units)
70    pub base_scale_offset: f64,
71
72    /// The initial contrast factor parameter
73    pub initial_contrast: f64,
74
75    /// Percentile level for the contrast factor
76    pub contrast_percentile: f64,
77
78    /// Number of bins for the contrast factor histogram
79    pub contrast_factor_num_bins: usize,
80
81    /// Factor for the multiscale derivatives
82    pub derivative_factor: f64,
83
84    /// Detector response threshold to accept point
85    pub detector_threshold: f64,
86
87    /// Number of channels in the descriptor (1, 2, 3)
88    pub descriptor_channels: usize,
89
90    /// Actual patch size is 2*pattern_size*point.scale
91    pub descriptor_pattern_size: usize,
92}
93
94impl Akaze {
95    /// This convenience constructor is provided for the very common case
96    /// that the detector threshold needs to be modified.
97    pub fn new(threshold: f64) -> Self {
98        Self {
99            detector_threshold: threshold,
100            ..Default::default()
101        }
102    }
103
104    /// Create a `Config` that sparsely detects features.
105    ///
106    /// Uses a threshold of `0.01` (default is `0.001`).
107    pub fn sparse() -> Self {
108        Self::new(0.01)
109    }
110
111    /// Create a `Config` that densely detects features.
112    ///
113    /// Uses a threshold of `0.0001` (default is `0.001`).
114    pub fn dense() -> Self {
115        Self::new(0.0001)
116    }
117}
118
119impl Default for Akaze {
120    fn default() -> Akaze {
121        Akaze {
122            num_sublevels: 4,
123            max_octave_evolution: 4,
124            base_scale_offset: 1.6f64,
125            initial_contrast: 0.001f64,
126            contrast_percentile: 0.7f64,
127            contrast_factor_num_bins: 300,
128            derivative_factor: 1.5f64,
129            detector_threshold: 0.001f64,
130            descriptor_channels: 3usize,
131            descriptor_pattern_size: 10usize,
132        }
133    }
134}
135
136impl Akaze {
137    /// A nonlinear scale space performs selective blurring to preserve edges.
138    ///
139    /// # Arguments
140    /// * `evolutions` - The output scale space.
141    /// * `image` - The input image.
142    fn create_nonlinear_scale_space(
143        &self,
144        evolutions: &mut Vec<EvolutionStep>,
145        image: &GrayFloatImage,
146    ) {
147        trace!("Creating first evolution.");
148        evolutions[0].Lt = gaussian_blur(image, self.base_scale_offset as f32);
149        trace!("Gaussian blur finished.");
150        evolutions[0].Lsmooth = evolutions[0].Lt.clone();
151        debug!(
152            "Convolving first evolution with sigma={} Gaussian.",
153            self.base_scale_offset
154        );
155        let mut contrast_factor = contrast_factor::compute_contrast_factor(
156            &evolutions[0].Lsmooth,
157            self.contrast_percentile,
158            1.0f64,
159            self.contrast_factor_num_bins,
160        );
161        trace!("Computing contrast factor finished.");
162        debug!(
163            "Contrast percentile={}, Num bins={}, Initial contrast factor={}",
164            self.contrast_percentile, self.contrast_factor_num_bins, contrast_factor
165        );
166        for i in 1..evolutions.len() {
167            trace!("Creating evolution {}.", i);
168            if evolutions[i].octave > evolutions[i - 1].octave {
169                evolutions[i].Lt = evolutions[i - 1].Lt.half_size();
170                trace!("Half-sizing done.");
171                contrast_factor *= 0.75;
172                debug!(
173                    "New image size: {}x{}, new contrast factor: {}",
174                    evolutions[i].Lt.width(),
175                    evolutions[i].Lt.height(),
176                    contrast_factor
177                );
178            } else {
179                evolutions[i].Lt = evolutions[i - 1].Lt.clone();
180            }
181            evolutions[i].Lsmooth = gaussian_blur(&evolutions[i].Lt, 1.0f32);
182            trace!("Gaussian blur finished.");
183            evolutions[i].Lx = derivatives::scharr_horizontal(&evolutions[i].Lsmooth, 1);
184            trace!("Computing derivative Lx done.");
185            evolutions[i].Ly = derivatives::scharr_vertical(&evolutions[i].Lsmooth, 1);
186            trace!("Computing derivative Ly done.");
187            evolutions[i].Lflow = pm_g2(&evolutions[i].Lx, &evolutions[i].Ly, contrast_factor);
188            trace!("Lflow finished.");
189            for j in 0..evolutions[i].fed_tau_steps.len() {
190                trace!("Starting diffusion step.");
191                let step_size = evolutions[i].fed_tau_steps[j];
192                nonlinear_diffusion::calculate_step(&mut evolutions[i], step_size as f32);
193                trace!("Diffusion step finished with step size {}", step_size);
194            }
195        }
196    }
197
198    /// Find image keypoints using the Akaze feature extractor.
199    ///
200    /// # Arguments
201    /// * `input_image` - An image from which to extract features.
202    /// * `options` the options for the algorithm.
203    /// # Return Value
204    /// The resulting keypoints.
205    ///
206    fn find_image_keypoints(&self, evolutions: &mut Vec<EvolutionStep>) -> Vec<KeyPoint> {
207        self.detector_response(evolutions);
208        trace!("Computing detector response finished.");
209        self.detect_keypoints(evolutions)
210    }
211
212    /// Extract features using the Akaze feature extractor.
213    ///
214    /// This performs all operations end-to-end. The client might be only interested
215    /// in certain portions of the process, all of which are exposed in public functions,
216    /// but this function can document how the various parts fit together.
217    ///
218    /// # Arguments
219    /// * `image` - The input image for which to extract features.
220    /// * `options` - The options for the algorithm. Set this to `None` for default options.
221    ///
222    /// Returns the keypoints and the descriptors.
223    ///
224    /// # Example
225    /// ```
226    /// use std::path::Path;
227    /// let akaze = akaze::Akaze::default();
228    /// let (keypoints, descriptors) = akaze.extract(&image::open("res/0000000000.png").unwrap());
229    /// ```
230    ///
231    pub fn extract(&self, image: &DynamicImage) -> (Vec<KeyPoint>, Vec<BitArray<64>>) {
232        let float_image = GrayFloatImage::from_dynamic(&image);
233        info!("Loaded a {} x {} image", image.width(), image.height());
234        let mut evolutions = self.allocate_evolutions(image.width(), image.height());
235        self.create_nonlinear_scale_space(&mut evolutions, &float_image);
236        trace!("Creating scale space finished.");
237        let keypoints = self.find_image_keypoints(&mut evolutions);
238        let descriptors = self.extract_descriptors(&evolutions, &keypoints);
239        trace!("Computing descriptors finished.");
240        (keypoints, descriptors)
241    }
242
243    /// Extract features using the Akaze feature extractor from an image on disk.
244    ///
245    /// This performs all operations end-to-end. The client might be only interested
246    /// in certain portions of the process, all of which are exposed in public functions,
247    /// but this function can document how the various parts fit together.
248    ///
249    /// # Arguments
250    /// * `path` - The input image path for which to extract features.
251    /// * `options` - The options for the algorithm. Set this to `None` for default options.
252    ///
253    /// Returns an `ImageResult` of the keypoints and the descriptors.
254    ///
255    /// # Examples
256    /// ```
257    /// use std::path::Path;
258    /// let akaze = akaze::Akaze::default();
259    /// let (keypoints, descriptors) = akaze.extract_path("res/0000000000.png").unwrap();
260    /// ```
261    ///
262    pub fn extract_path(
263        &self,
264        path: impl AsRef<Path>,
265    ) -> ImageResult<(Vec<KeyPoint>, Vec<BitArray<64>>)> {
266        Ok(self.extract(&::image::open(path)?))
267    }
268}