diff --git a/README.md b/README.md index 60aa3cc..5988e63 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/fov_estimator_node.py b/fov_estimator_node.py index ee839c1..a785c12 100644 --- a/fov_estimator_node.py +++ b/fov_estimator_node.py @@ -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)" }