Enhance tracking point management in VideoEditor with rotated frame coordinates

This commit updates the VideoEditor class to store and manage tracking points in rotated frame coordinates, improving accuracy in overlay rendering and user interactions. It introduces a new method for mapping rotated coordinates to screen space and modifies existing methods to ensure consistent handling of coordinates throughout the editing process. These changes enhance the overall functionality and user experience when working with motion tracking in video editing.
This commit is contained in:
2025-09-17 01:44:58 +02:00
parent d478b28e0d
commit eeaeff6fe0

View File

@@ -1154,9 +1154,7 @@ class VideoEditor:
return (x, y, w, h) return (x, y, w, h)
def _get_interpolated_tracking_position(self, frame_number): def _get_interpolated_tracking_position(self, frame_number):
"""Linear interpolation between keyed tracking points. """Linear interpolation in ROTATED frame coords. Returns (rx, ry) or None."""
Returns (x, y) in original frame coords or None.
"""
if not self.tracking_points: if not self.tracking_points:
return None return None
frames = sorted(self.tracking_points.keys()) frames = sorted(self.tracking_points.keys())
@@ -1299,6 +1297,38 @@ class VideoEditor:
oy = max(0, min(int(round(oy)), self.frame_height - 1)) oy = max(0, min(int(round(oy)), self.frame_height - 1))
return ox, oy return ox, oy
def _map_rotated_to_screen(self, rx, ry):
"""Map a point in ROTATED frame coords to canvas screen coords (post-crop)."""
# Subtract crop offset in rotated space
cx, cy, cw, ch = self._get_effective_crop_rect_for_frame(getattr(self, 'current_frame', 0))
rx2 = rx - cx
ry2 = ry - cy
# Zoomed dimensions of cropped-rotated frame
new_w = int(cw * self.zoom_factor)
new_h = int(ch * self.zoom_factor)
cropped_due_to_zoom = (self.zoom_factor != 1.0) and (new_w > self.window_width or new_h > self.window_height)
if cropped_due_to_zoom:
offx_max = max(0, new_w - self.window_width)
offy_max = max(0, new_h - self.window_height)
offx = max(0, min(int(self.display_offset[0]), offx_max))
offy = max(0, min(int(self.display_offset[1]), offy_max))
else:
offx = 0
offy = 0
zx = rx2 * self.zoom_factor - offx
zy = ry2 * self.zoom_factor - offy
visible_w = new_w if not cropped_due_to_zoom else min(new_w, self.window_width)
visible_h = new_h if not cropped_due_to_zoom else min(new_h, self.window_height)
available_height = self.window_height - (0 if self.is_image_mode else self.TIMELINE_HEIGHT)
scale_canvas = min(self.window_width / max(1, visible_w), available_height / max(1, visible_h))
final_w = int(visible_w * scale_canvas)
final_h = int(visible_h * scale_canvas)
start_x_canvas = (self.window_width - final_w) // 2
start_y_canvas = (available_height - final_h) // 2
sx = int(round(start_x_canvas + zx * scale_canvas))
sy = int(round(start_y_canvas + zy * scale_canvas))
return sx, sy
def _map_screen_to_rotated(self, sx, sy): def _map_screen_to_rotated(self, sx, sy):
"""Map a point on canvas screen coords back to ROTATED frame coords (pre-crop).""" """Map a point on canvas screen coords back to ROTATED frame coords (pre-crop)."""
frame_number = getattr(self, 'current_frame', 0) frame_number = getattr(self, 'current_frame', 0)
@@ -1969,16 +1999,16 @@ class VideoEditor:
1, 1,
) )
# Draw tracking overlays (points and interpolated cross) # Draw tracking overlays (points and interpolated cross), points stored in ROTATED space
pts = self.tracking_points.get(self.current_frame, []) if not self.is_image_mode else [] pts = self.tracking_points.get(self.current_frame, []) if not self.is_image_mode else []
for (ox, oy) in pts: for (rx, ry) in pts:
sx, sy = self._map_original_to_screen(ox, oy) sx, sy = self._map_rotated_to_screen(rx, ry)
cv2.circle(canvas, (sx, sy), 6, (0, 255, 0), -1) cv2.circle(canvas, (sx, sy), 6, (0, 255, 0), -1)
cv2.circle(canvas, (sx, sy), 6, (255, 255, 255), 1) cv2.circle(canvas, (sx, sy), 6, (255, 255, 255), 1)
if self.tracking_enabled and not self.is_image_mode: if self.tracking_enabled and not self.is_image_mode:
interp = self._get_interpolated_tracking_position(self.current_frame) interp = self._get_interpolated_tracking_position(self.current_frame)
if interp: if interp:
sx, sy = self._map_original_to_screen(interp[0], interp[1]) sx, sy = self._map_rotated_to_screen(interp[0], interp[1])
cv2.line(canvas, (sx - 10, sy), (sx + 10, sy), (255, 0, 0), 2) cv2.line(canvas, (sx - 10, sy), (sx + 10, sy), (255, 0, 0), 2)
cv2.line(canvas, (sx, sy - 10), (sx, sy + 10), (255, 0, 0), 2) cv2.line(canvas, (sx, sy - 10), (sx, sy + 10), (255, 0, 0), 2)
@@ -2045,13 +2075,14 @@ class VideoEditor:
# Handle right-click for tracking points (no modifiers) # 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 event == cv2.EVENT_RBUTTONDOWN and not (flags & (cv2.EVENT_FLAG_CTRLKEY | cv2.EVENT_FLAG_SHIFTKEY)):
if not self.is_image_mode: if not self.is_image_mode:
ox, oy = self._map_screen_to_original(x, y) # Store tracking points in ROTATED frame coordinates (pre-crop)
rx, ry = self._map_screen_to_rotated(x, y)
threshold = 50 threshold = 50
removed = False removed = False
if self.current_frame in self.tracking_points: if self.current_frame in self.tracking_points:
pts_screen = [] pts_screen = []
for idx, (px, py) in enumerate(self.tracking_points[self.current_frame]): for idx, (px, py) in enumerate(self.tracking_points[self.current_frame]):
sxp, syp = self._map_original_to_screen(px, py) sxp, syp = self._map_rotated_to_screen(px, py)
pts_screen.append((idx, sxp, syp)) pts_screen.append((idx, sxp, syp))
for idx, sxp, syp in pts_screen: for idx, sxp, syp in pts_screen:
if (sxp - x) ** 2 + (syp - y) ** 2 <= threshold ** 2: if (sxp - x) ** 2 + (syp - y) ** 2 <= threshold ** 2:
@@ -2062,7 +2093,7 @@ class VideoEditor:
removed = True removed = True
break break
if not removed: if not removed:
self.tracking_points.setdefault(self.current_frame, []).append((int(ox), int(oy))) self.tracking_points.setdefault(self.current_frame, []).append((int(rx), int(ry)))
self.show_feedback_message("Tracking point added") self.show_feedback_message("Tracking point added")
self.clear_transformation_cache() self.clear_transformation_cache()
self.save_state() self.save_state()