use crate::error::{CvError, CvResult};
use crate::interpolate::optical_flow::FlowField;
#[derive(Debug, Clone)]
pub struct OcclusionMap {
pub forward_occluded: Vec<bool>,
pub backward_occluded: Vec<bool>,
pub width: u32,
pub height: u32,
}
impl OcclusionMap {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
let size = width as usize * height as usize;
Self {
forward_occluded: vec![false; size],
backward_occluded: vec![false; size],
width,
height,
}
}
#[must_use]
pub fn is_occluded_forward(&self, x: u32, y: u32) -> bool {
if x >= self.width || y >= self.height {
return false;
}
let idx = (y * self.width + x) as usize;
idx < self.forward_occluded.len() && self.forward_occluded[idx]
}
#[must_use]
pub fn is_occluded_backward(&self, x: u32, y: u32) -> bool {
if x >= self.width || y >= self.height {
return false;
}
let idx = (y * self.width + x) as usize;
idx < self.backward_occluded.len() && self.backward_occluded[idx]
}
pub fn set_forward_occluded(&mut self, x: u32, y: u32, occluded: bool) {
if x >= self.width || y >= self.height {
return;
}
let idx = (y * self.width + x) as usize;
if idx < self.forward_occluded.len() {
self.forward_occluded[idx] = occluded;
}
}
pub fn set_backward_occluded(&mut self, x: u32, y: u32, occluded: bool) {
if x >= self.width || y >= self.height {
return;
}
let idx = (y * self.width + x) as usize;
if idx < self.backward_occluded.len() {
self.backward_occluded[idx] = occluded;
}
}
#[must_use]
pub fn forward_occlusion_percentage(&self) -> f32 {
let count = self.forward_occluded.iter().filter(|&&x| x).count();
count as f32 / self.forward_occluded.len() as f32 * 100.0
}
#[must_use]
pub fn backward_occlusion_percentage(&self) -> f32 {
let count = self.backward_occluded.iter().filter(|&&x| x).count();
count as f32 / self.backward_occluded.len() as f32 * 100.0
}
pub fn dilate(&mut self, radius: u32) {
let dilated_forward = self.dilate_mask(&self.forward_occluded, radius);
let dilated_backward = self.dilate_mask(&self.backward_occluded, radius);
self.forward_occluded = dilated_forward;
self.backward_occluded = dilated_backward;
}
fn dilate_mask(&self, mask: &[bool], radius: u32) -> Vec<bool> {
let mut result = vec![false; mask.len()];
let r = radius as i32;
for y in 0..self.height {
for x in 0..self.width {
let idx = (y * self.width + x) as usize;
let mut is_dilated = false;
for dy in -r..=r {
for dx in -r..=r {
let nx = x as i32 + dx;
let ny = y as i32 + dy;
if nx >= 0 && nx < self.width as i32 && ny >= 0 && ny < self.height as i32 {
let nidx = (ny as u32 * self.width + nx as u32) as usize;
if nidx < mask.len() && mask[nidx] {
is_dilated = true;
break;
}
}
}
if is_dilated {
break;
}
}
result[idx] = is_dilated;
}
}
result
}
}
pub struct OcclusionDetector {
consistency_threshold: f32,
enable_dilation: bool,
dilation_radius: u32,
}
impl OcclusionDetector {
#[must_use]
pub fn new() -> Self {
Self {
consistency_threshold: 1.0,
enable_dilation: true,
dilation_radius: 1,
}
}
#[must_use]
pub const fn with_threshold(mut self, threshold: f32) -> Self {
self.consistency_threshold = threshold;
self
}
#[must_use]
pub const fn with_dilation(mut self, enabled: bool, radius: u32) -> Self {
self.enable_dilation = enabled;
self.dilation_radius = radius;
self
}
pub fn detect(
&self,
flow_forward: &FlowField,
flow_backward: &FlowField,
width: u32,
height: u32,
) -> CvResult<OcclusionMap> {
if flow_forward.width != width || flow_forward.height != height {
return Err(CvError::invalid_dimensions(
flow_forward.width,
flow_forward.height,
));
}
if flow_backward.width != width || flow_backward.height != height {
return Err(CvError::invalid_dimensions(
flow_backward.width,
flow_backward.height,
));
}
let mut occlusion_map = OcclusionMap::new(width, height);
for y in 0..height {
for x in 0..width {
let forward_occluded = self.check_forward_consistency(
flow_forward,
flow_backward,
x,
y,
width,
height,
);
occlusion_map.set_forward_occluded(x, y, forward_occluded);
let backward_occluded = self.check_backward_consistency(
flow_forward,
flow_backward,
x,
y,
width,
height,
);
occlusion_map.set_backward_occluded(x, y, backward_occluded);
}
}
if self.enable_dilation {
occlusion_map.dilate(self.dilation_radius);
}
Ok(occlusion_map)
}
fn check_forward_consistency(
&self,
flow_forward: &FlowField,
flow_backward: &FlowField,
x: u32,
y: u32,
width: u32,
height: u32,
) -> bool {
let (dx_fwd, dy_fwd) = flow_forward.get(x, y);
let x2 = (x as f32 + dx_fwd).round() as i32;
let y2 = (y as f32 + dy_fwd).round() as i32;
if x2 < 0 || x2 >= width as i32 || y2 < 0 || y2 >= height as i32 {
return true; }
let (dx_bwd, dy_bwd) = flow_backward.get(x2 as u32, y2 as u32);
let consistency_error = ((dx_fwd + dx_bwd).powi(2) + (dy_fwd + dy_bwd).powi(2)).sqrt();
consistency_error > self.consistency_threshold
}
fn check_backward_consistency(
&self,
flow_forward: &FlowField,
flow_backward: &FlowField,
x: u32,
y: u32,
width: u32,
height: u32,
) -> bool {
let (dx_bwd, dy_bwd) = flow_backward.get(x, y);
let x1 = (x as f32 + dx_bwd).round() as i32;
let y1 = (y as f32 + dy_bwd).round() as i32;
if x1 < 0 || x1 >= width as i32 || y1 < 0 || y1 >= height as i32 {
return true; }
let (dx_fwd, dy_fwd) = flow_forward.get(x1 as u32, y1 as u32);
let consistency_error = ((dx_bwd + dx_fwd).powi(2) + (dy_bwd + dy_fwd).powi(2)).sqrt();
consistency_error > self.consistency_threshold
}
}
impl Default for OcclusionDetector {
fn default() -> Self {
Self::new()
}
}