-
Aaron Councilman authoredAaron Councilman authored
main.rs 9.91 KiB
#![feature(concat_idents)]
mod edge_detection_rust;
use hercules_rt::{runner, HerculesCPURef};
#[cfg(feature = "cuda")]
use hercules_rt::CUDABox;
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)]
struct EdgeDetectionInputs {
input: String,
#[clap(short, long)]
display: bool,
#[clap(short, long, value_name = "PATH")]
output: Option<String>,
#[clap(short, long)]
verify: bool,
#[clap(long = "display-verify")]
display_verify: bool,
#[clap(long = "output-verify", value_name = "PATH")]
output_verify: Option<String>,
#[clap(short, long, value_name = "COUNT")]
frames: Option<usize>,
}
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
}
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
}
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,
];
#[cfg(not(feature = "cuda"))]
let gaussian_filter_h = HerculesCPURef::from_slice(&gaussian_filter);
#[cfg(feature = "cuda")]
let gaussian_filter_cuda = CUDABox::from_cpu_ref(HerculesCPURef::from_slice(&gaussian_filter));
#[cfg(feature = "cuda")]
let gaussian_filter_h = gaussian_filter_cuda.get_ref();
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];
#[cfg(not(feature = "cuda"))]
let structure_h = HerculesCPURef::from_slice(&structure);
#[cfg(feature = "cuda")]
let structure_cuda = CUDABox::from_cpu_ref(HerculesCPURef::from_slice(&structure));
#[cfg(feature = "cuda")]
let structure_h = structure_cuda.get_ref();
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];
#[cfg(not(feature = "cuda"))]
let sx_h = HerculesCPURef::from_slice(&sx);
#[cfg(feature = "cuda")]
let sx_cuda = CUDABox::from_cpu_ref(HerculesCPURef::from_slice(&sx));
#[cfg(feature = "cuda")]
let sx_h = sx_cuda.get_ref();
let sy: Vec<f32> = vec![-1.0, -2.0, -1.0, 0.0, 0.0, 0.0, 1.0, 2.0, 1.0];
#[cfg(not(feature = "cuda"))]
let sy_h = HerculesCPURef::from_slice(&sy);
#[cfg(feature = "cuda")]
let sy_cuda = CUDABox::from_cpu_ref(HerculesCPURef::from_slice(&sy));
#[cfg(feature = "cuda")]
let sy_h = sy_cuda.get_ref();
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
};
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) };
#[cfg(not(feature = "cuda"))]
let input_h = HerculesCPURef::from_slice(input);
#[cfg(feature = "cuda")]
let input_cuda = CUDABox::from_cpu_ref(HerculesCPURef::from_slice(input));
#[cfg(feature = "cuda")]
let input_h = input_cuda.get_ref();
let result = async_std::task::block_on(async {
r.run(
height as u64,
width as u64,
gs as u64,
sz as u64,
sb as u64,
input_h,
gaussian_filter_h.clone(),
structure_h.clone(),
sx_h.clone(),
sy_h.clone(),
theta,
)
.await
});
#[cfg(not(feature = "cuda"))]
let result : Box<[f32]> = result.as_slice::<f32>().to_vec().into_boxed_slice();
#[cfg(feature = "cuda")]
let result : Box<[f32]> = {
let num_out = unsafe { result.__size() / std::mem::size_of::<f32>() };
let mut res_cpu: Box<[f32]> = vec![0.0; num_out].into_boxed_slice();
result.to_cpu_ref(&mut res_cpu);
res_cpu
};
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,
);
assert_eq!(result.as_ref(), <Vec<f32> as AsRef<[f32]>>::as_ref(&rust_result));
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");
}
}
fn main() {
let args = EdgeDetectionInputs::parse();
edge_detection_harness(args);
}
#[test]
fn edge_detection_test() {
edge_detection_harness(EdgeDetectionInputs {
input: "examples/formula1_scaled.mp4".to_string(),
display: false,
output: None,
verify: true,
display_verify: false,
output_verify: None,
// Limit frames to keep runtime low
frames: Some(2),
});
}