Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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