Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement hessian response #196

Merged
merged 3 commits into from
Dec 26, 2024
Merged
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
105 changes: 105 additions & 0 deletions crates/kornia-imgproc/src/features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use kornia_image::{Image, ImageError};

use rayon::prelude::*;

/// Compute the Hessian response of an image.
///
/// The Hessian response is computed as the absolute value of the determinant of the Hessian matrix.
///
/// Args:
/// src: The source image with shape (H, W).
/// dst: The destination image with shape (H, W).
pub fn hessian_response(src: &Image<f32, 1>, dst: &mut Image<f32, 1>) -> Result<(), ImageError> {
if src.size() != dst.size() {
return Err(ImageError::InvalidImageSize(
src.cols(),
src.rows(),
dst.cols(),
dst.rows(),
));
}

let src_data = src.as_slice();

dst.as_slice_mut()
.par_chunks_exact_mut(src.cols())
.enumerate()
.for_each(|(row_idx, row_chunk)| {
if row_idx == 0 || row_idx == src.rows() - 1 {
// skip the first and last row
return;
}

let row_offset = row_idx * src.cols();

row_chunk
.iter_mut()
.enumerate()
.for_each(|(col_idx, dst_pixel)| {
if col_idx == 0 || col_idx == src.cols() - 1 {
// skip the first and last column
return;
}

let current_idx = row_offset + col_idx;
let prev_row_idx = current_idx - src.cols();
let next_row_idx = current_idx + src.cols();

let v11 = src_data[prev_row_idx - 1];
let v12 = src_data[prev_row_idx];
let v13 = src_data[prev_row_idx + 1];
let v21 = src_data[current_idx - 1];
let v22 = src_data[current_idx];
let v23 = src_data[current_idx + 1];
let v31 = src_data[next_row_idx - 1];
let v32 = src_data[next_row_idx];
let v33 = src_data[next_row_idx + 1];

let dxx = v21 - 2.0 * v22 + v23;
let dyy = v12 - 2.0 * v22 + v32;
let dxy = 0.25 * (v31 - v11 - v33 + v13);

let det = dxx * dyy - dxy * dxy;

*dst_pixel = det;
});
});

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_hessian_response() -> Result<(), ImageError> {
#[rustfmt::skip]
let src = Image::from_size_slice(
[5, 5].into(),
&[
0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 1.0, 1.0, 1.0, 0.0,
0.0, 1.0, 0.0, 1.0, 0.0,
0.0, 1.0, 1.0, 1.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0,
],
)?;

let mut dst = Image::from_size_val([5, 5].into(), 0.0)?;
hessian_response(&src, &mut dst)?;

#[rustfmt::skip]
assert_eq!(
dst.as_slice(),
&[
0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 1.0, 0.0,
0.0, 0.0, 4.0, 0.0, 0.0,
0.0, 1.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0,
]
);
Ok(())
}
}
3 changes: 3 additions & 0 deletions crates/kornia-imgproc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub mod draw;
/// image enhancement module.
pub mod enhance;

/// feature detection module.
pub mod features;

/// image flipping module.
pub mod flip;

Expand Down
12 changes: 12 additions & 0 deletions examples/features/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "features"
version = "0.1.0"
authors = ["Edgar Riba <[email protected]>"]
license = "Apache-2.0"
edition = "2021"
publish = false

[dependencies]
ctrlc = "3.4.4"
kornia = { workspace = true, features = ["gstreamer"] }
rerun = { workspace = true }
9 changes: 9 additions & 0 deletions examples/features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
An example showing how to compute the corners of an image using the Hessian response.

Example:

```bash
cargo run --bin features --release
```

![Screenshot from 2024-12-26 12-23-18](https://github.com/user-attachments/assets/aab6d989-41a8-4ad8-ac5f-857eb3ae76a0)
82 changes: 82 additions & 0 deletions examples/features/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};

use kornia::{
image::{ops, Image},
imgproc,
io::stream::V4L2CameraConfig,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
// start the recording stream
let rec = rerun::RecordingStreamBuilder::new("Kornia Webcapture App").spawn()?;

// image size
let size = [640, 480].into();

// create a webcam capture object with camera id 0
// and force the image size to 640x480
let mut webcam = V4L2CameraConfig::new().with_size(size).build()?;

// start the background pipeline
webcam.start()?;

// create a cancel token to stop the webcam capture
let cancel_token = Arc::new(AtomicBool::new(false));

ctrlc::set_handler({
let cancel_token = cancel_token.clone();
move || {
println!("Received Ctrl-C signal. Sending cancel signal !!");
cancel_token.store(true, Ordering::SeqCst);
}
})?;

// preallocate images
let mut img_f32 = Image::from_size_val(size, 0f32)?;
let mut gray = Image::from_size_val(size, 0f32)?;
let mut hessian = Image::from_size_val(size, 0f32)?;
let mut corners = Image::from_size_val(size, 0f32)?;

// start grabbing frames from the camera
while !cancel_token.load(Ordering::SeqCst) {
let Some(img) = webcam.grab()? else {
continue;
};

// convert to grayscale
ops::cast_and_scale(&img, &mut img_f32, 1. / 255.)?;
imgproc::color::gray_from_rgb(&img_f32, &mut gray)?;

// compute the hessian response
imgproc::features::hessian_response(&gray, &mut hessian)?;

// compute the corners
imgproc::threshold::threshold_binary(&hessian, &mut corners, 0.01, 1.0)?;

// log the image
rec.log_static(
"image",
&rerun::Image::from_elements(img.as_slice(), img.size().into(), rerun::ColorModel::RGB),
)?;

// log the corners
rec.log_static(
"corners",
&rerun::Image::from_elements(
corners.as_slice(),
corners.size().into(),
rerun::ColorModel::L,
),
)?;
}

// NOTE: this is important to close the webcam properly, otherwise the app will hang
webcam.close()?;

println!("Finished recording. Closing app.");

Ok(())
}
Loading