Skip to content
Open
43 changes: 30 additions & 13 deletions examples/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,42 @@
*/

use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Rgb};
use purecv::core::logging::tags;
use purecv::core::{normalize, BorderTypes, Matrix, NormTypes, Size};
use purecv::imgproc::{
bilateral_filter, blur, canny, cvt_color_rgb_to_gray, gaussian_blur, laplacian, median_blur,
scharr, sobel,
};
use purecv::version;
use purecv::{cv_log_error, cv_log_info};
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("--- purecv Filters Example ---");
println!("purecv v{}", version::get_version());
purecv::core::logging::init_basic_logger()?;

cv_log_info!(tags::PURECV, "--- Filters Example ---");
version::print_version();

// 1. Load the image
let img_path = "examples/data/butterfly.jpg";
if !Path::new(img_path).exists() {
eprintln!("Error: {} not found. Run from the project root.", img_path);
cv_log_error!(
tags::IMGPROC,
"{} not found. Run from the project root.",
img_path
);
return Ok(());
}

let img = image::open(img_path)?;
let (width, height) = img.dimensions();
println!("Loaded image: {} ({}x{})", img_path, width, height);
cv_log_info!(
tags::IMGPROC,
"loaded image: {} ({}x{})",
img_path,
width,
height
);

// 2. Convert to purecv Matrix<u8> (RGB)
let rgb_img = img.to_rgb8();
Expand All @@ -68,7 +82,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// --- Apply Filters ---

