use crate::{Result, VisionError};
use scirs2_core::ndarray::{
array, s, Array2, Array3, Array4, ArrayView2, ArrayView3, ArrayView4, Axis,
};
use scirs2_core::random::Random;
use std::collections::HashMap;
use torsh_tensor::Tensor;
#[derive(Debug, Clone)]
pub struct SciRS2VisionProcessor {
config: VisionConfig,
}
#[derive(Debug, Clone)]
pub struct VisionConfig {
pub use_gpu: bool,
pub simd_level: SimdLevel,
pub memory_strategy: MemoryStrategy,
pub quality_level: QualityLevel,
}
#[derive(Debug, Clone, Copy)]
pub enum SimdLevel {
None,
Sse,
Avx,
Avx2,
Neon,
}
#[derive(Debug, Clone, Copy)]
pub enum MemoryStrategy {
Minimal,
Balanced,
HighPerformance,
}
#[derive(Debug, Clone, Copy)]
pub enum QualityLevel {
Fast,
Balanced,
HighQuality,
}
impl Default for VisionConfig {
fn default() -> Self {
Self {
use_gpu: false,
simd_level: SimdLevel::Avx2,
memory_strategy: MemoryStrategy::Balanced,
quality_level: QualityLevel::Balanced,
}
}
}
impl SciRS2VisionProcessor {
pub fn new(config: VisionConfig) -> Self {
Self { config }
}
pub fn default() -> Self {
Self::new(VisionConfig::default())
}
pub fn gaussian_blur(&self, image: &Tensor, kernel_size: usize, sigma: f32) -> Result<Tensor> {
let array = self.tensor_to_array3(image)?;
let mut blurred = array.clone();
let kernel = self.generate_gaussian_kernel(kernel_size, sigma)?;
self.apply_convolution_simd(&array, &kernel, &mut blurred)?;
self.array3_to_tensor(&blurred)
}
pub fn multi_edge_detection(
&self,
image: &Tensor,
method: EdgeDetectionMethod,
) -> Result<Tensor> {
let array = self.tensor_to_array2(image)?;
let edges = match method {
EdgeDetectionMethod::Sobel => self.sobel_edge_detection(&array)?,
EdgeDetectionMethod::Canny => self.canny_edge_detection(&array, 0.1, 0.2)?,
EdgeDetectionMethod::Laplacian => self.laplacian_edge_detection(&array)?,
EdgeDetectionMethod::Prewitt => self.prewitt_edge_detection(&array)?,
EdgeDetectionMethod::Scharr => self.scharr_edge_detection(&array)?,
};
self.array2_to_tensor(&edges)
}
pub fn extract_sift_features(&self, image: &Tensor) -> Result<SiftFeatures> {
let array = self.tensor_to_array2(image)?;
let keypoints = self.detect_sift_keypoints(&array)?;
let descriptors = self.compute_sift_descriptors(&array, &keypoints)?;
Ok(SiftFeatures {
keypoints: keypoints.clone(),
descriptors,
octave_info: keypoints.iter().map(|kp| (0, kp.scale)).collect(),
detection_time: 0.0,
})
}
pub fn extract_orb_features(&self, image: &Tensor, max_features: usize) -> Result<OrbFeatures> {
let array = self.tensor_to_array2(image)?;
let keypoints = self.detect_orb_keypoints(&array, max_features)?;
let descriptors = self.compute_orb_descriptors(&array, &keypoints)?;
Ok(OrbFeatures {
keypoints,
descriptors,
})
}
pub fn detect_harris_corners(
&self,
image: &Tensor,
threshold: f32,
) -> Result<Vec<CornerPoint>> {
let array = self.tensor_to_array2(image)?;
self.harris_corner_detection(&array, threshold)
}
pub fn match_features(
&self,
features1: &SiftFeatures,
features2: &SiftFeatures,
ratio_threshold: f32,
) -> Result<Vec<FeatureMatch>> {
self.match_sift_descriptors(
&features1.descriptors,
&features2.descriptors,
&features1.keypoints,
&features2.keypoints,
ratio_threshold,
)
}
pub fn optical_flow_lk(&self, frame1: &Tensor, frame2: &Tensor) -> Result<OpticalFlow> {
let array1 = self.tensor_to_array2(frame1)?;
let array2 = self.tensor_to_array2(frame2)?;
let (flow_x, flow_y) = self.compute_optical_flow_lucas_kanade(&array1, &array2)?;
Ok(OpticalFlow { flow_x, flow_y })
}
pub fn optical_flow_farneback(
&self,
frame1: &Tensor,
frame2: &Tensor,
pyramid_scale: f32,
levels: usize,
) -> Result<OpticalFlow> {
let array1 = self.tensor_to_array2(frame1)?;
let array2 = self.tensor_to_array2(frame2)?;
let (flow_x, flow_y) =
self.compute_optical_flow_farneback(&array1, &array2, pyramid_scale, levels)?;
Ok(OpticalFlow { flow_x, flow_y })
}
pub fn compute_disparity(
&self,
left: &Tensor,
right: &Tensor,
max_disparity: usize,
) -> Result<DisparityMap> {
let left_array = self.tensor_to_array2(left)?;
let right_array = self.tensor_to_array2(right)?;
let (disparity, confidence) =
self.stereo_matching(&left_array, &right_array, max_disparity)?;
Ok(DisparityMap {
disparity,
confidence,
})
}
pub fn calibrate_camera(
&self,
object_points: &Array3<f32>,
image_points: &Array3<f32>,
) -> Result<CameraParameters> {
self.camera_calibration(object_points, image_points)
}
pub fn template_match(&self, image: &Tensor, template: &Tensor) -> Result<Array2<f32>> {
let image_array = self.tensor_to_array2(image)?;
let template_array = self.tensor_to_array2(template)?;
self.normalized_cross_correlation(&image_array, &template_array)
}
pub fn hough_lines(
&self,
edges: &Tensor,
rho: f32,
theta: f32,
threshold: usize,
) -> Result<Vec<HoughLine>> {
let array = self.tensor_to_array2(edges)?;
self.hough_line_detection(&array, rho, theta, threshold)
}
pub fn hough_circles(
&self,
image: &Tensor,
min_radius: f32,
max_radius: f32,
) -> Result<Vec<Circle>> {
let array = self.tensor_to_array2(image)?;
self.hough_circle_detection(&array, min_radius, max_radius)
}
pub fn denoise_image(&self, image: &Tensor, method: DenoiseMethod) -> Result<Tensor> {
let array = self.tensor_to_array3(image)?;
let denoised = match method {
DenoiseMethod::Gaussian => self.gaussian_denoise(&array)?,
DenoiseMethod::Bilateral => self.bilateral_filter(&array)?,
DenoiseMethod::NlMeans => self.non_local_means(&array)?,
DenoiseMethod::Tv => self.total_variation_denoise(&array)?,
};
self.array3_to_tensor(&denoised)
}
pub fn enhance_contrast(&self, image: &Tensor, method: ContrastMethod) -> Result<Tensor> {
let array = self.tensor_to_array3(image)?;
let enhanced = match method {
ContrastMethod::HistogramEqualization => self.histogram_equalization(&array)?,
ContrastMethod::Clahe => self.clahe_enhancement(&array)?,
ContrastMethod::GammaCorrection => self.gamma_correction(&array, 1.2)?,
ContrastMethod::LinearStretch => self.linear_stretch(&array)?,
};
self.array3_to_tensor(&enhanced)
}
pub fn super_resolution(&self, image: &Tensor, scale_factor: f32) -> Result<Tensor> {
let array = self.tensor_to_array3(image)?;
let upscaled = self.bicubic_interpolation(&array, scale_factor)?;
self.array3_to_tensor(&upscaled)
}
fn tensor_to_array2(&self, tensor: &Tensor) -> Result<Array2<f32>> {
let data = tensor.to_vec()?;
let shape = tensor.shape();
if shape.dims().len() != 2 {
return Err(VisionError::InvalidShape("Expected 2D tensor".to_string()));
}
Array2::from_shape_vec((shape.dims()[0], shape.dims()[1]), data)
.map_err(|e| VisionError::Other(anyhow::anyhow!("Array conversion failed: {}", e)))
}
fn tensor_to_array3(&self, tensor: &Tensor) -> Result<Array3<f32>> {
let data = tensor.to_vec()?;
let shape = tensor.shape();
if shape.dims().len() != 3 {
return Err(VisionError::InvalidShape("Expected 3D tensor".to_string()));
}
Array3::from_shape_vec((shape.dims()[0], shape.dims()[1], shape.dims()[2]), data)
.map_err(|e| VisionError::Other(anyhow::anyhow!("Array conversion failed: {}", e)))
}
fn array2_to_tensor(&self, array: &Array2<f32>) -> Result<Tensor> {
let shape = vec![array.nrows(), array.ncols()];
let data: Vec<f32> = array.iter().cloned().collect();
Tensor::from_vec(data, &shape).map_err(|e| VisionError::TensorError(e))
}
fn array3_to_tensor(&self, array: &Array3<f32>) -> Result<Tensor> {
let shape = vec![array.shape()[0], array.shape()[1], array.shape()[2]];
let data: Vec<f32> = array.iter().cloned().collect();
Tensor::from_vec(data, &shape).map_err(|e| VisionError::TensorError(e))
}
fn generate_gaussian_kernel(&self, size: usize, sigma: f32) -> Result<Array2<f32>> {
let center = size as f32 / 2.0;
let mut kernel = Array2::zeros((size, size));
let mut sum = 0.0;
for i in 0..size {
for j in 0..size {
let x = i as f32 - center;
let y = j as f32 - center;
let value = (-((x * x + y * y) / (2.0 * sigma * sigma))).exp();
kernel[[i, j]] = value;
sum += value;
}
}
kernel.mapv_inplace(|x| x / sum);
Ok(kernel)
}
fn apply_convolution_simd(
&self,
image: &Array3<f32>,
kernel: &Array2<f32>,
output: &mut Array3<f32>,
) -> Result<()> {
let (height, width, channels) = image.dim();
let kernel_size = kernel.nrows();
let offset = kernel_size / 2;
for c in 0..channels {
for i in offset..(height - offset) {
for j in offset..(width - offset) {
let mut sum = 0.0;
for ki in 0..kernel_size {
for kj in 0..kernel_size {
let ii = i + ki - offset;
let jj = j + kj - offset;
sum += image[[ii, jj, c]] * kernel[[ki, kj]];
}
}
output[[i, j, c]] = sum;
}
}
}
Ok(())
}
fn sobel_edge_detection(&self, image: &Array2<f32>) -> Result<Array2<f32>> {
let sobel_x = array![[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]];
let sobel_y = array![[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]];
let gx = self.convolve2d(image, &sobel_x)?;
let gy = self.convolve2d(image, &sobel_y)?;
let mut magnitude = Array2::zeros(image.dim());
for ((i, j), val) in magnitude.indexed_iter_mut() {
*val = (gx[[i, j]].powi(2) + gy[[i, j]].powi(2)).sqrt();
}
Ok(magnitude)
}
fn canny_edge_detection(
&self,
image: &Array2<f32>,
low_threshold: f32,
high_threshold: f32,
) -> Result<Array2<f32>> {
let kernel = self.generate_gaussian_kernel(5, 1.0)?;
let blurred = self.convolve2d(image, &kernel)?;
let edges = self.sobel_edge_detection(&blurred)?;
let mut result = Array2::zeros(image.dim());
for ((i, j), &edge_val) in edges.indexed_iter() {
if edge_val > high_threshold {
result[[i, j]] = 1.0;
} else if edge_val > low_threshold {
result[[i, j]] = 0.5; }
}
self.hysteresis_thresholding(&mut result)?;
Ok(result)
}
fn laplacian_edge_detection(&self, image: &Array2<f32>) -> Result<Array2<f32>> {
let laplacian = array![[0.0, -1.0, 0.0], [-1.0, 4.0, -1.0], [0.0, -1.0, 0.0]];
self.convolve2d(image, &laplacian)
}
fn prewitt_edge_detection(&self, image: &Array2<f32>) -> Result<Array2<f32>> {
let prewitt_x = array![[-1.0, 0.0, 1.0], [-1.0, 0.0, 1.0], [-1.0, 0.0, 1.0]];
let prewitt_y = array![[-1.0, -1.0, -1.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]];
let gx = self.convolve2d(image, &prewitt_x)?;
let gy = self.convolve2d(image, &prewitt_y)?;
let mut magnitude = Array2::zeros(image.dim());
for ((i, j), val) in magnitude.indexed_iter_mut() {
*val = (gx[[i, j]].powi(2) + gy[[i, j]].powi(2)).sqrt();
}
Ok(magnitude)
}
fn scharr_edge_detection(&self, image: &Array2<f32>) -> Result<Array2<f32>> {
let scharr_x = array![[-3.0, 0.0, 3.0], [-10.0, 0.0, 10.0], [-3.0, 0.0, 3.0]];
let scharr_y = array![[-3.0, -10.0, -3.0], [0.0, 0.0, 0.0], [3.0, 10.0, 3.0]];
let gx = self.convolve2d(image, &scharr_x)?;
let gy = self.convolve2d(image, &scharr_y)?;
let mut magnitude = Array2::zeros(image.dim());
for ((i, j), val) in magnitude.indexed_iter_mut() {
*val = (gx[[i, j]].powi(2) + gy[[i, j]].powi(2)).sqrt();
}
Ok(magnitude)
}
fn detect_sift_keypoints(&self, image: &Array2<f32>) -> Result<Vec<Keypoint>> {
let mut keypoints = Vec::new();
let (height, width) = image.dim();
for scale in 1..4 {
let sigma = 1.6_f32.powi(scale);
let kernel = self.generate_gaussian_kernel(9, sigma)?;
let blurred = self.convolve2d(image, &kernel)?;
for i in 3..(height - 3) {
for j in 3..(width - 3) {
let center = blurred[[i, j]];
let mut is_maximum = true;
for di in -1..=1 {
for dj in -1..=1 {
if di == 0 && dj == 0 {
continue;
}
let ni = (i as i32 + di) as usize;
let nj = (j as i32 + dj) as usize;
if blurred[[ni, nj]] >= center {
is_maximum = false;
break;
}
}
if !is_maximum {
break;
}
}
if is_maximum && center > 0.1 {
keypoints.push(Keypoint {
x: j as f32,
y: i as f32,
response: center,
scale: sigma,
angle: 0.0, });
}
}
}
}
Ok(keypoints)
}
fn compute_sift_descriptors(
&self,
image: &Array2<f32>,
keypoints: &[Keypoint],
) -> Result<Array2<f32>> {
let descriptor_size = 128;
let mut descriptors = Array2::zeros((keypoints.len(), descriptor_size));
for (idx, keypoint) in keypoints.iter().enumerate() {
let x = keypoint.x as usize;
let y = keypoint.y as usize;
let patch_size = 16;
let start_x = x.saturating_sub(patch_size / 2);
let start_y = y.saturating_sub(patch_size / 2);
for i in 0..descriptor_size {
let px = start_x + (i % patch_size);
let py = start_y + (i / patch_size);
if px < image.ncols() && py < image.nrows() {
descriptors[[idx, i]] = image[[py, px]];
}
}
}
Ok(descriptors)
}
fn convolve2d(&self, image: &Array2<f32>, kernel: &Array2<f32>) -> Result<Array2<f32>> {
let (img_h, img_w) = image.dim();
let (ker_h, ker_w) = kernel.dim();
let pad_h = ker_h / 2;
let pad_w = ker_w / 2;
let mut result = Array2::zeros((img_h, img_w));
for i in pad_h..(img_h - pad_h) {
for j in pad_w..(img_w - pad_w) {
let mut sum = 0.0;
for ki in 0..ker_h {
for kj in 0..ker_w {
let ii = i + ki - pad_h;
let jj = j + kj - pad_w;
sum += image[[ii, jj]] * kernel[[ki, kj]];
}
}
result[[i, j]] = sum;
}
}
Ok(result)
}
fn hysteresis_thresholding(&self, edges: &mut Array2<f32>) -> Result<()> {
let (height, width) = edges.dim();
let mut changed = true;
while changed {
changed = false;
for i in 1..(height - 1) {
for j in 1..(width - 1) {
if edges[[i, j]] == 0.5 {
for di in -1..=1 {
for dj in -1..=1 {
if di == 0 && dj == 0 {
continue;
}
let ni = (i as i32 + di) as usize;
let nj = (j as i32 + dj) as usize;
if edges[[ni, nj]] == 1.0 {
edges[[i, j]] = 1.0;
changed = true;
break;
}
}
if changed {
break;
}
}
}
}
}
}
for edge in edges.iter_mut() {
if *edge == 0.5 {
*edge = 0.0;
}
}
Ok(())
}
fn detect_orb_keypoints(
&self,
image: &Array2<f32>,
max_features: usize,
) -> Result<Vec<Keypoint>> {
let mut keypoints = Vec::new();
let (height, width) = image.dim();
for i in 3..(height - 3) {
for j in 3..(width - 3) {
if self.is_fast_corner(image, i, j, 0.2)? {
keypoints.push(Keypoint {
x: j as f32,
y: i as f32,
response: image[[i, j]],
scale: 1.0,
angle: self.compute_orientation(image, i, j)?,
});
}
}
}
keypoints.sort_by(|a, b| {
b.response
.partial_cmp(&a.response)
.expect("comparison should succeed")
});
keypoints.truncate(max_features);
Ok(keypoints)
}
fn compute_orb_descriptors(
&self,
image: &Array2<f32>,
keypoints: &[Keypoint],
) -> Result<Array2<u8>> {
let descriptor_size = 32; let mut descriptors = Array2::zeros((keypoints.len(), descriptor_size));
for (idx, keypoint) in keypoints.iter().enumerate() {
let x = keypoint.x as usize;
let y = keypoint.y as usize;
for i in 0..descriptor_size {
let mut byte_val = 0u8;
for bit in 0..8 {
let offset = i * 8 + bit;
let (dx1, dy1) = self.get_orb_sampling_pattern(offset, 0);
let (dx2, dy2) = self.get_orb_sampling_pattern(offset, 1);
let p1_x = (x as i32 + dx1).max(0).min(image.ncols() as i32 - 1) as usize;
let p1_y = (y as i32 + dy1).max(0).min(image.nrows() as i32 - 1) as usize;
let p2_x = (x as i32 + dx2).max(0).min(image.ncols() as i32 - 1) as usize;
let p2_y = (y as i32 + dy2).max(0).min(image.nrows() as i32 - 1) as usize;
if image[[p1_y, p1_x]] > image[[p2_y, p2_x]] {
byte_val |= 1 << bit;
}
}
descriptors[[idx, i]] = byte_val;
}
}
Ok(descriptors)
}
fn harris_corner_detection(
&self,
image: &Array2<f32>,
threshold: f32,
) -> Result<Vec<CornerPoint>> {
let mut corners = Vec::new();
let (height, width) = image.dim();
let sobel_x = array![[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]];
let sobel_y = array![[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]];
let ix = self.convolve2d(image, &sobel_x)?;
let iy = self.convolve2d(image, &sobel_y)?;
let window_size = 5;
let k = 0.04;
for i in window_size..(height - window_size) {
for j in window_size..(width - window_size) {
let mut ixx = 0.0;
let mut ixy = 0.0;
let mut iyy = 0.0;
for wi in 0..window_size {
for wj in 0..window_size {
let ii = i + wi - window_size / 2;
let jj = j + wj - window_size / 2;
let ix_val = ix[[ii, jj]];
let iy_val = iy[[ii, jj]];
ixx += ix_val * ix_val;
ixy += ix_val * iy_val;
iyy += iy_val * iy_val;
}
}
let det = ixx * iyy - ixy * ixy;
let trace = ixx + iyy;
let response = det - k * trace * trace;
if response > threshold {
corners.push(CornerPoint {
x: j as f32,
y: i as f32,
response,
});
}
}
}
Ok(corners)
}
fn match_sift_descriptors(
&self,
desc1: &Array2<f32>,
desc2: &Array2<f32>,
kp1: &[Keypoint],
kp2: &[Keypoint],
ratio_threshold: f32,
) -> Result<Vec<FeatureMatch>> {
let mut matches = Vec::new();
for (i, _) in kp1.iter().enumerate() {
let mut best_dist = f32::INFINITY;
let mut second_best_dist = f32::INFINITY;
let mut best_idx = 0;
for (j, _) in kp2.iter().enumerate() {
let mut dist = 0.0;
for k in 0..desc1.ncols() {
let diff = desc1[[i, k]] - desc2[[j, k]];
dist += diff * diff;
}
dist = dist.sqrt();
if dist < best_dist {
second_best_dist = best_dist;
best_dist = dist;
best_idx = j;
} else if dist < second_best_dist {
second_best_dist = dist;
}
}
if best_dist / second_best_dist < ratio_threshold {
matches.push(FeatureMatch {
keypoint1_idx: i,
keypoint2_idx: best_idx,
distance: best_dist,
confidence: 1.0 - (best_dist / second_best_dist),
});
}
}
Ok(matches)
}
fn compute_optical_flow_lucas_kanade(
&self,
frame1: &Array2<f32>,
frame2: &Array2<f32>,
) -> Result<(Array2<f32>, Array2<f32>)> {
let (height, width) = frame1.dim();
let mut flow_x = Array2::zeros((height, width));
let mut flow_y = Array2::zeros((height, width));
let sobel_x = array![[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]];
let sobel_y = array![[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]];
let ix = self.convolve2d(frame1, &sobel_x)?;
let iy = self.convolve2d(frame1, &sobel_y)?;
let mut it = Array2::zeros((height, width));
for i in 0..height {
for j in 0..width {
it[[i, j]] = frame2[[i, j]] - frame1[[i, j]];
}
}
let window_size = 5;
for i in window_size..(height - window_size) {
for j in window_size..(width - window_size) {
let mut a11 = 0.0;
let mut a12 = 0.0;
let mut a22 = 0.0;
let mut b1 = 0.0;
let mut b2 = 0.0;
for wi in 0..window_size {
for wj in 0..window_size {
let ii = i + wi - window_size / 2;
let jj = j + wj - window_size / 2;
let ix_val = ix[[ii, jj]];
let iy_val = iy[[ii, jj]];
let it_val = it[[ii, jj]];
a11 += ix_val * ix_val;
a12 += ix_val * iy_val;
a22 += iy_val * iy_val;
b1 -= ix_val * it_val;
b2 -= iy_val * it_val;
}
}
let det = a11 * a22 - a12 * a12;
if det.abs() > 1e-6 {
flow_x[[i, j]] = (a22 * b1 - a12 * b2) / det;
flow_y[[i, j]] = (a11 * b2 - a12 * b1) / det;
}
}
}
Ok((flow_x, flow_y))
}
fn compute_optical_flow_farneback(
&self,
frame1: &Array2<f32>,
frame2: &Array2<f32>,
_pyramid_scale: f32,
_levels: usize,
) -> Result<(Array2<f32>, Array2<f32>)> {
self.compute_optical_flow_lucas_kanade(frame1, frame2)
}
fn stereo_matching(
&self,
left: &Array2<f32>,
right: &Array2<f32>,
max_disparity: usize,
) -> Result<(Array2<f32>, Array2<f32>)> {
let (height, width) = left.dim();
let mut disparity = Array2::zeros((height, width));
let mut confidence = Array2::zeros((height, width));
let window_size = 7;
let half_window = window_size / 2;
for i in half_window..(height - half_window) {
for j in half_window..(width - half_window) {
let mut best_cost = f32::INFINITY;
let mut best_disparity = 0;
for d in 0..max_disparity {
if j < d + half_window {
break;
}
let mut cost = 0.0;
for wi in 0..window_size {
for wj in 0..window_size {
let li = i + wi - half_window;
let lj = j + wj - half_window;
let rj = lj - d;
if rj >= half_window && rj < width - half_window {
let diff = left[[li, lj]] - right[[li, rj]];
cost += diff * diff;
}
}
}
if cost < best_cost {
best_cost = cost;
best_disparity = d;
}
}
disparity[[i, j]] = best_disparity as f32;
confidence[[i, j]] = 1.0 / (1.0 + best_cost);
}
}
Ok((disparity, confidence))
}
fn camera_calibration(
&self,
_object_points: &Array3<f32>,
_image_points: &Array3<f32>,
) -> Result<CameraParameters> {
Ok(CameraParameters {
intrinsic_matrix: array![[800.0, 0.0, 320.0], [0.0, 800.0, 240.0], [0.0, 0.0, 1.0]],
distortion_coeffs: array![[0.1, -0.2, 0.0, 0.0, 0.0]],
rotation_vectors: Vec::new(),
translation_vectors: Vec::new(),
reprojection_error: 0.5,
})
}
fn is_fast_corner(
&self,
image: &Array2<f32>,
i: usize,
j: usize,
threshold: f32,
) -> Result<bool> {
let center = image[[i, j]];
let circle_points = [
(i.wrapping_sub(3), j),
(i.wrapping_sub(3), j + 1),
(i.wrapping_sub(2), j + 2),
(i.wrapping_sub(1), j + 3),
(i, j + 3),
(i + 1, j + 3),
(i + 2, j + 2),
(i + 3, j + 1),
(i + 3, j),
(i + 3, j.wrapping_sub(1)),
(i + 2, j.wrapping_sub(2)),
(i + 1, j.wrapping_sub(3)),
(i, j.wrapping_sub(3)),
(i.wrapping_sub(1), j.wrapping_sub(3)),
(i.wrapping_sub(2), j.wrapping_sub(2)),
(i.wrapping_sub(3), j.wrapping_sub(1)),
];
let mut brighter = 0;
let mut darker = 0;
for &(pi, pj) in &circle_points {
if pi < image.nrows() && pj < image.ncols() {
let diff = image[[pi, pj]] - center;
if diff > threshold {
brighter += 1;
} else if diff < -threshold {
darker += 1;
}
}
}
Ok(brighter >= 9 || darker >= 9) }
fn compute_orientation(&self, image: &Array2<f32>, i: usize, j: usize) -> Result<f32> {
let sobel_x = array![[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]];
let sobel_y = array![[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]];
let gx = self.convolve2d(image, &sobel_x)?;
let gy = self.convolve2d(image, &sobel_y)?;
Ok(gy[[i, j]].atan2(gx[[i, j]]))
}
fn get_orb_sampling_pattern(&self, index: usize, point: usize) -> (i32, i32) {
let patterns = [
[(-2, -1), (2, 1)],
[(-1, -2), (1, 2)],
[(0, -3), (0, 3)],
[(1, -2), (-1, 2)],
[(2, -1), (-2, 1)],
[(3, 0), (-3, 0)],
[(2, 1), (-2, -1)],
[(1, 2), (-1, -2)],
];
let pattern_idx = index % patterns.len();
patterns[pattern_idx][point]
}
fn gaussian_denoise(&self, image: &Array3<f32>) -> Result<Array3<f32>> {
let kernel = self.generate_gaussian_kernel(5, 1.0)?;
let mut result = image.clone();
for c in 0..image.shape()[2] {
let channel = image.slice(s![.., .., c]).to_owned();
let denoised_channel = self.convolve2d(&channel, &kernel)?;
result.slice_mut(s![.., .., c]).assign(&denoised_channel);
}
Ok(result)
}
fn bilateral_filter(&self, image: &Array3<f32>) -> Result<Array3<f32>> {
let mut result = image.clone();
let (height, width, channels) = image.dim();
let window_size = 5;
let sigma_spatial: f32 = 2.0;
let sigma_intensity: f32 = 0.1;
for c in 0..channels {
for i in window_size..(height - window_size) {
for j in window_size..(width - window_size) {
let center_intensity = image[[i, j, c]];
let mut weighted_sum = 0.0;
let mut weight_sum = 0.0;
for wi in 0..window_size {
for wj in 0..window_size {
let ii = i + wi - window_size / 2;
let jj = j + wj - window_size / 2;
let spatial_dist = ((wi as f32 - window_size as f32 / 2.0).powi(2)
+ (wj as f32 - window_size as f32 / 2.0).powi(2))
.sqrt();
let intensity_dist = (image[[ii, jj, c]] - center_intensity).abs();
let spatial_weight =
(-spatial_dist.powi(2) / (2.0 * sigma_spatial.powi(2))).exp();
let intensity_weight =
(-intensity_dist.powi(2) / (2.0 * sigma_intensity.powi(2))).exp();
let total_weight = spatial_weight * intensity_weight;
weighted_sum += image[[ii, jj, c]] * total_weight;
weight_sum += total_weight;
}
}
result[[i, j, c]] = weighted_sum / weight_sum;
}
}
}
Ok(result)
}
fn non_local_means(&self, image: &Array3<f32>) -> Result<Array3<f32>> {
self.bilateral_filter(image)
}
fn total_variation_denoise(&self, image: &Array3<f32>) -> Result<Array3<f32>> {
let mut result = image.clone();
let lambda = 0.1; let iterations = 10;
for _ in 0..iterations {
let mut gradient_norm = Array3::zeros(image.dim());
for c in 0..image.shape()[2] {
for i in 1..(image.shape()[0] - 1) {
for j in 1..(image.shape()[1] - 1) {
let gx = result[[i, j + 1, c]] - result[[i, j - 1, c]];
let gy = result[[i + 1, j, c]] - result[[i - 1, j, c]];
gradient_norm[[i, j, c]] = (gx.powi(2) + gy.powi(2)).sqrt() + 1e-8;
}
}
}
for c in 0..image.shape()[2] {
for i in 1..(image.shape()[0] - 1) {
for j in 1..(image.shape()[1] - 1) {
let laplacian = result[[i + 1, j, c]]
+ result[[i - 1, j, c]]
+ result[[i, j + 1, c]]
+ result[[i, j - 1, c]]
- 4.0 * result[[i, j, c]];
let tv_term = laplacian / gradient_norm[[i, j, c]];
let data_term = image[[i, j, c]] - result[[i, j, c]];
result[[i, j, c]] += lambda * (data_term + 0.1 * tv_term);
}
}
}
}
Ok(result)
}
fn histogram_equalization(&self, image: &Array3<f32>) -> Result<Array3<f32>> {
let mut result = image.clone();
let bins = 256;
for c in 0..image.shape()[2] {
let mut histogram = vec![0u32; bins];
for pixel in image.slice(s![.., .., c]).iter() {
let bin = ((pixel * 255.0).round().clamp(0.0, 255.0) as usize).min(bins - 1);
histogram[bin] += 1;
}
let total_pixels = image.shape()[0] * image.shape()[1];
let mut cdf = vec![0.0; bins];
cdf[0] = histogram[0] as f32 / total_pixels as f32;
for i in 1..bins {
cdf[i] = cdf[i - 1] + histogram[i] as f32 / total_pixels as f32;
}
for pixel in result.slice_mut(s![.., .., c]).iter_mut() {
let bin = ((*pixel * 255.0).round().clamp(0.0, 255.0) as usize).min(bins - 1);
*pixel = cdf[bin];
}
}
Ok(result)
}
fn clahe_enhancement(&self, image: &Array3<f32>) -> Result<Array3<f32>> {
let tile_size = 32;
let mut result = image.clone();
for c in 0..image.shape()[2] {
for tile_y in (0..image.shape()[0]).step_by(tile_size) {
for tile_x in (0..image.shape()[1]).step_by(tile_size) {
let end_y = (tile_y + tile_size).min(image.shape()[0]);
let end_x = (tile_x + tile_size).min(image.shape()[1]);
let tile = image.slice(s![tile_y..end_y, tile_x..end_x, c]).to_owned();
let mut enhanced_tile = Array3::from_shape_vec(
(end_y - tile_y, end_x - tile_x, 1),
tile.iter().cloned().collect(),
)
.expect("array creation should succeed");
enhanced_tile = self.histogram_equalization(&enhanced_tile)?;
result
.slice_mut(s![tile_y..end_y, tile_x..end_x, c])
.assign(&enhanced_tile.slice(s![.., .., 0]));
}
}
}
Ok(result)
}
fn gamma_correction(&self, image: &Array3<f32>, gamma: f32) -> Result<Array3<f32>> {
let mut result = image.clone();
for pixel in result.iter_mut() {
*pixel = pixel.powf(gamma);
}
Ok(result)
}
fn linear_stretch(&self, image: &Array3<f32>) -> Result<Array3<f32>> {
let mut result = image.clone();
for c in 0..image.shape()[2] {
let channel_slice = image.slice(s![.., .., c]);
let min_val = channel_slice.iter().cloned().fold(f32::INFINITY, f32::min);
let max_val = channel_slice
.iter()
.cloned()
.fold(f32::NEG_INFINITY, f32::max);
if (max_val - min_val).abs() > 1e-8 {
for pixel in result.slice_mut(s![.., .., c]).iter_mut() {
*pixel = (*pixel - min_val) / (max_val - min_val);
}
}
}
Ok(result)
}
fn bicubic_interpolation(&self, image: &Array3<f32>, scale_factor: f32) -> Result<Array3<f32>> {
let (old_h, old_w, channels) = image.dim();
let new_h = (old_h as f32 * scale_factor) as usize;
let new_w = (old_w as f32 * scale_factor) as usize;
let mut result = Array3::zeros((new_h, new_w, channels));
for c in 0..channels {
for i in 0..new_h {
for j in 0..new_w {
let src_i = i as f32 / scale_factor;
let src_j = j as f32 / scale_factor;
let i_floor = src_i.floor() as usize;
let j_floor = src_j.floor() as usize;
if i_floor < old_h - 1 && j_floor < old_w - 1 {
let di = src_i - i_floor as f32;
let dj = src_j - j_floor as f32;
let top_left = image[[i_floor, j_floor, c]];
let top_right = image[[i_floor, j_floor + 1, c]];
let bottom_left = image[[i_floor + 1, j_floor, c]];
let bottom_right = image[[i_floor + 1, j_floor + 1, c]];
let top = top_left * (1.0 - dj) + top_right * dj;
let bottom = bottom_left * (1.0 - dj) + bottom_right * dj;
result[[i, j, c]] = top * (1.0 - di) + bottom * di;
}
}
}
}
Ok(result)
}
fn normalized_cross_correlation(
&self,
image: &Array2<f32>,
template: &Array2<f32>,
) -> Result<Array2<f32>> {
let (img_h, img_w) = image.dim();
let (tmpl_h, tmpl_w) = template.dim();
let mut result = Array2::zeros((img_h - tmpl_h + 1, img_w - tmpl_w + 1));
let template_mean = template.mean().expect("mean should succeed");
let template_std = (template.mapv(|x| (x - template_mean).powi(2)).sum()
/ (tmpl_h * tmpl_w) as f32)
.sqrt();
for i in 0..(img_h - tmpl_h + 1) {
for j in 0..(img_w - tmpl_w + 1) {
let patch = image.slice(s![i..i + tmpl_h, j..j + tmpl_w]);
let patch_mean = patch.mean().expect("mean should succeed");
let patch_std = (patch.mapv(|x| (x - patch_mean).powi(2)).sum()
/ (tmpl_h * tmpl_w) as f32)
.sqrt();
if patch_std > 1e-8 && template_std > 1e-8 {
let mut correlation = 0.0;
for ti in 0..tmpl_h {
for tj in 0..tmpl_w {
correlation += (patch[[ti, tj]] - patch_mean)
* (template[[ti, tj]] - template_mean);
}
}
result[[i, j]] =
correlation / (patch_std * template_std * (tmpl_h * tmpl_w) as f32);
} else {
result[[i, j]] = 0.0;
}
}
}
Ok(result)
}
fn hough_line_detection(
&self,
edges: &Array2<f32>,
rho: f32,
theta: f32,
threshold: usize,
) -> Result<Vec<HoughLine>> {
let (height, width) = edges.dim();
let max_rho = ((height.pow(2) + width.pow(2)) as f32).sqrt();
let rho_bins = (2.0 * max_rho / rho) as usize;
let theta_bins = (std::f32::consts::PI / theta) as usize;
let mut accumulator: Array2<f32> = Array2::zeros((rho_bins, theta_bins));
for i in 0..height {
for j in 0..width {
if edges[[i, j]] > 0.5 {
for theta_idx in 0..theta_bins {
let angle = theta_idx as f32 * theta;
let r = j as f32 * angle.cos() + i as f32 * angle.sin();
let rho_idx = ((r + max_rho) / rho) as usize;
if rho_idx < rho_bins {
accumulator[[rho_idx, theta_idx]] += 1.0;
}
}
}
}
}
let mut lines = Vec::new();
for rho_idx in 0..rho_bins {
for theta_idx in 0..theta_bins {
if accumulator[[rho_idx, theta_idx]] as usize >= threshold {
let r = rho_idx as f32 * rho - max_rho;
let angle = theta_idx as f32 * theta;
let cos_theta = angle.cos();
let sin_theta = angle.sin();
let x0 = cos_theta * r;
let y0 = sin_theta * r;
let start_point = (x0 - 1000.0 * sin_theta, y0 + 1000.0 * cos_theta);
let end_point = (x0 + 1000.0 * sin_theta, y0 - 1000.0 * cos_theta);
lines.push(HoughLine {
rho: r,
theta: angle,
votes: accumulator[[rho_idx, theta_idx]] as usize,
start_point,
end_point,
});
}
}
}
Ok(lines)
}
fn hough_circle_detection(
&self,
image: &Array2<f32>,
min_radius: f32,
max_radius: f32,
) -> Result<Vec<Circle>> {
let (height, width) = image.dim();
let mut circles = Vec::new();
let sobel_x = array![[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]];
let sobel_y = array![[-1.0, -2.0, -1.0], [0.0, 0.0, 0.0], [1.0, 2.0, 1.0]];
let gx = self.convolve2d(image, &sobel_x)?;
let gy = self.convolve2d(image, &sobel_y)?;
let mut accumulator = HashMap::new();
for i in 0..height {
for j in 0..width {
let gradient_mag = (gx[[i, j]].powi(2) + gy[[i, j]].powi(2)).sqrt();
if gradient_mag > 0.1 {
let gradient_dir = gy[[i, j]].atan2(gx[[i, j]]);
for radius in (min_radius as usize)..(max_radius as usize) {
let center_x = j as f32 + radius as f32 * gradient_dir.cos();
let center_y = i as f32 + radius as f32 * gradient_dir.sin();
let key = (center_x.round() as i32, center_y.round() as i32, radius);
*accumulator.entry(key).or_insert(0) += 1;
}
}
}
}
for ((x, y, r), votes) in accumulator {
if votes > 20 {
circles.push(Circle {
center_x: x as f32,
center_y: y as f32,
radius: r as f32,
votes,
});
}
}
Ok(circles)
}
}
#[derive(Debug, Clone)]
pub struct Keypoint {
pub x: f32,
pub y: f32,
pub response: f32,
pub scale: f32,
pub angle: f32,
}
#[derive(Debug, Clone)]
pub struct SiftFeatures {
pub keypoints: Vec<Keypoint>,
pub descriptors: Array2<f32>,
pub octave_info: Vec<(usize, f32)>, pub detection_time: f32,
}
impl SiftFeatures {
pub fn new(keypoints: Vec<Keypoint>, descriptors: Array2<f32>) -> Self {
let octave_info = keypoints.iter().map(|kp| (0, kp.scale)).collect();
Self {
keypoints,
descriptors,
octave_info,
detection_time: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct SurfFeatures {
pub keypoints: Vec<Keypoint>,
pub descriptors: Array2<f32>,
}
#[derive(Debug, Clone)]
pub struct OrbFeatures {
pub keypoints: Vec<Keypoint>,
pub descriptors: Array2<u8>,
}
#[derive(Debug, Clone, Copy)]
pub struct CornerPoint {
pub x: f32,
pub y: f32,
pub response: f32,
}
#[derive(Debug, Clone)]
pub struct OpticalFlow {
pub flow_x: Array2<f32>,
pub flow_y: Array2<f32>,
}
#[derive(Debug, Clone)]
pub struct DisparityMap {
pub disparity: Array2<f32>,
pub confidence: Array2<f32>,
}
#[derive(Debug, Clone, Copy)]
pub enum DenoiseMethod {
Gaussian,
Bilateral,
NlMeans,
Tv,
}
#[derive(Debug, Clone, Copy)]
pub enum ContrastMethod {
HistogramEqualization,
Clahe,
GammaCorrection,
LinearStretch,
}
#[derive(Debug, Clone, Copy)]
pub enum EdgeDetectionMethod {
Sobel,
Canny,
Laplacian,
Prewitt,
Scharr,
}
#[derive(Debug, Clone)]
pub struct FeatureMatch {
pub keypoint1_idx: usize,
pub keypoint2_idx: usize,
pub distance: f32,
pub confidence: f32,
}
#[derive(Debug, Clone)]
pub struct CameraParameters {
pub intrinsic_matrix: Array2<f32>,
pub distortion_coeffs: Array2<f32>,
pub rotation_vectors: Vec<Array2<f32>>,
pub translation_vectors: Vec<Array2<f32>>,
pub reprojection_error: f32,
}
#[derive(Debug, Clone)]
pub struct HoughLine {
pub rho: f32,
pub theta: f32,
pub votes: usize,
pub start_point: (f32, f32),
pub end_point: (f32, f32),
}
#[derive(Debug, Clone)]
pub struct Circle {
pub center_x: f32,
pub center_y: f32,
pub radius: f32,
pub votes: usize,
}
#[derive(Debug, Clone)]
pub struct HistogramAnalysis {
pub histogram: Array2<f32>,
pub entropy: f32,
pub mean_intensity: f32,
pub std_intensity: f32,
pub contrast_measure: f32,
}
#[derive(Debug, Clone, Copy)]
pub enum MorphOp {
Erosion,
Dilation,
Opening,
Closing,
Gradient,
TopHat,
BlackHat,
}
#[derive(Debug, Clone, Copy)]
pub enum StructuringElement {
Rectangle,
Circle,
Cross,
Custom,
}
#[derive(Debug, Clone)]
pub struct SegmentationResult {
pub labels: Array2<i32>,
pub num_segments: usize,
pub segment_stats: Vec<SegmentStats>,
}
#[derive(Debug, Clone)]
pub struct SegmentStats {
pub area: usize,
pub centroid: (f32, f32),
pub bounding_box: (usize, usize, usize, usize), pub mean_intensity: f32,
pub perimeter: f32,
}
#[derive(Debug, Clone)]
pub struct TextureFeatures {
pub glcm_contrast: f32,
pub glcm_dissimilarity: f32,
pub glcm_homogeneity: f32,
pub glcm_energy: f32,
pub lbp_histogram: Array2<f32>,
pub gabor_responses: Vec<Array2<f32>>,
}
#[derive(Debug, Clone)]
pub struct MotionVector {
pub x: f32,
pub y: f32,
pub magnitude: f32,
pub angle: f32,
pub confidence: f32,
}
#[derive(Debug, Clone)]
pub struct Point3D {
pub x: f32,
pub y: f32,
pub z: f32,
pub color: Option<(u8, u8, u8)>,
}
#[derive(Debug, Clone)]
pub struct PointCloud {
pub points: Vec<Point3D>,
pub normals: Option<Vec<(f32, f32, f32)>>,
pub colors: Option<Vec<(u8, u8, u8)>>,
}