mod deconvolve;
mod psf;
mod removal;
mod synthesis;
pub use deconvolve::{
DeconvolutionMethod, Deconvolver, PsfPadStrategy, RichardsonLucyParams, WienerFftParams,
WienerParams,
};
pub use psf::{MotionPSF, PSFEstimator, PSFShape};
pub use removal::{DeblurMethod, DeblurQuality, MotionBlurRemover, MultiFrameDeblur};
pub use synthesis::{
AccumulationBuffer, CameraShake, MotionBlurSynthesizer, MotionTrail, PerObjectBlur, SpeedRamp,
};
use crate::error::{CvError, CvResult};
use crate::tracking::FlowField;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum QualityMode {
Fast,
#[default]
Balanced,
HighQuality,
}
#[derive(Debug, Clone, Copy)]
pub struct MotionVector {
pub dx: f32,
pub dy: f32,
pub confidence: f32,
}
impl MotionVector {
#[must_use]
pub const fn new(dx: f32, dy: f32) -> Self {
Self {
dx,
dy,
confidence: 1.0,
}
}
#[must_use]
pub const fn with_confidence(dx: f32, dy: f32, confidence: f32) -> Self {
Self { dx, dy, confidence }
}
#[must_use]
pub fn magnitude(&self) -> f32 {
(self.dx * self.dx + self.dy * self.dy).sqrt()
}
#[must_use]
pub fn direction(&self) -> f32 {
self.dy.atan2(self.dx)
}
#[must_use]
pub const fn scale(&self, factor: f32) -> Self {
Self {
dx: self.dx * factor,
dy: self.dy * factor,
confidence: self.confidence,
}
}
#[must_use]
pub fn lerp(&self, other: &Self, t: f32) -> Self {
Self {
dx: self.dx + (other.dx - self.dx) * t,
dy: self.dy + (other.dy - self.dy) * t,
confidence: self.confidence + (other.confidence - self.confidence) * t,
}
}
#[must_use]
pub fn is_negligible(&self) -> bool {
self.magnitude() < 0.5
}
}
impl Default for MotionVector {
fn default() -> Self {
Self::new(0.0, 0.0)
}
}
#[derive(Debug, Clone)]
pub struct MotionVectorField {
pub vectors: Vec<MotionVector>,
pub width: u32,
pub height: u32,
}
impl MotionVectorField {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
let size = width as usize * height as usize;
Self {
vectors: vec![MotionVector::default(); size],
width,
height,
}
}
#[must_use]
pub fn from_flow_field(flow: &FlowField) -> Self {
let mut field = Self::new(flow.width, flow.height);
for i in 0..flow.flow_x.len() {
field.vectors[i] = MotionVector::new(flow.flow_x[i], flow.flow_y[i]);
}
field
}
#[must_use]
pub fn get(&self, x: u32, y: u32) -> MotionVector {
if x >= self.width || y >= self.height {
return MotionVector::default();
}
let idx = (y * self.width + x) as usize;
if idx < self.vectors.len() {
self.vectors[idx]
} else {
MotionVector::default()
}
}
pub fn set(&mut self, x: u32, y: u32, vector: MotionVector) {
if x >= self.width || y >= self.height {
return;
}
let idx = (y * self.width + x) as usize;
if idx < self.vectors.len() {
self.vectors[idx] = vector;
}
}
#[must_use]
pub fn get_interpolated(&self, x: f32, y: f32) -> MotionVector {
let x0 = x.floor() as u32;
let y0 = y.floor() as u32;
let x1 = (x0 + 1).min(self.width - 1);
let y1 = (y0 + 1).min(self.height - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let v00 = self.get(x0, y0);
let v10 = self.get(x1, y0);
let v01 = self.get(x0, y1);
let v11 = self.get(x1, y1);
let v0 = v00.lerp(&v10, fx);
let v1 = v01.lerp(&v11, fx);
v0.lerp(&v1, fy)
}
#[must_use]
pub fn average_magnitude(&self) -> f32 {
if self.vectors.is_empty() {
return 0.0;
}
let sum: f32 = self.vectors.iter().map(MotionVector::magnitude).sum();
sum / self.vectors.len() as f32
}
#[must_use]
pub fn max_magnitude(&self) -> f32 {
self.vectors
.iter()
.map(MotionVector::magnitude)
.fold(0.0f32, f32::max)
}
pub fn scale(&mut self, factor: f32) {
for vector in &mut self.vectors {
*vector = vector.scale(factor);
}
}
pub fn median_filter(&mut self, radius: u32) {
let mut filtered = self.vectors.clone();
let rad = radius as i32;
for y in 0..self.height {
for x in 0..self.width {
let mut dx_vals = Vec::new();
let mut dy_vals = Vec::new();
for dy in -rad..=rad {
for dx in -rad..=rad {
let nx = (x as i32 + dx).max(0).min(self.width as i32 - 1) as u32;
let ny = (y as i32 + dy).max(0).min(self.height as i32 - 1) as u32;
let v = self.get(nx, ny);
dx_vals.push(v.dx);
dy_vals.push(v.dy);
}
}
dx_vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
dy_vals.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let median_idx = dx_vals.len() / 2;
let idx = (y * self.width + x) as usize;
if idx < filtered.len() {
filtered[idx].dx = dx_vals[median_idx];
filtered[idx].dy = dy_vals[median_idx];
}
}
}
self.vectors = filtered;
}
#[must_use]
pub fn separate_camera_motion(&self) -> (MotionVector, Self) {
let mut all_dx: Vec<f32> = self.vectors.iter().map(|v| v.dx).collect();
let mut all_dy: Vec<f32> = self.vectors.iter().map(|v| v.dy).collect();
all_dx.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
all_dy.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let median_idx = all_dx.len() / 2;
let camera_motion = if median_idx < all_dx.len() && median_idx < all_dy.len() {
MotionVector::new(all_dx[median_idx], all_dy[median_idx])
} else {
MotionVector::default()
};
let mut object_field = self.clone();
for vector in &mut object_field.vectors {
vector.dx -= camera_motion.dx;
vector.dy -= camera_motion.dy;
}
(camera_motion, object_field)
}
}
#[derive(Debug, Clone)]
pub struct MotionBlurConfig {
pub shutter_angle: f32,
pub samples: usize,
pub quality: QualityMode,
pub depth_aware: bool,
pub rolling_shutter: bool,
}
impl Default for MotionBlurConfig {
fn default() -> Self {
Self {
shutter_angle: 180.0,
samples: 8,
quality: QualityMode::Balanced,
depth_aware: false,
rolling_shutter: false,
}
}
}
impl MotionBlurConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_shutter_angle(mut self, angle: f32) -> Self {
self.shutter_angle = angle;
self
}
#[must_use]
pub const fn with_samples(mut self, samples: usize) -> Self {
self.samples = samples;
self
}
#[must_use]
pub const fn with_quality(mut self, quality: QualityMode) -> Self {
self.quality = quality;
self
}
#[must_use]
pub const fn with_depth_aware(mut self, enabled: bool) -> Self {
self.depth_aware = enabled;
self
}
#[must_use]
pub const fn with_rolling_shutter(mut self, enabled: bool) -> Self {
self.rolling_shutter = enabled;
self
}
pub fn validate(&self) -> CvResult<()> {
if self.shutter_angle < 0.0 || self.shutter_angle > 360.0 {
return Err(CvError::invalid_parameter(
"shutter_angle",
format!("{} (must be 0-360)", self.shutter_angle),
));
}
if self.samples == 0 {
return Err(CvError::invalid_parameter("samples", "0 (must be > 0)"));
}
if self.samples > 64 {
return Err(CvError::invalid_parameter(
"samples",
format!("{} (must be <= 64)", self.samples),
));
}
Ok(())
}
#[must_use]
pub fn motion_scale(&self) -> f32 {
self.shutter_angle / 360.0
}
}
#[derive(Debug, Clone)]
pub struct DepthMap {
pub depths: Vec<f32>,
pub width: u32,
pub height: u32,
}
impl DepthMap {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
let size = width as usize * height as usize;
Self {
depths: vec![0.5; size],
width,
height,
}
}
#[must_use]
pub fn get(&self, x: u32, y: u32) -> f32 {
if x >= self.width || y >= self.height {
return 0.5;
}
let idx = (y * self.width + x) as usize;
if idx < self.depths.len() {
self.depths[idx]
} else {
0.5
}
}
pub fn set(&mut self, x: u32, y: u32, depth: f32) {
if x >= self.width || y >= self.height {
return;
}
let idx = (y * self.width + x) as usize;
if idx < self.depths.len() {
self.depths[idx] = depth.clamp(0.0, 1.0);
}
}
#[must_use]
pub fn get_interpolated(&self, x: f32, y: f32) -> f32 {
let x0 = x.floor() as u32;
let y0 = y.floor() as u32;
let x1 = (x0 + 1).min(self.width - 1);
let y1 = (y0 + 1).min(self.height - 1);
let fx = x - x0 as f32;
let fy = y - y0 as f32;
let d00 = self.get(x0, y0);
let d10 = self.get(x1, y0);
let d01 = self.get(x0, y1);
let d11 = self.get(x1, y1);
let d0 = d00 + (d10 - d00) * fx;
let d1 = d01 + (d11 - d01) * fx;
d0 + (d1 - d0) * fy
}
}
#[derive(Debug, Clone)]
pub struct RollingShutterParams {
pub top_to_bottom: bool,
pub scan_time: f32,
pub readout_offset: f32,
}
impl Default for RollingShutterParams {
fn default() -> Self {
Self {
top_to_bottom: true,
scan_time: 1.0,
readout_offset: 0.0,
}
}
}
impl RollingShutterParams {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn time_offset(&self, y: u32, height: u32) -> f32 {
let normalized_y = if self.top_to_bottom {
y as f32 / height as f32
} else {
1.0 - (y as f32 / height as f32)
};
normalized_y * self.scan_time + self.readout_offset
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_motion_vector_new() {
let v = MotionVector::new(10.0, 20.0);
assert_eq!(v.dx, 10.0);
assert_eq!(v.dy, 20.0);
assert_eq!(v.confidence, 1.0);
}
#[test]
fn test_motion_vector_magnitude() {
let v = MotionVector::new(3.0, 4.0);
assert!((v.magnitude() - 5.0).abs() < 0.001);
}
#[test]
fn test_motion_vector_scale() {
let v = MotionVector::new(10.0, 20.0);
let scaled = v.scale(0.5);
assert_eq!(scaled.dx, 5.0);
assert_eq!(scaled.dy, 10.0);
}
#[test]
fn test_motion_vector_lerp() {
let v1 = MotionVector::new(0.0, 0.0);
let v2 = MotionVector::new(10.0, 20.0);
let lerped = v1.lerp(&v2, 0.5);
assert_eq!(lerped.dx, 5.0);
assert_eq!(lerped.dy, 10.0);
}
#[test]
fn test_motion_vector_field_new() {
let field = MotionVectorField::new(100, 100);
assert_eq!(field.width, 100);
assert_eq!(field.height, 100);
assert_eq!(field.vectors.len(), 10000);
}
#[test]
fn test_motion_vector_field_get_set() {
let mut field = MotionVectorField::new(10, 10);
let v = MotionVector::new(5.0, 10.0);
field.set(5, 5, v);
let retrieved = field.get(5, 5);
assert_eq!(retrieved.dx, 5.0);
assert_eq!(retrieved.dy, 10.0);
}
#[test]
fn test_motion_blur_config_default() {
let config = MotionBlurConfig::default();
assert_eq!(config.shutter_angle, 180.0);
assert_eq!(config.samples, 8);
}
#[test]
fn test_motion_blur_config_validate() {
let config = MotionBlurConfig::new()
.with_shutter_angle(180.0)
.with_samples(16);
assert!(config.validate().is_ok());
let bad_config = MotionBlurConfig::new().with_shutter_angle(400.0);
assert!(bad_config.validate().is_err());
}
#[test]
fn test_depth_map_new() {
let depth = DepthMap::new(100, 100);
assert_eq!(depth.width, 100);
assert_eq!(depth.height, 100);
assert_eq!(depth.depths.len(), 10000);
}
#[test]
fn test_depth_map_get_set() {
let mut depth = DepthMap::new(10, 10);
depth.set(5, 5, 0.8);
assert!((depth.get(5, 5) - 0.8).abs() < 0.001);
}
#[test]
fn test_rolling_shutter_params() {
let params = RollingShutterParams::default();
assert!(params.top_to_bottom);
let offset = params.time_offset(50, 100);
assert!((offset - 0.5).abs() < 0.001);
}
}