// Blur (Box Filter)
println!("Applying Blur...");
cv_log_info!(tags::IMGPROC, "applying blur...");
let blurred = blur(
&mat_rgb,
Size::new(5, 5),
Expand All @@ -78,22 +92,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
save_matrix_rgb(&blurred, "examples/data/out/output_blur.png")?;

// Gaussian Blur
println!("Applying Gaussian Blur...");
cv_log_info!(tags::IMGPROC, "applying gaussian blur...");
let g_blurred = gaussian_blur(&mat_rgb, Size::new(5, 5), 1.5, 1.5, BorderTypes::Reflect101)?;
save_matrix_rgb(&g_blurred, "examples/data/out/output_gaussian_blur.png")?;

// Median Blur
println!("Applying Median Blur...");
cv_log_info!(tags::IMGPROC, "applying median blur...");
let m_blurred = median_blur(&mat_rgb, 5)?;
save_matrix_rgb(&m_blurred, "examples/data/out/output_median_blur.png")?;

// Bilateral Filter
println!("Applying Bilateral Filter...");
cv_log_info!(tags::IMGPROC, "applying bilateral filter...");
let b_filtered = bilateral_filter(&mat_rgb, 9, 75.0, 75.0, BorderTypes::Reflect101)?;
save_matrix_rgb(&b_filtered, "examples/data/out/output_bilateral.png")?;

// Sobel (on grayscale)
println!("Applying Sobel...");
cv_log_info!(tags::IMGPROC, "applying sobel...");
let sob_x = sobel(&mat_gray, 1, 0, 3, 1.0, 0.0, BorderTypes::Reflect101)?;
// Normalize for visualization
let mut sob_x_norm = Matrix::<u8>::new(sob_x.rows, sob_x.cols, sob_x.channels);
Expand All @@ -109,7 +123,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
save_matrix_gray(&sob_x_norm, "examples/data/out/output_sobel_x.png")?;

// Scharr (on grayscale)
println!("Applying Scharr...");
cv_log_info!(tags::IMGPROC, "applying scharr...");
let sch_y = scharr(&mat_gray, 0, 1, 1.0, 0.0, BorderTypes::Reflect101)?;
let mut sch_y_norm = Matrix::<u8>::new(sch_y.rows, sch_y.cols, sch_y.channels);
normalize(
Expand All @@ -124,18 +138,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
save_matrix_gray(&sch_y_norm, "examples/data/out/output_scharr_y.png")?;

// Laplacian (on grayscale)
println!("Applying Laplacian...");
cv_log_info!(tags::IMGPROC, "applying laplacian...");
let lap = laplacian(&mat_gray, 3, 1.0, 0.0, BorderTypes::Reflect101)?;
let mut lap_norm = Matrix::<u8>::new(lap.rows, lap.cols, lap.channels);
normalize(&lap, &mut lap_norm, 0.0, 255.0, NormTypes::MinMax, -1, None)?;
save_matrix_gray(&lap_norm, "examples/data/out/output_laplacian.png")?;

// Canny (on grayscale)
println!("Applying Canny...");
cv_log_info!(tags::IMGPROC, "applying canny...");
let edges = canny(&mat_gray, 50.0, 150.0, 3, false)?;
save_matrix_gray(&edges, "examples/data/out/output_canny.png")?;

println!("\nAll filters applied successfully! Check the output_*.png files.");
cv_log_info!(
tags::IMGPROC,
"all filters applied successfully! Check the output_*.png files."
);
Ok(())
}

Expand Down
72 changes: 47 additions & 25 deletions examples/match_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,26 @@
//! ```

use image::{DynamicImage, GenericImageView, ImageBuffer, Rgb};
use purecv::core::logging::tags;
use purecv::core::Matrix;
use purecv::features2d::{draw_matches, BFMatcher, DescriptorMatcher, NormType, Orb, ScoreType};
use purecv::imgproc::cvt_color_rgb_to_gray;
use purecv::version;
use purecv::{cv_log_debug, cv_log_error, cv_log_info};
use std::path::Path;
use std::time::Instant;

fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("--- purecv Feature Matching Example ---");
println!("purecv version: v{}", version::get_version());
purecv::core::logging::init_basic_logger()?;

cv_log_info!(tags::PURECV, "--- Feature Matching Example ---");
version::print_version();

let img_path = "examples/data/graf.png";
if !Path::new(img_path).exists() {
eprintln!(
"Error: {} not found. Make sure you are in the project root.",
cv_log_error!(
tags::FEATURES2D,
"{} not found. Make sure you are in the project root.",
img_path
);
return Ok(());
Expand All @@ -79,18 +84,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let load_start = Instant::now();
let img = image::open(img_path)?;
let (width, height) = img.dimensions();
println!(
"Loaded stitched image: {} ({}x{}) in {:.2?}",
cv_log_info!(
tags::FEATURES2D,
"loaded stitched image: {} ({}x{}) in {:.2?}",
img_path,
width,
height,
load_start.elapsed()
);

let half_width = width / 2;
println!(
"Splitting image into left half ({}x{}) and right half ({}x{})",
half_width, height, half_width, height
cv_log_debug!(
tags::FEATURES2D,
"splitting into left ({}x{}) and right ({}x{})",
half_width,
height,
half_width,
height
);

// 2. Convert to RGB Matrix
Expand Down Expand Up @@ -123,42 +133,52 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
}
println!("Cropped images in {:.2?}", crop_start.elapsed());
cv_log_debug!(
tags::FEATURES2D,
"cropped images in {:.2?}",
crop_start.elapsed()
);

// 5. Detect and Compute ORB Features on both halves (extract 1000 features for higher density)
let orb = Orb::new(1000, 1.2, 8, 31, 0, 2, ScoreType::Harris, 31, 20);
println!("Extracting ORB features on left half...");
cv_log_info!(tags::FEATURES2D, "extracting ORB features on left half...");
let start_left = Instant::now();
let (kps1, desc1) = orb.detect_and_compute(&left_gray)?;
println!(
" Left: extracted {} keypoints in {:.2?}",
cv_log_info!(
tags::FEATURES2D,
" left: {} keypoints in {:.2?}",
kps1.len(),
start_left.elapsed()
);

println!("Extracting ORB features on right half...");
cv_log_info!(tags::FEATURES2D, "extracting ORB features on right half...");
let start_right = Instant::now();
let (kps2, desc2) = orb.detect_and_compute(&right_gray)?;
println!(
" Right: extracted {} keypoints in {:.2?}",
cv_log_info!(
tags::FEATURES2D,
" right: {} keypoints in {:.2?}",
kps2.len(),
start_right.elapsed()
);

// 6. Match features using BFMatcher (Hamming distance with cross-check enabled)
println!("Matching features using BFMatcher (NormHamming + Cross Check)...");
cv_log_info!(
tags::FEATURES2D,
"matching features using BFMatcher (NormHamming + Cross Check)..."
);
let match_start = Instant::now();
let matcher = BFMatcher::new(NormType::NormHamming, true)?;

let mutual_matches = matcher.match_descriptors(&desc1, &desc2)?;
println!(
" Matched in {:.2?}. Total mutual matches: {}",
cv_log_info!(
tags::FEATURES2D,
" matched in {:.2?}, {} mutual matches",
match_start.elapsed(),
mutual_matches.len()
);

// 7. Draw matches with random line colors and no unmatched keypoint clutter
println!("Drawing matches...");
cv_log_info!(tags::FEATURES2D, "drawing matches...");
let draw_start = Instant::now();

let matched_img = draw_matches(
Expand All @@ -170,8 +190,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
None, // Random colors for matches
None, // Do not draw unmatched keypoints
)?;
println!(
" Drawn matching visualization in {:.2?}",
cv_log_debug!(
tags::FEATURES2D,
"drawn matching visualization in {:.2?}",
draw_start.elapsed()
);

Expand All @@ -185,12 +206,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
)
.ok_or("Failed to construct image buffer from matched matrix data")?;
DynamicImage::ImageRgb8(img_out).save(out_path)?;
println!(
" Saved match visualization to: {} in {:.2?}",
cv_log_info!(
tags::FEATURES2D,
"saved match visualization to: {} in {:.2?}",
out_path,
save_start.elapsed()
);

println!("\nDone! Run 'cargo run --example match_features' to run again.");
cv_log_info!(tags::PURECV, "done!");
Ok(())
}
57 changes: 40 additions & 17 deletions examples/optical_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,13 @@
//! ```

use image::{DynamicImage, GenericImageView, ImageBuffer, Rgb};
use purecv::core::logging::tags;
use purecv::core::types::{Point2f, Size2i, TermCriteria, TermType};
use purecv::core::Matrix;
use purecv::imgproc::{cvt_color_rgb_to_gray, good_features_to_track};
use purecv::version;
use purecv::video::{calc_optical_flow_pyramid_lk, OPTFLOW_LK_GET_MIN_EIGENVALS};
use purecv::{cv_log_debug, cv_log_error, cv_log_info, cv_log_warning};
use std::io::Write;
use std::path::Path;

Expand All @@ -81,9 +83,14 @@ const SHIFT_X: usize = 4;
const SHIFT_Y: usize = 3;

fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("--- purecv Optical Flow Example (static image) ---");
println!("purecv v{}", version::get_version());
println!("Simulating motion: +{SHIFT_X} px in x, +{SHIFT_Y} px in y\n");
purecv::core::logging::init_basic_logger()?;

cv_log_info!(tags::PURECV, "--- Optical Flow Example (static image) ---");
version::print_version();
cv_log_info!(
tags::VIDEO,
"simulating motion: +{SHIFT_X} px in x, +{SHIFT_Y} px in y"
);

// Accept an optional image path from the command line.
let default_path = "examples/data/butterfly.jpg";
Expand All @@ -92,8 +99,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.unwrap_or_else(|| default_path.to_string());

if !Path::new(&img_path).exists() {
eprintln!(
"Error: '{}' not found. Run from the project root.",
cv_log_error!(
tags::VIDEO,
"'{}' not found. Run from the project root.",
img_path
);
return Ok(());
Expand All @@ -106,7 +114,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// -----------------------------------------------------------------------
let img = image::open(&img_path)?;
let (width, height) = img.dimensions();
println!("Loaded: {} ({}×{})", img_path, width, height);
cv_log_info!(tags::VIDEO, "loaded: {} ({}x{})", img_path, width, height);

let rgb_img = img.to_rgb8();
let mat_rgb = Matrix::from_vec(
Expand All @@ -116,9 +124,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
rgb_img.clone().into_raw(),
);
let prev_gray = cvt_color_rgb_to_gray(&mat_rgb)?;
println!(
"Grayscale: {}×{}, {} channel(s)\n",
prev_gray.rows, prev_gray.cols, prev_gray.channels
cv_log_debug!(
tags::VIDEO,
"grayscale: {}x{}, {} channel(s)",
prev_gray.rows,
prev_gray.cols,
prev_gray.channels
);

// -----------------------------------------------------------------------
Expand All @@ -141,12 +152,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
let next_gray = Matrix::<u8>::from_vec(rows, cols, 1, next_data);
println!("Synthesised next frame: translated by (+{SHIFT_X}, +{SHIFT_Y}) pixels\n");
cv_log_debug!(
tags::VIDEO,
"synthesised next frame: translated by (+{SHIFT_X}, +{SHIFT_Y}) pixels"
);

// -----------------------------------------------------------------------
// 3. Detect good features to track in the previous (original) frame.
// -----------------------------------------------------------------------
println!("=== Step 1: Detect features (goodFeaturesToTrack) ===");
cv_log_info!(
tags::VIDEO,
"=== Step 1: Detect features (goodFeaturesToTrack) ==="
);
let corners = good_features_to_track(&prev_gray, 100, 0.01, 10.0, 3, false, 0.04)?;
println!("Detected {} corner(s)", corners.len());
for (i, pt) in corners.iter().take(5).enumerate() {
Expand All @@ -157,14 +174,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}

if corners.is_empty() {
eprintln!("\nNo features detected — try a different image.");
cv_log_warning!(tags::VIDEO, "no features detected — try a different image.");
return Ok(());
}

// -----------------------------------------------------------------------
// 4. Track features from prev to next using pyramidal LK.
// -----------------------------------------------------------------------
println!("\n=== Step 2: Track features (calcOpticalFlowPyrLK) ===");
cv_log_info!(
tags::VIDEO,
"=== Step 2: Track features (calcOpticalFlowPyrLK) ==="
);
let criteria = TermCriteria::new(TermType::Both, 30, 0.001);
let (next_pts, status, err) = calc_optical_flow_pyramid_lk(
&prev_gray,
Expand Down Expand Up @@ -271,10 +291,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let csv_path = "examples/data/out/optical_flow_vectors.csv";
save_flow_csv(&corners, &next_pts, &status, &err, csv_path)?;

println!("\nOutput saved to:");
println!(" {result_path} (annotated image: green = tracked, red = lost)");
println!(" {csv_path}");
println!("\nDone.");
cv_log_info!(tags::VIDEO, "output saved to:");
cv_log_info!(
tags::VIDEO,
" {result_path} (annotated image: green = tracked, red = lost)"
);
cv_log_info!(tags::VIDEO, " {csv_path}");
cv_log_info!(tags::PURECV, "done.");
Ok(())
}

Expand Down
Loading