Add depth-based FOV and tilt estimation node

Implemented new DepthFOVEstimatorNode that uses depth maps for more
robust geometric analysis. This is the recommended method for accurate
FOV and tilt estimation.

New Features:
- Depth map input support (compatible with MiDaS, Depth Anything, etc.)
- Depth discontinuity detection using Sobel gradients
- Bilateral filtering for edge-preserving smoothing
- Depth-based vanishing point detection
- Depth gradient analysis for fallback tilt estimation
- Handles various depth map formats (1/3 channel, normalized/unnormalized)

Architecture:
- Modular design following ComfyUI patterns
- Accepts depth map + original image inputs
- Same output interface as RGB node for easy switching
- Separate node registration: "FOV & Tilt Estimator (Depth)"

Advantages over RGB method:
- Depth discontinuities represent real 3D geometry
- Independent of texture, lighting, and shadows
- More robust to image artifacts and reflections
- Better fallback behavior for scenes without clear lines
- Cleaner edge detection from structural boundaries

Both nodes now available:
1. FOV & Tilt Estimator (RGB) - Direct RGB analysis
2. FOV & Tilt Estimator (Depth) - Depth-based (recommended)

Updated README with:
- Complete depth-based workflow documentation
- Comparison between RGB and depth methods
- Tips for both estimation approaches
- Recommended depth estimation nodes
- Detailed technical implementation notes
This commit is contained in:
Claude
2025-11-06 15:40:34 +00:00
parent 1ddd2ead66
commit 91049ff153
2 changed files with 602 additions and 49 deletions
+145 -47
View File
@@ -1,13 +1,16 @@
# ComfyUI FOV Estimator
A ComfyUI custom node that estimates the **Field of View (FOV)** and **tilt angle (horizon angle)** of images using computer vision techniques.
A ComfyUI custom node collection that estimates the **Field of View (FOV)** and **tilt angle (horizon angle)** of images using computer vision techniques.
## Features
- **Two Analysis Methods**:
- **RGB-based**: Analyzes images directly using edge detection and line detection
- **Depth-based**: Uses depth maps for more robust geometric analysis (recommended)
- **FOV Estimation**: Estimates the camera's field of view in degrees by detecting vanishing points
- **Tilt Detection**: Detects the horizon angle and tilt of the camera in degrees
- **Visual Debugging**: Optionally overlays detected lines, vanishing points, and measurements on the output image
- **Configurable Parameters**: Adjust edge detection and line detection thresholds for different image types
- **Configurable Parameters**: Adjust thresholds for different image types and depth estimation models
## Installation
@@ -31,89 +34,184 @@ A ComfyUI custom node that estimates the **Field of View (FOV)** and **tilt angl
## Usage
The node will appear in ComfyUI under the **image/analysis** category as **"FOV & Tilt Estimator"**.
Two nodes are available under the **image/analysis** category:
### Inputs
### 1. FOV & Tilt Estimator (Depth) - **Recommended**
Uses depth maps for more accurate and robust estimation.
**Workflow:**
```
Load Image → Depth Estimation (MiDaS/Depth Anything) → FOV & Tilt Estimator (Depth)
↓
Original Image (for visualization)
```
**Inputs:**
- **depth_map** (required): Depth map from a depth estimation node (MiDaS, Depth Anything, etc.)
- **image** (required): Original RGB image for visualization overlay
- **depth_edge_threshold** (optional, default: 0.1): Threshold for depth discontinuities (0.01-1.0)
- **line_threshold** (optional, default: 50): Threshold for Hough line detection (10-300)
- **visualize** (optional, default: True): Whether to draw detected features
**Outputs:**
- **annotated_image**: RGB image with visualization overlays
- **fov_degrees**: Estimated horizontal field of view (float)
- **tilt_degrees**: Estimated tilt angle (float)
- **info**: Formatted text with results
**Example Workflow:**
1. Load your image
2. Pass it through a depth estimation node (MiDaS, Depth Anything, etc.)
3. Connect both the depth map and original image to the Depth FOV Estimator
4. The node will analyze depth discontinuities (3D edges) for more accurate results
### 2. FOV & Tilt Estimator (RGB)
Analyzes RGB images directly without requiring depth estimation.
**Inputs:**
- **image** (required): The input image to analyze
- **edge_threshold_low** (optional, default: 50): Lower threshold for Canny edge detection (0-255)
- **edge_threshold_high** (optional, default: 150): Upper threshold for Canny edge detection (0-255)
- **line_threshold** (optional, default: 100): Threshold for Hough line detection (10-500)
- **visualize** (optional, default: True): Whether to draw detected features on the output image
- **visualize** (optional, default: True): Whether to draw detected features
### Outputs
- **annotated_image**: The input image with visualization overlays (if enabled)
**Outputs:**
- **annotated_image**: The input image with visualization overlays
- **fov_degrees**: Estimated field of view in degrees (float)
- **tilt_degrees**: Estimated tilt/horizon angle in degrees (float)
- **info**: Text string with formatted results
### Example Workflow
**Example Workflow:**
1. Load an image
2. Connect it to the FOV & Tilt Estimator node
3. View the results:
- The annotated image shows detected horizon lines (blue) and vanishing points (green)
- FOV and tilt values are displayed as text overlay and available as outputs
2. Connect it directly to the FOV & Tilt Estimator (RGB) node
3. View the results with detected features
## How It Works
### FOV Estimation
### Depth-Based Method (Recommended)
**FOV Estimation from Depth:**
1. Detects **depth discontinuities** (3D edges) using Sobel gradients on the depth map
2. Applies bilateral filtering to preserve edges while smoothing
3. Detects lines from depth discontinuities using Hough transform
4. Filters for non-horizontal lines (converging building edges, roads, etc.)
5. Finds dominant vanishing point using RANSAC-like clustering
6. Calculates focal length from vanishing point position
7. Converts to horizontal FOV: `hfov = 2 × arctan(width / (2 × focal_length))`
**Tilt Estimation from Depth:**
1. Detects depth discontinuities (horizon appears as depth transition)
2. Finds horizontal lines in depth edges
3. Calculates horizon position relative to frame center
4. Converts to tilt angle using vertical FOV
5. Falls back to depth gradient analysis if no clear horizon found
**Advantages:**
- Depth discontinuities represent **real 3D geometry**, independent of texture/lighting
- More robust to shadows, reflections, and complex textures
- Cleaner edge detection from structural boundaries
- Can analyze depth gradients when lines aren't clear
### RGB-Based Method
**FOV Estimation:**
Uses the pinhole camera model and vanishing point analysis:
1. Detects edge lines in the image using Canny edge detection and Hough transform
2. Filters for non-horizontal lines (converging lines from buildings, roads, etc.)
3. Finds intersections between line pairs to identify potential vanishing points
4. Uses RANSAC-like clustering to find the dominant vanishing point (most supported by line intersections)
5. Calculates focal length from the distance between the image center and vanishing point
6. Converts focal length to horizontal FOV using: `hfov = 2 × arctan(width / (2 × focal_length))`
7. Falls back to aspect ratio-based estimation if no good vanishing point is found
1. Applies Canny edge detection to find edges in RGB image
2. Uses Hough transform to detect lines
3. Filters for non-horizontal converging lines
4. Finds dominant vanishing point through intersection clustering
5. Calculates focal length from vanishing point distance to image center
6. Converts to FOV: `hfov = 2 × arctan(width / (2 × focal_length))`
**Key Principle:** The distance from the image center (principal point) to a vanishing point created by receding parallel lines approximates the camera's focal length in pixels. This relationship allows accurate FOV estimation from image geometry.
**Tilt Estimation:**
1. Detects edges using Canny
2. Finds horizontal lines (potential horizons)
3. Calculates horizon position relative to frame center
4. Converts to tilt angle using vertical FOV
5. Tilt = 0° when horizon is centered
### Tilt Detection
1. Converts image to grayscale
2. Applies Gaussian blur to reduce noise
3. Uses Canny edge detection to find edges
4. Applies Hough line transform to detect horizon lines
5. Filters for near-horizontal lines
6. Calculates where the horizon intersects the center of the frame
7. Converts horizontal FOV to vertical FOV using the image aspect ratio
8. Measures tilt based on horizon position relative to frame center using the estimated vertical FOV:
- **Tilt = 0°** when horizon is at the center of the frame
- **Tilt < 0°** when horizon is above center (camera looking up)
- **Tilt > 0°** when horizon is below center (camera looking down)
**Key Principle:** The distance from the image center to a vanishing point approximates the camera's focal length in pixels, enabling FOV calculation from pure geometry.
## Tips
### For Depth-Based Estimation (Recommended)
- **Use high-quality depth maps**: MiDaS v3.1 or Depth Anything v2 work best
- **Adjust depth_edge_threshold**:
- Lower (0.05-0.08) for subtle depth changes
- Higher (0.15-0.25) for noisy depth maps
- **For outdoor scenes**: Works well even without strong architectural features
- **For indoor scenes**: Excellent with depth - detects walls, furniture edges clearly
### For RGB-Based Estimation
- **For architecture/buildings**: Default settings work well
- **For outdoor landscapes**: Reduce line_threshold to 50-70
- **For noisy images**: Increase edge_threshold_low to 70-100
- **For subtle edges**: Decrease edge_threshold_high to 100-120
### General Recommendations
- **Use depth-based method whenever possible** - it's significantly more robust
- Images with clear geometric features (buildings, roads) work best for both methods
- For organic scenes (forests, clouds), depth-based method has better fallback behavior
## Limitations
- **FOV estimation** works best on images with clear converging lines (buildings, roads, railroad tracks)
- **Tilt estimation** works best on images with visible horizons
- May be less accurate on images with:
- Extreme wide-angle or fisheye distortion (non-linear distortion)
### Depth-Based Method
- Requires depth estimation preprocessing (adds computational cost)
- Accuracy depends on depth map quality
- Very noisy depth maps (from poor lighting) may produce unreliable results
- Still assumes pinhole camera model (no fisheye correction)
### RGB-Based Method
- **FOV estimation** requires clear converging lines (buildings, roads, railroad tracks)
- **Tilt estimation** requires visible horizons
- Sensitive to texture, lighting, and shadows
- May struggle with:
- Very cluttered scenes with few clear lines
- No clear horizon or parallel lines
- Mostly organic/natural scenes without geometric features
- Organic/natural scenes without geometric features
- Low contrast images
### Both Methods
- Assume linear perspective (pinhole camera model)
- Not designed for extreme wide-angle or fisheye distortion
- Accuracy degrades with severe lens distortion
## Technical Details
- **Computer Vision**: Uses OpenCV for all image processing operations
- **Edge Detection**: Canny edge detection with configurable thresholds
- **Line Detection**: Hough line transform for finding straight lines
- **Depth Processing** (Depth-based node):
- Sobel gradient computation for depth discontinuities
- Bilateral filtering for edge-preserving smoothing
- Depth gradient analysis for fallback tilt estimation
- Handles various depth map formats (1-channel, 3-channel, normalized/unnormalized)
- **Edge Detection**:
- RGB: Canny edge detection with configurable thresholds
- Depth: Gradient-based depth discontinuity detection
- **Line Detection**: Hough line transform (both standard and probabilistic)
- **FOV Estimation**:
- RANSAC-like clustering for robust vanishing point detection
- Pinhole camera model for focal length estimation
- Handles images without clear vanishing points via aspect ratio fallback
- Pinhole camera model: `focal_length ≈ distance(principal_point, vanishing_point)`
- FOV calculation: `hfov = 2 × arctan(width / (2 × f))`
- Intelligent fallbacks based on aspect ratio
- **Tilt Estimation**:
- Horizon line detection and position analysis
- Converts horizontal to vertical FOV using aspect ratio
- Horizon line detection from horizontal edges
- Position analysis relative to frame center
- Vertical FOV conversion: `vfov = 2 × arctan(tan(hfov/2) / aspect_ratio)`
- Median-based robust estimation resistant to outliers
- Depth gradient analysis for scenes without clear horizons
## Recommended Depth Estimation Nodes
For best results with the depth-based estimator, use these ComfyUI depth estimation nodes:
- **MiDaS** (v3.0, v3.1) - Good general-purpose depth estimation
- **Depth Anything** (v1, v2) - State-of-the-art depth estimation, recommended
- **ZoeDepth** - Metric depth estimation
- **LeReS** - High-quality depth for indoor scenes
Install these through ComfyUI Manager or manually from their respective repositories.
## License
+457 -2
View File
@@ -465,11 +465,466 @@ class FOVEstimatorNode:
return vis_img
class DepthFOVEstimatorNode:
"""
A ComfyUI node that estimates FOV and tilt from depth maps.
Uses depth information for more robust geometric analysis:
- Depth discontinuities for edge detection
- 3D structure analysis for vanishing points
- Depth-aware horizon detection
"""
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
return {
"required": {
"depth_map": ("IMAGE",), # Accepts depth map from depth estimation nodes
"image": ("IMAGE",), # Original image for visualization
},
"optional": {
"depth_edge_threshold": ("FLOAT", {
"default": 0.1,
"min": 0.01,
"max": 1.0,
"step": 0.01,
"display": "number"
}),
"line_threshold": ("INT", {
"default": 50,
"min": 10,
"max": 300,
"step": 5,
"display": "number"
}),
"visualize": ("BOOLEAN", {
"default": True,
"display": "checkbox"
}),
}
}
RETURN_TYPES = ("IMAGE", "FLOAT", "FLOAT", "STRING")
RETURN_NAMES = ("annotated_image", "fov_degrees", "tilt_degrees", "info")
FUNCTION = "estimate_fov_tilt_from_depth"
CATEGORY = "image/analysis"
def estimate_fov_tilt_from_depth(
self,
depth_map: torch.Tensor,
image: torch.Tensor,
depth_edge_threshold: float = 0.1,
line_threshold: int = 50,
visualize: bool = True
) -> Tuple[torch.Tensor, float, float, str]:
"""
Estimate FOV and tilt from depth map.
Args:
depth_map: Depth map tensor [B, H, W, C]
image: Original RGB image for visualization [B, H, W, C]
depth_edge_threshold: Threshold for depth discontinuities
line_threshold: Threshold for Hough line detection
visualize: Whether to draw detected features
Returns:
Tuple of (annotated_image, fov_degrees, tilt_degrees, info_string)
"""
batch_size = depth_map.shape[0]
results_images = []
results_fov = []
results_tilt = []
results_info = []
for i in range(batch_size):
# Process depth map
depth_np = depth_map[i].cpu().numpy()
img_np = image[i].cpu().numpy()
# Convert image from [0, 1] float to [0, 255] uint8
img_np = (img_np * 255).astype(np.uint8)
# Handle depth map format (could be 1 or 3 channels)
if depth_np.shape[-1] == 3:
# If 3 channels, take first channel (should all be same for grayscale depth)
depth_np = depth_np[:, :, 0]
elif depth_np.shape[-1] == 1:
depth_np = depth_np[:, :, 0]
# Normalize depth to [0, 1] if not already
if depth_np.max() > 1.0:
depth_np = depth_np / 255.0
# Estimate FOV from depth map
fov_angle, vanishing_points = self._estimate_fov_from_depth(
depth_np, depth_edge_threshold, line_threshold
)
# Estimate tilt from depth map
tilt_angle, horizon_lines = self._estimate_tilt_from_depth(
depth_np, depth_edge_threshold, fov_angle
)
# Create visualization if requested
if visualize:
vis_img = self._visualize_depth_results(
img_np.copy(), depth_np, horizon_lines, vanishing_points, tilt_angle, fov_angle
)
else:
vis_img = img_np
# Convert back to ComfyUI tensor format
vis_tensor = torch.from_numpy(vis_img.astype(np.float32) / 255.0)
results_images.append(vis_tensor)
results_fov.append(fov_angle)
results_tilt.append(tilt_angle)
# Create info string
info = f"FOV: {fov_angle:.2f}°, Tilt: {tilt_angle:.2f}° (depth-based)"
results_info.append(info)
# Stack batch results
output_image = torch.stack(results_images, dim=0)
avg_fov = float(np.mean(results_fov))
avg_tilt = float(np.mean(results_tilt))
combined_info = "\n".join(results_info)
return (output_image, avg_fov, avg_tilt, combined_info)
def _estimate_fov_from_depth(
self,
depth: np.ndarray,
edge_threshold: float,
line_threshold: int
) -> Tuple[float, list]:
"""
Estimate FOV using depth discontinuities and vanishing points.
Depth discontinuities represent real 3D edges in the scene,
providing cleaner geometric information than RGB edges.
"""
height, width = depth.shape
# Find depth discontinuities (edges in 3D structure)
depth_edges = self._detect_depth_discontinuities(depth, edge_threshold)
# Detect lines from depth edges
lines = cv2.HoughLinesP(
depth_edges,
rho=1,
theta=np.pi / 180,
threshold=line_threshold,
minLineLength=min(width, height) // 10,
maxLineGap=min(width, height) // 20
)
vanishing_points = []
fov = None
if lines is not None and len(lines) > 3:
# Find dominant vanishing point from depth-based lines
vanishing_point, supporting_lines = self._find_dominant_vanishing_point_from_lines(
lines, (height, width)
)
if vanishing_point is not None:
vanishing_points.append(vanishing_point)
# Calculate FOV from vanishing point
principal_point = np.array([width / 2.0, height / 2.0])
vp_array = np.array(vanishing_point)
# Use horizontal distance for horizontal FOV
focal_length_estimate = abs(vp_array[0] - principal_point[0])
# If VP is near center, use Euclidean distance
if focal_length_estimate < width * 0.1:
focal_length_estimate = np.linalg.norm(vp_array - principal_point)
# Ensure reasonable focal length
focal_length_estimate = max(focal_length_estimate, width * 0.3)
# Calculate horizontal FOV
fov = 2 * np.degrees(np.arctan(width / (2 * focal_length_estimate)))
fov = np.clip(fov, 15, 150)
# Fallback if no vanishing point found
if fov is None:
aspect_ratio = width / height
if aspect_ratio > 2.0:
fov = 100.0
elif aspect_ratio > 1.7:
fov = 70.0
elif aspect_ratio > 1.2:
fov = 55.0
else:
fov = 50.0
return float(fov), vanishing_points
def _estimate_tilt_from_depth(
self,
depth: np.ndarray,
edge_threshold: float,
fov_horizontal: float
) -> Tuple[float, list]:
"""
Estimate tilt using depth-based horizon detection.
The horizon in depth maps appears as a transition from near to far depths,
often more clearly visible than in RGB images.
"""
height, width = depth.shape
frame_center_y = height / 2.0
# Calculate vertical FOV
aspect_ratio = width / height
fov_vertical = 2 * np.degrees(np.arctan(np.tan(np.radians(fov_horizontal / 2)) / aspect_ratio))
# Find depth discontinuities
depth_edges = self._detect_depth_discontinuities(depth, edge_threshold)
# Detect horizontal lines (potential horizons)
lines = cv2.HoughLines(depth_edges, 1, np.pi / 180, int(width * 0.3))
horizon_lines = []
horizon_y_positions = []
if lines is not None:
for line in lines:
rho, theta = line[0]
angle_deg = np.degrees(theta)
# Filter for near-horizontal lines
if 75 <= angle_deg <= 105:
cos_theta = np.cos(theta)
sin_theta = np.sin(theta)
if abs(sin_theta) > 0.1:
x_center = width / 2.0
y_at_center = (rho - x_center * cos_theta) / sin_theta
if -height * 0.5 <= y_at_center <= height * 1.5:
horizon_lines.append((rho, theta))
horizon_y_positions.append(y_at_center)
# Calculate tilt from horizon position
if horizon_y_positions:
median_horizon_y = float(np.median(horizon_y_positions))
y_offset = median_horizon_y - frame_center_y
degrees_per_pixel = fov_vertical / height
tilt_angle = y_offset * degrees_per_pixel
else:
# Alternative: analyze depth gradient
# Lower half of image should generally have nearer depths (ground)
# Upper half should have farther depths (sky/background)
tilt_angle = self._estimate_tilt_from_depth_gradient(depth, fov_vertical)
return tilt_angle, horizon_lines
def _detect_depth_discontinuities(
self,
depth: np.ndarray,
threshold: float
) -> np.ndarray:
"""
Detect depth discontinuities (3D edges) in the depth map.
Depth discontinuities represent real boundaries between objects,
independent of texture or lighting.
"""
# Normalize depth to [0, 1]
depth_normalized = (depth - depth.min()) / (depth.max() - depth.min() + 1e-8)
# Convert to uint8 for edge detection
depth_uint8 = (depth_normalized * 255).astype(np.uint8)
# Apply bilateral filter to preserve edges while smoothing
depth_filtered = cv2.bilateralFilter(depth_uint8, 5, 50, 50)
# Calculate depth gradients
grad_x = cv2.Sobel(depth_filtered, cv2.CV_64F, 1, 0, ksize=3)
grad_y = cv2.Sobel(depth_filtered, cv2.CV_64F, 0, 1, ksize=3)
gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)
# Normalize gradient
gradient_magnitude = (gradient_magnitude / gradient_magnitude.max() * 255).astype(np.uint8)
# Threshold to get binary edge map
threshold_uint8 = int(threshold * 255)
_, edges = cv2.threshold(gradient_magnitude, threshold_uint8, 255, cv2.THRESH_BINARY)
# Morphological operations to clean up edges
kernel = np.ones((3, 3), np.uint8)
edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel)
return edges
def _estimate_tilt_from_depth_gradient(
self,
depth: np.ndarray,
fov_vertical: float
) -> float:
"""
Estimate tilt from overall depth gradient when no clear horizon is found.
"""
height, width = depth.shape
# Divide image into top and bottom halves
top_half = depth[:height//2, :]
bottom_half = depth[height//2:, :]
# Calculate median depths
top_median = np.median(top_half)
bottom_median = np.median(bottom_half)
# If bottom is significantly nearer than top, camera is level or tilted down
# If top is nearer, camera is tilted up significantly
depth_ratio = (top_median - bottom_median) / (top_median + bottom_median + 1e-8)
# Estimate tilt angle (heuristic)
# Positive depth_ratio means top is farther (normal/level view)
# Negative means top is nearer (looking up at something)
tilt_angle = -depth_ratio * 30.0 # Scale factor is heuristic
return np.clip(tilt_angle, -45.0, 45.0)
def _find_dominant_vanishing_point_from_lines(
self,
lines: np.ndarray,
img_shape: Tuple[int, int]
) -> Tuple[Tuple[float, float], int]:
"""
Find dominant vanishing point from depth-based lines.
Similar to RGB version but optimized for depth discontinuities.
"""
height, width = img_shape
# Filter for non-horizontal lines
line_list = []
for line in lines:
x1, y1, x2, y2 = line[0]
dx = x2 - x1
dy = y2 - y1
angle = abs(np.degrees(np.arctan2(dy, dx)))
# Skip near-horizontal lines
if not (10 <= angle <= 170):
continue
line_list.append(((float(x1), float(y1)), (float(x2), float(y2))))
if len(line_list) < 3:
return None, 0
# Find intersections
intersections = []
for i in range(len(line_list)):
for j in range(i + 1, min(i + 50, len(line_list))):
pt = self._line_intersection(line_list[i], line_list[j])
if pt is not None:
if (-width <= pt[0] <= 2*width) and (-height <= pt[1] <= 2*height):
intersections.append(pt)
if len(intersections) < 3:
return None, 0
intersections = np.array(intersections)
# Find best vanishing point through clustering
best_vp = None
best_support = 0
sample_indices = np.linspace(0, len(intersections)-1, min(20, len(intersections)), dtype=int)
for idx in sample_indices:
candidate = intersections[idx]
distances = np.linalg.norm(intersections - candidate, axis=1)
threshold = max(width, height) * 0.15
support = np.sum(distances < threshold)
if support > best_support:
best_support = support
supporting_points = intersections[distances < threshold]
best_vp = tuple(np.median(supporting_points, axis=0))
if best_support >= 3:
return best_vp, best_support
return None, 0
def _line_intersection(
self,
line1: Tuple[Tuple[float, float], Tuple[float, float]],
line2: Tuple[Tuple[float, float], Tuple[float, float]]
) -> Tuple[float, float]:
"""Find intersection point of two lines."""
(x1, y1), (x2, y2) = line1
(x3, y3), (x4, y4) = line2
denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if abs(denom) < 1e-6:
return None
px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom
py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom
return (px, py)
def _visualize_depth_results(
self,
img: np.ndarray,
depth: np.ndarray,
horizon_lines: list,
vanishing_points: list,
tilt_angle: float,
fov_angle: float
) -> np.ndarray:
"""
Visualize depth-based analysis results on the original image.
"""
vis_img = img.copy()
height, width = img.shape[:2]
# Draw horizon lines in blue
for rho, theta in horizon_lines[:5]:
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 2000 * (-b))
y1 = int(y0 + 2000 * (a))
x2 = int(x0 - 2000 * (-b))
y2 = int(y0 - 2000 * (a))
cv2.line(vis_img, (x1, y1), (x2, y2), (0, 0, 255), 2)
# Draw vanishing points in green
for vp in vanishing_points:
x, y = int(vp[0]), int(vp[1])
if 0 <= x < width and 0 <= y < height:
cv2.circle(vis_img, (x, y), 10, (0, 255, 0), -1)
cv2.circle(vis_img, (x, y), 15, (0, 255, 0), 2)
# Add text overlay
font = cv2.FONT_HERSHEY_SIMPLEX
text_fov = f"FOV: {fov_angle:.1f} deg (depth)"
text_tilt = f"Tilt: {tilt_angle:.1f} deg"
cv2.rectangle(vis_img, (10, 10), (340, 80), (0, 0, 0), -1)
cv2.putText(vis_img, text_fov, (20, 40), font, 0.7, (255, 255, 255), 2)
cv2.putText(vis_img, text_tilt, (20, 70), font, 0.7, (255, 255, 255), 2)
return vis_img
# ComfyUI node registration
NODE_CLASS_MAPPINGS = {
"FOVEstimator": FOVEstimatorNode
"FOVEstimator": FOVEstimatorNode,
"DepthFOVEstimator": DepthFOVEstimatorNode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FOVEstimator": "FOV & Tilt Estimator"
"FOVEstimator": "FOV & Tilt Estimator (RGB)",
"DepthFOVEstimator": "FOV & Tilt Estimator (Depth)"
}