use crate::{data::FFTData, imgtools};
use core::cmp::max;
use image::{ImageBuffer, Luma};
use rayon::prelude::*;
use rustfft::{num_complex::Complex, Fft, FftPlanner};
use super::{compute_integral_images, sum_region};
pub fn fft_ncc(
image: &ImageBuffer<Luma<u8>, Vec<u8>>,
precision: f32,
prepared_data: &FFTData,
) -> Vec<(u32, u32, f64)> {
let mut planner = FftPlanner::<f32>::new();
let fft: std::sync::Arc<dyn Fft<f32>> =
planner.plan_fft_forward((prepared_data.padded_size * prepared_data.padded_size) as usize);
let (image_width, image_height) = image.dimensions();
let image_vec: Vec<Vec<u8>> = imgtools::imagebuffer_to_vec(image);
if (image_width < prepared_data.template_width)
|| (image_height < prepared_data.template_height)
{
return Vec::new();
}
let (image_integral, squared_image_integral) = compute_integral_images(&image_vec);
let sum_image: u64 = sum_region(&image_integral, 0, 0, image_width, image_height);
let image_average_total = sum_image as f32 / (image_height * image_width) as f32; let mut zero_mean_image: Vec<Vec<f32>> =
vec![vec![0.0; image_width as usize]; image_height as usize];
for y in 0..image_height {
for x in 0..image_width {
let image_pixel_value = image.get_pixel(x, y)[0] as f32;
zero_mean_image[y as usize][x as usize] = image_pixel_value - image_average_total;
}
}
let mut image_padded: Vec<Complex<f32>> = vec![
Complex::new(0.0, 0.0);
(prepared_data.padded_size * prepared_data.padded_size)
as usize
];
for dy in 0..image_height {
for dx in 0..image_width {
let image_pixel_value = zero_mean_image[dy as usize][dx as usize];
image_padded[dy as usize * prepared_data.padded_size as usize + dx as usize] =
Complex::new(image_pixel_value, 0.0);
}
}
let ifft: std::sync::Arc<dyn Fft<f32>> =
planner.plan_fft_inverse((prepared_data.padded_size * prepared_data.padded_size) as usize);
fft.process(&mut image_padded);
let product_freq: Vec<Complex<f32>> = image_padded
.iter()
.zip(prepared_data.template_conj_freq.iter())
.map(|(&img_val, &tmpl_val)| img_val * tmpl_val)
.collect();
let mut fft_result: Vec<Complex<f32>> = product_freq.clone();
ifft.process(&mut fft_result);
let coords: Vec<(u32, u32)> = (0..=(image_height - prepared_data.template_height)) .flat_map(|y| (0..=(image_width - prepared_data.template_width)).map(move |x| (x, y))) .collect();
let mut found_points: Vec<(u32, u32, f64)> = coords
.par_iter()
.map(|&(x, y)| {
let corr = fft_correlation_calculation(
&image_integral,
&squared_image_integral,
prepared_data.template_width,
prepared_data.template_height,
prepared_data.template_sum_squared_deviations,
x,
y,
prepared_data.padded_size,
&fft_result,
);
(x, y, corr)
})
.filter(|&(_, _, corr)| corr > precision as f64)
.collect();
found_points.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
found_points
}
#[allow(dead_code)]
fn fft_correlation_calculation(
image_integral: &[Vec<u64>],
squared_image_integral: &[Vec<u64>],
template_width: u32,
template_height: u32,
template_sum_squared_deviations: f32,
x: u32, y: u32, padded_size: u32,
fft_result: &[Complex<f32>],
) -> f64 {
let sum_image: u64 = sum_region(image_integral, x, y, template_width, template_height);
let sum_squared_image: u64 = sum_region(
squared_image_integral,
x,
y,
template_width,
template_height,
);
let image_sum_squared_deviations = sum_squared_image as f64
- (sum_image as f64).powi(2) / (template_height * template_width) as f64; let denominator =
(image_sum_squared_deviations * template_sum_squared_deviations as f64).sqrt();
let numerator_value =
fft_result[(y * padded_size) as usize + x as usize].re / (padded_size * padded_size) as f32; let mut corr = numerator_value as f64 / denominator;
if corr > 2.0 {
corr = -100.0;
}
corr
}
pub fn prepare_template_picture(
template: &ImageBuffer<Luma<u8>, Vec<u8>>,
image_width: u32,
image_height: u32,
) -> FFTData {
let (template_width, template_height) = template.dimensions();
let padded_width = image_width.next_power_of_two();
let padded_height = image_height.next_power_of_two();
let padded_size = max(padded_width, padded_height);
let mut sum_template = 0.0;
for y in 0..template_height {
for x in 0..template_width {
let template_value = template.get_pixel(x, y)[0] as f32;
sum_template += template_value;
}
}
let mean_template_value = sum_template / (template_height * template_width) as f32;
let mut zero_mean_template: Vec<Vec<f32>> =
vec![vec![0.0; template_width as usize]; template_height as usize];
let mut template_sum_squared_deviations: f32 = 0.0;
for y in 0..template_height {
for x in 0..template_width {
let template_value = template.get_pixel(x, y)[0] as f32;
let squared_deviation = (template_value - mean_template_value).powf(2.0);
template_sum_squared_deviations += squared_deviation;
zero_mean_template[y as usize][x as usize] = template_value - mean_template_value;
}
}
let mut template_padded: Vec<Complex<f32>> =
vec![Complex::new(0.0, 0.0); (padded_size * padded_size) as usize];
for dy in 0..template_height {
for dx in 0..template_width {
let template_pixel_value = zero_mean_template[dy as usize][dx as usize];
template_padded[dy as usize * padded_size as usize + dx as usize] =
Complex::new(template_pixel_value, 0.0);
}
}
let mut planner = FftPlanner::<f32>::new();
let fft: std::sync::Arc<dyn Fft<f32>> =
planner.plan_fft_forward((padded_size * padded_size) as usize);
fft.process(&mut template_padded);
let template_conj_freq: Vec<Complex<f32>> =
template_padded.iter().map(|&val| val.conj()).collect();
FFTData {
template_conj_freq,
template_sum_squared_deviations,
template_width,
template_height,
padded_size,
}
}