use crate::definitions::{Position, Score};
use image::{GenericImageView, GrayImage};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Corner {
pub x: u32,
pub y: u32,
pub score: f32,
}
impl Corner {
pub fn new(x: u32, y: u32, score: f32) -> Corner {
Corner { x, y, score }
}
}
impl Position for Corner {
fn x(&self) -> u32 {
self.x
}
fn y(&self) -> u32 {
self.y
}
}
impl Score for Corner {
fn score(&self) -> f32 {
self.score
}
}
pub enum Fast {
Nine,
Twelve,
}
pub fn corners_fast12(image: &GrayImage, threshold: u8) -> Vec<Corner> {
let (width, height) = image.dimensions();
let mut corners = vec![];
for y in 0..height {
for x in 0..width {
if is_corner_fast12(image, threshold, x, y) {
let score = fast_corner_score(image, threshold, x, y, Fast::Twelve);
corners.push(Corner::new(x, y, score as f32));
}
}
}
corners
}
pub fn corners_fast9(image: &GrayImage, threshold: u8) -> Vec<Corner> {
let (width, height) = image.dimensions();
let mut corners = vec![];
for y in 0..height {
for x in 0..width {
if is_corner_fast9(image, threshold, x, y) {
let score = fast_corner_score(image, threshold, x, y, Fast::Nine);
corners.push(Corner::new(x, y, score as f32));
}
}
}
corners
}
pub fn fast_corner_score(image: &GrayImage, threshold: u8, x: u32, y: u32, variant: Fast) -> u8 {
let mut max = 255u8;
let mut min = threshold;
loop {
if max == min {
return max;
}
let mean = ((max as u16 + min as u16) / 2u16) as u8;
let probe = if max == min + 1 { max } else { mean };
let is_corner = match variant {
Fast::Nine => is_corner_fast9(image, probe, x, y),
Fast::Twelve => is_corner_fast12(image, probe, x, y),
};
if is_corner {
min = probe;
} else {
max = probe - 1;
}
}
}
fn is_corner_fast9(image: &GrayImage, threshold: u8, x: u32, y: u32) -> bool {
let (width, height) = image.dimensions();
if x >= u32::max_value() - 3
|| y >= u32::max_value() - 3
|| x < 3
|| y < 3
|| width <= x + 3
|| height <= y + 3
{
return false;
}
let c = unsafe { image.unsafe_get_pixel(x, y)[0] };
let low_thresh: i16 = c as i16 - threshold as i16;
let high_thresh: i16 = c as i16 + threshold as i16;
let (p0, p4, p8, p12) = unsafe {
(
image.unsafe_get_pixel(x, y - 3)[0] as i16,
image.unsafe_get_pixel(x, y + 3)[0] as i16,
image.unsafe_get_pixel(x + 3, y)[0] as i16,
image.unsafe_get_pixel(x - 3, y)[0] as i16,
)
};
let above = (p0 > high_thresh && p4 > high_thresh)
|| (p4 > high_thresh && p8 > high_thresh)
|| (p8 > high_thresh && p12 > high_thresh)
|| (p12 > high_thresh && p0 > high_thresh);
let below = (p0 < low_thresh && p4 < low_thresh)
|| (p4 < low_thresh && p8 < low_thresh)
|| (p8 < low_thresh && p12 < low_thresh)
|| (p12 < low_thresh && p0 < low_thresh);
if !above && !below {
return false;
}
let pixels = unsafe { get_circle(image, x, y, p0, p4, p8, p12) };
(above && has_bright_span(&pixels, 9, high_thresh))
|| (below && has_dark_span(&pixels, 9, low_thresh))
}
fn is_corner_fast12(image: &GrayImage, threshold: u8, x: u32, y: u32) -> bool {
let (width, height) = image.dimensions();
if x >= u32::max_value() - 3
|| y >= u32::max_value() - 3
|| x < 3
|| y < 3
|| width <= x + 3
|| height <= y + 3
{
return false;
}
let c = unsafe { image.unsafe_get_pixel(x, y)[0] };
let low_thresh: i16 = c as i16 - threshold as i16;
let high_thresh: i16 = c as i16 + threshold as i16;
let (p0, p8) = unsafe {
(
image.unsafe_get_pixel(x, y - 3)[0] as i16,
image.unsafe_get_pixel(x, y + 3)[0] as i16,
)
};
let mut above = p0 > high_thresh && p8 > high_thresh;
let mut below = p0 < low_thresh && p8 < low_thresh;
if !above && !below {
return false;
}
let (p4, p12) = unsafe {
(
image.unsafe_get_pixel(x + 3, y)[0] as i16,
image.unsafe_get_pixel(x - 3, y)[0] as i16,
)
};
above = above && ((p4 > high_thresh) || (p12 > high_thresh));
below = below && ((p4 < low_thresh) || (p12 < low_thresh));
if !above && !below {
return false;
}
let pixels = unsafe { get_circle(image, x, y, p0, p4, p8, p12) };
if above {
has_bright_span(&pixels, 12, high_thresh)
} else {
has_dark_span(&pixels, 12, low_thresh)
}
}
#[inline]
unsafe fn get_circle(
image: &GrayImage,
x: u32,
y: u32,
p0: i16,
p4: i16,
p8: i16,
p12: i16,
) -> [i16; 16] {
[
p0,
image.unsafe_get_pixel(x + 1, y - 3)[0] as i16,
image.unsafe_get_pixel(x + 2, y - 2)[0] as i16,
image.unsafe_get_pixel(x + 3, y - 1)[0] as i16,
p4,
image.unsafe_get_pixel(x + 3, y + 1)[0] as i16,
image.unsafe_get_pixel(x + 2, y + 2)[0] as i16,
image.unsafe_get_pixel(x + 1, y + 3)[0] as i16,
p8,
image.unsafe_get_pixel(x - 1, y + 3)[0] as i16,
image.unsafe_get_pixel(x - 2, y + 2)[0] as i16,
image.unsafe_get_pixel(x - 3, y + 1)[0] as i16,
p12,
image.unsafe_get_pixel(x - 3, y - 1)[0] as i16,
image.unsafe_get_pixel(x - 2, y - 2)[0] as i16,
image.unsafe_get_pixel(x - 1, y - 3)[0] as i16,
]
}
fn has_bright_span(circle: &[i16; 16], length: u8, threshold: i16) -> bool {
search_span(circle, length, |c| *c > threshold)
}
fn has_dark_span(circle: &[i16; 16], length: u8, threshold: i16) -> bool {
search_span(circle, length, |c| *c < threshold)
}
fn search_span<F>(circle: &[i16; 16], length: u8, f: F) -> bool
where
F: Fn(&i16) -> bool,
{
if length > 16 {
return false;
}
let mut nb_ok = 0u8;
let mut nb_ok_start = None;
for c in circle.iter() {
if f(c) {
nb_ok += 1;
if nb_ok == length {
return true;
}
} else {
if nb_ok_start.is_none() {
nb_ok_start = Some(nb_ok);
}
nb_ok = 0;
}
}
nb_ok + nb_ok_start.unwrap() >= length
}
#[cfg(test)]
mod tests {
use super::*;
use test::{black_box, Bencher};
#[test]
fn test_is_corner_fast12_12_contiguous_darker_pixels() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 00, 10, 10, 10, 10, 10;
10, 10, 00, 00, 00, 10, 10);
assert_eq!(is_corner_fast12(&image, 8, 3, 3), true);
}
#[test]
fn test_is_corner_fast12_12_contiguous_darker_pixels_large_threshold() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 00, 10, 10, 10, 10, 10;
10, 10, 00, 00, 00, 10, 10);
assert_eq!(is_corner_fast12(&image, 15, 3, 3), false);
}
#[test]
fn test_is_corner_fast12_12_contiguous_lighter_pixels() {
let image = gray_image!(
00, 00, 10, 10, 10, 00, 00;
00, 10, 00, 00, 00, 10, 00;
10, 00, 00, 00, 00, 00, 00;
10, 00, 00, 00, 00, 00, 00;
10, 00, 00, 00, 00, 00, 00;
00, 10, 00, 00, 00, 00, 00;
00, 00, 10, 10, 10, 00, 00);
assert_eq!(is_corner_fast12(&image, 8, 3, 3), true);
}
#[test]
fn test_is_corner_fast12_12_noncontiguous() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 10, 10, 10, 10, 10, 00;
10, 00, 10, 10, 10, 10, 10;
10, 10, 00, 00, 00, 10, 10);
assert_eq!(is_corner_fast12(&image, 8, 3, 3), false);
}
#[bench]
fn bench_is_corner_fast12_12_noncontiguous(b: &mut Bencher) {
let image = black_box(gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 10, 10, 10, 10, 10, 00;
10, 00, 10, 10, 10, 10, 10;
10, 10, 00, 00, 00, 10, 10));
b.iter(|| black_box(is_corner_fast12(&image, 8, 3, 3)));
}
#[test]
fn test_is_corner_fast12_near_image_boundary() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 00, 10, 10, 10, 10, 10;
10, 10, 00, 00, 00, 10, 10);
assert_eq!(is_corner_fast12(&image, 8, 1, 1), false);
}
#[test]
fn test_fast_corner_score_12() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 00, 10, 10, 10, 10, 10;
10, 10, 00, 00, 00, 10, 10);
let score = fast_corner_score(&image, 5, 3, 3, Fast::Twelve);
assert_eq!(score, 9);
let score = fast_corner_score(&image, 9, 3, 3, Fast::Twelve);
assert_eq!(score, 9);
}
#[test]
fn test_is_corner_fast9_9_contiguous_darker_pixels() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 00, 10, 10, 10, 10, 10;
10, 10, 10, 10, 10, 10, 10);
assert_eq!(is_corner_fast9(&image, 8, 3, 3), true);
}
#[test]
fn test_is_corner_fast9_9_contiguous_lighter_pixels() {
let image = gray_image!(
00, 00, 10, 10, 10, 00, 00;
00, 10, 00, 00, 00, 10, 00;
10, 00, 00, 00, 00, 00, 00;
10, 00, 00, 00, 00, 00, 00;
10, 00, 00, 00, 00, 00, 00;
00, 10, 00, 00, 00, 00, 00;
00, 00, 00, 00, 00, 00, 00);
assert_eq!(is_corner_fast9(&image, 8, 3, 3), true);
}
#[bench]
fn bench_is_corner_fast9_9_contiguous_lighter_pixels(b: &mut Bencher) {
let image = black_box(gray_image!(
00, 00, 10, 10, 10, 00, 00;
00, 10, 00, 00, 00, 10, 00;
10, 00, 00, 00, 00, 00, 00;
10, 00, 00, 00, 00, 00, 00;
10, 00, 00, 00, 00, 00, 00;
00, 10, 00, 00, 00, 00, 00;
00, 00, 00, 00, 00, 00, 00));
b.iter(|| black_box(is_corner_fast9(&image, 8, 3, 3)));
}
#[test]
fn test_is_corner_fast9_12_noncontiguous() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 10, 10, 10, 10, 10, 00;
10, 00, 10, 10, 10, 10, 10;
10, 10, 00, 00, 00, 10, 10);
assert_eq!(is_corner_fast9(&image, 8, 3, 3), false);
}
#[test]
fn test_corner_score_fast9() {
let image = gray_image!(
10, 10, 00, 00, 00, 10, 10;
10, 00, 10, 10, 10, 00, 10;
00, 10, 10, 10, 10, 10, 10;
00, 10, 10, 20, 10, 10, 10;
00, 10, 10, 10, 10, 10, 10;
10, 10, 10, 10, 10, 10, 10;
10, 10, 10, 10, 10, 10, 10);
let score = fast_corner_score(&image, 5, 3, 3, Fast::Nine);
assert_eq!(score, 9);
let score = fast_corner_score(&image, 9, 3, 3, Fast::Nine);
assert_eq!(score, 9);
}
}