Implement shift-right click and ctrl-right click for feature extraction
This commit is contained in:
207
croppa/main.py
207
croppa/main.py
@@ -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)
|
||||
|
Reference in New Issue
Block a user