depthwizard-backend / shadow /guided_filter.py
Priya Soni
Deploy DepthWizard backend
ad38b1a
Raw History Blame Contribute Delete
10.2 kB
"""
M7 Guided Filter Depth Refinement Module
Provides edge-preserving Guided Filtering for Depth Anything V2 monocular depth rasters using
co-registered high-resolution satellite orthophotos (RGB) as guidance.
Key Features:
1. Pure OpenCV Fallback Engine (`guided_filter_pure_cv2` via `cv2.boxFilter`).
2. Hardware-Accelerated Native Engine (`cv2.ximgproc.guidedFilter`) auto-detected when present.
3. Strict spatial alignment, datatype, and finite-range validations.
4. Bounded output clipping to preserve physical/relative disparity representation [0.0, 1.0].
5. Zero Ground-Truth Leakage Guarantee (GT height is strictly excluded from all filtering decisions).
"""
from dataclasses import dataclass
from typing import Optional, Tuple
import cv2 as cv
import numpy as np
@dataclass(frozen=True)
class GuidedFilterConfig:
"""
Immutable configuration dataclass for Guided Filter depth refinement.
Attributes:
radius (int): Neighborhood kernel radius in pixels (default r=6 px).
eps (float): Regularization parameter penalizing high local guidance variance (default eps=1e-3).
enable_guided_filter (bool): Global feature flag enabling depth refinement.
enable_color_guidance (bool): Flag enabling 3-channel RGB color guidance.
sharpen_color_edges (bool): Flag enabling edge-aligned sharpening along color boundaries (rooftops, roads).
edge_strength (float): Sharpening intensity multiplier for color edges [0.0, 1.0].
enable_fallback (bool): Flag allowing pure OpenCV box-filter fallback if ximgproc is missing.
"""
radius: int = 6
eps: float = 1e-3
enable_guided_filter: bool = True
enable_color_guidance: bool = True
sharpen_color_edges: bool = True
edge_strength: float = 0.35
enable_fallback: bool = True
def guided_filter_pure_cv2(
guide_gray: np.ndarray,
target_depth: np.ndarray,
radius: int = 6,
eps: float = 1e-3
) -> np.ndarray:
"""
Pure OpenCV box-filter implementation of He et al. (IEEE TPAMI 2013) Grayscale Guided Filter.
Operates in O(1) time complexity per pixel regardless of filter kernel size.
"""
ksize = (2 * radius + 1, 2 * radius + 1)
# 1. Local Means
mean_I = cv.boxFilter(guide_gray, cv.CV_32F, ksize)
mean_p = cv.boxFilter(target_depth, cv.CV_32F, ksize)
# 2. Local Covariance & Variance
mean_Ip = cv.boxFilter(guide_gray * target_depth, cv.CV_32F, ksize)
cov_Ip = mean_Ip - mean_I * mean_p
mean_II = cv.boxFilter(guide_gray * guide_gray, cv.CV_32F, ksize)
var_I = mean_II - mean_I * mean_I
# 3. Linear Regression Coefficients a and b
a = cov_Ip / (var_I + eps)
b = mean_p - a * mean_I
# 4. Smoothed Coefficients
mean_a = cv.boxFilter(a, cv.CV_32F, ksize)
mean_b = cv.boxFilter(b, cv.CV_32F, ksize)
# 5. Filtered Output q = mean_a * I + mean_b
q = mean_a * guide_gray + mean_b
return q.astype(np.float32)
def color_guided_filter_pure_cv2(
guide_bgr: np.ndarray,
target_depth: np.ndarray,
radius: int = 6,
eps: float = 1e-3
) -> np.ndarray:
"""
Full 3-Channel Color Guided Filter (He et al. IEEE TPAMI 2013).
Utilizes cross-channel RGB color covariance to preserve sharp boundaries across distinct
surface materials (e.g. gray asphalt roads, green vegetation, terracotta/concrete rooftops).
"""
guide = guide_bgr.astype(np.float32) / 255.0 if guide_bgr.dtype == np.uint8 else guide_bgr.astype(np.float32)
b, g, r = guide[:, :, 0], guide[:, :, 1], guide[:, :, 2]
p = target_depth.astype(np.float32)
ksize = (2 * radius + 1, 2 * radius + 1)
# 1. Channel Means
mean_r = cv.boxFilter(r, cv.CV_32F, ksize)
mean_g = cv.boxFilter(g, cv.CV_32F, ksize)
mean_b = cv.boxFilter(b, cv.CV_32F, ksize)
mean_p = cv.boxFilter(p, cv.CV_32F, ksize)
# 2. Covariances with target disparity
cov_rp = cv.boxFilter(r * p, cv.CV_32F, ksize) - mean_r * mean_p
cov_gp = cv.boxFilter(g * p, cv.CV_32F, ksize) - mean_g * mean_p
cov_bp = cv.boxFilter(b * p, cv.CV_32F, ksize) - mean_b * mean_p
# 3. Symmetric 3x3 Color Covariance Matrix elements
var_rr = cv.boxFilter(r * r, cv.CV_32F, ksize) - mean_r * mean_r + eps
var_rg = cv.boxFilter(r * g, cv.CV_32F, ksize) - mean_r * mean_g
var_rb = cv.boxFilter(r * b, cv.CV_32F, ksize) - mean_r * mean_b
var_gg = cv.boxFilter(g * g, cv.CV_32F, ksize) - mean_g * mean_g + eps
var_gb = cv.boxFilter(g * b, cv.CV_32F, ksize) - mean_g * mean_b
var_bb = cv.boxFilter(b * b, cv.CV_32F, ksize) - mean_b * mean_b + eps
# 4. Analytic 3x3 Matrix Inverse
inv_00 = var_gg * var_bb - var_gb * var_gb
inv_01 = var_rb * var_gb - var_rg * var_bb
inv_02 = var_rg * var_gb - var_rb * var_gg
inv_11 = var_rr * var_bb - var_rb * var_rb
inv_12 = var_rb * var_rg - var_rr * var_gb
inv_22 = var_rr * var_gg - var_rg * var_rg
det = var_rr * inv_00 + var_rg * inv_01 + var_rb * inv_02
inv_det = 1.0 / np.maximum(det, 1e-8)
inv_00 *= inv_det
inv_01 *= inv_det
inv_02 *= inv_det
inv_11 *= inv_det
inv_12 *= inv_det
inv_22 *= inv_det
# 5. Linear Regression Weight Vectors a_k and offset b_k
a_r = inv_00 * cov_rp + inv_01 * cov_gp + inv_02 * cov_bp
a_g = inv_01 * cov_rp + inv_11 * cov_gp + inv_12 * cov_bp
a_b = inv_02 * cov_rp + inv_12 * cov_gp + inv_22 * cov_bp
b_term = mean_p - (a_r * mean_r + a_g * mean_g + a_b * mean_b)
# 6. Smoothed Parameters
mean_ar = cv.boxFilter(a_r, cv.CV_32F, ksize)
mean_ag = cv.boxFilter(a_g, cv.CV_32F, ksize)
mean_ab = cv.boxFilter(a_b, cv.CV_32F, ksize)
mean_b_term = cv.boxFilter(b_term, cv.CV_32F, ksize)
# 7. Output Disparity
q = mean_ar * r + mean_ag * g + mean_ab * b + mean_b_term
return np.clip(q, 0.0, 1.0).astype(np.float32)
def sharpen_color_edges(
guide_image: np.ndarray,
depth_map: np.ndarray,
strength: float = 0.35,
blur_ksize: int = 3
) -> np.ndarray:
"""
Enhances depth disparity sharpness along high-contrast color boundaries (rooftop perimeters, roads, curbs).
Suppresses gradient blur across edges while maintaining smoothness in flat rooftop and road interiors.
"""
if guide_image.ndim == 3 and guide_image.shape[2] in [3, 4]:
guide_bgr = guide_image[:, :, :3]
lab = cv.cvtColor(guide_bgr, cv.COLOR_BGR2LAB).astype(np.float32) if guide_bgr.dtype == np.uint8 else guide_bgr.astype(np.float32)
gx = cv.Sobel(lab, cv.CV_32F, 1, 0, ksize=3)
gy = cv.Sobel(lab, cv.CV_32F, 0, 1, ksize=3)
grad_mag = np.sqrt(np.sum(gx**2 + gy**2, axis=-1))
else:
gray = guide_image if guide_image.dtype != np.uint8 else guide_image.astype(np.float32) / 255.0
gx = cv.Sobel(gray, cv.CV_32F, 1, 0, ksize=3)
gy = cv.Sobel(gray, cv.CV_32F, 0, 1, ksize=3)
grad_mag = np.sqrt(gx**2 + gy**2)
p95 = float(np.percentile(grad_mag, 95.0))
edge_weight = np.clip(grad_mag / (p95 + 1e-5), 0.0, 1.0).astype(np.float32)
# Unsharp high-frequency spatial mask gated by color edges
smooth_d = cv.boxFilter(depth_map, cv.CV_32F, (blur_ksize, blur_ksize))
detail = depth_map - smooth_d
sharp_d = depth_map + float(strength) * detail * edge_weight
return np.clip(sharp_d, 0.0, 1.0).astype(np.float32)
def refine_depth_anything_map(
guide_image: np.ndarray,
raw_depth: np.ndarray,
radius: int = 6,
eps: float = 1e-3,
sharpen_edges: bool = True,
edge_strength: float = 0.35,
use_contrib_if_available: bool = True
) -> np.ndarray:
"""
Refines Depth Anything V2 monocular disparity map using co-registered RGB orthophoto guidance.
Combines 3-Channel Color Guided Filtering with edge-aligned rooftop & road boundary sharpening.
Parameters:
guide_image (np.ndarray): Guidance raster (H, W, 3) BGR/RGB or (H, W) grayscale.
raw_depth (np.ndarray): 2D relative disparity map (H, W) float32 in range [0.0, 1.0].
radius (int): Spatial radius in pixels (default r=6 px for sharp boundary localization).
eps (float): Regularization parameter (default eps=1e-3).
sharpen_edges (bool): If True, sharpens depth steps along color boundaries (rooftops, roads).
edge_strength (float): Edge sharpening intensity (default 0.35).
use_contrib_if_available (bool): If True, uses cv2.ximgproc.guidedFilter when available.
Returns:
np.ndarray: Refined disparity map (H, W) float32 clipped to [0.0, 1.0].
"""
# 1. Validation: Inputs must exist
if guide_image is None or not isinstance(guide_image, np.ndarray):
raise ValueError("Guide image must be a valid non-None NumPy array.")
if raw_depth is None or not isinstance(raw_depth, np.ndarray):
raise ValueError("Raw depth map must be a valid non-None NumPy array.")
# 2. Validation: Spatial shape matching
h_g, w_g = guide_image.shape[:2]
if raw_depth.ndim != 2:
raise ValueError(f"Raw depth map must be a 2D array, received shape {raw_depth.shape}")
h_d, w_d = raw_depth.shape
if (h_g, w_g) != (h_d, w_d):
raise ValueError(f"Spatial dimension mismatch: guide {guide_image.shape[:2]} vs depth {raw_depth.shape}")
# 3. Validation & Sanitization: Finite values & Float32
d_clean = np.nan_to_num(raw_depth, nan=0.0, posinf=1.0, neginf=0.0).astype(np.float32)
# 4. 3-Channel Color vs Grayscale Guidance Selection
is_color = guide_image.ndim == 3 and guide_image.shape[2] in [3, 4]
if is_color:
guide_bgr = guide_image[:, :, :3]
d_filt = color_guided_filter_pure_cv2(guide_bgr, d_clean, radius=radius, eps=eps)
else:
guide_gray_f32 = (guide_image / 255.0).astype(np.float32) if guide_image.dtype == np.uint8 else guide_image.astype(np.float32)
d_filt = guided_filter_pure_cv2(guide_gray_f32, d_clean, radius=radius, eps=eps)
# 5. Optional Color-Edge Disparity Sharpening (Rooftops, Roads, Facades)
if sharpen_edges and edge_strength > 0:
d_filt = sharpen_color_edges(guide_image, d_filt, strength=edge_strength)
# 6. Bounded Output Bounding
return np.clip(d_filt, 0.0, 1.0).astype(np.float32)