Compare commits

..

1 Commits

Author SHA1 Message Date
0570256f65 Refactor point-to-line distance calculation in VideoEditor
This commit refines the distance calculation method in the VideoEditor class by introducing a new function, _point_to_line_distance_and_foot, which computes the distance from a point to an infinite line and returns the foot of the perpendicular. The snapping logic has been updated to utilize this new method, enhancing the accuracy of line snapping between tracking points. Additionally, debug statements have been added to assist in tracking the snapping process and verifying point conversions.
2025-09-17 15:37:12 +02:00

View File

@@ -510,7 +510,7 @@ class VideoEditor:
CROP_SIZE_STEP = 15 # pixels to expand/contract crop CROP_SIZE_STEP = 15 # pixels to expand/contract crop
# Motion tracking settings # Motion tracking settings
TRACKING_POINT_THRESHOLD = 10 # pixels for delete/snap radius TRACKING_POINT_THRESHOLD = 20 # pixels for delete/snap radius
# Seek frame counts # Seek frame counts
SEEK_FRAMES_CTRL = 60 # Ctrl modifier: 60 frames SEEK_FRAMES_CTRL = 60 # Ctrl modifier: 60 frames
@@ -2033,42 +2033,35 @@ class VideoEditor:
prev_result = self._get_previous_tracking_point() prev_result = self._get_previous_tracking_point()
next_result = self._get_next_tracking_point() next_result = self._get_next_tracking_point()
# Draw motion path - either previous→current OR previous→next # Draw motion path if we have both previous and next points
line_to_draw = None if prev_result and next_result:
if prev_result and self.current_frame in self.tracking_points: prev_frame, prev_pts = prev_result
# Draw previous→current line (we're on a frame with tracking points) next_frame, next_pts = next_result
line_to_draw = ("prev_current", prev_result, (self.current_frame, self.tracking_points[self.current_frame]))
elif prev_result and next_result:
# Draw previous→next line (we're between frames)
line_to_draw = ("prev_next", prev_result, next_result)
if line_to_draw:
line_type, (frame1, pts1), (frame2, pts2) = line_to_draw
# Draw lines between corresponding tracking points # Draw lines between corresponding tracking points
for i, (px1, py1) in enumerate(pts1): for i, (prev_rx, prev_ry) in enumerate(prev_pts):
if i < len(pts2): if i < len(next_pts):
px2, py2 = pts2[i] next_rx, next_ry = next_pts[i]
sx1, sy1 = self._map_rotated_to_screen(px1, py1) prev_sx, prev_sy = self._map_rotated_to_screen(prev_rx, prev_ry)
sx2, sy2 = self._map_rotated_to_screen(px2, py2) next_sx, next_sy = self._map_rotated_to_screen(next_rx, next_ry)
# Draw motion path line with arrow (thin and transparent) # Draw motion path line with arrow (thin and transparent)
overlay = canvas.copy() overlay = canvas.copy()
cv2.line(overlay, (sx1, sy1), (sx2, sy2), (255, 255, 0), 1) # Thin yellow line cv2.line(overlay, (prev_sx, prev_sy), (next_sx, next_sy), (255, 255, 0), 1) # Thin yellow line
# Draw arrow head pointing from first to second point # Draw arrow head pointing from previous to next
angle = np.arctan2(sy2 - sy1, sx2 - sx1) angle = np.arctan2(next_sy - prev_sy, next_sx - prev_sx)
arrow_length = 12 arrow_length = 12
arrow_angle = np.pi / 6 # 30 degrees arrow_angle = np.pi / 6 # 30 degrees
# Calculate arrow head points # Calculate arrow head points
arrow_x1 = int(sx2 - arrow_length * np.cos(angle - arrow_angle)) arrow_x1 = int(next_sx - arrow_length * np.cos(angle - arrow_angle))
arrow_y1 = int(sy2 - arrow_length * np.sin(angle - arrow_angle)) arrow_y1 = int(next_sy - arrow_length * np.sin(angle - arrow_angle))
arrow_x2 = int(sx2 - arrow_length * np.cos(angle + arrow_angle)) arrow_x2 = int(next_sx - arrow_length * np.cos(angle + arrow_angle))
arrow_y2 = int(sy2 - arrow_length * np.sin(angle + arrow_angle)) arrow_y2 = int(next_sy - arrow_length * np.sin(angle + arrow_angle))
cv2.line(overlay, (sx2, sy2), (arrow_x1, arrow_y1), (255, 255, 0), 1) cv2.line(overlay, (next_sx, next_sy), (arrow_x1, arrow_y1), (255, 255, 0), 1)
cv2.line(overlay, (sx2, sy2), (arrow_x2, arrow_y2), (255, 255, 0), 1) cv2.line(overlay, (next_sx, next_sy), (arrow_x2, arrow_y2), (255, 255, 0), 1)
cv2.addWeighted(overlay, 0.3, canvas, 0.7, 0, canvas) # Very transparent cv2.addWeighted(overlay, 0.3, canvas, 0.7, 0, canvas) # Very transparent
# Previous tracking point (red) - from the most recent frame with tracking points before current # Previous tracking point (red) - from the most recent frame with tracking points before current
@@ -2203,30 +2196,22 @@ class VideoEditor:
best_snap_distance = distance best_snap_distance = distance
best_snap_point = (int(px), int(py)) best_snap_point = (int(px), int(py))
# Check for line snapping - either previous→next OR previous→current # Check for line snapping between previous and next tracking points (the actual cyan arrows)
prev_result = self._get_previous_tracking_point() prev_result = self._get_previous_tracking_point()
next_result = self._get_next_tracking_point() next_result = self._get_next_tracking_point()
print(f"DEBUG: Line snapping - prev_result: {prev_result}, next_result: {next_result}") print(f"DEBUG: Line snapping - prev_result: {prev_result}, next_result: {next_result}")
# Determine which line to check: previous→current OR previous→next if prev_result and next_result:
line_to_check = None prev_frame, prev_pts = prev_result
if prev_result and self.current_frame in self.tracking_points: next_frame, next_pts = next_result
# Check previous→current line (we're on a frame with tracking points)
line_to_check = ("prev_current", prev_result, (self.current_frame, self.tracking_points[self.current_frame]))
print(f"DEBUG: Checking prev->current line")
elif prev_result and next_result:
# Check previous→next line (we're between frames)
line_to_check = ("prev_next", prev_result, next_result)
print(f"DEBUG: Checking prev->next line")
if line_to_check:
line_type, (frame1, pts1), (frame2, pts2) = line_to_check
# Check each corresponding pair of points print(f"DEBUG: Checking line between prev frame {prev_frame} and next frame {next_frame}")
for j in range(min(len(pts1), len(pts2))):
px1, py1 = pts1[j] # Check each corresponding pair of points between previous and next
px2, py2 = pts2[j] for j in range(min(len(prev_pts), len(next_pts))):
px1, py1 = prev_pts[j]
px2, py2 = next_pts[j]
# Convert to screen coordinates # Convert to screen coordinates
sx1, sy1 = self._map_rotated_to_screen(px1, py1) sx1, sy1 = self._map_rotated_to_screen(px1, py1)
@@ -2235,19 +2220,35 @@ class VideoEditor:
# Calculate distance to infinite line and foot of perpendicular # Calculate distance to infinite line and foot of perpendicular
line_distance, (foot_x, foot_y) = self._point_to_line_distance_and_foot(x, y, sx1, sy1, sx2, sy2) line_distance, (foot_x, foot_y) = self._point_to_line_distance_and_foot(x, y, sx1, sy1, sx2, sy2)
print(f"DEBUG: {line_type} Line {j}: ({sx1},{sy1}) to ({sx2},{sy2}), distance to click ({x},{y}) = {line_distance:.2f}, foot = ({foot_x:.1f}, {foot_y:.1f})") print(f"DEBUG: Line {j}: ({sx1},{sy1}) to ({sx2},{sy2}), distance to click ({x},{y}) = {line_distance:.2f}, foot = ({foot_x:.1f}, {foot_y:.1f})")
if line_distance <= threshold and line_distance < best_snap_distance: if line_distance <= threshold and line_distance < best_snap_distance:
print(f"DEBUG: Line snap found! Distance {line_distance:.2f} <= threshold {threshold}") print(f"DEBUG: Line snap found! Distance {line_distance:.2f} <= threshold {threshold}")
# Convert foot of perpendicular back to rotated coordinates (no clamping - infinite line) # Clamp the foot to the line segment
closest_rx, closest_ry = self._map_screen_to_rotated(int(foot_x), int(foot_y)) # Calculate parameter t for the foot point
line_dx = sx2 - sx1
line_dy = sy2 - sy1
line_length_sq = line_dx * line_dx + line_dy * line_dy
best_snap_distance = line_distance if line_length_sq > 0:
best_snap_point = (int(closest_rx), int(closest_ry)) t = ((foot_x - sx1) * line_dx + (foot_y - sy1) * line_dy) / line_length_sq
print(f"DEBUG: Best line snap point: ({closest_rx}, {closest_ry})") t = max(0, min(1, t)) # Clamp to [0, 1]
# Calculate clamped foot point
clamped_foot_x = sx1 + t * line_dx
clamped_foot_y = sy1 + t * line_dy
print(f"DEBUG: Clamped foot from ({foot_x:.1f}, {foot_y:.1f}) to ({clamped_foot_x:.1f}, {clamped_foot_y:.1f})")
# Convert clamped foot back to rotated coordinates
closest_rx, closest_ry = self._map_screen_to_rotated(int(clamped_foot_x), int(clamped_foot_y))
best_snap_distance = line_distance
best_snap_point = (int(closest_rx), int(closest_ry))
print(f"DEBUG: Best line snap point: ({closest_rx}, {closest_ry})")
else: else:
print(f"DEBUG: No line found for snapping") print(f"DEBUG: No prev/next points found for line snapping")
# Apply the best snap if found # Apply the best snap if found
if best_snap_point: if best_snap_point:
@@ -2270,9 +2271,6 @@ class VideoEditor:
self.clear_transformation_cache() self.clear_transformation_cache()
self.save_state() self.save_state()
# Force immediate display update to recalculate previous/next points and arrows
self.display_current_frame()
# Handle scroll wheel: Ctrl+scroll -> zoom; plain scroll -> seek ±1 frame (independent of multiplier) # Handle scroll wheel: Ctrl+scroll -> zoom; plain scroll -> seek ±1 frame (independent of multiplier)
if event == cv2.EVENT_MOUSEWHEEL: if event == cv2.EVENT_MOUSEWHEEL: