use crate::error::Result;
use crate::feature::image_to_array;
use crate::simd_ops;
use image::{DynamicImage, GrayImage};
use scirs2_core::ndarray::Array2;
use scirs2_core::simd_ops::SimdUnifiedOps;
use std::f32::consts::PI;
#[allow(dead_code)]
pub fn sobel_edges_oriented(
img: &DynamicImage,
threshold: f32,
compute_orientation: bool,
) -> Result<(GrayImage, Option<Array2<f32>>)> {
let array = image_to_array(img)?;
let (height, width) = array.dim();
let sobel_x = [[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]];
let sobel_y = [[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]];
let mut gradient_x = Array2::zeros((height, width));
let mut gradient_y = Array2::zeros((height, width));
let mut magnitude = Array2::zeros((height, width));
let mut _orientation = if compute_orientation {
Some(Array2::zeros((height, width)))
} else {
None
};
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let mut gx = 0.0;
let mut gy = 0.0;
for ky in 0..3 {
for kx in 0..3 {
let pixel = array[[y + ky - 1, x + kx - 1]];
gx += pixel * sobel_x[ky][kx];
gy += pixel * sobel_y[ky][kx];
}
}
gradient_x[[y, x]] = gx;
gradient_y[[y, x]] = gy;
let mag = (gx * gx + gy * gy).sqrt();
magnitude[[y, x]] = mag;
if let Some(ref mut orient) = _orientation {
orient[[y, x]] = gy.atan2(gx);
}
}
}
let edges =
crate::feature::array_to_image(&magnitude.mapv(|x| if x > threshold { 1.0 } else { 0.0 }))?;
Ok((edges, _orientation))
}
#[allow(dead_code)]
pub fn sobel_edges(img: &DynamicImage, threshold: f32) -> Result<GrayImage> {
let (edges, _) = sobel_edges_oriented(img, threshold, false)?;
Ok(edges)
}
#[allow(dead_code)]
pub fn sobel_edges_simd(
img: &DynamicImage,
threshold: f32,
compute_orientation: bool,
) -> Result<(GrayImage, Option<Array2<f32>>)> {
if f32::simd_available() {
let array = image_to_array(img)?;
let (grad_x, grad_y, magnitude) = simd_ops::simd_sobel_gradients(&array.view())?;
let _orientation = if compute_orientation {
let (height, width) = array.dim();
let mut orient = Array2::zeros((height, width));
for y in 0..height {
for x in 0..width {
orient[[y, x]] = grad_y[[y, x]].atan2(grad_x[[y, x]]);
}
}
Some(orient)
} else {
None
};
let edges =
crate::feature::array_to_image(
&magnitude.mapv(|x| if x > threshold { 1.0 } else { 0.0 }),
)?;
Ok((edges, _orientation))
} else {
sobel_edges_oriented(img, threshold, compute_orientation)
}
}
#[allow(dead_code)]
pub fn compute_gradients(img: &DynamicImage) -> Result<(Array2<f32>, Array2<f32>)> {
let array = image_to_array(img)?;
let (height, width) = array.dim();
let sobel_x = [[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]];
let sobel_y = [[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]];
let mut magnitude = Array2::zeros((height, width));
let mut orientation = Array2::zeros((height, width));
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let mut gx = 0.0;
let mut gy = 0.0;
for ky in 0..3 {
for kx in 0..3 {
let pixel = array[[y + ky - 1, x + kx - 1]];
gx += pixel * sobel_x[ky][kx];
gy += pixel * sobel_y[ky][kx];
}
}
magnitude[[y, x]] = (gx * gx + gy * gy).sqrt();
orientation[[y, x]] = gy.atan2(gx);
}
}
Ok((magnitude, orientation))
}
#[allow(dead_code)]
pub fn visualize_gradient_orientation(
magnitude: &Array2<f32>,
orientation: &Array2<f32>,
mag_threshold: f32,
) -> Result<image::RgbImage> {
use image::{Rgb, RgbImage};
let (height, width) = magnitude.dim();
let mut output = RgbImage::new(width as u32, height as u32);
let max_mag = magnitude.iter().fold(0.0f32, |a, &b| a.max(b));
for y in 0..height {
for x in 0..width {
let mag = magnitude[[y, x]];
let angle = orientation[[y, x]];
if mag > mag_threshold {
let hue = ((angle + PI) / (2.0 * PI)) * 360.0;
let saturation = (mag / max_mag).min(1.0);
let value = saturation;
let (r, g, b) = hsv_to_rgb(hue, saturation, value);
output.put_pixel(x as u32, y as u32, Rgb([r, g, b]));
} else {
output.put_pixel(x as u32, y as u32, Rgb([0, 0, 0]));
}
}
}
Ok(output)
}
#[allow(dead_code)]
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
let c = v * s;
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
let m = v - c;
let (r, g, b) = if h < 60.0 {
(c, x, 0.0)
} else if h < 120.0 {
(x, c, 0.0)
} else if h < 180.0 {
(0.0, c, x)
} else if h < 240.0 {
(0.0, x, c)
} else if h < 300.0 {
(x, 0.0, c)
} else {
(c, 0.0, x)
};
(
((r + m) * 255.0) as u8,
((g + m) * 255.0) as u8,
((b + m) * 255.0) as u8,
)
}
#[allow(dead_code)]
pub fn compute_hog_histogram(
magnitude: &Array2<f32>,
orientation: &Array2<f32>,
num_bins: usize,
) -> Vec<f32> {
let mut histogram = vec![0.0; num_bins];
let bin_size = 2.0 * PI / num_bins as f32;
for (&mag, &angle) in magnitude.iter().zip(orientation.iter()) {
if mag > 0.0 {
let normalized_angle = if angle < 0.0 { angle + 2.0 * PI } else { angle };
let bin = ((normalized_angle / bin_size) as usize).min(num_bins - 1);
histogram[bin] += mag;
}
}
let sum: f32 = histogram.iter().sum();
if sum > 0.0 {
for val in &mut histogram {
*val /= sum;
}
}
histogram
}
#[cfg(test)]
mod tests {
use super::*;
use image::DynamicImage;
#[test]
fn test_sobel_edges() {
let img = DynamicImage::new_luma8(10, 10);
let result = sobel_edges(&img, 0.1);
assert!(result.is_ok());
}
#[test]
fn test_sobel_edges_oriented() {
let img = DynamicImage::new_luma8(10, 10);
let result = sobel_edges_oriented(&img, 0.1, true);
assert!(result.is_ok());
let (edges, orientations) = result.expect("Operation failed");
assert!(orientations.is_some());
assert_eq!(edges.dimensions(), (10, 10));
}
#[test]
fn test_compute_gradients() {
let mut img = GrayImage::new(10, 10);
for y in 0..10 {
for x in 0..10 {
img.put_pixel(x, y, image::Luma([if x < 5 { 0 } else { 255 }]));
}
}
let dynamic_img = DynamicImage::ImageLuma8(img);
let result = compute_gradients(&dynamic_img);
assert!(result.is_ok());
let (magnitude, orientation) = result.expect("Operation failed");
assert_eq!(magnitude.dim(), (10, 10));
assert_eq!(orientation.dim(), (10, 10));
assert!(magnitude[[5, 5]] > 0.0);
}
#[test]
fn test_hsv_to_rgb() {
assert_eq!(hsv_to_rgb(0.0, 1.0, 1.0), (255, 0, 0)); assert_eq!(hsv_to_rgb(120.0, 1.0, 1.0), (0, 255, 0)); assert_eq!(hsv_to_rgb(240.0, 1.0, 1.0), (0, 0, 255));
assert_eq!(hsv_to_rgb(0.0, 0.0, 0.5), (127, 127, 127)); }
#[test]
fn test_hog_histogram() {
let magnitude =
Array2::from_shape_vec((3, 3), vec![1.0, 0.0, 1.0, 0.0, 2.0, 0.0, 1.0, 0.0, 1.0])
.expect("Operation failed");
let orientation = Array2::from_shape_vec(
(3, 3),
vec![0.0, 0.0, PI / 2.0, 0.0, PI, 0.0, -PI / 2.0, 0.0, PI],
)
.expect("Operation failed");
let histogram = compute_hog_histogram(&magnitude, &orientation, 4);
assert_eq!(histogram.len(), 4);
let sum: f32 = histogram.iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
}
#[test]
fn test_sobel_simd() {
let mut img = GrayImage::new(10, 10);
for y in 0..10 {
for x in 0..10 {
img.put_pixel(x, y, image::Luma([if x < 5 { 0 } else { 255 }]));
}
}
let dynamic_img = DynamicImage::ImageLuma8(img);
let result = sobel_edges_simd(&dynamic_img, 100.0, true);
assert!(result.is_ok());
let (edges, orientations) = result.expect("Operation failed");
assert!(orientations.is_some());
assert_eq!(edges.dimensions(), (10, 10));
let (edges_regular_, _) =
sobel_edges_oriented(&dynamic_img, 100.0, true).expect("Operation failed");
assert_eq!(edges.dimensions(), edges_regular_.dimensions());
}
}