1use crate::definitions::{Position, Score};
4use image::{GenericImageView, GrayImage};
5
6#[derive(Copy, Clone, Debug, PartialEq)]
10pub struct Corner {
11 pub x: u32,
13 pub y: u32,
15 pub score: f32,
17}
18
19impl Corner {
20 pub fn new(x: u32, y: u32, score: f32) -> Corner {
22 Corner { x, y, score }
23 }
24}
25
26impl Position for Corner {
27 fn x(&self) -> u32 {
29 self.x
30 }
31
32 fn y(&self) -> u32 {
34 self.y
35 }
36}
37
38impl Score for Corner {
39 fn score(&self) -> f32 {
40 self.score
41 }
42}
43
44pub enum Fast {
53 Nine,
55 Twelve,
57}
58
59pub fn corners_fast12(image: &GrayImage, threshold: u8) -> Vec<Corner> {
61 let (width, height) = image.dimensions();
62 let mut corners = vec![];
63
64 for y in 0..height {
65 for x in 0..width {
66 if is_corner_fast12(image, threshold, x, y) {
67 let score = fast_corner_score(image, threshold, x, y, Fast::Twelve);
68 corners.push(Corner::new(x, y, score as f32));
69 }
70 }
71 }
72
73 corners
74}
75
76pub fn corners_fast9(image: &GrayImage, threshold: u8) -> Vec<Corner> {
78 let (width, height) = image.dimensions();
79 let mut corners = vec![];
80
81 for y in 0..height {
82 for x in 0..width {
83 if is_corner_fast9(image, threshold, x, y) {
84 let score = fast_corner_score(image, threshold, x, y, Fast::Nine);
85 corners.push(Corner::new(x, y, score as f32));
86 }
87 }
88 }
89
90 corners
91}
92
93pub fn fast_corner_score(image: &GrayImage, threshold: u8, x: u32, y: u32, variant: Fast) -> u8 {
101 let mut max = 255u8;
102 let mut min = threshold;
103
104 loop {
105 if max == min {
106 return max;
107 }
108
109 let mean = ((max as u16 + min as u16) / 2u16) as u8;
110 let probe = if max == min + 1 { max } else { mean };
111
112 let is_corner = match variant {
113 Fast::Nine => is_corner_fast9(image, probe, x, y),
114 Fast::Twelve => is_corner_fast12(image, probe, x, y),
115 };
116
117 if is_corner {
118 min = probe;
119 } else {
120 max = probe - 1;
121 }
122 }
123}
124
125fn is_corner_fast9(image: &GrayImage, threshold: u8, x: u32, y: u32) -> bool {
139 let (width, height) = image.dimensions();
150 if x >= u32::max_value() - 3
151 || y >= u32::max_value() - 3
152 || x < 3
153 || y < 3
154 || width <= x + 3
155 || height <= y + 3
156 {
157 return false;
158 }
159
160 let c = unsafe { image.unsafe_get_pixel(x, y)[0] };
162 let low_thresh: i16 = c as i16 - threshold as i16;
163 let high_thresh: i16 = c as i16 + threshold as i16;
164
165 let (p0, p4, p8, p12) = unsafe {
168 (
169 image.unsafe_get_pixel(x, y - 3)[0] as i16,
170 image.unsafe_get_pixel(x, y + 3)[0] as i16,
171 image.unsafe_get_pixel(x + 3, y)[0] as i16,
172 image.unsafe_get_pixel(x - 3, y)[0] as i16,
173 )
174 };
175
176 let above = (p0 > high_thresh && p4 > high_thresh)
177 || (p4 > high_thresh && p8 > high_thresh)
178 || (p8 > high_thresh && p12 > high_thresh)
179 || (p12 > high_thresh && p0 > high_thresh);
180
181 let below = (p0 < low_thresh && p4 < low_thresh)
182 || (p4 < low_thresh && p8 < low_thresh)
183 || (p8 < low_thresh && p12 < low_thresh)
184 || (p12 < low_thresh && p0 < low_thresh);
185
186 if !above && !below {
187 return false;
188 }
189
190 let pixels = unsafe { get_circle(image, x, y, p0, p4, p8, p12) };
192
193 (above && has_bright_span(&pixels, 9, high_thresh))
195 || (below && has_dark_span(&pixels, 9, low_thresh))
196}
197
198fn is_corner_fast12(image: &GrayImage, threshold: u8, x: u32, y: u32) -> bool {
200 let (width, height) = image.dimensions();
211 if x >= u32::max_value() - 3
212 || y >= u32::max_value() - 3
213 || x < 3
214 || y < 3
215 || width <= x + 3
216 || height <= y + 3
217 {
218 return false;
219 }
220
221 let c = unsafe { image.unsafe_get_pixel(x, y)[0] };
223 let low_thresh: i16 = c as i16 - threshold as i16;
224 let high_thresh: i16 = c as i16 + threshold as i16;
225
226 let (p0, p8) = unsafe {
229 (
230 image.unsafe_get_pixel(x, y - 3)[0] as i16,
231 image.unsafe_get_pixel(x, y + 3)[0] as i16,
232 )
233 };
234
235 let mut above = p0 > high_thresh && p8 > high_thresh;
236 let mut below = p0 < low_thresh && p8 < low_thresh;
237
238 if !above && !below {
239 return false;
240 }
241
242 let (p4, p12) = unsafe {
244 (
245 image.unsafe_get_pixel(x + 3, y)[0] as i16,
246 image.unsafe_get_pixel(x - 3, y)[0] as i16,
247 )
248 };
249
250 above = above && ((p4 > high_thresh) || (p12 > high_thresh));
251 below = below && ((p4 < low_thresh) || (p12 < low_thresh));
252
253 if !above && !below {
254 return false;
255 }
256
257 let pixels = unsafe { get_circle(image, x, y, p0, p4, p8, p12) };
265
266 if above {
268 has_bright_span(&pixels, 12, high_thresh)
269 } else {
270 has_dark_span(&pixels, 12, low_thresh)
271 }
272}
273
274#[inline]
284unsafe fn get_circle(
285 image: &GrayImage,
286 x: u32,
287 y: u32,
288 p0: i16,
289 p4: i16,
290 p8: i16,
291 p12: i16,
292) -> [i16; 16] {
293 [
294 p0,
295 image.unsafe_get_pixel(x + 1, y - 3)[0] as i16,
296 image.unsafe_get_pixel(x + 2, y - 2)[0] as i16,
297 image.unsafe_get_pixel(x + 3, y - 1)[0] as i16,
298 p4,
299 image.unsafe_get_pixel(x + 3, y + 1)[0] as i16,
300 image.unsafe_get_pixel(x + 2, y + 2)[0] as i16,
301 image.unsafe_get_pixel(x + 1, y + 3)[0] as i16,
302 p8,
303 image.unsafe_get_pixel(x - 1, y + 3)[0] as i16,
304 image.unsafe_get_pixel(x - 2, y + 2)[0] as i16,
305 image.unsafe_get_pixel(x - 3, y + 1)[0] as i16,
306 p12,
307 image.unsafe_get_pixel(x - 3, y - 1)[0] as i16,
308 image.unsafe_get_pixel(x - 2, y - 2)[0] as i16,
309 image.unsafe_get_pixel(x - 1, y - 3)[0] as i16,
310 ]
311}
312
313fn has_bright_span(circle: &[i16; 16], length: u8, threshold: i16) -> bool {
316 search_span(circle, length, |c| *c > threshold)
317}
318
319fn has_dark_span(circle: &[i16; 16], length: u8, threshold: i16) -> bool {
322 search_span(circle, length, |c| *c < threshold)
323}
324
325fn search_span<F>(circle: &[i16; 16], length: u8, f: F) -> bool
328where
329 F: Fn(&i16) -> bool,
330{
331 if length > 16 {
332 return false;
333 }
334
335 let mut nb_ok = 0u8;
336 let mut nb_ok_start = None;
337
338 for c in circle.iter() {
339 if f(c) {
340 nb_ok += 1;
341 if nb_ok == length {
342 return true;
343 }
344 } else {
345 if nb_ok_start.is_none() {
346 nb_ok_start = Some(nb_ok);
347 }
348 nb_ok = 0;
349 }
350 }
351
352 nb_ok + nb_ok_start.unwrap() >= length
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358 use test::{black_box, Bencher};
359
360 #[test]
361 fn test_is_corner_fast12_12_contiguous_darker_pixels() {
362 let image = gray_image!(
363 10, 10, 00, 00, 00, 10, 10;
364 10, 00, 10, 10, 10, 00, 10;
365 00, 10, 10, 10, 10, 10, 10;
366 00, 10, 10, 10, 10, 10, 10;
367 00, 10, 10, 10, 10, 10, 10;
368 10, 00, 10, 10, 10, 10, 10;
369 10, 10, 00, 00, 00, 10, 10);
370
371 assert_eq!(is_corner_fast12(&image, 8, 3, 3), true);
372 }
373
374 #[test]
375 fn test_is_corner_fast12_12_contiguous_darker_pixels_large_threshold() {
376 let image = gray_image!(
377 10, 10, 00, 00, 00, 10, 10;
378 10, 00, 10, 10, 10, 00, 10;
379 00, 10, 10, 10, 10, 10, 10;
380 00, 10, 10, 10, 10, 10, 10;
381 00, 10, 10, 10, 10, 10, 10;
382 10, 00, 10, 10, 10, 10, 10;
383 10, 10, 00, 00, 00, 10, 10);
384
385 assert_eq!(is_corner_fast12(&image, 15, 3, 3), false);
386 }
387
388 #[test]
389 fn test_is_corner_fast12_12_contiguous_lighter_pixels() {
390 let image = gray_image!(
391 00, 00, 10, 10, 10, 00, 00;
392 00, 10, 00, 00, 00, 10, 00;
393 10, 00, 00, 00, 00, 00, 00;
394 10, 00, 00, 00, 00, 00, 00;
395 10, 00, 00, 00, 00, 00, 00;
396 00, 10, 00, 00, 00, 00, 00;
397 00, 00, 10, 10, 10, 00, 00);
398
399 assert_eq!(is_corner_fast12(&image, 8, 3, 3), true);
400 }
401
402 #[test]
403 fn test_is_corner_fast12_12_noncontiguous() {
404 let image = gray_image!(
405 10, 10, 00, 00, 00, 10, 10;
406 10, 00, 10, 10, 10, 00, 10;
407 00, 10, 10, 10, 10, 10, 10;
408 00, 10, 10, 10, 10, 10, 10;
409 10, 10, 10, 10, 10, 10, 00;
410 10, 00, 10, 10, 10, 10, 10;
411 10, 10, 00, 00, 00, 10, 10);
412
413 assert_eq!(is_corner_fast12(&image, 8, 3, 3), false);
414 }
415
416 #[bench]
417 fn bench_is_corner_fast12_12_noncontiguous(b: &mut Bencher) {
418 let image = black_box(gray_image!(
419 10, 10, 00, 00, 00, 10, 10;
420 10, 00, 10, 10, 10, 00, 10;
421 00, 10, 10, 10, 10, 10, 10;
422 00, 10, 10, 10, 10, 10, 10;
423 10, 10, 10, 10, 10, 10, 00;
424 10, 00, 10, 10, 10, 10, 10;
425 10, 10, 00, 00, 00, 10, 10));
426
427 b.iter(|| black_box(is_corner_fast12(&image, 8, 3, 3)));
428 }
429
430 #[test]
431 fn test_is_corner_fast12_near_image_boundary() {
432 let image = gray_image!(
433 10, 10, 00, 00, 00, 10, 10;
434 10, 00, 10, 10, 10, 00, 10;
435 00, 10, 10, 10, 10, 10, 10;
436 00, 10, 10, 10, 10, 10, 10;
437 00, 10, 10, 10, 10, 10, 10;
438 10, 00, 10, 10, 10, 10, 10;
439 10, 10, 00, 00, 00, 10, 10);
440
441 assert_eq!(is_corner_fast12(&image, 8, 1, 1), false);
442 }
443
444 #[test]
445 fn test_fast_corner_score_12() {
446 let image = gray_image!(
447 10, 10, 00, 00, 00, 10, 10;
448 10, 00, 10, 10, 10, 00, 10;
449 00, 10, 10, 10, 10, 10, 10;
450 00, 10, 10, 10, 10, 10, 10;
451 00, 10, 10, 10, 10, 10, 10;
452 10, 00, 10, 10, 10, 10, 10;
453 10, 10, 00, 00, 00, 10, 10);
454
455 let score = fast_corner_score(&image, 5, 3, 3, Fast::Twelve);
456 assert_eq!(score, 9);
457
458 let score = fast_corner_score(&image, 9, 3, 3, Fast::Twelve);
459 assert_eq!(score, 9);
460 }
461
462 #[test]
463 fn test_is_corner_fast9_9_contiguous_darker_pixels() {
464 let image = gray_image!(
465 10, 10, 00, 00, 00, 10, 10;
466 10, 00, 10, 10, 10, 00, 10;
467 00, 10, 10, 10, 10, 10, 10;
468 00, 10, 10, 10, 10, 10, 10;
469 00, 10, 10, 10, 10, 10, 10;
470 10, 00, 10, 10, 10, 10, 10;
471 10, 10, 10, 10, 10, 10, 10);
472
473 assert_eq!(is_corner_fast9(&image, 8, 3, 3), true);
474 }
475
476 #[test]
477 fn test_is_corner_fast9_9_contiguous_lighter_pixels() {
478 let image = gray_image!(
479 00, 00, 10, 10, 10, 00, 00;
480 00, 10, 00, 00, 00, 10, 00;
481 10, 00, 00, 00, 00, 00, 00;
482 10, 00, 00, 00, 00, 00, 00;
483 10, 00, 00, 00, 00, 00, 00;
484 00, 10, 00, 00, 00, 00, 00;
485 00, 00, 00, 00, 00, 00, 00);
486
487 assert_eq!(is_corner_fast9(&image, 8, 3, 3), true);
488 }
489
490 #[bench]
491 fn bench_is_corner_fast9_9_contiguous_lighter_pixels(b: &mut Bencher) {
492 let image = black_box(gray_image!(
493 00, 00, 10, 10, 10, 00, 00;
494 00, 10, 00, 00, 00, 10, 00;
495 10, 00, 00, 00, 00, 00, 00;
496 10, 00, 00, 00, 00, 00, 00;
497 10, 00, 00, 00, 00, 00, 00;
498 00, 10, 00, 00, 00, 00, 00;
499 00, 00, 00, 00, 00, 00, 00));
500
501 b.iter(|| black_box(is_corner_fast9(&image, 8, 3, 3)));
502 }
503
504 #[test]
505 fn test_is_corner_fast9_12_noncontiguous() {
506 let image = gray_image!(
507 10, 10, 00, 00, 00, 10, 10;
508 10, 00, 10, 10, 10, 00, 10;
509 00, 10, 10, 10, 10, 10, 10;
510 00, 10, 10, 10, 10, 10, 10;
511 10, 10, 10, 10, 10, 10, 00;
512 10, 00, 10, 10, 10, 10, 10;
513 10, 10, 00, 00, 00, 10, 10);
514
515 assert_eq!(is_corner_fast9(&image, 8, 3, 3), false);
516 }
517
518 #[test]
519 fn test_corner_score_fast9() {
520 let image = gray_image!(
522 10, 10, 00, 00, 00, 10, 10;
523 10, 00, 10, 10, 10, 00, 10;
524 00, 10, 10, 10, 10, 10, 10;
525 00, 10, 10, 20, 10, 10, 10;
526 00, 10, 10, 10, 10, 10, 10;
527 10, 10, 10, 10, 10, 10, 10;
528 10, 10, 10, 10, 10, 10, 10);
529
530 let score = fast_corner_score(&image, 5, 3, 3, Fast::Nine);
531 assert_eq!(score, 9);
532
533 let score = fast_corner_score(&image, 9, 3, 3, Fast::Nine);
534 assert_eq!(score, 9);
535 }
536}