Implement shift-right click and ctrl-right click for feature extraction

This commit is contained in:
2025-09-26 13:57:00 +02:00
parent 366c338c5d
commit ea008ba23c

View File

@@ -770,6 +770,12 @@ class VideoEditor:
# Feature tracking system
self.feature_tracker = FeatureTracker()
# Initialize selective feature extraction/deletion
self.selective_feature_extraction_start = None
self.selective_feature_extraction_rect = None
self.selective_feature_deletion_start = None
self.selective_feature_deletion_rect = None
# Project view mode
self.project_view_mode = False
self.project_view = None
@@ -1479,50 +1485,6 @@ class VideoEditor:
h = min(h, rot_h - y)
return (x, y, w, h)
def _map_transformed_to_original_coords(self, x, y):
"""Map coordinates from transformed frame back to original frame coordinates."""
# The transformed frame is the result of apply_crop_zoom_and_rotation
# We need to reverse the transformations to get back to original frame coordinates
# First, reverse the crop transformation
if self.crop_rect:
crop_x, crop_y, crop_w, crop_h = self.crop_rect
# Add crop offset back
orig_x = x + crop_x
orig_y = y + crop_y
else:
orig_x, orig_y = x, y
# Then reverse the rotation
if self.rotation_angle == 90:
# 90° clockwise -> 270° counterclockwise
orig_x, orig_y = self.frame_height - orig_y, orig_x
elif self.rotation_angle == 180:
# 180° -> flip both axes
orig_x = self.frame_width - orig_x
orig_y = self.frame_height - orig_y
elif self.rotation_angle == 270:
# 270° clockwise -> 90° counterclockwise
orig_x, orig_y = orig_y, self.frame_width - orig_x
return (int(orig_x), int(orig_y))
def _map_original_to_rotated_coords(self, x, y):
"""Map coordinates from original frame to rotated frame coordinates."""
# Apply rotation (same as existing system)
if self.rotation_angle == 90:
# 90° clockwise
rot_x, rot_y = self.frame_height - y, x
elif self.rotation_angle == 180:
# 180° -> flip both axes
rot_x, rot_y = self.frame_width - x, self.frame_height - y
elif self.rotation_angle == 270:
# 270° clockwise
rot_x, rot_y = y, self.frame_width - x
else:
rot_x, rot_y = x, y
return (int(rot_x), int(rot_y))
def _get_interpolated_tracking_position(self, frame_number):
"""Linear interpolation in ROTATED frame coords. Returns (rx, ry) or None."""
@@ -1656,6 +1618,109 @@ class VideoEditor:
self.cached_frame_number = None
self.cached_transform_hash = None
def _extract_features_from_region(self, screen_rect):
"""Extract features from a specific screen region"""
x, y, w, h = screen_rect
print(f"DEBUG: Extracting features from region ({x}, {y}, {w}, {h})")
# Map screen coordinates to rotated frame coordinates
rx1, ry1 = self._map_screen_to_rotated(x, y)
rx2, ry2 = self._map_screen_to_rotated(x + w, y + h)
# Get the region in rotated frame coordinates
left_r = min(rx1, rx2)
top_r = min(ry1, ry2)
right_r = max(rx1, rx2)
bottom_r = max(ry1, ry2)
# Extract features from this region of the original frame
if self.rotation_angle in (90, 270):
# For rotated frames, we need to map back to original frame coordinates
if self.rotation_angle == 90:
orig_x = top_r
orig_y = self.frame_height - right_r
orig_w = bottom_r - top_r
orig_h = right_r - left_r
else: # 270
orig_x = self.frame_width - bottom_r
orig_y = left_r
orig_w = bottom_r - top_r
orig_h = right_r - left_r
else:
orig_x, orig_y = left_r, top_r
orig_w, orig_h = right_r - left_r, bottom_r - top_r
# Extract features from this region
if (orig_x >= 0 and orig_y >= 0 and
orig_x + orig_w <= self.frame_width and
orig_y + orig_h <= self.frame_height):
region_frame = self.current_display_frame[orig_y:orig_y+orig_h, orig_x:orig_x+orig_w]
if region_frame.size > 0:
# Map coordinates from region to rotated frame coordinates
def coord_mapper(px, py):
# Map from region coordinates to rotated frame coordinates
if self.rotation_angle == 90:
rot_x = orig_x + py
rot_y = self.frame_height - (orig_y + px)
elif self.rotation_angle == 270:
rot_x = self.frame_width - (orig_y + py)
rot_y = orig_x + px
else:
rot_x = orig_x + px
rot_y = orig_y + py
return (int(rot_x), int(rot_y))
success = self.feature_tracker.extract_features(region_frame, self.current_frame, coord_mapper)
if success:
count = self.feature_tracker.get_feature_count(self.current_frame)
self.show_feedback_message(f"Extracted {count} features from selected region")
else:
self.show_feedback_message("Failed to extract features from region")
else:
self.show_feedback_message("Region too small")
else:
self.show_feedback_message("Region outside frame bounds")
def _delete_features_from_region(self, screen_rect):
"""Delete features from a specific screen region"""
x, y, w, h = screen_rect
print(f"DEBUG: Deleting features from region ({x}, {y}, {w}, {h})")
if self.current_frame not in self.feature_tracker.features:
self.show_feedback_message("No features to delete")
return
# Map screen coordinates to rotated frame coordinates
rx1, ry1 = self._map_screen_to_rotated(x, y)
rx2, ry2 = self._map_screen_to_rotated(x + w, y + h)
# Get the region in rotated frame coordinates
left_r = min(rx1, rx2)
top_r = min(ry1, ry2)
right_r = max(rx1, rx2)
bottom_r = max(ry1, ry2)
# Remove features within this region
features = self.feature_tracker.features[self.current_frame]
original_count = len(features['positions'])
# Filter out features within the region
filtered_positions = []
for fx, fy in features['positions']:
if not (left_r <= fx <= right_r and top_r <= fy <= bottom_r):
filtered_positions.append((fx, fy))
# Update the features
features['positions'] = filtered_positions
removed_count = original_count - len(filtered_positions)
if removed_count > 0:
self.show_feedback_message(f"Removed {removed_count} features from selected region")
self.save_state()
else:
self.show_feedback_message("No features found in selected region")
def apply_rotation(self, frame):
"""Apply rotation to frame"""
@@ -2322,6 +2387,15 @@ class VideoEditor:
cv2.circle(canvas, (sx, sy), 4, (0, 255, 0), -1) # Green circles for features
cv2.circle(canvas, (sx, sy), 4, (255, 255, 255), 1)
# Draw selection rectangles for feature extraction/deletion
if self.selective_feature_extraction_rect:
x, y, w, h = self.selective_feature_extraction_rect
cv2.rectangle(canvas, (x, y), (x + w, y + h), (0, 255, 255), 2) # Yellow for extraction
if self.selective_feature_deletion_rect:
x, y, w, h = self.selective_feature_deletion_rect
cv2.rectangle(canvas, (x, y), (x + w, y + h), (0, 0, 255), 2) # Red for deletion
# Draw previous and next tracking points with motion path visualization
if not self.is_image_mode and self.tracking_points:
prev_result = self._get_previous_tracking_point()
@@ -2459,6 +2533,46 @@ class VideoEditor:
if flags & cv2.EVENT_FLAG_CTRLKEY and event == cv2.EVENT_LBUTTONDOWN:
self.zoom_center = (x, y)
# Handle Shift+Right-click+drag for selective feature extraction
if event == cv2.EVENT_RBUTTONDOWN and (flags & cv2.EVENT_FLAG_SHIFTKEY):
if not self.is_image_mode and self.feature_tracker.tracking_enabled:
self.selective_feature_extraction_start = (x, y)
self.selective_feature_extraction_rect = None
print(f"DEBUG: Started selective feature extraction at ({x}, {y})")
# Handle Shift+Right-click+drag for selective feature extraction
if event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_SHIFTKEY) and self.selective_feature_extraction_start:
if not self.is_image_mode:
start_x, start_y = self.selective_feature_extraction_start
self.selective_feature_extraction_rect = (min(start_x, x), min(start_y, y), abs(x - start_x), abs(y - start_y))
# Handle Shift+Right-click release for selective feature extraction
if event == cv2.EVENT_RBUTTONUP and (flags & cv2.EVENT_FLAG_SHIFTKEY) and self.selective_feature_extraction_start:
if not self.is_image_mode and self.feature_tracker.tracking_enabled and self.selective_feature_extraction_rect:
self._extract_features_from_region(self.selective_feature_extraction_rect)
self.selective_feature_extraction_start = None
self.selective_feature_extraction_rect = None
# Handle Ctrl+Right-click+drag for selective feature deletion
if event == cv2.EVENT_RBUTTONDOWN and (flags & cv2.EVENT_FLAG_CTRLKEY):
if not self.is_image_mode and self.feature_tracker.tracking_enabled:
self.selective_feature_deletion_start = (x, y)
self.selective_feature_deletion_rect = None
print(f"DEBUG: Started selective feature deletion at ({x}, {y})")
# Handle Ctrl+Right-click+drag for selective feature deletion
if event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_CTRLKEY) and self.selective_feature_deletion_start:
if not self.is_image_mode:
start_x, start_y = self.selective_feature_deletion_start
self.selective_feature_deletion_rect = (min(start_x, x), min(start_y, y), abs(x - start_x), abs(y - start_y))
# Handle Ctrl+Right-click release for selective feature deletion
if event == cv2.EVENT_RBUTTONUP and (flags & cv2.EVENT_FLAG_CTRLKEY) and self.selective_feature_deletion_start:
if not self.is_image_mode and self.feature_tracker.tracking_enabled and self.selective_feature_deletion_rect:
self._delete_features_from_region(self.selective_feature_deletion_rect)
self.selective_feature_deletion_start = None
self.selective_feature_deletion_rect = None
# Handle right-click for tracking points (no modifiers)
if event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)):
if not self.is_image_mode:
@@ -3307,6 +3421,8 @@ class VideoEditor:
print(" g: Toggle auto feature extraction")
print(" G: Clear all feature data")
print(" H: Switch detector (SIFT/SURF/ORB)")
print(" Shift+Right-click+drag: Extract features from selected region")
print(" Ctrl+Right-click+drag: Delete features from selected region")
if len(self.video_files) > 1:
print(" N: Next video")
print(" n: Previous video")
@@ -3621,11 +3737,12 @@ class VideoEditor:
elif key == ord("H"):
# Switch detector type (SIFT -> SURF -> ORB -> SIFT)
current_type = self.feature_tracker.detector_type
print(f"Current detector type: {current_type}")
if current_type == 'SIFT':
new_type = 'SURF'
elif current_type == 'SURF':
new_type = 'ORB'
elif current_type == 'ORB':
new_type = 'SIFT'
else:
new_type = 'SIFT'
self.feature_tracker.set_detector_type(new_type)