Skip to main content

scirs2_vision/depth_completion/
completion.rs

1//! High-level depth completion API: sparse-to-dense depth map filling.
2//!
3//! Provides `DepthCompleter`, a unified entry-point that dispatches to:
4//!
5//! * **NearestNeighbor** – BFS from sparse points to fill every empty pixel.
6//! * **InvDistWeighted** – weighted average of K nearest valid depth points
7//!   (`weight = 1/d²`).
8//! * **PropagationFill** – iterative 8-connected neighbourhood averaging until
9//!   convergence or `max_iterations`.
10//! * **SurfaceNormals** – Sobel-based normal estimation from an RGB guide,
11//!   followed by normal-integration anchored at the sparse depth points.
12//!
13//! All public items use `f32` for consistency with the rest of the vision crate.
14
15use std::collections::VecDeque;
16
17// ---------------------------------------------------------------------------
18// Public types specific to this interface
19// ---------------------------------------------------------------------------
20
21/// Depth completion method used by `DepthCompleter`.
22#[non_exhaustive]
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum DepthMethod {
25    /// BFS nearest-neighbour fill.
26    NearestNeighbor,
27    /// Inverse-distance-weighted interpolation (K = 8 nearest valid points).
28    InvDistWeighted,
29    /// Normal integration from RGB luminance gradients (needs `rgb` argument).
30    SurfaceNormals,
31    /// Iterative 8-connected propagation until convergence.
32    PropagationFill,
33}
34
35/// Configuration for `DepthCompleter`.
36#[non_exhaustive]
37#[derive(Debug, Clone)]
38pub struct DepthCompletionConfig {
39    /// Algorithm to use.
40    pub method: DepthMethod,
41    /// Maximum valid depth value; deeper readings are clamped/ignored.
42    pub max_depth: f32,
43    /// Minimum valid depth value; shallower readings are clamped/ignored.
44    pub min_depth: f32,
45    /// Maximum iterations for iterative methods (`PropagationFill`).
46    pub iterations: usize,
47}
48
49impl Default for DepthCompletionConfig {
50    fn default() -> Self {
51        Self {
52            method: DepthMethod::PropagationFill,
53            max_depth: 100.0,
54            min_depth: 0.1,
55            iterations: 5,
56        }
57    }
58}
59
60/// Result of a depth completion operation.
61pub struct DepthResult {
62    /// Dense depth map; `dense_depth[row][col]` gives the estimated depth at
63    /// that pixel. Pixels that could not be filled retain the value 0.
64    pub dense_depth: Vec<Vec<f32>>,
65    /// Per-pixel confidence in `[0, 1]`.  Sparse points receive confidence 1.0;
66    /// interpolated pixels receive a value derived from the chosen method.
67    pub confidence: Vec<Vec<f32>>,
68    /// Number of pixels that were filled (were `None` in the input but have a
69    /// non-zero value in the output).
70    pub filled_pixels: usize,
71}
72
73// ---------------------------------------------------------------------------
74// DepthCompleter
75// ---------------------------------------------------------------------------
76
77/// Depth completer: fills a sparse `Option<f32>` depth map into a dense one.
78pub struct DepthCompleter {
79    config: DepthCompletionConfig,
80}
81
82impl DepthCompleter {
83    /// Create a new depth completer with the given configuration.
84    pub fn new(config: DepthCompletionConfig) -> Self {
85        Self { config }
86    }
87
88    /// Complete a sparse depth map.
89    ///
90    /// # Arguments
91    ///
92    /// * `sparse_depth` – `sparse_depth[row][col]` is `None` where depth is
93    ///   unknown, `Some(d)` where a valid measurement exists.  All rows must
94    ///   have the same length.
95    /// * `rgb` – optional RGB guide image required for `SurfaceNormals`.
96    ///   Each pixel is `[R, G, B]` with values in `[0, 255]`.
97    ///
98    /// # Returns
99    ///
100    /// A `DepthResult` whose `dense_depth` has the same shape as `sparse_depth`.
101    pub fn complete(
102        &self,
103        sparse_depth: &[Vec<Option<f32>>],
104        rgb: Option<&[Vec<[u8; 3]>]>,
105    ) -> DepthResult {
106        if sparse_depth.is_empty() {
107            return DepthResult {
108                dense_depth: Vec::new(),
109                confidence: Vec::new(),
110                filled_pixels: 0,
111            };
112        }
113
114        let height = sparse_depth.len();
115        let width = sparse_depth[0].len();
116
117        // Initialise dense grid and confidence from sparse data.
118        let mut dense = vec![vec![0.0f32; width]; height];
119        let mut confidence = vec![vec![0.0f32; width]; height];
120
121        let min_d = self.config.min_depth;
122        let max_d = self.config.max_depth;
123
124        for r in 0..height {
125            for c in 0..width {
126                if let Some(d) = sparse_depth[r][c] {
127                    if d >= min_d && d <= max_d {
128                        dense[r][c] = d;
129                        confidence[r][c] = 1.0;
130                    }
131                }
132            }
133        }
134
135        let initial_filled = dense
136            .iter()
137            .flat_map(|row| row.iter())
138            .filter(|&&v| v > 0.0)
139            .count();
140
141        match self.config.method {
142            DepthMethod::NearestNeighbor => {
143                fill_nearest_neighbor(&mut dense, &mut confidence, height, width);
144            }
145            DepthMethod::InvDistWeighted => {
146                fill_inv_dist_weighted(&mut dense, &mut confidence, height, width);
147            }
148            DepthMethod::PropagationFill => {
149                fill_propagation(
150                    &mut dense,
151                    &mut confidence,
152                    height,
153                    width,
154                    self.config.iterations,
155                );
156            }
157            DepthMethod::SurfaceNormals => {
158                fill_surface_normals(&mut dense, &mut confidence, height, width, rgb);
159            }
160        }
161
162        // Count newly filled pixels (were 0.0 before, non-zero after).
163        let total_filled = dense
164            .iter()
165            .flat_map(|row| row.iter())
166            .filter(|&&v| v > 0.0)
167            .count();
168        let filled_pixels = total_filled.saturating_sub(initial_filled);
169
170        DepthResult {
171            dense_depth: dense,
172            confidence,
173            filled_pixels,
174        }
175    }
176}
177
178// ---------------------------------------------------------------------------
179// NearestNeighbor – BFS from all valid pixels
180// ---------------------------------------------------------------------------
181
182fn fill_nearest_neighbor(
183    dense: &mut [Vec<f32>],
184    confidence: &mut [Vec<f32>],
185    height: usize,
186    width: usize,
187) {
188    let mut queue: VecDeque<(usize, usize)> = VecDeque::new();
189    let mut dist: Vec<Vec<u32>> = vec![vec![u32::MAX; width]; height];
190
191    // Seed BFS from every known pixel.
192    for r in 0..height {
193        for c in 0..width {
194            if dense[r][c] > 0.0 {
195                queue.push_back((r, c));
196                dist[r][c] = 0;
197            }
198        }
199    }
200
201    let dirs: [(i32, i32); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
202
203    while let Some((r, c)) = queue.pop_front() {
204        let d = dist[r][c];
205        for (dr, dc) in &dirs {
206            let nr = r as i32 + dr;
207            let nc = c as i32 + dc;
208            if nr < 0 || nr >= height as i32 || nc < 0 || nc >= width as i32 {
209                continue;
210            }
211            let (nr, nc) = (nr as usize, nc as usize);
212            if dist[nr][nc] == u32::MAX {
213                dist[nr][nc] = d + 1;
214                dense[nr][nc] = dense[r][c]; // inherit from nearest seed
215                                             // Confidence decays with distance: conf = 1 / (1 + d)
216                let src_conf = confidence[r][c];
217                confidence[nr][nc] = src_conf / (1.0 + (d + 1) as f32);
218                queue.push_back((nr, nc));
219            }
220        }
221    }
222}
223
224// ---------------------------------------------------------------------------
225// InvDistWeighted – weighted average of up to K=16 nearest valid pixels
226// ---------------------------------------------------------------------------
227
228fn fill_inv_dist_weighted(
229    dense: &mut [Vec<f32>],
230    confidence: &mut [Vec<f32>],
231    height: usize,
232    width: usize,
233) {
234    // Collect positions of all valid pixels (snapshot before mutation).
235    let mut valid: Vec<(usize, usize, f32)> = Vec::new();
236    for (r, dense_row) in dense.iter().enumerate().take(height) {
237        for (c, &d) in dense_row.iter().enumerate().take(width) {
238            if d > 0.0 {
239                valid.push((r, c, d));
240            }
241        }
242    }
243
244    if valid.is_empty() {
245        return;
246    }
247
248    const K: usize = 16;
249    // Search radius in pixels: start small and expand.
250    let max_radius = (height.max(width)) as f32;
251
252    for r in 0..height {
253        if dense[r].iter().all(|&d| d > 0.0) {
254            // Row is fully filled; skip.
255            continue;
256        }
257        for c in 0..width {
258            if dense[r][c] > 0.0 {
259                continue; // already known
260            }
261
262            // Collect K nearest valid points.
263            let mut distances: Vec<(f32, f32)> = valid
264                .iter()
265                .map(|&(vr, vc, vd)| {
266                    let dr = r as f32 - vr as f32;
267                    let dc = c as f32 - vc as f32;
268                    let dist = (dr * dr + dc * dc).sqrt();
269                    (dist, vd)
270                })
271                .filter(|&(dist, _)| dist > 0.0 && dist <= max_radius)
272                .collect();
273
274            if distances.is_empty() {
275                continue;
276            }
277
278            distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
279            distances.truncate(K);
280
281            let mut wsum = 0.0f32;
282            let mut dsum = 0.0f32;
283            for (dist, depth) in &distances {
284                let w = 1.0 / (dist * dist + 1e-6);
285                wsum += w;
286                dsum += w * depth;
287            }
288
289            if wsum > 0.0 {
290                dense[r][c] = dsum / wsum;
291                // Confidence based on nearest-point distance.
292                let min_dist = distances[0].0;
293                confidence[r][c] = 1.0 / (1.0 + min_dist);
294            }
295        }
296    }
297}
298
299// ---------------------------------------------------------------------------
300// PropagationFill – iterative 8-connected neighbourhood averaging
301// ---------------------------------------------------------------------------
302
303fn fill_propagation(
304    dense: &mut [Vec<f32>],
305    confidence: &mut [Vec<f32>],
306    height: usize,
307    width: usize,
308    max_iterations: usize,
309) {
310    for _iter in 0..max_iterations {
311        let mut changed = false;
312        let old_dense = dense.to_vec();
313        let old_conf = confidence.to_vec();
314
315        for r in 0..height {
316            for c in 0..width {
317                if old_dense[r][c] > 0.0 {
318                    continue; // already filled
319                }
320                // Gather 8-connected neighbours that are filled.
321                let mut wsum = 0.0f32;
322                let mut dsum = 0.0f32;
323                let mut csum = 0.0f32;
324
325                for dr in -1i32..=1 {
326                    for dc in -1i32..=1 {
327                        if dr == 0 && dc == 0 {
328                            continue;
329                        }
330                        let nr = r as i32 + dr;
331                        let nc = c as i32 + dc;
332                        if nr < 0 || nr >= height as i32 || nc < 0 || nc >= width as i32 {
333                            continue;
334                        }
335                        let (nr, nc) = (nr as usize, nc as usize);
336                        let nd = old_dense[nr][nc];
337                        if nd > 0.0 {
338                            let w = old_conf[nr][nc].max(1e-6);
339                            wsum += w;
340                            dsum += w * nd;
341                            csum += w;
342                        }
343                    }
344                }
345
346                if wsum > 0.0 {
347                    let new_depth = dsum / wsum;
348                    // average confidence of contributing neighbours, slight decay
349                    let avg_conf = (csum / wsum.max(1e-6_f32)) * 0.9_f32;
350                    dense[r][c] = new_depth;
351                    confidence[r][c] = avg_conf;
352                    changed = true;
353                }
354            }
355        }
356
357        if !changed {
358            break;
359        }
360    }
361
362    // Depth consistency check: flag pixels whose depth differs too much from
363    // local average (relative threshold 30 %).
364    depth_consistency_check(dense, confidence, height, width);
365}
366
367/// Flag pixels whose depth deviates more than 30 % from the local 3×3 mean.
368fn depth_consistency_check(
369    dense: &mut [Vec<f32>],
370    confidence: &mut [Vec<f32>],
371    height: usize,
372    width: usize,
373) {
374    let snap = dense.to_vec();
375    for r in 0..height {
376        for c in 0..width {
377            let d = snap[r][c];
378            if d <= 0.0 {
379                continue;
380            }
381            let mut sum = 0.0f32;
382            let mut cnt = 0usize;
383            for dr in -1i32..=1 {
384                for dc in -1i32..=1 {
385                    let nr = r as i32 + dr;
386                    let nc = c as i32 + dc;
387                    if nr < 0 || nr >= height as i32 || nc < 0 || nc >= width as i32 {
388                        continue;
389                    }
390                    let nd = snap[nr as usize][nc as usize];
391                    if nd > 0.0 {
392                        sum += nd;
393                        cnt += 1;
394                    }
395                }
396            }
397            if cnt > 1 {
398                let mean = sum / cnt as f32;
399                let rel_diff = (d - mean).abs() / mean.max(1e-6);
400                if rel_diff > 0.30 {
401                    // Reduce confidence proportionally.
402                    confidence[r][c] *= (1.0_f32 - rel_diff).max(0.0);
403                }
404            }
405        }
406    }
407}
408
409// ---------------------------------------------------------------------------
410// SurfaceNormals – Sobel gradients + normal integration
411// ---------------------------------------------------------------------------
412
413fn fill_surface_normals(
414    dense: &mut [Vec<f32>],
415    confidence: &mut [Vec<f32>],
416    height: usize,
417    width: usize,
418    rgb: Option<&[Vec<[u8; 3]>]>,
419) {
420    // 1. Compute luminance from RGB (or use a uniform gradient if no RGB given).
421    let lum: Vec<Vec<f32>> = match rgb {
422        Some(img) if img.len() == height => img
423            .iter()
424            .map(|row| {
425                row.iter()
426                    .map(|&[r, g, b]| {
427                        // BT.601 luminance
428                        0.299 * r as f32 / 255.0
429                            + 0.587 * g as f32 / 255.0
430                            + 0.114 * b as f32 / 255.0
431                    })
432                    .collect()
433            })
434            .collect(),
435        _ => vec![vec![0.5f32; width]; height],
436    };
437
438    // 2. Sobel gradients (Gx, Gy) of luminance.
439    let mut gx = vec![vec![0.0f32; width]; height];
440    let mut gy = vec![vec![0.0f32; width]; height];
441
442    for r in 1..(height.saturating_sub(1)) {
443        for c in 1..(width.saturating_sub(1)) {
444            gx[r][c] = -lum[r - 1][c - 1] - 2.0 * lum[r][c - 1] - lum[r + 1][c - 1]
445                + lum[r - 1][c + 1]
446                + 2.0 * lum[r][c + 1]
447                + lum[r + 1][c + 1];
448            gy[r][c] = -lum[r - 1][c - 1] - 2.0 * lum[r - 1][c] - lum[r - 1][c + 1]
449                + lum[r + 1][c - 1]
450                + 2.0 * lum[r + 1][c]
451                + lum[r + 1][c + 1];
452        }
453    }
454
455    // 3. Compute average depth from sparse anchors.
456    let mut total_d = 0.0f32;
457    let mut anchor_cnt = 0usize;
458    for dense_row in dense.iter().take(height) {
459        for &d in dense_row.iter().take(width) {
460            if d > 0.0 {
461                total_d += d;
462                anchor_cnt += 1;
463            }
464        }
465    }
466    let anchor_mean = if anchor_cnt > 0 {
467        total_d / anchor_cnt as f32
468    } else {
469        1.0
470    };
471
472    // 4. Integrate normals to produce a smooth depth surface.
473    //    We use a simplified Frankot-Chellappa scheme: iterative Poisson solve.
474    let mut integrated = vec![vec![anchor_mean; width]; height];
475
476    // Copy anchor depths.
477    for r in 0..height {
478        for c in 0..width {
479            if dense[r][c] > 0.0 {
480                integrated[r][c] = dense[r][c];
481            }
482        }
483    }
484
485    // Poisson iterations.
486    let n_iter = 10usize;
487    for _ in 0..n_iter {
488        let prev = integrated.clone();
489        for r in 1..(height.saturating_sub(1)) {
490            for c in 1..(width.saturating_sub(1)) {
491                if dense[r][c] > 0.0 {
492                    continue; // anchor: do not modify
493                }
494                // Discrete divergence of the gradient field.
495                let lap = prev[r - 1][c] + prev[r + 1][c] + prev[r][c - 1] + prev[r][c + 1]
496                    - 4.0 * prev[r][c];
497                let rhs = gx[r][c] + gy[r][c];
498                integrated[r][c] = prev[r][c] + 0.25 * (lap - rhs);
499                integrated[r][c] = integrated[r][c].max(0.0);
500            }
501        }
502    }
503
504    // 5. Write integrated depth into output wherever it was empty.
505    for r in 0..height {
506        for c in 0..width {
507            if dense[r][c] <= 0.0 {
508                let d = integrated[r][c];
509                if d > 0.0 {
510                    dense[r][c] = d;
511                    // Confidence based on gradient magnitude (strong edge → lower confidence).
512                    let grad_mag = (gx[r][c] * gx[r][c] + gy[r][c] * gy[r][c]).sqrt();
513                    confidence[r][c] = 1.0 / (1.0 + grad_mag);
514                }
515            }
516        }
517    }
518}
519
520// ---------------------------------------------------------------------------
521// Helper: apply bilateral filter to a dense depth map
522// ---------------------------------------------------------------------------
523
524/// Apply a joint bilateral filter to `depth` using spatial sigma
525/// `sigma_space` and depth-range sigma `sigma_depth`.
526///
527/// Pixels with depth ≤ 0 are treated as invalid and are not used as filter
528/// centres, but can be filled by valid neighbours.
529pub fn apply_bilateral_filter(
530    depth: &[Vec<f32>],
531    sigma_space: f32,
532    sigma_depth: f32,
533) -> Vec<Vec<f32>> {
534    let height = depth.len();
535    if height == 0 {
536        return Vec::new();
537    }
538    let width = depth[0].len();
539    let radius = (2.0 * sigma_space).ceil() as usize;
540    let mut out = depth.to_vec();
541
542    for r in 0..height {
543        for c in 0..width {
544            let centre = depth[r][c];
545            if centre <= 0.0 {
546                continue;
547            }
548
549            let mut wsum = 0.0f32;
550            let mut dsum = 0.0f32;
551
552            let r0 = r.saturating_sub(radius);
553            let r1 = (r + radius + 1).min(height);
554            let c0 = c.saturating_sub(radius);
555            let c1 = (c + radius + 1).min(width);
556
557            for (nr, depth_row) in depth.iter().enumerate().take(r1).skip(r0) {
558                for (nc, &nd) in depth_row.iter().enumerate().take(c1).skip(c0) {
559                    if nd <= 0.0 {
560                        continue;
561                    }
562                    let dr = (r as f32 - nr as f32) / sigma_space;
563                    let dc = (c as f32 - nc as f32) / sigma_space;
564                    let dd = (centre - nd) / sigma_depth;
565                    let w = (-(dr * dr + dc * dc + dd * dd) * 0.5).exp();
566                    wsum += w;
567                    dsum += w * nd;
568                }
569            }
570
571            if wsum > 0.0 {
572                out[r][c] = dsum / wsum;
573            }
574        }
575    }
576
577    out
578}
579
580// ---------------------------------------------------------------------------
581// Helper: morphological dilation-based hole filling
582// ---------------------------------------------------------------------------
583
584/// Fill holes in `depth` using 3×3 morphological dilation.
585///
586/// Pixels with depth ≤ 0 are considered holes.  Dilation is applied once:
587/// each empty pixel receives the maximum depth value of its 3×3 neighbourhood.
588pub fn fill_holes_morphological(depth: &[Vec<f32>]) -> Vec<Vec<f32>> {
589    let height = depth.len();
590    if height == 0 {
591        return Vec::new();
592    }
593    let width = depth[0].len();
594    let mut out = depth.to_vec();
595
596    for r in 0..height {
597        for c in 0..width {
598            if depth[r][c] > 0.0 {
599                continue; // not a hole
600            }
601            let mut max_d = 0.0f32;
602            for dr in -1i32..=1 {
603                for dc in -1i32..=1 {
604                    let nr = r as i32 + dr;
605                    let nc = c as i32 + dc;
606                    if nr < 0 || nr >= height as i32 || nc < 0 || nc >= width as i32 {
607                        continue;
608                    }
609                    max_d = max_d.max(depth[nr as usize][nc as usize]);
610                }
611            }
612            if max_d > 0.0 {
613                out[r][c] = max_d;
614            }
615        }
616    }
617    out
618}