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}