Spaces:
Running on Zero
Running on Zero
Download shadow/guided_filter.py from eira06/depthwizard-backend: direct link, hf CLI and curl.
- Browser
- Download file 10.2 kB
-
https://huggingface.co/spaces/eira06/depthwizard-backend/resolve/main/shadow/guided_filter.py
- Command line
-
hf download hf://spaces/eira06/depthwizard-backend/shadow/guided_filter.py
-
curl -L -o guided_filter.py https://huggingface.co/spaces/eira06/depthwizard-backend/resolve/main/shadow/guided_filter.py
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 | |
| 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) | |