MogensR commited on
Commit
8f982d3
·
1 Parent(s): a074475

Create edge.py

Browse files
Files changed (1) hide show
  1. edge.py +660 -0
edge.py ADDED
@@ -0,0 +1,660 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Professional Edge Detection & Refinement Module
3
+ ==============================================
4
+
5
+ This module provides advanced edge detection, refinement, and processing
6
+ specifically optimized for hair segmentation in video processing pipelines.
7
+
8
+ Features:
9
+ - Multi-scale edge detection
10
+ - Hair-specific edge refinement
11
+ - Temporal edge consistency
12
+ - Sub-pixel edge accuracy
13
+ - GPU-accelerated processing
14
+
15
+ Author: Your Project
16
+ License: MIT
17
+ """
18
+
19
+ import os
20
+ import cv2
21
+ import numpy as np
22
+ import logging
23
+ from typing import Dict, List, Tuple, Optional, Union
24
+ from dataclasses import dataclass
25
+ from enum import Enum
26
+ import time
27
+
28
+ try:
29
+ import torch
30
+ import torch.nn.functional as F
31
+ TORCH_AVAILABLE = True
32
+ except ImportError:
33
+ TORCH_AVAILABLE = False
34
+ logging.warning("PyTorch not available - using CPU-only edge detection")
35
+
36
+ # Configure logging
37
+ logging.basicConfig(level=logging.INFO)
38
+ logger = logging.getLogger(__name__)
39
+
40
+ class EdgeDetectionMethod(Enum):
41
+ """Available edge detection methods"""
42
+ CANNY = "canny"
43
+ SOBEL = "sobel"
44
+ LAPLACIAN = "laplacian"
45
+ SCHARR = "scharr"
46
+ PREWITT = "prewitt"
47
+ ROBERTS = "roberts"
48
+ MULTISCALE = "multiscale"
49
+ HAIR_OPTIMIZED = "hair_optimized"
50
+
51
+ @dataclass
52
+ class EdgeDetectionResult:
53
+ """Result container for edge detection"""
54
+ edges: np.ndarray
55
+ confidence_map: np.ndarray
56
+ edge_strength: float
57
+ processing_time: float
58
+ method_used: str
59
+ quality_score: float
60
+
61
+ class EdgeQualityMetrics:
62
+ """Calculate edge quality metrics"""
63
+
64
+ @staticmethod
65
+ def calculate_edge_strength(edges: np.ndarray) -> float:
66
+ """Calculate overall edge strength"""
67
+ return np.mean(edges[edges > 0]) if np.any(edges > 0) else 0.0
68
+
69
+ @staticmethod
70
+ def calculate_edge_density(edges: np.ndarray) -> float:
71
+ """Calculate edge density (ratio of edge pixels)"""
72
+ return np.sum(edges > 0) / edges.size
73
+
74
+ @staticmethod
75
+ def calculate_edge_continuity(edges: np.ndarray) -> float:
76
+ """Calculate edge continuity score"""
77
+ # Use morphological operations to measure continuity
78
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
79
+ dilated = cv2.dilate(edges, kernel, iterations=1)
80
+ eroded = cv2.erode(dilated, kernel, iterations=1)
81
+
82
+ # Continuity is measured by how much structure is preserved
83
+ original_pixels = np.sum(edges > 0)
84
+ preserved_pixels = np.sum(eroded > 0)
85
+
86
+ return preserved_pixels / max(original_pixels, 1)
87
+
88
+ @staticmethod
89
+ def calculate_edge_thickness_variation(edges: np.ndarray) -> float:
90
+ """Calculate variation in edge thickness"""
91
+ # Use distance transform to measure edge thickness
92
+ dist_transform = cv2.distanceTransform(
93
+ (edges > 0).astype(np.uint8),
94
+ cv2.DIST_L2,
95
+ 5
96
+ )
97
+
98
+ edge_pixels = edges > 0
99
+ if not np.any(edge_pixels):
100
+ return 0.0
101
+
102
+ thicknesses = dist_transform[edge_pixels]
103
+ return np.std(thicknesses) / (np.mean(thicknesses) + 1e-6)
104
+
105
+ @staticmethod
106
+ def calculate_overall_quality(edges: np.ndarray) -> float:
107
+ """Calculate overall edge quality score"""
108
+ strength = EdgeQualityMetrics.calculate_edge_strength(edges)
109
+ density = EdgeQualityMetrics.calculate_edge_density(edges)
110
+ continuity = EdgeQualityMetrics.calculate_edge_continuity(edges)
111
+ thickness_var = EdgeQualityMetrics.calculate_edge_thickness_variation(edges)
112
+
113
+ # Combine metrics (lower thickness variation is better)
114
+ quality = (
115
+ strength * 0.3 +
116
+ density * 0.2 +
117
+ continuity * 0.4 +
118
+ (1.0 - min(thickness_var, 1.0)) * 0.1
119
+ )
120
+
121
+ return min(quality, 1.0)
122
+
123
+ class BaseEdgeDetector:
124
+ """Base class for edge detectors"""
125
+
126
+ def __init__(self, name: str):
127
+ self.name = name
128
+
129
+ def detect(self, image: np.ndarray, **kwargs) -> np.ndarray:
130
+ """Detect edges in image"""
131
+ raise NotImplementedError
132
+
133
+ def get_default_params(self) -> Dict:
134
+ """Get default parameters"""
135
+ return {}
136
+
137
+ class CannyEdgeDetector(BaseEdgeDetector):
138
+ """Canny edge detector with adaptive thresholds"""
139
+
140
+ def __init__(self):
141
+ super().__init__("Canny")
142
+
143
+ def detect(self, image: np.ndarray, **kwargs) -> np.ndarray:
144
+ """Detect edges using Canny"""
145
+ # Convert to grayscale if needed
146
+ if len(image.shape) == 3:
147
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
148
+ else:
149
+ gray = image
150
+
151
+ # Adaptive threshold calculation
152
+ low_threshold = kwargs.get('low_threshold', None)
153
+ high_threshold = kwargs.get('high_threshold', None)
154
+
155
+ if low_threshold is None or high_threshold is None:
156
+ # Calculate adaptive thresholds using Otsu's method
157
+ _, otsu_thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
158
+ low_threshold = 0.5 * otsu_thresh
159
+ high_threshold = otsu_thresh
160
+
161
+ # Apply Gaussian blur
162
+ blur_kernel = kwargs.get('blur_kernel', 5)
163
+ if blur_kernel > 0:
164
+ gray = cv2.GaussianBlur(gray, (blur_kernel, blur_kernel), 0)
165
+
166
+ # Detect edges
167
+ edges = cv2.Canny(
168
+ gray,
169
+ int(low_threshold),
170
+ int(high_threshold),
171
+ apertureSize=kwargs.get('aperture_size', 3),
172
+ L2gradient=kwargs.get('l2_gradient', False)
173
+ )
174
+
175
+ return edges.astype(np.float32) / 255.0
176
+
177
+ def get_default_params(self) -> Dict:
178
+ return {
179
+ 'low_threshold': None,
180
+ 'high_threshold': None,
181
+ 'blur_kernel': 5,
182
+ 'aperture_size': 3,
183
+ 'l2_gradient': False
184
+ }
185
+
186
+ class HairOptimizedEdgeDetector(BaseEdgeDetector):
187
+ """Hair-specific edge detection optimized for fine details"""
188
+
189
+ def __init__(self):
190
+ super().__init__("HairOptimized")
191
+
192
+ def detect(self, image: np.ndarray, **kwargs) -> np.ndarray:
193
+ """Detect hair edges using multi-scale approach"""
194
+ if len(image.shape) == 3:
195
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
196
+ else:
197
+ gray = image
198
+
199
+ # Multi-scale edge detection
200
+ scales = kwargs.get('scales', [1.0, 0.7, 1.4])
201
+ edge_maps = []
202
+
203
+ for scale in scales:
204
+ # Resize image
205
+ if scale != 1.0:
206
+ h, w = gray.shape
207
+ new_h, new_w = int(h * scale), int(w * scale)
208
+ scaled_gray = cv2.resize(gray, (new_w, new_h))
209
+ else:
210
+ scaled_gray = gray
211
+
212
+ # Detect edges at this scale
213
+ scale_edges = self._detect_single_scale(scaled_gray, **kwargs)
214
+
215
+ # Resize back to original size
216
+ if scale != 1.0:
217
+ scale_edges = cv2.resize(scale_edges, (gray.shape[1], gray.shape[0]))
218
+
219
+ edge_maps.append(scale_edges)
220
+
221
+ # Combine edge maps
222
+ combined_edges = self._combine_edge_maps(edge_maps)
223
+
224
+ # Hair-specific post-processing
225
+ refined_edges = self._hair_specific_refinement(combined_edges, gray)
226
+
227
+ return refined_edges
228
+
229
+ def _detect_single_scale(self, gray: np.ndarray, **kwargs) -> np.ndarray:
230
+ """Detect edges at single scale"""
231
+ # Use multiple gradient operators
232
+ sobel_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
233
+ sobel_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
234
+ sobel_magnitude = np.sqrt(sobel_x**2 + sobel_y**2)
235
+
236
+ # Scharr operator for better fine detail detection
237
+ scharr_x = cv2.Scharr(gray, cv2.CV_64F, 1, 0)
238
+ scharr_y = cv2.Scharr(gray, cv2.CV_64F, 0, 1)
239
+ scharr_magnitude = np.sqrt(scharr_x**2 + scharr_y**2)
240
+
241
+ # Combine operators
242
+ combined = 0.6 * sobel_magnitude + 0.4 * scharr_magnitude
243
+
244
+ # Normalize
245
+ combined = combined / (np.max(combined) + 1e-6)
246
+
247
+ return combined.astype(np.float32)
248
+
249
+ def _combine_edge_maps(self, edge_maps: List[np.ndarray]) -> np.ndarray:
250
+ """Combine multiple edge maps"""
251
+ # Weighted combination - give more weight to original scale
252
+ weights = [0.5, 0.25, 0.25] # Adjust based on scales
253
+
254
+ combined = np.zeros_like(edge_maps[0])
255
+ for edge_map, weight in zip(edge_maps, weights):
256
+ combined += edge_map * weight
257
+
258
+ return combined
259
+
260
+ def _hair_specific_refinement(self, edges: np.ndarray, original: np.ndarray) -> np.ndarray:
261
+ """Apply hair-specific refinements"""
262
+ # Enhance thin structures (hair strands)
263
+ kernel_thin = np.array([[-1, -1, -1],
264
+ [ 2, 2, 2],
265
+ [-1, -1, -1]]) / 3.0
266
+
267
+ thin_enhanced = cv2.filter2D(edges, -1, kernel_thin)
268
+
269
+ # Combine with original edges
270
+ refined = 0.7 * edges + 0.3 * np.abs(thin_enhanced)
271
+
272
+ # Apply non-maximum suppression for thin edges
273
+ refined = self._thin_edge_nms(refined)
274
+
275
+ return refined
276
+
277
+ def _thin_edge_nms(self, edges: np.ndarray) -> np.ndarray:
278
+ """Non-maximum suppression optimized for thin edges"""
279
+ # Simple 3x3 NMS
280
+ kernel = np.ones((3, 3), np.uint8)
281
+ dilated = cv2.dilate(edges, kernel, iterations=1)
282
+
283
+ # Keep only local maxima
284
+ nms_edges = np.where(edges == dilated, edges, 0)
285
+
286
+ return nms_edges
287
+
288
+ class MultiScaleEdgeDetector(BaseEdgeDetector):
289
+ """Multi-scale edge detection with scale fusion"""
290
+
291
+ def __init__(self):
292
+ super().__init__("MultiScale")
293
+
294
+ def detect(self, image: np.ndarray, **kwargs) -> np.ndarray:
295
+ """Multi-scale edge detection"""
296
+ if len(image.shape) == 3:
297
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
298
+ else:
299
+ gray = image
300
+
301
+ scales = kwargs.get('scales', [0.5, 1.0, 1.5, 2.0])
302
+ sigma_base = kwargs.get('sigma_base', 1.0)
303
+
304
+ edge_pyramid = []
305
+
306
+ for scale in scales:
307
+ # Calculate sigma for this scale
308
+ sigma = sigma_base * scale
309
+
310
+ # Apply Gaussian blur
311
+ blurred = cv2.GaussianBlur(gray, (0, 0), sigma)
312
+
313
+ # Detect edges
314
+ edges = cv2.Canny(
315
+ blurred,
316
+ int(50 / scale), # Adaptive thresholds
317
+ int(150 / scale),
318
+ apertureSize=3
319
+ )
320
+
321
+ edge_pyramid.append(edges.astype(np.float32) / 255.0)
322
+
323
+ # Combine scales with weighted fusion
324
+ weights = np.array([0.1, 0.4, 0.3, 0.2]) # Favor middle scales
325
+ combined_edges = np.zeros_like(edge_pyramid[0])
326
+
327
+ for edges, weight in zip(edge_pyramid, weights):
328
+ combined_edges += edges * weight
329
+
330
+ return combined_edges
331
+
332
+ class GPUEdgeDetector(BaseEdgeDetector):
333
+ """GPU-accelerated edge detection using PyTorch"""
334
+
335
+ def __init__(self):
336
+ super().__init__("GPU")
337
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
338
+
339
+ if not TORCH_AVAILABLE:
340
+ logger.warning("PyTorch not available - GPU edge detection disabled")
341
+
342
+ def detect(self, image: np.ndarray, **kwargs) -> np.ndarray:
343
+ """GPU-accelerated edge detection"""
344
+ if not TORCH_AVAILABLE:
345
+ # Fallback to CPU Canny
346
+ detector = CannyEdgeDetector()
347
+ return detector.detect(image, **kwargs)
348
+
349
+ # Convert to tensor
350
+ if len(image.shape) == 3:
351
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
352
+ else:
353
+ gray = image
354
+
355
+ tensor = torch.from_numpy(gray).float().unsqueeze(0).unsqueeze(0).to(self.device)
356
+ tensor = tensor / 255.0
357
+
358
+ # Sobel operators
359
+ sobel_x = torch.tensor([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=torch.float32).view(1, 1, 3, 3).to(self.device)
360
+ sobel_y = torch.tensor([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=torch.float32).view(1, 1, 3, 3).to(self.device)
361
+
362
+ # Apply convolutions
363
+ grad_x = F.conv2d(tensor, sobel_x, padding=1)
364
+ grad_y = F.conv2d(tensor, sobel_y, padding=1)
365
+
366
+ # Calculate magnitude
367
+ magnitude = torch.sqrt(grad_x**2 + grad_y**2)
368
+
369
+ # Apply threshold
370
+ threshold = kwargs.get('threshold', 0.1)
371
+ edges = (magnitude > threshold).float()
372
+
373
+ # Convert back to numpy
374
+ result = edges.squeeze().cpu().numpy()
375
+
376
+ return result
377
+
378
+ class TemporalEdgeConsistency:
379
+ """Ensure temporal consistency in edge detection across frames"""
380
+
381
+ def __init__(self, memory_frames: int = 3, consistency_threshold: float = 0.1):
382
+ self.memory_frames = memory_frames
383
+ self.consistency_threshold = consistency_threshold
384
+ self.frame_buffer = []
385
+
386
+ def apply_temporal_consistency(self, current_edges: np.ndarray) -> np.ndarray:
387
+ """Apply temporal consistency to current frame edges"""
388
+ if len(self.frame_buffer) == 0:
389
+ # First frame - just store and return
390
+ self.frame_buffer.append(current_edges.copy())
391
+ return current_edges
392
+
393
+ # Calculate consistency with previous frames
394
+ consistent_edges = self._calculate_consistent_edges(current_edges)
395
+
396
+ # Update buffer
397
+ self.frame_buffer.append(current_edges.copy())
398
+ if len(self.frame_buffer) > self.memory_frames:
399
+ self.frame_buffer.pop(0)
400
+
401
+ return consistent_edges
402
+
403
+ def _calculate_consistent_edges(self, current_edges: np.ndarray) -> np.ndarray:
404
+ """Calculate temporally consistent edges"""
405
+ # Weight recent frames more heavily
406
+ weights = np.linspace(0.1, 0.9, len(self.frame_buffer))
407
+ weights = weights / np.sum(weights)
408
+
409
+ # Create weighted average of previous frames
410
+ avg_previous = np.zeros_like(current_edges)
411
+ for frame, weight in zip(self.frame_buffer, weights):
412
+ avg_previous += frame * weight
413
+
414
+ # Blend current with historical average
415
+ consistency_factor = 0.3 # How much to blend with history
416
+ blended_edges = (1 - consistency_factor) * current_edges + consistency_factor * avg_previous
417
+
418
+ return blended_edges
419
+
420
+ class EdgeRefinementProcessor:
421
+ """Post-process edges for better quality"""
422
+
423
+ @staticmethod
424
+ def remove_noise(edges: np.ndarray, min_area: int = 10) -> np.ndarray:
425
+ """Remove small noise components"""
426
+ # Find connected components
427
+ edges_uint8 = (edges * 255).astype(np.uint8)
428
+ num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(edges_uint8, connectivity=8)
429
+
430
+ # Filter by area
431
+ filtered_edges = np.zeros_like(edges)
432
+ for i in range(1, num_labels): # Skip background (label 0)
433
+ area = stats[i, cv2.CC_STAT_AREA]
434
+ if area >= min_area:
435
+ filtered_edges[labels == i] = edges[labels == i]
436
+
437
+ return filtered_edges
438
+
439
+ @staticmethod
440
+ def smooth_edges(edges: np.ndarray, iterations: int = 1) -> np.ndarray:
441
+ """Smooth edges while preserving structure"""
442
+ smoothed = edges.copy()
443
+
444
+ for _ in range(iterations):
445
+ # Apply gentle Gaussian smoothing
446
+ smoothed = cv2.GaussianBlur(smoothed, (3, 3), 0.5)
447
+
448
+ return smoothed
449
+
450
+ @staticmethod
451
+ def enhance_hair_edges(edges: np.ndarray, original_image: np.ndarray) -> np.ndarray:
452
+ """Enhance edges specifically for hair"""
453
+ # Convert original to grayscale if needed
454
+ if len(original_image.shape) == 3:
455
+ gray = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY)
456
+ else:
457
+ gray = original_image
458
+
459
+ # Use structure tensor to find hair-like structures
460
+ # Calculate gradients
461
+ grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
462
+ grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
463
+
464
+ # Structure tensor components
465
+ J11 = cv2.GaussianBlur(grad_x * grad_x, (5, 5), 1.0)
466
+ J22 = cv2.GaussianBlur(grad_y * grad_y, (5, 5), 1.0)
467
+ J12 = cv2.GaussianBlur(grad_x * grad_y, (5, 5), 1.0)
468
+
469
+ # Calculate coherence (measure of linear structure)
470
+ trace = J11 + J22
471
+ det = J11 * J22 - J12 * J12
472
+
473
+ # Avoid division by zero
474
+ coherence = np.divide(
475
+ (trace - 2 * np.sqrt(det + 1e-6))**2,
476
+ (trace + 1e-6)**2,
477
+ out=np.zeros_like(trace),
478
+ where=(trace + 1e-6) != 0
479
+ )
480
+
481
+ # Normalize coherence
482
+ coherence = coherence / (np.max(coherence) + 1e-6)
483
+
484
+ # Enhance edges where coherence is high (linear structures like hair)
485
+ enhanced_edges = edges * (1.0 + coherence * 0.5)
486
+
487
+ return np.clip(enhanced_edges, 0, 1)
488
+
489
+ class EdgeDetectionPipeline:
490
+ """Main edge detection pipeline with multiple methods and post-processing"""
491
+
492
+ def __init__(self, config: Optional[Dict] = None):
493
+ self.config = config or {}
494
+ self.detectors = {}
495
+ self.temporal_processor = TemporalEdgeConsistency(
496
+ memory_frames=self.config.get('temporal_memory', 3),
497
+ consistency_threshold=self.config.get('consistency_threshold', 0.1)
498
+ )
499
+ self.refinement_processor = EdgeRefinementProcessor()
500
+
501
+ # Initialize detectors
502
+ self._initialize_detectors()
503
+
504
+ def _initialize_detectors(self):
505
+ """Initialize available edge detectors"""
506
+ self.detectors[EdgeDetectionMethod.CANNY] = CannyEdgeDetector()
507
+ self.detectors[EdgeDetectionMethod.HAIR_OPTIMIZED] = HairOptimizedEdgeDetector()
508
+ self.detectors[EdgeDetectionMethod.MULTISCALE] = MultiScaleEdgeDetector()
509
+
510
+ if TORCH_AVAILABLE:
511
+ self.detectors[EdgeDetectionMethod.GPU] = GPUEdgeDetector()
512
+
513
+ def detect_edges(self,
514
+ image: np.ndarray,
515
+ method: EdgeDetectionMethod = EdgeDetectionMethod.HAIR_OPTIMIZED,
516
+ apply_temporal_consistency: bool = True,
517
+ apply_refinement: bool = True,
518
+ **kwargs) -> EdgeDetectionResult:
519
+ """Detect edges with specified method and post-processing"""
520
+
521
+ start_time = time.time()
522
+
523
+ # Select detector
524
+ if method not in self.detectors:
525
+ logger.warning(f"Method {method} not available, using Canny")
526
+ method = EdgeDetectionMethod.CANNY
527
+
528
+ detector = self.detectors[method]
529
+
530
+ # Detect edges
531
+ try:
532
+ edges = detector.detect(image, **kwargs)
533
+ except Exception as e:
534
+ logger.error(f"Edge detection failed with {method.value}: {e}")
535
+ # Fallback to Canny
536
+ edges = self.detectors[EdgeDetectionMethod.CANNY].detect(image, **kwargs)
537
+ method = EdgeDetectionMethod.CANNY
538
+
539
+ # Apply temporal consistency
540
+ if apply_temporal_consistency:
541
+ edges = self.temporal_processor.apply_temporal_consistency(edges)
542
+
543
+ # Apply refinement
544
+ if apply_refinement:
545
+ # Remove noise
546
+ edges = self.refinement_processor.remove_noise(
547
+ edges,
548
+ min_area=self.config.get('min_edge_area', 10)
549
+ )
550
+
551
+ # Smooth edges
552
+ edges = self.refinement_processor.smooth_edges(
553
+ edges,
554
+ iterations=self.config.get('smoothing_iterations', 1)
555
+ )
556
+
557
+ # Enhance hair edges
558
+ edges = self.refinement_processor.enhance_hair_edges(edges, image)
559
+
560
+ # Calculate metrics
561
+ processing_time = time.time() - start_time
562
+ quality_score = EdgeQualityMetrics.calculate_overall_quality(edges)
563
+ edge_strength = EdgeQualityMetrics.calculate_edge_strength(edges)
564
+
565
+ # Create confidence map (edges as confidence)
566
+ confidence_map = edges.copy()
567
+
568
+ return EdgeDetectionResult(
569
+ edges=edges,
570
+ confidence_map=confidence_map,
571
+ edge_strength=edge_strength,
572
+ processing_time=processing_time,
573
+ method_used=method.value,
574
+ quality_score=quality_score
575
+ )
576
+
577
+ def get_best_method_for_image(self, image: np.ndarray) -> EdgeDetectionMethod:
578
+ """Automatically select best edge detection method for image"""
579
+ # Analyze image characteristics
580
+ if len(image.shape) == 3:
581
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
582
+ else:
583
+ gray = image
584
+
585
+ # Calculate image statistics
586
+ contrast = np.std(gray)
587
+ brightness = np.mean(gray)
588
+
589
+ # High contrast images work well with Canny
590
+ if contrast > 50:
591
+ return EdgeDetectionMethod.CANNY
592
+
593
+ # Low contrast or complex textures benefit from hair-optimized
594
+ if contrast < 20 or brightness < 50:
595
+ return EdgeDetectionMethod.HAIR_OPTIMIZED
596
+
597
+ # Default to multiscale for balanced cases
598
+ return EdgeDetectionMethod.MULTISCALE
599
+
600
+ # Convenience functions
601
+ def detect_hair_edges(image: np.ndarray, config: Optional[Dict] = None) -> EdgeDetectionResult:
602
+ """Convenience function to detect hair edges with optimal settings"""
603
+ pipeline = EdgeDetectionPipeline(config)
604
+ return pipeline.detect_edges(
605
+ image,
606
+ method=EdgeDetectionMethod.HAIR_OPTIMIZED,
607
+ apply_temporal_consistency=False,
608
+ apply_refinement=True
609
+ )
610
+
611
+ def detect_video_edges(frames: List[np.ndarray], config: Optional[Dict] = None) -> List[EdgeDetectionResult]:
612
+ """Detect edges in video frames with temporal consistency"""
613
+ pipeline = EdgeDetectionPipeline(config)
614
+ results = []
615
+
616
+ for frame in frames:
617
+ result = pipeline.detect_edges(
618
+ frame,
619
+ method=EdgeDetectionMethod.HAIR_OPTIMIZED,
620
+ apply_temporal_consistency=True,
621
+ apply_refinement=True
622
+ )
623
+ results.append(result)
624
+
625
+ return results
626
+
627
+ # Example usage and testing
628
+ if __name__ == "__main__":
629
+ # Test with synthetic image
630
+ test_image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
631
+
632
+ # Create pipeline
633
+ config = {
634
+ 'temporal_memory': 3,
635
+ 'consistency_threshold': 0.1,
636
+ 'min_edge_area': 10,
637
+ 'smoothing_iterations': 1
638
+ }
639
+
640
+ pipeline = EdgeDetectionPipeline(config)
641
+
642
+ # Test different methods
643
+ methods = [
644
+ EdgeDetectionMethod.CANNY,
645
+ EdgeDetectionMethod.HAIR_OPTIMIZED,
646
+ EdgeDetectionMethod.MULTISCALE
647
+ ]
648
+
649
+ for method in methods:
650
+ if method in pipeline.detectors:
651
+ result = pipeline.detect_edges(test_image, method=method)
652
+
653
+ print(f"\n{method.value} Results:")
654
+ print(f" Edge strength: {result.edge_strength:.3f}")
655
+ print(f" Quality score: {result.quality_score:.3f}")
656
+ print(f" Processing time: {result.processing_time:.3f}s")
657
+
658
+ # Test automatic method selection
659
+ best_method = pipeline.get_best_method_for_image(test_image)
660
+ print(f"\nBest method for this image: {best_method.value}")