Refactor feature extraction to handle SURF fallback and add features from regions

This commit updates the feature extraction process to provide a fallback to SIFT when SURF is not available, enhancing robustness. It introduces a new method to extract features from specific regions of a frame, allowing for the addition of features to existing ones. Feedback messages have been improved to reflect these changes, ensuring better user experience and clarity during feature extraction.
This commit is contained in:
2025-09-26 14:00:01 +02:00
parent ea008ba23c
commit e7571a78f4

View File

@@ -49,7 +49,10 @@ class FeatureTracker:
if self.detector_type == 'SIFT': if self.detector_type == 'SIFT':
self.detector = cv2.SIFT_create(nfeatures=self.max_features) self.detector = cv2.SIFT_create(nfeatures=self.max_features)
elif self.detector_type == 'SURF': elif self.detector_type == 'SURF':
self.detector = cv2.xfeatures2d.SURF_create(hessianThreshold=400) # SURF requires opencv-contrib-python, fallback to SIFT
print("Warning: SURF requires opencv-contrib-python package. Using SIFT instead.")
self.detector = cv2.SIFT_create(nfeatures=self.max_features)
self.detector_type = 'SIFT'
elif self.detector_type == 'ORB': elif self.detector_type == 'ORB':
self.detector = cv2.ORB_create(nfeatures=self.max_features) self.detector = cv2.ORB_create(nfeatures=self.max_features)
else: else:
@@ -107,6 +110,53 @@ class FeatureTracker:
print(f"Error extracting features from frame {frame_number}: {e}") print(f"Error extracting features from frame {frame_number}: {e}")
return False return False
def extract_features_from_region(self, frame: np.ndarray, frame_number: int, coord_mapper=None) -> bool:
"""Extract features from a frame and ADD them to existing features"""
try:
# Convert to grayscale if needed
if len(frame.shape) == 3:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
else:
gray = frame
# Extract keypoints and descriptors
keypoints, descriptors = self.detector.detectAndCompute(gray, None)
if keypoints is None or descriptors is None:
return False
# Map coordinates back to original frame space if mapper provided
if coord_mapper:
mapped_positions = []
for kp in keypoints:
orig_x, orig_y = coord_mapper(kp.pt[0], kp.pt[1])
mapped_positions.append((int(orig_x), int(orig_y)))
else:
mapped_positions = [(int(kp.pt[0]), int(kp.pt[1])) for kp in keypoints]
# Add to existing features or create new entry
if frame_number in self.features:
# Append to existing features
existing_features = self.features[frame_number]
existing_features['keypoints'] = np.concatenate([existing_features['keypoints'], keypoints])
existing_features['descriptors'] = np.concatenate([existing_features['descriptors'], descriptors])
existing_features['positions'].extend(mapped_positions)
print(f"Added {len(keypoints)} features to frame {frame_number} (total: {len(existing_features['positions'])})")
else:
# Create new features entry
self.features[frame_number] = {
'keypoints': keypoints,
'descriptors': descriptors,
'positions': mapped_positions
}
print(f"Extracted {len(keypoints)} features from frame {frame_number}")
return True
except Exception as e:
print(f"Error extracting features from frame {frame_number}: {e}")
return False
def get_tracking_position(self, frame_number: int) -> Optional[Tuple[float, float]]: def get_tracking_position(self, frame_number: int) -> Optional[Tuple[float, float]]:
@@ -1248,8 +1298,11 @@ class VideoEditor:
self.feature_tracker.auto_tracking and self.feature_tracker.auto_tracking and
self.current_display_frame is not None): self.current_display_frame is not None):
print(f"DEBUG: Auto-tracking conditions met for frame {self.current_frame}")
print(f"DEBUG: tracking_enabled={self.feature_tracker.tracking_enabled}, auto_tracking={self.feature_tracker.auto_tracking}")
# Only extract if we don't already have features for this frame # Only extract if we don't already have features for this frame
if self.current_frame not in self.feature_tracker.features: if self.current_frame not in self.feature_tracker.features:
print(f"DEBUG: Extracting features for frame {self.current_frame}")
# Extract features from the transformed frame (what user sees) # Extract features from the transformed frame (what user sees)
# This handles all transformations (crop, zoom, rotation) correctly # This handles all transformations (crop, zoom, rotation) correctly
display_frame = self.apply_crop_zoom_and_rotation(self.current_display_frame) display_frame = self.apply_crop_zoom_and_rotation(self.current_display_frame)
@@ -1271,6 +1324,8 @@ class VideoEditor:
return self._map_screen_to_rotated(screen_x, screen_y) return self._map_screen_to_rotated(screen_x, screen_y)
self.feature_tracker.extract_features(display_frame, self.current_frame, coord_mapper) self.feature_tracker.extract_features(display_frame, self.current_frame, coord_mapper)
else:
print(f"DEBUG: Frame {self.current_frame} already has features, skipping")
def jump_to_previous_marker(self): def jump_to_previous_marker(self):
"""Jump to the previous tracking marker (frame with tracking points).""" """Jump to the previous tracking marker (frame with tracking points)."""
@@ -1671,10 +1726,11 @@ class VideoEditor:
rot_y = orig_y + py rot_y = orig_y + py
return (int(rot_x), int(rot_y)) return (int(rot_x), int(rot_y))
success = self.feature_tracker.extract_features(region_frame, self.current_frame, coord_mapper) # Extract features and add them to existing features
success = self.feature_tracker.extract_features_from_region(region_frame, self.current_frame, coord_mapper)
if success: if success:
count = self.feature_tracker.get_feature_count(self.current_frame) count = self.feature_tracker.get_feature_count(self.current_frame)
self.show_feedback_message(f"Extracted {count} features from selected region") self.show_feedback_message(f"Added features from selected region (total: {count})")
else: else:
self.show_feedback_message("Failed to extract features from region") self.show_feedback_message("Failed to extract features from region")
else: else:
@@ -3420,7 +3476,7 @@ class VideoEditor:
print(" Shift+T: Extract features from current frame") print(" Shift+T: Extract features from current frame")
print(" g: Toggle auto feature extraction") print(" g: Toggle auto feature extraction")
print(" G: Clear all feature data") print(" G: Clear all feature data")
print(" H: Switch detector (SIFT/SURF/ORB)") print(" H: Switch detector (SIFT/ORB)")
print(" Shift+Right-click+drag: Extract features from selected region") print(" Shift+Right-click+drag: Extract features from selected region")
print(" Ctrl+Right-click+drag: Delete features from selected region") print(" Ctrl+Right-click+drag: Delete features from selected region")
if len(self.video_files) > 1: if len(self.video_files) > 1:
@@ -3727,6 +3783,7 @@ class VideoEditor:
elif key == ord("g"): elif key == ord("g"):
# Toggle auto tracking # Toggle auto tracking
self.feature_tracker.auto_tracking = not self.feature_tracker.auto_tracking self.feature_tracker.auto_tracking = not self.feature_tracker.auto_tracking
print(f"DEBUG: Auto tracking toggled to {self.feature_tracker.auto_tracking}")
self.show_feedback_message(f"Auto tracking {'ON' if self.feature_tracker.auto_tracking else 'OFF'}") self.show_feedback_message(f"Auto tracking {'ON' if self.feature_tracker.auto_tracking else 'OFF'}")
self.save_state() self.save_state()
elif key == ord("G"): elif key == ord("G"):
@@ -3735,11 +3792,9 @@ class VideoEditor:
self.show_feedback_message("Feature tracking data cleared") self.show_feedback_message("Feature tracking data cleared")
self.save_state() self.save_state()
elif key == ord("H"): elif key == ord("H"):
# Switch detector type (SIFT -> SURF -> ORB -> SIFT) # Switch detector type (SIFT -> ORB -> SIFT) - SURF not available
current_type = self.feature_tracker.detector_type current_type = self.feature_tracker.detector_type
if current_type == 'SIFT': if current_type == 'SIFT':
new_type = 'SURF'
elif current_type == 'SURF':
new_type = 'ORB' new_type = 'ORB'
elif current_type == 'ORB': elif current_type == 'ORB':
new_type = 'SIFT' new_type = 'SIFT'