Files
asepharyana-hub-tools/backend/workers/src/scanner/edge.rs
T
asepharyanaandKilo a00ad62f6c feat: initial tools service with document scanner, image & PDF tools
Self-hosted document scanner and media processing tools.
- Rust Axum gateway + worker pool with NATS JetStream
- Next.js 16 frontend with shadcn/ui
- Scanner pipeline: edge detection, warp, binarization, OCR
- Image tools: compress, resize, convert
- PDF tools: merge, split, compress
- CI/CD with Docker multi-stage build

Co-Authored-By: Kilo <kilo@kilo.ai>
2026-07-24 13:10:59 +07:00

77 lines
2.1 KiB
Rust

use image::{GrayImage};
use imageproc::edges::canny;
use imageproc::filter::gaussian_blur_f32;
use imageproc::distance_transform::Norm;
use imageproc::morphology::close;
use tools_common::error::PipelineError;
/// Detect edges using Canny algorithm with adaptive threshold.
pub fn detect_edges(img: &GrayImage) -> Result<GrayImage, PipelineError> {
// 1. Gaussian blur for noise reduction
let blurred = gaussian_blur_f32(img, 3.0);
// 2. First attempt: Canny with standard thresholds
let edges = canny(&blurred, 50.0, 150.0);
// 3. Morphological close to connect broken edges
let closed = close(&edges, Norm::L1, 5);
// 4. Check edge coverage
let edge_count = count_non_zero(&closed);
let total_pixels = (closed.width() * closed.height()) as u32;
// If too few edges (<1%), retry with lower thresholds
if edge_count < total_pixels / 100 {
let edges2 = canny(&blurred, 20.0, 80.0);
let closed2 = close(&edges2, Norm::L1, 5);
let edge_count2 = count_non_zero(&closed2);
if edge_count2 < total_pixels / 200 {
return Err(PipelineError::EdgeDetection(
"Too few edges detected even with low threshold".to_string(),
));
}
return Ok(closed2);
}
Ok(closed)
}
/// Count non-zero (white) pixels in a binary image.
fn count_non_zero(img: &GrayImage) -> u32 {
let mut count = 0u32;
for pixel in img.iter() {
if *pixel > 0 {
count += 1;
}
}
count
}
#[cfg(test)]
mod tests {
use super::*;
use image::Luma;
#[test]
fn test_edge_detection_on_simple_image() {
let mut img = GrayImage::new(200, 200);
for y in 30..170 {
for x in 30..170 {
img.put_pixel(x, y, Luma([255]));
}
}
let result = detect_edges(&img);
assert!(result.is_ok());
let edges = result.unwrap();
assert!(count_non_zero(&edges) > 0);
}
#[test]
fn test_empty_image_returns_error() {
let img = GrayImage::new(100, 100);
let result = detect_edges(&img);
assert!(result.is_err());
}
}