#![cfg(feature = "opencv")] #![feature(concat_idents)] mod edge_detection_rust; use hercules_rt::{runner, HerculesImmBox, HerculesImmBoxTo, HerculesMutBox}; use std::slice::from_raw_parts; use clap::Parser; use opencv::core::{Mat, Size, CV_32F, CV_8U}; use opencv::highgui::{imshow, wait_key}; use opencv::imgproc::{cvt_color_def, ColorConversionCodes}; use opencv::prelude::{MatTraitConst, VideoCaptureTrait, VideoCaptureTraitConst}; use opencv::videoio::{VideoCapture, VideoCaptureProperties, VideoWriter, VideoWriterTrait}; juno_build::juno!("edge_detection"); #[derive(Parser)] #[clap(author, version, about, long_about = None)] pub struct EdgeDetectionInputs { pub input: String, #[clap(short, long)] pub display: bool, #[clap(short, long, value_name = "PATH")] pub output: Option<String>, #[clap(short, long)] pub verify: bool, #[clap(long = "display-verify")] pub display_verify: bool, #[clap(long = "output-verify", value_name = "PATH")] pub output_verify: Option<String>, #[clap(short, long, value_name = "COUNT")] pub frames: Option<usize>, } pub fn load_frame(video: &mut VideoCapture) -> Mat { let mut frame = Mat::default(); let Ok(true) = video.read(&mut frame) else { panic!("Failed to load frame"); }; let result = if frame.channels() == 3 { let mut converted = Mat::default(); let () = cvt_color_def( &frame, &mut converted, ColorConversionCodes::COLOR_BGR2GRAY.into(), ) .expect("Failure in conversion to grayscale"); let mut result = Mat::default(); let () = converted .convert_to(&mut result, CV_32F, 1.0 / 255.0, 0.0) .expect("Failure in conversion to f32"); result } else if frame.channels() == 1 { let mut result = Mat::default(); let () = frame .convert_to(&mut result, CV_32F, 1.0 / 255.0, 0.0) .expect("Failure in conversion to f32"); result } else { panic!("Expected either RGB or grayscale image"); }; assert!(result.is_continuous()); result } pub fn frame_from_slice(frame: &[f32], height: usize, width: usize) -> Mat { let result = Mat::from_slice(frame) .expect("Failed to create matrix from result") .reshape(1, height as i32) .expect("Failed to reshape result matrix") .clone_pointee(); assert!(result.cols() == width as i32); // Convert to u8 since the VideoWriter seems to require that let mut converted = Mat::default(); let () = result .convert_to(&mut converted, CV_8U, 255.0, 0.0) .expect("Failure in conversion to u8"); converted } pub fn edge_detection_harness(args: EdgeDetectionInputs) { let EdgeDetectionInputs { input, display, output, verify, display_verify, output_verify, frames, } = args; let gs: usize = 7; let gaussian_filter: Vec<f32> = vec![ 0.000036, 0.000363, 0.001446, 0.002291, 0.001446, 0.000363, 0.000036, 0.000363, 0.003676, 0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.001446, 0.014662, 0.058488, 0.092651, 0.058488, 0.014662, 0.001446, 0.002291, 0.023226, 0.092651, 0.146768, 0.092651, 0.023226, 0.002291, 0.001446, 0.014662, 0.058488, 0.092651, 0.058488, 0.014662, 0.001446, 0.000363, 0.003676, 0.014662, 0.023226, 0.014662, 0.003676, 0.000363, 0.000036, 0.000363, 0.001446, 0.002291, 0.001446, 0.000363, 0.000036, ]; let gaussian_filter_h = HerculesImmBox::from(gaussian_filter.as_slice()); let sz: usize = 3; let structure: Vec<f32> = vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; let structure_h = HerculesImmBox::from(structure.as_slice()); let sb: usize = 3; let sx: Vec<f32> = vec![-1.0, 0.0, 1.0, -2.0, 0.0, 2.0, -1.0, 0.0, 1.0]; let sx_h = HerculesImmBox::from(sx.as_slice()); let sy: Vec<f32> = vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0]; let sy_h = HerculesImmBox::from(sy.as_slice()); let theta: f32 = 0.1; let mut video = VideoCapture::from_file_def(&input).expect("Error loading video"); assert!(video.is_opened().unwrap()); let fps = video .get(VideoCaptureProperties::CAP_PROP_FPS.into()) .expect("Error getting fps"); let num_frames = video .get(VideoCaptureProperties::CAP_PROP_FRAME_COUNT.into()) .expect("Error getting number of frames") as usize; let width = video .get(VideoCaptureProperties::CAP_PROP_FRAME_WIDTH.into()) .expect("Error getting width") as usize; let height = video .get(VideoCaptureProperties::CAP_PROP_FRAME_HEIGHT.into()) .expect("Error getting height") as usize; let num_frames = if let Some(frames) = frames { usize::min(frames, num_frames) } else { num_frames }; println!( "Running edge with {} rows, {} columns, {} gs, {} sz, and {} sb.", height, width, gs, sz, sb, ); let mut r = runner!(edge_detection); let mut output = output.map(|filename| { VideoWriter::new( &filename, VideoWriter::fourcc('m', 'p', '4', 'v').unwrap(), fps, Size { width: width as i32, height: height as i32, }, false, ) .expect("Error opening output video") }); let mut output_verify = output_verify.map(|filename| { VideoWriter::new( &filename, VideoWriter::fourcc('m', 'p', '4', 'v').unwrap(), fps, Size { width: width as i32, height: height as i32, }, false, ) .expect("Error opening output video") }); for i in 0..num_frames { let frame = load_frame(&mut video); let ptr = frame.ptr_def().unwrap() as *const f32; assert!(frame.rows() as usize == height); assert!(frame.cols() as usize == width); let input = unsafe { from_raw_parts(ptr, height * width) }; let input_h = HerculesImmBox::from(input); let result = async_std::task::block_on(async { HerculesMutBox::from( r.run( height as u64, width as u64, input_h.to(), gaussian_filter_h.to(), structure_h.to(), sx_h.to(), sy_h.to(), theta, ) .await, ) }) .as_slice() .to_vec(); if display { let result = frame_from_slice(&result, height, width); let () = imshow("Juno", &result).expect("Failure in displaying image"); } if let Some(ref mut output) = output { let result = frame_from_slice(&result, height, width); let () = output.write(&result).expect("Failure in writing frame"); } if verify { let rust_result = edge_detection_rust::edge_detection( height, width, gs, sz, sb, input, &gaussian_filter, &structure, &sx, &sy, theta, ); let mut all = true; for idx in 0..rust_result.len() { if result[idx] != rust_result[idx] { all = false; println!("Found mismatch in images at {}.", idx); } } assert!(all); println!("Frames {} match", i); if display_verify { let rust_result = frame_from_slice(&rust_result, height, width); let () = imshow("Rust", &rust_result).expect("Failure in displaying image"); } if let Some(ref mut output) = output_verify { let result = frame_from_slice(&rust_result, height, width); let () = output.write(&result).expect("Failure in writing frame"); } } if display || (verify && display_verify) { let _ = wait_key(0); } } if let Some(mut output) = output { let () = output.release().expect("Failure releasing output video"); } if let Some(mut output) = output_verify { let () = output.release().expect("Failure releasing output video"); } }