pub mod utils;
pub use opencv;
use opencv::core::{Point2f, Vector};
use opencv::{calib3d, core, features2d, imgcodecs, imgproc, prelude::*};
use rayon::prelude::*;
use std::path::PathBuf;
use thiserror::Error;
use utils::SetMValue;
#[derive(Error, Debug)]
pub enum StackerError {
#[error(transparent)]
OpenCvError(#[from] opencv::Error),
#[error("Not enough files")]
NotEnoughFiles,
#[error("Not implemented")]
NotImplemented,
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
PoisonError(#[from] std::sync::PoisonError<core::MatExprResult<core::MatExpr>>),
#[error("Invalid path encoding {0}")]
InvalidPathEncoding(PathBuf),
#[error("Invalid parameter(s) {0}")]
InvalidParams(String),
#[error("Internal error {0}")]
ProcessingError(String),
}
#[derive(Debug, Clone, Copy)]
pub struct KeyPointMatchParameters {
pub method: i32,
pub ransac_reproj_threshold: f64,
pub match_keep_ratio: f32,
pub match_ratio: f32,
pub border_mode: i32,
pub border_value: core::Scalar,
}
pub fn keypoint_match<I, P>(
files: I,
params: KeyPointMatchParameters,
scale_down_width: Option<f32>,
) -> Result<(i32, Mat), StackerError>
where
I: IntoIterator<Item = P>,
P: AsRef<std::path::Path>,
{
let files = files.into_iter().map(|p| p.as_ref().to_path_buf());
if let Some(scale_down_width) = scale_down_width {
keypoint_match_scale_down(files, params, scale_down_width)
} else {
keypoint_match_no_scale(files, params)
}
}
fn keypoint_match_no_scale<I>(
files: I,
params: KeyPointMatchParameters,
) -> Result<(i32, Mat), StackerError>
where
I: IntoIterator<Item = PathBuf>,
{
let files_vec: Vec<PathBuf> = files.into_iter().collect();
if files_vec.is_empty() {
return Err(StackerError::NotEnoughFiles);
}
let (first_grey_img, first_img_f32_wr) = {
let (g, f) = utils::read_grey_and_f32(&files_vec[0], imgcodecs::IMREAD_UNCHANGED)?;
(g, utils::UnsafeMatSyncWrapper(f))
};
let img_size = first_grey_img.size()?;
let (first_kp_wr, first_des_wr) = {
let (kp, des) = utils::orb_detect_and_compute(&first_grey_img)?;
(
utils::UnsafeVectorKeyPointSyncWrapper(kp),
utils::UnsafeMatSyncWrapper(des),
)
};
drop(first_grey_img);
let result = {
let first_kp_wrmv = &first_kp_wr;
let first_des_wrmv = &first_des_wr;
let first_img_f32_wrmv = &first_img_f32_wr;
let files_vec_mw = &files_vec;
(0..files_vec.len())
.into_par_iter()
.try_fold(
|| None, move |acc: Option<(i32, utils::UnsafeMatSyncWrapper)>, index| {
let warped_img = if index == 0 {
Some(first_img_f32_wrmv.0.clone())
} else {
let (grey_img, img_f32) = utils::read_grey_and_f32(&files_vec_mw[index], imgcodecs::IMREAD_UNCHANGED)?;
let (kp, des) = utils::orb_detect_and_compute(&grey_img)?;
let matches: Vec<core::DMatch> = {
let mut matcher = features2d::BFMatcher::create(core::NORM_HAMMING, false)?; matcher.add(&des)?;
let mut knn_matches = core::Vector::<core::Vector<core::DMatch>>::new();
matcher.knn_match(
&first_des_wrmv.0, &mut knn_matches, 2, &Mat::default(), false, )?;
let mut filtered_matches = knn_matches
.iter()
.filter_map(|m| {
if m.len() == 2 && m.get(0).unwrap().distance < params.match_ratio * m.get(1).unwrap().distance {
Some(m.get(0).unwrap())
} else {
None
}
})
.collect::<Vec<_>>();
filtered_matches.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap_or(std::cmp::Ordering::Equal));
let num_to_keep = (filtered_matches.len() as f32 * params.match_keep_ratio).round() as usize;
filtered_matches.truncate(num_to_keep);
filtered_matches
};
if matches.len() < 5 {
return Ok(None)
}
let src_pts = {
let mut pts: Vector<Point2f> = Vector::with_capacity(matches.len());
for m in matches.iter() {
pts.push(first_kp_wrmv.0.get(m.query_idx as usize)?.pt());
}
let mut mat = Mat::from_exact_iter(pts.into_iter())?;
mat.reshape_mut(2, 0)?;
mat
};
let dst_pts = {
let mut pts: Vector<Point2f> = Vector::with_capacity(matches.len());
for m in matches.iter() {
pts.push(kp.get(m.train_idx as usize)?.pt());
}
let mut mat = Mat::from_exact_iter(pts.into_iter())?;
mat.reshape_mut(2, 0)?;
mat
};
let h = match calib3d::find_homography(
&dst_pts,
&src_pts,
&mut Mat::default(),
params.method,
params.ransac_reproj_threshold,
) {
Ok(matrix) => matrix,
Err(_) => return Ok(None), };
if h.empty() || h.rows() != 3 || h.cols() != 3 {
return Ok(None); }
if core::determinant(&h)?.abs() < 1e-6 {
return Ok(None);
}
let mut warped_image = Mat::default();
imgproc::warp_perspective(
&img_f32,
&mut warped_image,
&h,
img_size,
imgproc::INTER_LINEAR,
params.border_mode,
params.border_value,
)?;
Some(warped_image)
};
let result = match (acc, warped_img) {
(None, None) => (1_i32,utils::UnsafeMatSyncWrapper(first_img_f32_wrmv.0.clone())),
(Some((acc_d, acc_mat)), None) => (1_i32 + acc_d, acc_mat),
(None, Some(warped_image)) => (0_i32, utils::UnsafeMatSyncWrapper(warped_image)),
(Some((acc_d, acc_mat)), Some(warped_image)) => {
let mat = utils::UnsafeMatSyncWrapper(
(&acc_mat.0 + &warped_image).into_result()?.to_mat()?,
);
(acc_d, mat)
}
};
Ok::<Option<(i32, utils::UnsafeMatSyncWrapper)>, StackerError>(Some(result))
},
)
.try_reduce(
|| None,
|acc1, acc2| match (acc1, acc2) {
(None, None) => Err(StackerError::InvalidParams("All images discarded: try modifying KeyPointMatchParameters::match_distance_threshold".to_string())),
(Some(acc1), None) => Ok(Some(acc1)),
(None, Some(acc2)) => Ok(Some(acc2)),
(Some(acc1), Some(acc2)) => {
let combined_img = utils::UnsafeMatSyncWrapper(
(&acc1.1.0 + &acc2.1.0).into_result()?.to_mat()?,
);
Ok(Some((acc1.0+ acc2.0, combined_img)))
}
},
)
}?;
let final_result = if let Some((dropped, img)) = result {
(
dropped,
(img.0 / (files_vec.len() - dropped as usize) as f64)
.into_result()?
.to_mat()?,
)
} else {
return Err(StackerError::ProcessingError(
"Empty result after reduction".to_string(),
));
};
Ok(final_result)
}
fn keypoint_match_scale_down<I>(
files: I,
params: KeyPointMatchParameters,
scale_down_width: f32,
) -> Result<(i32, Mat), StackerError>
where
I: IntoIterator<Item = PathBuf>,
{
let files_vec: Vec<PathBuf> = files.into_iter().collect();
if files_vec.is_empty() {
return Err(StackerError::NotEnoughFiles);
}
let (grey_first_img, first_img_f32_wr) = {
let (g, f) = utils::read_grey_and_f32(&files_vec[0], imgcodecs::IMREAD_UNCHANGED)?;
(g, utils::UnsafeMatSyncWrapper(f))
};
let full_size = first_img_f32_wr.0.size()?;
if scale_down_width >= full_size.width as f32 {
return Err(StackerError::InvalidParams(format!(
"scale_down_to was larger (or equal) to the full image width: full_size:{}, scale_down_to:{}",
full_size.width, scale_down_width
)));
}
let (first_kp_small_wr, first_des_small_wr) = {
let grey_img_small = utils::scale_image(&grey_first_img, scale_down_width)?;
let (kp, des) = utils::orb_detect_and_compute(&grey_img_small)?;
(
utils::UnsafeVectorKeyPointSyncWrapper(kp),
utils::UnsafeMatSyncWrapper(des),
)
};
drop(grey_first_img);
let result = {
let first_kp_small_wrmv = &first_kp_small_wr;
let first_des_small_wrmv = &first_des_small_wr;
let first_img_f32_wrmv = &first_img_f32_wr;
let files_vec_wr = &files_vec;
(0..files_vec.len())
.into_par_iter()
.try_fold(
|| None, move |acc: Option<(i32, utils::UnsafeMatSyncWrapper)>, index| {
let warped_img = if index == 0 {
Some(first_img_f32_wrmv.0.clone())
} else {
let (img_f32, grey_img_small) = {
let (grey_img, img_f32) = utils::read_grey_and_f32(
&files_vec_wr[index],
imgcodecs::IMREAD_UNCHANGED,
)?;
let grey_img_small = utils::scale_image(&grey_img, scale_down_width)?;
(img_f32, grey_img_small)
};
let (kp_small, des_small) = utils::orb_detect_and_compute(&grey_img_small)?;
let matches: Vec<core::DMatch> = {
let mut matcher =
features2d::BFMatcher::create(core::NORM_HAMMING, false)?; matcher.add(&des_small)?;
let mut knn_matches = core::Vector::<core::Vector<core::DMatch>>::new();
matcher.knn_match(
&first_des_small_wrmv.0, &mut knn_matches, 2, &Mat::default(), false, )?;
let mut filtered_matches = knn_matches
.iter()
.filter_map(|m| {
if m.len() == 2
&& m.get(0).unwrap().distance
< params.match_ratio * m.get(1).unwrap().distance
{
Some(m.get(0).unwrap())
} else {
None
}
})
.collect::<Vec<_>>();
filtered_matches.sort_by(|a, b| {
a.distance
.partial_cmp(&b.distance)
.unwrap_or(std::cmp::Ordering::Equal)
});
let num_to_keep = (filtered_matches.len() as f32
* params.match_keep_ratio)
.round() as usize;
filtered_matches.truncate(num_to_keep);
filtered_matches
};
if matches.len() < 5 {
return Ok(None);
}
let src_pts = {
let mut pts: Vector<Point2f> = Vector::with_capacity(matches.len());
for m in matches.iter() {
pts.push(first_kp_small_wrmv.0.get(m.query_idx as usize)?.pt());
}
let mut mat = Mat::from_exact_iter(pts.into_iter())?;
mat.reshape_mut(2, 0)?;
mat
};
let dst_pts = {
let mut pts: Vector<Point2f> = Vector::with_capacity(matches.len());
for m in matches.iter() {
pts.push(kp_small.get(m.train_idx as usize)?.pt());
}
let mut mat = Mat::from_exact_iter(pts.into_iter())?;
mat.reshape_mut(2, 0)?;
mat
};
let h_small = match calib3d::find_homography(
&dst_pts,
&src_pts,
&mut Mat::default(),
params.method,
params.ransac_reproj_threshold,
) {
Ok(matrix) => matrix,
Err(_) => return Ok(None), };
if h_small.empty() || h_small.rows() != 3 || h_small.cols() != 3 {
return Ok(None); }
if core::determinant(&h_small)?.abs() < 1e-6 {
return Ok(None);
}
let h = utils::adjust_homography_for_scale_f64(
&h_small,
&grey_img_small,
&img_f32,
)?;
let mut warped_image = Mat::default();
imgproc::warp_perspective(
&img_f32,
&mut warped_image,
&h,
full_size,
imgproc::INTER_LINEAR,
params.border_mode,
params.border_value,
)?;
Some(warped_image)
};
let result = match (acc, warped_img) {
(None, None) => (
1_i32,
utils::UnsafeMatSyncWrapper(first_img_f32_wrmv.0.clone()),
),
(Some((acc_d, acc_mat)), None) => (1_i32 + acc_d, acc_mat),
(None, Some(warped_image)) => {
(0_i32, utils::UnsafeMatSyncWrapper(warped_image))
}
(Some((acc_d, acc_mat)), Some(warped_image)) => {
let mat = utils::UnsafeMatSyncWrapper(
(&acc_mat.0 + &warped_image).into_result()?.to_mat()?,
);
(acc_d, mat)
}
};
Ok::<Option<(i32, utils::UnsafeMatSyncWrapper)>, StackerError>(Some(result))
},
)
.try_reduce(
|| None,
|acc1, acc2| match (acc1, acc2) {
(Some(acc1), None) => Ok(Some(acc1)),
(None, Some(acc2)) => Ok(Some(acc2)),
(Some(acc1), Some(acc2)) => {
let combined_img = utils::UnsafeMatSyncWrapper(
(&acc1.1.0 + &acc2.1.0).into_result()?.to_mat()?,
);
Ok(Some((acc1.0 + acc2.0, combined_img)))
}
_ => unreachable!(),
},
)
}?;
let final_result = if let Some((dropped, img)) = result {
(
dropped,
(img.0 / (files_vec.len() - dropped as usize) as f64)
.into_result()?
.to_mat()?,
)
} else {
return Err(StackerError::ProcessingError(
"Empty result after reduction".to_string(),
));
};
Ok(final_result)
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MotionType {
Homography = opencv::video::MOTION_HOMOGRAPHY as isize,
Affine = opencv::video::MOTION_AFFINE as isize,
Euclidean = opencv::video::MOTION_EUCLIDEAN as isize,
Translation = opencv::video::MOTION_TRANSLATION as isize,
}
#[derive(Debug, Copy, Clone)]
pub struct EccMatchParameters {
pub motion_type: MotionType,
pub max_count: Option<i32>,
pub epsilon: Option<f64>,
pub gauss_filt_size: i32,
}
pub fn ecc_match<I, P>(
files: I,
params: EccMatchParameters,
scale_down_width: Option<f32>,
) -> Result<Mat, StackerError>
where
I: IntoIterator<Item = P>,
P: AsRef<std::path::Path>,
{
let files = files.into_iter().map(|p| p.as_ref().to_path_buf());
if let Some(scale_down_width) = scale_down_width {
ecc_match_scaling_down(files, params, scale_down_width)
} else {
ecc_match_no_scaling(files, params)
}
}
fn ecc_match_no_scaling<I>(files: I, params: EccMatchParameters) -> Result<Mat, StackerError>
where
I: IntoIterator<Item = PathBuf>,
{
let files_vec: Vec<PathBuf> = files.into_iter().collect();
if files_vec.is_empty() {
return Err(StackerError::NotEnoughFiles);
}
let criteria = Result::<core::TermCriteria, StackerError>::from(params)?;
let (first_img_grey_wr, first_img_f32_wr) = {
let (img_grey, first_img_f32) =
utils::read_grey_and_f32(&files_vec[0], imgcodecs::IMREAD_UNCHANGED)?;
(
utils::UnsafeMatSyncWrapper(img_grey),
utils::UnsafeMatSyncWrapper(first_img_f32),
)
};
let result = {
let first_img_f32_wrmv = &first_img_f32_wr;
let first_img_grey_wrmw = &first_img_grey_wr;
let files_vec_mv = &files_vec;
(0..files_vec.len())
.into_par_iter()
.with_min_len(1)
.try_fold(
|| None, move |acc: Option<utils::UnsafeMatSyncWrapper>, index| {
let warped_image = if index == 0 {
first_img_f32_wrmv.0.clone()
} else {
let (img_grey, img_f32) = utils::read_grey_and_f32(
&files_vec_mv[index],
imgcodecs::IMREAD_UNCHANGED,
)?;
let mut warp_matrix = if params.motion_type != MotionType::Homography {
Mat::eye(2, 3, opencv::core::CV_32F)?.to_mat()?
} else {
Mat::eye(3, 3, opencv::core::CV_32F)?.to_mat()?
};
let _ = opencv::video::find_transform_ecc(
&img_grey,
&first_img_grey_wrmw.0,
&mut warp_matrix,
params.motion_type as i32,
criteria,
&Mat::default(),
params.gauss_filt_size,
)?;
let mut warped_image = Mat::default();
if params.motion_type != MotionType::Homography {
imgproc::warp_affine(
&img_f32,
&mut warped_image,
&warp_matrix,
img_f32.size()?,
imgproc::INTER_LINEAR,
core::BORDER_CONSTANT,
core::Scalar::default(),
)?;
} else {
imgproc::warp_perspective(
&img_f32,
&mut warped_image,
&warp_matrix,
img_f32.size()?,
imgproc::INTER_LINEAR,
core::BORDER_CONSTANT,
core::Scalar::default(),
)?;
}
warped_image
};
let result = if let Some(acc) = acc {
let rv = utils::UnsafeMatSyncWrapper(
(&acc.0 + &warped_image).into_result()?.to_mat()?,
);
Some(rv)
} else {
Some(utils::UnsafeMatSyncWrapper(warped_image))
};
Ok::<Option<utils::UnsafeMatSyncWrapper>, StackerError>(result)
},
)
.try_reduce(
|| None,
|acc1, acc2| match (acc1, acc2) {
(Some(acc1), None) => Ok(Some(acc1)),
(None, Some(acc2)) => Ok(Some(acc2)),
(Some(acc1), Some(acc2)) => {
let combined = utils::UnsafeMatSyncWrapper(
(&acc1.0 + &acc2.0).into_result()?.to_mat()?,
);
Ok(Some(combined))
}
_ => unreachable!(),
},
)?
};
let final_result = if let Some(result) = result {
(result.0 / files_vec.len() as f64)
.into_result()?
.to_mat()?
} else {
return Err(StackerError::ProcessingError(
"Empty result after reduction".to_string(),
));
};
Ok(final_result)
}
fn ecc_match_scaling_down<I>(
files: I,
params: EccMatchParameters,
scale_down_width: f32,
) -> Result<Mat, StackerError>
where
I: IntoIterator<Item = PathBuf>,
{
let files_vec: Vec<PathBuf> = files.into_iter().collect();
if files_vec.is_empty() {
return Err(StackerError::NotEnoughFiles);
}
let criteria = Result::<core::TermCriteria, StackerError>::from(params)?;
let (first_img_grey_wr, first_img_f32_wr) = {
let (img_grey, first_img_f32) =
utils::read_grey_and_f32(&files_vec[0], imgcodecs::IMREAD_UNCHANGED)?;
(
utils::UnsafeMatSyncWrapper(img_grey),
utils::UnsafeMatSyncWrapper(first_img_f32),
)
};
let full_size = first_img_f32_wr.0.size()?;
if scale_down_width >= full_size.width as f32 {
return Err(StackerError::InvalidParams(format!(
"scale_down_to was larger (or equal) to the full image width: full_size:{}, scale_down_to:{}",
full_size.width, scale_down_width
)));
}
if scale_down_width <= 10.0 {
return Err(StackerError::InvalidParams(format!(
"scale_down_to was too small scale_down_to:{}",
scale_down_width
)));
}
let first_img_grey_small_wr =
utils::UnsafeMatSyncWrapper(utils::scale_image(&first_img_grey_wr.0, scale_down_width)?);
let small_size = first_img_grey_small_wr.0.size()?;
let result = {
let first_img_f32_wrmv = &first_img_f32_wr;
let files_vec_mv = &files_vec;
(0..files_vec.len())
.into_par_iter()
.with_min_len(1)
.try_fold(
|| None, move |acc: Option<utils::UnsafeMatSyncWrapper>, index| {
let warped_image = if index == 0 {
first_img_f32_wrmv.0.clone()
} else {
let (img_grey_original, img_f32) = utils::read_grey_and_f32(
&files_vec_mv[index],
imgcodecs::IMREAD_UNCHANGED,
)?;
let first_img_grey_small = &first_img_grey_small_wr;
let img_small = utils::scale_image(&img_f32, scale_down_width)?;
let img_grey_small =
utils::scale_image(&img_grey_original, scale_down_width)?;
let mut warp_matrix_small = if params.motion_type != MotionType::Homography
{
Mat::eye(2, 3, opencv::core::CV_32F)?.to_mat()?
} else {
Mat::eye(3, 3, opencv::core::CV_32F)?.to_mat()?
};
let _ = opencv::video::find_transform_ecc(
&img_grey_small,
&first_img_grey_small.0,
&mut warp_matrix_small,
params.motion_type as i32,
criteria,
&Mat::default(),
params.gauss_filt_size,
)?;
let warp_matrix = if params.motion_type != MotionType::Homography {
let mut full_warp = warp_matrix_small.clone();
let tx = full_warp.at_2d_mut::<f32>(0, 2)?;
*tx *= full_size.width as f32 / small_size.width as f32;
let ty = full_warp.at_2d_mut::<f32>(1, 2)?;
*ty *= full_size.height as f32 / small_size.height as f32;
full_warp
} else {
utils::adjust_homography_for_scale_f32(
&warp_matrix_small,
&img_small,
&img_f32,
)?
};
let mut warped_image = Mat::default();
if params.motion_type != MotionType::Homography {
imgproc::warp_affine(
&img_f32,
&mut warped_image,
&warp_matrix,
full_size,
imgproc::INTER_LINEAR,
core::BORDER_CONSTANT,
core::Scalar::default(),
)?;
} else {
imgproc::warp_perspective(
&img_f32,
&mut warped_image,
&warp_matrix,
full_size,
imgproc::INTER_LINEAR,
core::BORDER_CONSTANT,
core::Scalar::default(),
)?;
}
warped_image
};
let result = if let Some(acc) = acc {
let rv = utils::UnsafeMatSyncWrapper(
(&acc.0 + &warped_image).into_result()?.to_mat()?,
);
Some(rv)
} else {
Some(utils::UnsafeMatSyncWrapper(warped_image))
};
Ok::<Option<utils::UnsafeMatSyncWrapper>, StackerError>(result)
},
)
.try_reduce(
|| None,
|acc1, acc2| match (acc1, acc2) {
(Some(acc1), None) => Ok(Some(acc1)),
(None, Some(acc2)) => Ok(Some(acc2)),
(Some(acc1), Some(acc2)) => {
let combined = utils::UnsafeMatSyncWrapper(
(&acc1.0 + &acc2.0).into_result()?.to_mat()?,
);
Ok(Some(combined))
}
_ => unreachable!(),
},
)?
};
let final_result = if let Some(result) = result {
(result.0 / files_vec.len() as f64)
.into_result()?
.to_mat()?
} else {
return Err(StackerError::ProcessingError(
"Empty result after reduction".to_string(),
));
};
Ok(final_result)
}
pub fn sharpness_modified_laplacian(src_mat: &Mat) -> Result<f64, StackerError> {
let mut m = unsafe { Mat::new_rows_cols(1, 3, core::CV_64FC1)? };
m.set_2d::<f64>(0, 0, -1.0)?;
m.set_2d::<f64>(0, 1, 2.0)?;
m.set_2d::<f64>(0, 2, -1.0)?;
let g = imgproc::get_gaussian_kernel(3, -1.0, core::CV_64FC1)?;
let mut lx = Mat::default();
imgproc::sep_filter_2d(
src_mat,
&mut lx,
core::CV_64FC1,
&m,
&g,
core::Point::new(-1, -1),
0.0,
core::BORDER_DEFAULT,
)?;
let mut ly = Mat::default();
imgproc::sep_filter_2d(
src_mat,
&mut ly,
core::CV_64FC1,
&g,
&m,
core::Point::new(-1, -1),
0.0,
core::BORDER_DEFAULT,
)?;
let fm = (core::abs(&lx)? + core::abs(&ly)?)
.into_result()?
.to_mat()?;
Ok(*core::mean(&fm, &Mat::default())?
.0
.first()
.unwrap_or(&f64::MAX))
}
pub fn sharpness_variance_of_laplacian(src_mat: &Mat) -> Result<f64, StackerError> {
let mut lap = Mat::default();
imgproc::laplacian(
src_mat,
&mut lap,
core::CV_64FC1,
3,
1.0,
0.0,
core::BORDER_REPLICATE,
)?;
let mut mu = Mat::default();
let mut sigma = Mat::default();
opencv::core::mean_std_dev(&lap, &mut mu, &mut sigma, &Mat::default())?;
let focus_measure = sigma.at_2d::<f64>(0, 0)?;
Ok(focus_measure * focus_measure)
}
pub fn sharpness_tenengrad(src_grey_mat: &Mat, k_size: i32) -> Result<f64, StackerError> {
if ![1, 3, 5, 7].contains(&k_size) {
return Err(StackerError::InvalidParams(
"Kernel size must be 1, 3, 5, or 7".into(),
));
}
let mut gx = Mat::default();
let mut gy = Mat::default();
imgproc::sobel(
src_grey_mat,
&mut gx,
core::CV_64FC1,
1, 0, k_size,
1.0, 0.0, core::BORDER_DEFAULT,
)?;
imgproc::sobel(
src_grey_mat,
&mut gy,
core::CV_64FC1,
0, 1, k_size,
1.0,
0.0,
core::BORDER_DEFAULT,
)?;
let mut gx2 = Mat::default();
let mut gy2 = Mat::default();
core::multiply(&gx, &gx, &mut gx2, 1.0, -1)?;
core::multiply(&gy, &gy, &mut gy2, 1.0, -1)?;
let mut sum = Mat::default();
core::add(&gx2, &gy2, &mut sum, &core::no_array(), -1)?;
let mean = core::mean(&sum, &core::no_array())?;
Ok(mean[0])
}
pub fn sharpness_normalized_gray_level_variance(src_mat: &Mat) -> Result<f64, StackerError> {
let mut src_float = Mat::default();
src_mat.convert_to(&mut src_float, core::CV_64FC1, 1.0, 0.0)?;
let mut mu = Mat::default();
let mut sigma = Mat::default();
core::mean_std_dev(&src_float, &mut mu, &mut sigma, &Mat::default())?;
let variance = sigma.at_2d::<f64>(0, 0)?.powi(2);
let mu_value = mu.at_2d::<f64>(0, 0)?.max(f64::EPSILON);
Ok(variance / mu_value)
}
pub mod prelude {
pub use super::{
EccMatchParameters, KeyPointMatchParameters, MotionType, StackerError, ecc_match,
keypoint_match,
};
}