Enhance VideoEditor with improved point transformation, bounds checking, and debugging features

This commit refines the point transformation methods in the VideoEditor class, ensuring coordinates are validated against frame dimensions and crop areas. It adds detailed logging for point transformations and mouse interactions, improving visibility into the state of tracking points. Additionally, it introduces debug features for visualizing crop rectangles and point indices, enhancing the debugging experience during video editing.
This commit is contained in:
2025-09-16 20:32:33 +02:00
parent 70364d0458
commit cb097c55f1
2 changed files with 81 additions and 19 deletions

View File

@@ -1200,11 +1200,22 @@ class VideoEditor:
if point is None: if point is None:
return None return None
x, y = float(point[0]), float(point[1]) orig_x, orig_y = float(point[0]), float(point[1])
print(f"transform_point: original point ({orig_x}, {orig_y})")
print(f"transform_point: original point ({x}, {y})") # Get frame dimensions
if self.current_display_frame is None:
return None
frame_height, frame_width = self.current_display_frame.shape[:2]
# Step 1: Check if point is within frame bounds
if not (0 <= orig_x < frame_width and 0 <= orig_y < frame_height):
print(f"transform_point: point is outside frame bounds ({frame_width}x{frame_height})")
# We'll still transform it, but it might not be visible
# Step 1: Apply crop (adjust point relative to crop origin) # Step 1: Apply crop (adjust point relative to crop origin)
x, y = orig_x, orig_y
if self.crop_rect: if self.crop_rect:
crop_x, crop_y, crop_w, crop_h = self.crop_rect crop_x, crop_y, crop_w, crop_h = self.crop_rect
print(f"transform_point: crop_rect = {self.crop_rect}") print(f"transform_point: crop_rect = {self.crop_rect}")
@@ -1224,11 +1235,7 @@ class VideoEditor:
if self.crop_rect: if self.crop_rect:
crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3]) crop_w, crop_h = float(self.crop_rect[2]), float(self.crop_rect[3])
else: else:
if self.current_display_frame is not None: crop_h, crop_w = float(frame_height), float(frame_width)
crop_h, crop_w = self.current_display_frame.shape[:2]
crop_h, crop_w = float(crop_h), float(crop_w)
else:
return None
print(f"transform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})") print(f"transform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})")
@@ -1270,10 +1277,14 @@ 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 = float(point[0]), float(point[1]) display_x, display_y = float(point[0]), float(point[1])
print(f"untransform_point: original display point ({x}, {y})") print(f"untransform_point: original display point ({display_x}, {display_y})")
# Get frame dimensions
frame_height, frame_width = self.current_display_frame.shape[:2]
# Step 1: Reverse zoom # Step 1: Reverse zoom
x, y = display_x, display_y
if self.zoom_factor != 1.0: if self.zoom_factor != 1.0:
x /= self.zoom_factor x /= self.zoom_factor
y /= self.zoom_factor y /= self.zoom_factor
@@ -1285,8 +1296,7 @@ class VideoEditor:
if self.crop_rect: if self.crop_rect:
crop_w, crop_h = float(self.crop_rect[2]), float(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 = float(frame_height), float(frame_width)
crop_h, crop_w = float(crop_h), float(crop_w)
print(f"untransform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})") print(f"untransform_point: rotation_angle = {self.rotation_angle}, dimensions = ({crop_w}, {crop_h})")
@@ -1315,8 +1325,10 @@ class VideoEditor:
y += crop_y y += crop_y
print(f"untransform_point: after reverse crop ({x}, {y}), crop_rect = {self.crop_rect}") print(f"untransform_point: after reverse crop ({x}, {y}), crop_rect = {self.crop_rect}")
# Don't clamp coordinates - allow points outside frame bounds # Check if the resulting point is within frame bounds
# This is important for tracking points that may be outside the current crop if not (0 <= x < frame_width and 0 <= y < frame_height):
print(f"untransform_point: result is outside frame bounds ({frame_width}x{frame_height})")
# We'll still return it, but it might not be visible
print(f"untransform_point: final result = ({x}, {y})") print(f"untransform_point: final result = ({x}, {y})")
return (x, y) return (x, y)
@@ -1494,18 +1506,46 @@ class VideoEditor:
print(f"draw_tracking_points: offset=({offset_x},{offset_y}), scale={scale}") print(f"draw_tracking_points: offset=({offset_x},{offset_y}), scale={scale}")
# Draw tracking points for the current frame (green circles with white border) # Draw tracking points for the current frame
tracking_points = self.motion_tracker.get_tracking_points_for_frame(self.current_frame) tracking_points = self.motion_tracker.get_tracking_points_for_frame(self.current_frame)
print(f"draw_tracking_points: found {len(tracking_points)} tracking points for frame {self.current_frame}") print(f"draw_tracking_points: found {len(tracking_points)} tracking points for frame {self.current_frame}")
# Get current frame dimensions for bounds checking # Get current frame dimensions for bounds checking
frame_height, frame_width = self.current_display_frame.shape[:2] frame_height, frame_width = self.current_display_frame.shape[:2]
# Draw coordinate axes for debugging (if in debug mode)
debug_mode = True
if debug_mode and self.crop_rect:
# Draw crop rectangle outline on the canvas
crop_x, crop_y, crop_w, crop_h = self.crop_rect
# Transform the crop corners to display coordinates
top_left = self.transform_point((crop_x, crop_y))
top_right = self.transform_point((crop_x + crop_w, crop_y))
bottom_left = self.transform_point((crop_x, crop_y + crop_h))
bottom_right = self.transform_point((crop_x + crop_w, crop_y + crop_h))
# Draw crop outline if all corners are visible
if all([top_left, top_right, bottom_left, bottom_right]):
# Convert to canvas coordinates
tl_x, tl_y = int(offset_x + top_left[0] * scale), int(offset_y + top_left[1] * scale)
tr_x, tr_y = int(offset_x + top_right[0] * scale), int(offset_y + top_right[1] * scale)
bl_x, bl_y = int(offset_x + bottom_left[0] * scale), int(offset_y + bottom_left[1] * scale)
br_x, br_y = int(offset_x + bottom_right[0] * scale), int(offset_y + bottom_right[1] * scale)
# Draw crop outline
cv2.line(canvas, (tl_x, tl_y), (tr_x, tr_y), (255, 0, 255), 1)
cv2.line(canvas, (tr_x, tr_y), (br_x, br_y), (255, 0, 255), 1)
cv2.line(canvas, (br_x, br_y), (bl_x, bl_y), (255, 0, 255), 1)
cv2.line(canvas, (bl_x, bl_y), (tl_x, tl_y), (255, 0, 255), 1)
# Process each tracking point
for i, point in enumerate(tracking_points): for i, point in enumerate(tracking_points):
print(f"draw_tracking_points: processing point {i}: {point}") print(f"draw_tracking_points: processing point {i}: {point}")
# Check if the point is within the frame bounds # Check if the point is within the frame bounds
is_in_frame = (0 <= point[0] < frame_width and 0 <= point[1] < frame_height) is_in_frame = (0 <= point[0] < frame_width and 0 <= point[1] < frame_height)
print(f"draw_tracking_points: point {i} is {'inside' if is_in_frame else 'outside'} frame bounds")
# Check if the point is within the crop area (if cropping is active) # Check if the point is within the crop area (if cropping is active)
is_in_crop = True is_in_crop = True
@@ -1513,6 +1553,7 @@ class VideoEditor:
crop_x, crop_y, crop_w, crop_h = self.crop_rect crop_x, crop_y, crop_w, crop_h = self.crop_rect
is_in_crop = (crop_x <= point[0] < crop_x + crop_w and is_in_crop = (crop_x <= point[0] < crop_x + crop_w and
crop_y <= point[1] < crop_y + crop_h) crop_y <= point[1] < crop_y + crop_h)
print(f"draw_tracking_points: point {i} is {'inside' if is_in_crop else 'outside'} crop area")
# Transform point from original frame coordinates to display coordinates # Transform point from original frame coordinates to display coordinates
display_point = self.transform_point(point) display_point = self.transform_point(point)
@@ -1537,12 +1578,16 @@ class VideoEditor:
cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (255, 255, 255), 2) cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (255, 255, 255), 2)
# Draw green circle # Draw green circle
cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 0), -1) cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 0), -1)
# Draw point index for identification
cv2.putText(canvas, str(i), (x + 15, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
else: else:
# Point is outside crop area - draw with different color # Point is outside crop area - draw with different color
# Draw gray border # Draw gray border
cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (128, 128, 128), 2) cv2.circle(canvas, (x, y), self.tracking_point_radius + 2, (128, 128, 128), 2)
# Draw yellow circle # Draw yellow circle
cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 255), -1) cv2.circle(canvas, (x, y), self.tracking_point_radius, (0, 255, 255), -1)
# Draw point index for identification
cv2.putText(canvas, str(i), (x + 15, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
else: else:
print(f"draw_tracking_points: point {i} is outside canvas bounds") print(f"draw_tracking_points: point {i} is outside canvas bounds")
else: else:
@@ -2127,7 +2172,8 @@ class VideoEditor:
start_x = (self.window_width - final_display_width) // 2 start_x = (self.window_width - final_display_width) // 2
start_y = (available_height - final_display_height) // 2 start_y = (available_height - final_display_height) // 2
print(f"mouse_callback: right-click at ({x}, {y}), display frame at ({start_x}, {start_y}), scale={scale}") print(f"mouse_callback: right-click at ({x}, {y}), canvas dimensions: {self.window_width}x{self.window_height}")
print(f"mouse_callback: display frame at ({start_x}, {start_y}), size: {final_display_width}x{final_display_height}, scale={scale}")
# Check if click is within the frame area # Check if click is within the frame area
if (start_x <= x < start_x + final_display_width and if (start_x <= x < start_x + final_display_width and
@@ -2145,6 +2191,17 @@ class VideoEditor:
print(f"mouse_callback: untransformed to original coords {original_point}") print(f"mouse_callback: untransformed to original coords {original_point}")
if original_point: if original_point:
# Get original frame dimensions for validation
frame_height, frame_width = self.current_display_frame.shape[:2]
# Ensure point is within the original frame bounds
x_clamped = max(0, min(frame_width - 1, original_point[0]))
y_clamped = max(0, min(frame_height - 1, original_point[1]))
if x_clamped != original_point[0] or y_clamped != original_point[1]:
print(f"mouse_callback: clamped point from {original_point} to ({x_clamped}, {y_clamped})")
original_point = (x_clamped, y_clamped)
# Check if clicking on an existing tracking point to remove it # Check if clicking on an existing tracking point to remove it
removed = self.motion_tracker.remove_tracking_point( removed = self.motion_tracker.remove_tracking_point(
self.current_frame, self.current_frame,
@@ -2164,6 +2221,13 @@ class VideoEditor:
) )
print(f"mouse_callback: added tracking point at {original_point}") print(f"mouse_callback: added tracking point at {original_point}")
# Draw a debug marker at the exact point for visualization
if display_frame is not None:
# Transform the point back to display coordinates for verification
verification_point = self.transform_point(original_point)
if verification_point:
print(f"mouse_callback: verification - point transforms back to {verification_point}")
# Save state when tracking points change # Save state when tracking points change
self.save_state() self.save_state()
self.display_needs_update = True self.display_needs_update = True

View File

@@ -108,8 +108,6 @@ class MotionTracker:
def get_tracking_offset(self, frame_number: int) -> Tuple[float, float]: def get_tracking_offset(self, frame_number: int) -> Tuple[float, float]:
"""Get the offset to center the crop on the tracked point""" """Get the offset to center the crop on the tracked point"""
import logging
logger = logging.getLogger('croppa')
if not self.tracking_enabled: if not self.tracking_enabled:
print(f"get_tracking_offset: tracking not enabled, returning (0,0)") print(f"get_tracking_offset: tracking not enabled, returning (0,0)")