use anyhow::Result;
use image::{DynamicImage, ImageBuffer, Rgba, GenericImageView};
use tracing::info;
pub fn fast_preprocess(img: &DynamicImage, target_resolution: (u32, u32)) -> Result<DynamicImage> {
info!("Applying fast preprocessing to {}x{} -> {}x{}",
img.width(), img.height(), target_resolution.0, target_resolution.1);
let resized = img.resize_exact(
target_resolution.0,
target_resolution.1,
image::imageops::FilterType::Nearest
);
Ok(DynamicImage::ImageRgb8(resized.to_rgb8()))
}
pub fn balanced_preprocess(img: &DynamicImage, target_resolution: (u32, u32)) -> Result<DynamicImage> {
info!("Applying balanced preprocessing to {}x{} -> {}x{}",
img.width(), img.height(), target_resolution.0, target_resolution.1);
let mut processed = img.clone();
processed = processed.resize_exact(
target_resolution.0,
target_resolution.1,
image::imageops::FilterType::Lanczos3
);
processed = normalize_colors(&processed)?;
Ok(DynamicImage::ImageRgb8(processed.to_rgb8()))
}
pub fn quality_preprocess(img: &DynamicImage, target_resolution: (u32, u32)) -> Result<DynamicImage> {
info!("Applying quality preprocessing to {}x{} -> {}x{}",
img.width(), img.height(), target_resolution.0, target_resolution.1);
let mut processed = img.clone();
processed = reduce_noise(&processed)?;
processed = smart_resize(&processed, target_resolution)?;
processed = normalize_colors_enhanced(&processed)?;
processed = apply_sharpening(&processed)?;
Ok(DynamicImage::ImageRgb8(processed.to_rgb8()))
}
fn smart_resize(img: &DynamicImage, target_resolution: (u32, u32)) -> Result<DynamicImage> {
let (target_width, target_height) = target_resolution;
let (orig_width, orig_height) = img.dimensions();
let scale_x = target_width as f32 / orig_width as f32;
let scale_y = target_height as f32 / orig_height as f32;
let scale = scale_x.min(scale_y);
let new_width = (orig_width as f32 * scale) as u32;
let new_height = (orig_height as f32 * scale) as u32;
let resized = img.resize(new_width, new_height, image::imageops::FilterType::Lanczos3);
if new_width != target_width || new_height != target_height {
let mut padded = ImageBuffer::from_pixel(target_width, target_height, Rgba([0u8, 0u8, 0u8, 255u8]));
let x_offset = (target_width - new_width) / 2;
let y_offset = (target_height - new_height) / 2;
let resized_rgba = resized.to_rgba8();
for y in 0..new_height {
for x in 0..new_width {
let pixel = resized_rgba.get_pixel(x, y);
padded.put_pixel(x + x_offset, y + y_offset, *pixel);
}
}
Ok(DynamicImage::ImageRgba8(padded))
} else {
Ok(resized)
}
}
fn normalize_colors(img: &DynamicImage) -> Result<DynamicImage> {
let rgb_img = img.to_rgb8();
let mut normalized = rgb_img.clone();
for pixel in normalized.pixels_mut() {
for channel in pixel.0.iter_mut() {
let normalized_val = (*channel as f32 / 255.0).powf(0.9);
*channel = (normalized_val * 255.0) as u8;
}
}
Ok(DynamicImage::ImageRgb8(normalized))
}
fn normalize_colors_enhanced(img: &DynamicImage) -> Result<DynamicImage> {
let rgb_img = img.to_rgb8();
let mut enhanced = rgb_img.clone();
for channel_idx in 0..3 {
let mut histogram = [0u32; 256];
for pixel in enhanced.pixels() {
histogram[pixel.0[channel_idx] as usize] += 1;
}
let total_pixels = enhanced.width() * enhanced.height();
let mut cdf = [0f32; 256];
let mut cumulative = 0u32;
for (i, &count) in histogram.iter().enumerate() {
cumulative += count;
cdf[i] = cumulative as f32 / total_pixels as f32;
}
for pixel in enhanced.pixels_mut() {
let old_val = pixel.0[channel_idx] as usize;
let new_val = (cdf[old_val] * 255.0) as u8;
pixel.0[channel_idx] = new_val;
}
}
Ok(DynamicImage::ImageRgb8(enhanced))
}
fn reduce_noise(img: &DynamicImage) -> Result<DynamicImage> {
let blurred = img.blur(0.5);
Ok(blurred)
}
fn apply_sharpening(img: &DynamicImage) -> Result<DynamicImage> {
let rgb_img = img.to_rgb8();
let mut sharpened = rgb_img.clone();
let (width, height) = rgb_img.dimensions();
let kernel = [
[0.0, -0.1, 0.0],
[-0.1, 1.4, -0.1],
[0.0, -0.1, 0.0],
];
for y in 1..height-1 {
for x in 1..width-1 {
let mut new_pixel = [0f32; 3];
for ky in 0..3 {
for kx in 0..3 {
let px = (x as i32 + kx as i32 - 1) as u32;
let py = (y as i32 + ky as i32 - 1) as u32;
let pixel = rgb_img.get_pixel(px, py);
for c in 0..3 {
new_pixel[c] += pixel.0[c] as f32 * kernel[ky][kx];
}
}
}
let pixel = sharpened.get_pixel_mut(x, y);
for c in 0..3 {
pixel.0[c] = new_pixel[c].max(0.0).min(255.0) as u8;
}
}
}
Ok(DynamicImage::ImageRgb8(sharpened))
}
pub fn image_to_tensor(img: &DynamicImage, normalize_range: (f32, f32)) -> Vec<f32> {
let rgb_img = img.to_rgb8();
let (min_val, max_val) = normalize_range;
let range = max_val - min_val;
rgb_img.pixels()
.flat_map(|pixel| {
pixel.0.iter().map(|&channel| {
let normalized = channel as f32 / 255.0;
min_val + normalized * range
})
})
.collect()
}
pub fn imagenet_normalize(tensor: &mut [f32]) {
let mean = [0.485, 0.456, 0.406];
let std = [0.229, 0.224, 0.225];
let pixels = tensor.len() / 3;
for pixel_idx in 0..pixels {
for channel in 0..3 {
let idx = pixel_idx * 3 + channel;
tensor[idx] = (tensor[idx] - mean[channel]) / std[channel];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use image::RgbImage;
#[test]
fn test_fast_preprocess() {
let test_img = DynamicImage::ImageRgb8(RgbImage::new(100, 100));
let result = fast_preprocess(&test_img, (224, 224));
assert!(result.is_ok());
let processed = result.unwrap();
assert_eq!(processed.dimensions(), (224, 224));
}
#[test]
fn test_image_to_tensor() {
let test_img = DynamicImage::ImageRgb8(RgbImage::new(2, 2));
let tensor = image_to_tensor(&test_img, (-1.0, 1.0));
assert_eq!(tensor.len(), 12);
for &val in &tensor {
assert!(val >= -1.0 && val <= 1.0);
}
}
#[test]
fn test_smart_resize() {
let test_img = DynamicImage::ImageRgb8(RgbImage::new(100, 50));
let result = smart_resize(&test_img, (224, 224));
assert!(result.is_ok());
let resized = result.unwrap();
assert_eq!(resized.dimensions(), (224, 224));
}
}