Enhance VideoEditor with improved point transformation and tracking logic

This commit refines the point transformation process in the VideoEditor class by ensuring coordinates are converted to floats and validating their positions relative to the crop area. It also updates the right-click event handling to accurately convert display coordinates to original frame coordinates, allowing for better interaction with tracking points. Additionally, the MotionTracker class is modified to set a default zoom center based on the crop rect if none is provided, improving the tracking functionality.
This commit is contained in:
2025-09-16 20:04:34 +02:00
parent 85891a5f99
commit 9085a82bdd
2 changed files with 110 additions and 39 deletions

View File

@@ -1175,11 +1175,18 @@ class VideoEditor:
if point is None: if point is None:
return None return None
x, y = point x, y = float(point[0]), float(point[1])
# Step 1: Apply crop (adjust point relative to crop origin) # Step 1: Apply crop (adjust point relative to crop origin)
if self.crop_rect: if self.crop_rect:
crop_x, crop_y, _, _ = self.crop_rect crop_x, crop_y, crop_w, crop_h = self.crop_rect
# Check if point is inside the crop area
if not (crop_x <= x < crop_x + crop_w and crop_y <= y < crop_y + crop_h):
# Point is outside the crop area
return None
# Adjust coordinates relative to crop origin
x -= crop_x x -= crop_x
y -= crop_y y -= crop_y
@@ -1228,7 +1235,7 @@ class VideoEditor:
if point is None or self.current_display_frame is None: if point is None or self.current_display_frame is None:
return None return None
x, y = point x, y = float(point[0]), float(point[1])
# Step 1: Reverse zoom # Step 1: Reverse zoom
if self.zoom_factor != 1.0: if self.zoom_factor != 1.0:
@@ -1239,9 +1246,10 @@ class VideoEditor:
if self.rotation_angle != 0: if self.rotation_angle != 0:
# Get dimensions after crop but before rotation # Get dimensions after crop but before rotation
if self.crop_rect: if self.crop_rect:
crop_w, crop_h = self.crop_rect[2], self.crop_rect[3] crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3])
else: else:
crop_h, crop_w = self.current_display_frame.shape[:2] crop_h, crop_w = self.current_display_frame.shape[:2]
crop_h, crop_w = float(crop_h), float(crop_w)
# Apply inverse rotation to coordinates # Apply inverse rotation to coordinates
if self.rotation_angle == 90: if self.rotation_angle == 90:
@@ -1261,10 +1269,16 @@ class VideoEditor:
# Step 3: Reverse crop (add crop offset) # Step 3: Reverse crop (add crop offset)
if self.crop_rect: if self.crop_rect:
crop_x, crop_y, _, _ = self.crop_rect crop_x, crop_y = float(self.crop_rect[0]), float(self.crop_rect[1])
x += crop_x x += crop_x
y += crop_y y += crop_y
# Ensure coordinates are within the frame bounds
if self.current_display_frame is not None:
height, width = self.current_display_frame.shape[:2]
x = max(0, min(width - 1, x))
y = max(0, min(height - 1, y))
return (x, y) return (x, y)
@@ -2003,29 +2017,60 @@ class VideoEditor:
# Handle tracking points (Right-click) # Handle tracking points (Right-click)
if event == cv2.EVENT_RBUTTONDOWN: if event == cv2.EVENT_RBUTTONDOWN:
# Convert display coordinates to original frame coordinates # First, calculate the canvas offset and scale for the current frame
original_point = self.untransform_point((x, y)) if self.current_display_frame is not None:
# Get dimensions of the transformed frame
if original_point: display_frame = self.apply_crop_zoom_and_rotation(self.current_display_frame)
# Check if clicking on an existing tracking point to remove it if display_frame is None:
removed = self.motion_tracker.remove_tracking_point( return
self.current_frame,
original_point[0],
original_point[1],
self.tracking_point_distance
)
if not removed:
# If no point was removed, add a new tracking point
self.motion_tracker.add_tracking_point(
self.current_frame,
original_point[0],
original_point[1]
)
# Save state when tracking points change display_height, display_width = display_frame.shape[:2]
self.save_state() available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
self.display_needs_update = True
# Calculate scale and offset
scale = min(self.window_width / display_width, available_height / display_height)
if scale < 1.0:
final_display_width = int(display_width * scale)
final_display_height = int(display_height * scale)
else:
final_display_width = display_width
final_display_height = display_height
scale = 1.0
start_x = (self.window_width - final_display_width) // 2
start_y = (available_height - final_display_height) // 2
# Check if click is within the frame area
if (start_x <= x < start_x + final_display_width and
start_y <= y < start_y + final_display_height):
# Convert screen coordinates to display frame coordinates
display_x = (x - start_x) / scale
display_y = (y - start_y) / scale
# Now convert display coordinates to original frame coordinates
original_point = self.untransform_point((display_x, display_y))
if original_point:
# Check if clicking on an existing tracking point to remove it
removed = self.motion_tracker.remove_tracking_point(
self.current_frame,
original_point[0],
original_point[1],
self.tracking_point_distance
)
if not removed:
# If no point was removed, add a new tracking point
self.motion_tracker.add_tracking_point(
self.current_frame,
original_point[0],
original_point[1]
)
# Save state when tracking points change
self.save_state()
self.display_needs_update = True
# Handle zoom center (Ctrl + click) # Handle zoom center (Ctrl + click)
if flags & cv2.EVENT_FLAG_CTRLKEY and event == cv2.EVENT_LBUTTONDOWN: if flags & cv2.EVENT_FLAG_CTRLKEY and event == cv2.EVENT_LBUTTONDOWN:
@@ -2999,18 +3044,38 @@ class VideoEditor:
self.motion_tracker.stop_tracking() self.motion_tracker.stop_tracking()
print("Motion tracking disabled") print("Motion tracking disabled")
else: else:
# Start tracking with current crop and zoom center # If we have tracking points, start tracking
base_zoom_center = self.zoom_center if self.motion_tracker.has_tracking_points():
if not base_zoom_center and self.current_display_frame is not None: # Get the current interpolated position to use as base
# Use frame center if no zoom center is set current_pos = self.motion_tracker.get_interpolated_position(self.current_frame)
h, w = self.current_display_frame.shape[:2]
base_zoom_center = (w // 2, h // 2)
self.motion_tracker.start_tracking( # Use crop center if we have a crop rect
self.crop_rect, if self.crop_rect:
base_zoom_center x, y, w, h = self.crop_rect
) crop_center = (x + w//2, y + h//2)
print("Motion tracking enabled")
# If we have a current position from tracking points, use that as base
if current_pos:
# The base zoom center is the current position
base_zoom_center = current_pos
else:
# Use crop center as fallback
base_zoom_center = crop_center
else:
# No crop rect, use frame center
if self.current_display_frame is not None:
h, w = self.current_display_frame.shape[:2]
base_zoom_center = (w // 2, h // 2)
else:
base_zoom_center = None
self.motion_tracker.start_tracking(
self.crop_rect,
base_zoom_center
)
print("Motion tracking enabled")
else:
print("No tracking points available. Add tracking points with right-click first.")
self.save_state() self.save_state()
else: # V - Clear all tracking points else: # V - Clear all tracking points
self.motion_tracker.clear_tracking_points() self.motion_tracker.clear_tracking_points()

View File

@@ -126,7 +126,13 @@ class MotionTracker:
"""Start motion tracking with base positions""" """Start motion tracking with base positions"""
self.tracking_enabled = True self.tracking_enabled = True
self.base_crop_rect = base_crop_rect self.base_crop_rect = base_crop_rect
self.base_zoom_center = base_zoom_center
# If no base_zoom_center is provided, use the center of the crop rect
if base_zoom_center is None and base_crop_rect is not None:
x, y, w, h = base_crop_rect
self.base_zoom_center = (x + w//2, y + h//2)
else:
self.base_zoom_center = base_zoom_center
def stop_tracking(self): def stop_tracking(self):
"""Stop motion tracking""" """Stop motion tracking"""